mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Rewrite in-app-payment flows to prepare for backups support.
This commit is contained in:
committed by
Cody Henthorne
parent
b36b00a11c
commit
d719edf104
@@ -23,16 +23,13 @@ import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationCheckoutDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.RecipientPreference
|
||||
import org.thoughtcrime.securesms.components.settings.models.TextInput
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.keyboard.KeyboardPage
|
||||
import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
|
||||
@@ -41,6 +38,7 @@ import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.util.Debouncer
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Allows the user to confirm details about a gift, add a message, and finally make a payment.
|
||||
@@ -85,7 +83,11 @@ class GiftFlowConfirmationFragment :
|
||||
|
||||
keyboardPagerViewModel.setOnlyPage(KeyboardPage.EMOJI)
|
||||
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this, viewModel.uiSessionKey, DonationErrorSource.GIFT)
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(
|
||||
this,
|
||||
this,
|
||||
viewModel.state.mapOptional { Optional.ofNullable(it.inAppPaymentId) }.distinctUntilChanged()
|
||||
)
|
||||
|
||||
processingDonationPaymentDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(R.layout.processing_payment_dialog)
|
||||
@@ -104,23 +106,13 @@ class GiftFlowConfirmationFragment :
|
||||
|
||||
val continueButton = requireView().findViewById<MaterialButton>(R.id.continue_button)
|
||||
continueButton.setOnClickListener {
|
||||
findNavController().safeNavigate(
|
||||
GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToGatewaySelectorBottomSheet(
|
||||
with(viewModel.snapshot) {
|
||||
GatewayRequest(
|
||||
uiSessionKey = viewModel.uiSessionKey,
|
||||
donateToSignalType = DonateToSignalType.GIFT,
|
||||
badge = giftBadge!!,
|
||||
label = getString(R.string.preferences__one_time),
|
||||
price = giftPrices[currency]!!.amount,
|
||||
currencyCode = currency.currencyCode,
|
||||
level = giftLevel!!,
|
||||
recipientId = recipient!!.id,
|
||||
additionalMessage = additionalMessage?.toString()
|
||||
)
|
||||
}
|
||||
lifecycleDisposable += viewModel.insertInAppPayment(requireContext()).subscribe { inAppPayment ->
|
||||
findNavController().safeNavigate(
|
||||
GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToGatewaySelectorBottomSheet(
|
||||
inAppPayment
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val textInput = requireView().findViewById<FrameLayout>(R.id.text_input)
|
||||
@@ -253,27 +245,27 @@ class GiftFlowConfirmationFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun navigateToStripePaymentInProgress(gatewayRequest: GatewayRequest) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToStripePaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, gatewayRequest))
|
||||
override fun navigateToStripePaymentInProgress(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToStripePaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, inAppPayment, inAppPayment.type))
|
||||
}
|
||||
|
||||
override fun navigateToPayPalPaymentInProgress(gatewayRequest: GatewayRequest) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToPaypalPaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, gatewayRequest))
|
||||
override fun navigateToPayPalPaymentInProgress(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToPaypalPaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, inAppPayment, inAppPayment.type))
|
||||
}
|
||||
|
||||
override fun navigateToCreditCardForm(gatewayRequest: GatewayRequest) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToCreditCardFragment(gatewayRequest))
|
||||
override fun navigateToCreditCardForm(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToCreditCardFragment(inAppPayment))
|
||||
}
|
||||
|
||||
override fun navigateToIdealDetailsFragment(gatewayRequest: GatewayRequest) {
|
||||
override fun navigateToIdealDetailsFragment(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
error("Unsupported operation")
|
||||
}
|
||||
|
||||
override fun navigateToBankTransferMandate(gatewayResponse: GatewayResponse) {
|
||||
override fun navigateToBankTransferMandate(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
error("Unsupported operation")
|
||||
}
|
||||
|
||||
override fun onPaymentComplete(gatewayRequest: GatewayRequest) {
|
||||
override fun onPaymentComplete(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
val mainActivityIntent = MainActivity.clearTop(requireContext())
|
||||
|
||||
lifecycleDisposable += ConversationIntents
|
||||
@@ -291,5 +283,5 @@ class GiftFlowConfirmationFragment :
|
||||
|
||||
override fun onUserLaunchedAnExternalApplication() = Unit
|
||||
|
||||
override fun navigateToDonationPending(gatewayRequest: GatewayRequest) = error("Unsupported operation")
|
||||
override fun navigateToDonationPending(inAppPayment: InAppPaymentTable.InAppPayment) = error("Unsupported operation")
|
||||
}
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package org.thoughtcrime.securesms.badges.gifts.flow
|
||||
|
||||
import android.content.Context
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.R
|
||||
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.getGiftBadgeAmounts
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadges
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
|
||||
import java.util.Currency
|
||||
@@ -21,6 +29,25 @@ class GiftFlowRepository {
|
||||
private val TAG = Log.tag(GiftFlowRepository::class.java)
|
||||
}
|
||||
|
||||
fun insertInAppPayment(context: Context, giftSnapshot: GiftFlowState): Single<InAppPaymentTable.InAppPayment> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.inAppPayments.insert(
|
||||
type = InAppPaymentTable.Type.ONE_TIME_GIFT,
|
||||
state = InAppPaymentTable.State.CREATED,
|
||||
subscriberId = null,
|
||||
endOfPeriod = null,
|
||||
inAppPaymentData = InAppPaymentData(
|
||||
badge = Badges.toDatabaseBadge(giftSnapshot.giftBadge!!),
|
||||
label = context.getString(R.string.preferences__one_time),
|
||||
amount = giftSnapshot.giftPrices[giftSnapshot.currency]!!.toFiatValue(),
|
||||
level = giftSnapshot.giftLevel!!,
|
||||
recipientId = giftSnapshot.recipient!!.id.serialize(),
|
||||
additionalMessage = giftSnapshot.additionalMessage?.toString()
|
||||
)
|
||||
)
|
||||
}.flatMap { InAppPaymentsRepository.requireInAppPayment(it) }.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getGiftBadge(): Single<Pair<Int, Badge>> {
|
||||
return Single
|
||||
.fromCallable {
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.models.Ne
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle
|
||||
import org.thoughtcrime.securesms.components.settings.models.SplashImage
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
@@ -91,7 +92,7 @@ class GiftFlowStartFragment : DSLSettingsFragment(
|
||||
selectedCurrency = state.currency,
|
||||
isEnabled = state.stage == GiftFlowState.Stage.READY,
|
||||
onClick = {
|
||||
val action = GiftFlowStartFragmentDirections.actionGiftFlowStartFragmentToSetCurrencyFragment(true, viewModel.getSupportedCurrencyCodes().toTypedArray())
|
||||
val action = GiftFlowStartFragmentDirections.actionGiftFlowStartFragmentToSetCurrencyFragment(InAppPaymentTable.Type.ONE_TIME_GIFT, viewModel.getSupportedCurrencyCodes().toTypedArray())
|
||||
findNavController().safeNavigate(action)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.badges.gifts.flow
|
||||
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import java.util.Currency
|
||||
|
||||
@@ -9,6 +10,7 @@ import java.util.Currency
|
||||
* State maintained by the GiftFlowViewModel
|
||||
*/
|
||||
data class GiftFlowState(
|
||||
val inAppPaymentId: InAppPaymentTable.InAppPaymentId? = null,
|
||||
val currency: Currency,
|
||||
val giftLevel: Long? = null,
|
||||
val giftBadge: Badge? = null,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.thoughtcrime.securesms.badges.gifts.flow
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
@@ -14,6 +17,7 @@ import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationEvent
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.InternetConnectionObserver
|
||||
@@ -39,7 +43,6 @@ class GiftFlowViewModel(
|
||||
val state: Flowable<GiftFlowState> = store.stateFlowable
|
||||
val events: Observable<DonationEvent> = eventPublisher
|
||||
val snapshot: GiftFlowState get() = store.state
|
||||
val uiSessionKey: Long = System.currentTimeMillis()
|
||||
|
||||
init {
|
||||
refresh()
|
||||
@@ -101,6 +104,15 @@ class GiftFlowViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
fun insertInAppPayment(context: Context): Single<InAppPaymentTable.InAppPayment> {
|
||||
val giftSnapshot = snapshot
|
||||
return giftFlowRepository.insertInAppPayment(context, giftSnapshot)
|
||||
.doOnSuccess { inAppPayment ->
|
||||
store.update { it.copy(inAppPaymentId = inAppPayment.id) }
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
@@ -10,14 +10,19 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.badges.BadgeRepository
|
||||
import org.thoughtcrime.securesms.badges.gifts.viewgift.ViewGiftRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError.BadgeRedemptionError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobs.DonationReceiptRedemptionJob
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver.MessageObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.InAppPaymentRedemptionJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.requireGiftBadge
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ViewReceivedGiftViewModel(
|
||||
@@ -98,44 +103,22 @@ class ViewReceivedGiftViewModel(
|
||||
}
|
||||
|
||||
private fun awaitRedemptionCompletion(setAsPrimary: Boolean): Completable {
|
||||
return Completable.create {
|
||||
Log.i(TAG, "Enqueuing gift redemption and awaiting result...", true)
|
||||
|
||||
var finalJobState: JobTracker.JobState? = null
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
|
||||
DonationReceiptRedemptionJob.createJobChainForGift(messageId, setAsPrimary).enqueue { _, state ->
|
||||
if (state.isComplete) {
|
||||
finalJobState = state
|
||||
countDownLatch.countDown()
|
||||
return Completable.create { emitter ->
|
||||
val messageObserver = MessageObserver { messageId ->
|
||||
val message = SignalDatabase.messages.getMessageRecord(messageId.id)
|
||||
when (message.requireGiftBadge().redemptionState) {
|
||||
GiftBadge.RedemptionState.REDEEMED -> emitter.onComplete()
|
||||
GiftBadge.RedemptionState.FAILED -> emitter.onError(DonationError.genericBadgeRedemptionFailure(DonationErrorSource.GIFT_REDEMPTION))
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (countDownLatch.await(10, TimeUnit.SECONDS)) {
|
||||
when (finalJobState) {
|
||||
JobTracker.JobState.SUCCESS -> {
|
||||
Log.d(TAG, "Gift redemption job chain succeeded.", true)
|
||||
it.onComplete()
|
||||
}
|
||||
JobTracker.JobState.FAILURE -> {
|
||||
Log.d(TAG, "Gift redemption job chain failed permanently.", true)
|
||||
it.onError(DonationError.genericBadgeRedemptionFailure(DonationErrorSource.GIFT_REDEMPTION))
|
||||
}
|
||||
else -> {
|
||||
Log.w(TAG, "Gift redemption job chain ignored due to in-progress jobs.", true)
|
||||
it.onError(DonationError.timeoutWaitingForToken(DonationErrorSource.GIFT_REDEMPTION))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Timeout awaiting for gift token redemption and profile refresh", true)
|
||||
it.onError(DonationError.timeoutWaitingForToken(DonationErrorSource.GIFT_REDEMPTION))
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
Log.w(TAG, "Interrupted awaiting for gift token redemption and profile refresh", true)
|
||||
it.onError(DonationError.timeoutWaitingForToken(DonationErrorSource.GIFT_REDEMPTION))
|
||||
ApplicationDependencies.getJobManager().add(InAppPaymentRedemptionJob.create(MessageId(messageId), setAsPrimary))
|
||||
ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(messageObserver)
|
||||
emitter.setCancellable {
|
||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageObserver)
|
||||
}
|
||||
}
|
||||
}.timeout(10, TimeUnit.SECONDS, Completable.error(BadgeRedemptionError.TimeoutWaitingForTokenError(DonationErrorSource.GIFT_REDEMPTION)))
|
||||
}
|
||||
|
||||
class Factory(
|
||||
|
||||
@@ -16,6 +16,8 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
@@ -27,47 +29,68 @@ import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Texts
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImage112
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.mapToErrorStringResource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.manage.ManageDonationsFragment
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
|
||||
class MonthlyDonationCanceledBottomSheetDialogFragment : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG_IN_APP_PAYMENT_ID = "arg.inAppPaymentId"
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun show(fragmentManager: FragmentManager, inAppPaymentId: InAppPaymentTable.InAppPaymentId? = null) {
|
||||
val fragment = MonthlyDonationCanceledBottomSheetDialogFragment()
|
||||
fragment.arguments = bundleOf(
|
||||
ARG_IN_APP_PAYMENT_ID to inAppPaymentId
|
||||
)
|
||||
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
private val viewModel by viewModel {
|
||||
MonthlyDonationCanceledViewModel(arguments?.getParcelableCompat(ARG_IN_APP_PAYMENT_ID, InAppPaymentTable.InAppPaymentId::class.java))
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
val chargeFailure: ActiveSubscription.ChargeFailure? = SignalStore.donationsValues().getUnexpectedSubscriptionCancelationChargeFailure()
|
||||
val declineCode: StripeDeclineCode = StripeDeclineCode.getFromCode(chargeFailure?.outcomeNetworkReason)
|
||||
val failureCode: StripeFailureCode = StripeFailureCode.getFromCode(chargeFailure?.code)
|
||||
val state by viewModel.state
|
||||
|
||||
val errorMessage = if (declineCode.isKnown()) {
|
||||
declineCode.mapToErrorStringResource()
|
||||
} else if (failureCode.isKnown) {
|
||||
failureCode.mapToErrorStringResource()
|
||||
} else {
|
||||
declineCode.mapToErrorStringResource()
|
||||
if (state.loadState == MonthlyDonationCanceledState.LoadState.LOADING) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.loadState == MonthlyDonationCanceledState.LoadState.FAILED) {
|
||||
LaunchedEffect(Unit) {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
MonthlyDonationCanceled(
|
||||
badge = SignalStore.donationsValues().getExpiredBadge(),
|
||||
errorMessageRes = errorMessage,
|
||||
badge = state.badge,
|
||||
errorMessageRes = state.errorMessage,
|
||||
onRenewClicked = {
|
||||
startActivity(AppSettingsActivity.subscriptions(requireContext()))
|
||||
dismissAllowingStateLoss()
|
||||
@@ -78,15 +101,6 @@ class MonthlyDonationCanceledBottomSheetDialogFragment : ComposeBottomSheetDialo
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager) {
|
||||
val fragment = MonthlyDonationCanceledBottomSheetDialogFragment()
|
||||
|
||||
fragment.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "ShortName", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.badges.self.expired
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
|
||||
data class MonthlyDonationCanceledState(
|
||||
val loadState: LoadState = LoadState.LOADING,
|
||||
val badge: Badge? = null,
|
||||
@StringRes val errorMessage: Int = -1
|
||||
) {
|
||||
enum class LoadState {
|
||||
LOADING,
|
||||
READY,
|
||||
FAILED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.badges.self.expired
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toActiveSubscriptionChargeFailure
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.mapToErrorStringResource
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription.ChargeFailure
|
||||
|
||||
class MonthlyDonationCanceledViewModel(
|
||||
inAppPaymentId: InAppPaymentTable.InAppPaymentId?
|
||||
) : ViewModel() {
|
||||
private val internalState = mutableStateOf(MonthlyDonationCanceledState())
|
||||
val state: State<MonthlyDonationCanceledState> = internalState
|
||||
|
||||
init {
|
||||
if (inAppPaymentId != null) {
|
||||
initializeFromInAppPaymentId(inAppPaymentId)
|
||||
} else {
|
||||
initializeFromSignalStore()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeFromInAppPaymentId(inAppPaymentId: InAppPaymentTable.InAppPaymentId) {
|
||||
viewModelScope.launch {
|
||||
val inAppPayment = withContext(Dispatchers.IO) {
|
||||
SignalDatabase.inAppPayments.getById(inAppPaymentId)
|
||||
}
|
||||
|
||||
if (inAppPayment != null) {
|
||||
internalState.value = MonthlyDonationCanceledState(
|
||||
loadState = MonthlyDonationCanceledState.LoadState.READY,
|
||||
badge = Badges.fromDatabaseBadge(inAppPayment.data.badge!!),
|
||||
errorMessage = getErrorMessage(inAppPayment.data.cancellation?.chargeFailure?.toActiveSubscriptionChargeFailure())
|
||||
)
|
||||
} else {
|
||||
internalState.value = internalState.value.copy(loadState = MonthlyDonationCanceledState.LoadState.FAILED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeFromSignalStore() {
|
||||
internalState.value = MonthlyDonationCanceledState(
|
||||
loadState = MonthlyDonationCanceledState.LoadState.READY,
|
||||
badge = SignalStore.donationsValues().getExpiredBadge(),
|
||||
errorMessage = getErrorMessage(SignalStore.donationsValues().getUnexpectedSubscriptionCancelationChargeFailure())
|
||||
)
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private fun getErrorMessage(chargeFailure: ChargeFailure?): Int {
|
||||
val declineCode: StripeDeclineCode = StripeDeclineCode.getFromCode(chargeFailure?.outcomeNetworkReason)
|
||||
val failureCode: StripeFailureCode = StripeFailureCode.getFromCode(chargeFailure?.code)
|
||||
|
||||
return if (declineCode.isKnown()) {
|
||||
declineCode.mapToErrorStringResource()
|
||||
} else if (failureCode.isKnown) {
|
||||
failureCode.mapToErrorStringResource()
|
||||
} else {
|
||||
declineCode.mapToErrorStringResource()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.MonthlyDonationRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
@@ -21,7 +21,7 @@ class BecomeASustainerFragment : DSLSettingsBottomSheetFragment() {
|
||||
|
||||
private val viewModel: BecomeASustainerViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
BecomeASustainerViewModel.Factory(MonthlyDonationRepository(ApplicationDependencies.getDonationsService()))
|
||||
BecomeASustainerViewModel.Factory(RecurringInAppPaymentRepository(ApplicationDependencies.getDonationsService()))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.MonthlyDonationRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
|
||||
class BecomeASustainerViewModel(subscriptionsRepository: MonthlyDonationRepository) : ViewModel() {
|
||||
class BecomeASustainerViewModel(subscriptionsRepository: RecurringInAppPaymentRepository) : ViewModel() {
|
||||
|
||||
private val store = Store(BecomeASustainerState())
|
||||
|
||||
@@ -37,7 +37,7 @@ class BecomeASustainerViewModel(subscriptionsRepository: MonthlyDonationReposito
|
||||
private val TAG = Log.tag(BecomeASustainerViewModel::class.java)
|
||||
}
|
||||
|
||||
class Factory(private val subscriptionsRepository: MonthlyDonationRepository) : ViewModelProvider.Factory {
|
||||
class Factory(private val subscriptionsRepository: RecurringInAppPaymentRepository) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(BecomeASustainerViewModel(subscriptionsRepository))!!
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.MonthlyDonationRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -31,7 +31,7 @@ class BadgesOverviewFragment : DSLSettingsFragment(
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private val viewModel: BadgesOverviewViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
BadgesOverviewViewModel.Factory(BadgeRepository(requireContext()), MonthlyDonationRepository(ApplicationDependencies.getDonationsService()))
|
||||
BadgesOverviewViewModel.Factory(BadgeRepository(requireContext()), RecurringInAppPaymentRepository(ApplicationDependencies.getDonationsService()))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
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.MonthlyDonationRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.InternetConnectionObserver
|
||||
@@ -23,7 +24,7 @@ private val TAG = Log.tag(BadgesOverviewViewModel::class.java)
|
||||
|
||||
class BadgesOverviewViewModel(
|
||||
private val badgeRepository: BadgeRepository,
|
||||
private val subscriptionsRepository: MonthlyDonationRepository
|
||||
private val subscriptionsRepository: RecurringInAppPaymentRepository
|
||||
) : ViewModel() {
|
||||
private val store = Store(BadgesOverviewState())
|
||||
private val eventSubject = PublishSubject.create<BadgesOverviewEvent>()
|
||||
@@ -50,7 +51,7 @@ class BadgesOverviewViewModel(
|
||||
}
|
||||
|
||||
disposables += Single.zip(
|
||||
subscriptionsRepository.getActiveSubscription(),
|
||||
subscriptionsRepository.getActiveSubscription(InAppPaymentSubscriberRecord.Type.DONATION),
|
||||
subscriptionsRepository.getSubscriptions()
|
||||
) { active, all ->
|
||||
if (!active.isActive && active.activeSubscription?.willCancelAtPeriodEnd() == true) {
|
||||
@@ -89,7 +90,7 @@ class BadgesOverviewViewModel(
|
||||
|
||||
class Factory(
|
||||
private val badgeRepository: BadgeRepository,
|
||||
private val subscriptionsRepository: MonthlyDonationRepository
|
||||
private val subscriptionsRepository: RecurringInAppPaymentRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(BadgesOverviewViewModel(badgeRepository, subscriptionsRepository)))
|
||||
|
||||
Reference in New Issue
Block a user