diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt index 4caae85f8e..76254c834e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/PayPalRepository.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription import io.reactivex.rxjava3.core.Completable 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.signal.donations.PaymentSourceType import org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal.PayPalConfirmationResult @@ -24,6 +25,8 @@ class PayPalRepository(private val donationsService: DonationsService) { const val ONE_TIME_RETURN_URL = "https://signaldonations.org/return/onetime" const val MONTHLY_RETURN_URL = "https://signaldonations.org/return/monthly" const val CANCEL_URL = "https://signaldonations.org/cancel" + + private val TAG = Log.tag(PayPalRepository::class.java) } fun createOneTimePaymentIntent( @@ -53,6 +56,7 @@ class PayPalRepository(private val donationsService: DonationsService) { paypalConfirmationResult: PayPalConfirmationResult ): Single { return Single.fromCallable { + Log.d(TAG, "Confirming one-time payment intent...", true) donationsService .confirmPayPalOneTimePaymentIntent( amount.currency.currencyCode, @@ -78,11 +82,14 @@ class PayPalRepository(private val donationsService: DonationsService) { fun setDefaultPaymentMethod(paymentMethodId: String): Completable { return Single.fromCallable { + Log.d(TAG, "Setting default payment method...", true) donationsService.setDefaultPayPalPaymentMethod( SignalStore.donationsValues().requireSubscriber().subscriberId, paymentMethodId ) - }.flatMap { it.flattenResult() }.ignoreElement().andThen { + }.flatMap { it.flattenResult() }.ignoreElement().doOnComplete { + Log.d(TAG, "Set default payment method.", true) + Log.d(TAG, "Storing the subscription payment source type locally.", true) SignalStore.donationsValues().setSubscriptionPaymentSourceType(PaymentSourceType.PayPal) }.subscribeOn(Schedulers.io()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalType.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalType.kt index 5da3315581..3e4ff383bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalType.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalType.kt @@ -2,10 +2,19 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate import android.os.Parcelable import kotlinx.parcelize.Parcelize +import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource @Parcelize enum class DonateToSignalType(val requestCode: Short) : Parcelable { ONE_TIME(16141), MONTHLY(16142), - GIFT(16143) + GIFT(16143); + + fun toErrorSource(): DonationErrorSource { + return when (this) { + ONE_TIME -> DonationErrorSource.BOOST + MONTHLY -> DonationErrorSource.SUBSCRIPTION + GIFT -> DonationErrorSource.GIFT + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt index a31606769c..f0a1f4bb40 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt @@ -235,6 +235,12 @@ class DonationCheckoutDelegate( return } + if (throwable is DonationError.PayPalError.UserCancelledPaymentError) { + Log.d(TAG, "User cancelled out of paypal flow.", true) + fragment?.findNavController()?.popBackStack() + return + } + Log.d(TAG, "Displaying donation error dialog.", true) errorDialog = DonationErrorDialogs.show( fragment!!.requireContext(), throwable, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt index 0cda79c750..43a3603ab8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway +import android.content.Context import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.fragment.app.setFragmentResult @@ -60,11 +61,7 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() { space(12.dp) - when (args.request.donateToSignalType) { - DonateToSignalType.MONTHLY -> presentMonthlyText() - DonateToSignalType.ONE_TIME -> presentOneTimeText() - DonateToSignalType.GIFT -> presentGiftText() - } + presentTitleAndSubtitle(requireContext(), args.request) space(66.dp) @@ -114,64 +111,72 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() { } } - private fun DSLConfiguration.presentMonthlyText() { - noPadTextPref( - title = DSLSettingsText.from( - getString(R.string.GatewaySelectorBottomSheet__donate_s_month_to_signal, FiatMoneyUtil.format(resources, args.request.fiat)), - DSLSettingsText.CenterModifier, - DSLSettingsText.TitleLargeModifier - ) - ) - space(6.dp) - noPadTextPref( - title = DSLSettingsText.from( - getString(R.string.GatewaySelectorBottomSheet__get_a_s_badge, args.request.badge.name), - DSLSettingsText.CenterModifier, - DSLSettingsText.BodyLargeModifier, - DSLSettingsText.ColorModifier(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant)) - ) - ) - } - - private fun DSLConfiguration.presentOneTimeText() { - noPadTextPref( - title = DSLSettingsText.from( - getString(R.string.GatewaySelectorBottomSheet__donate_s_to_signal, FiatMoneyUtil.format(resources, args.request.fiat)), - DSLSettingsText.CenterModifier, - DSLSettingsText.TitleLargeModifier - ) - ) - space(6.dp) - noPadTextPref( - title = DSLSettingsText.from( - resources.getQuantityString(R.plurals.GatewaySelectorBottomSheet__get_a_s_badge_for_d_days, 30, args.request.badge.name, 30), - DSLSettingsText.CenterModifier, - DSLSettingsText.BodyLargeModifier, - DSLSettingsText.ColorModifier(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant)) - ) - ) - } - - private fun DSLConfiguration.presentGiftText() { - noPadTextPref( - title = DSLSettingsText.from( - getString(R.string.GatewaySelectorBottomSheet__donate_s_to_signal, FiatMoneyUtil.format(resources, args.request.fiat)), - DSLSettingsText.CenterModifier, - DSLSettingsText.TitleLargeModifier - ) - ) - space(6.dp) - noPadTextPref( - title = DSLSettingsText.from( - R.string.GatewaySelectorBottomSheet__send_a_gift_badge, - DSLSettingsText.CenterModifier, - DSLSettingsText.BodyLargeModifier, - DSLSettingsText.ColorModifier(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant)) - ) - ) - } - companion object { const val REQUEST_KEY = "payment_checkout_mode" + + fun DSLConfiguration.presentTitleAndSubtitle(context: Context, request: GatewayRequest) { + when (request.donateToSignalType) { + DonateToSignalType.MONTHLY -> presentMonthlyText(context, request) + DonateToSignalType.ONE_TIME -> presentOneTimeText(context, request) + DonateToSignalType.GIFT -> presentGiftText(context, request) + } + } + + private fun DSLConfiguration.presentMonthlyText(context: Context, request: GatewayRequest) { + noPadTextPref( + title = DSLSettingsText.from( + context.getString(R.string.GatewaySelectorBottomSheet__donate_s_month_to_signal, FiatMoneyUtil.format(context.resources, request.fiat)), + DSLSettingsText.CenterModifier, + DSLSettingsText.TitleLargeModifier + ) + ) + space(6.dp) + noPadTextPref( + title = DSLSettingsText.from( + context.getString(R.string.GatewaySelectorBottomSheet__get_a_s_badge, request.badge.name), + DSLSettingsText.CenterModifier, + DSLSettingsText.BodyLargeModifier, + DSLSettingsText.ColorModifier(ContextCompat.getColor(context, R.color.signal_colorOnSurfaceVariant)) + ) + ) + } + + private fun DSLConfiguration.presentOneTimeText(context: Context, request: GatewayRequest) { + noPadTextPref( + title = DSLSettingsText.from( + context.getString(R.string.GatewaySelectorBottomSheet__donate_s_to_signal, FiatMoneyUtil.format(context.resources, request.fiat)), + DSLSettingsText.CenterModifier, + DSLSettingsText.TitleLargeModifier + ) + ) + space(6.dp) + noPadTextPref( + title = DSLSettingsText.from( + context.resources.getQuantityString(R.plurals.GatewaySelectorBottomSheet__get_a_s_badge_for_d_days, 30, request.badge.name, 30), + DSLSettingsText.CenterModifier, + DSLSettingsText.BodyLargeModifier, + DSLSettingsText.ColorModifier(ContextCompat.getColor(context, R.color.signal_colorOnSurfaceVariant)) + ) + ) + } + + private fun DSLConfiguration.presentGiftText(context: Context, request: GatewayRequest) { + noPadTextPref( + title = DSLSettingsText.from( + context.getString(R.string.GatewaySelectorBottomSheet__donate_s_to_signal, FiatMoneyUtil.format(context.resources, request.fiat)), + DSLSettingsText.CenterModifier, + DSLSettingsText.TitleLargeModifier + ) + ) + space(6.dp) + noPadTextPref( + title = DSLSettingsText.from( + R.string.GatewaySelectorBottomSheet__send_a_gift_badge, + DSLSettingsText.CenterModifier, + DSLSettingsText.BodyLargeModifier, + DSLSettingsText.ColorModifier(ContextCompat.getColor(context, R.color.signal_colorOnSurfaceVariant)) + ) + ) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalCompleteOrderBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalCompleteOrderBottomSheet.kt new file mode 100644 index 0000000000..bd1d1ea1b0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalCompleteOrderBottomSheet.kt @@ -0,0 +1,78 @@ +package org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal + +import android.content.DialogInterface +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import org.signal.core.util.dp +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.badges.models.BadgeDisplay112 +import org.thoughtcrime.securesms.components.settings.DSLConfiguration +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.subscription.donate.gateway.GatewaySelectorBottomSheet.Companion.presentTitleAndSubtitle +import org.thoughtcrime.securesms.components.settings.configure + +/** + * Bottom sheet for final order confirmation from PayPal + */ +class PayPalCompleteOrderBottomSheet : DSLSettingsBottomSheetFragment() { + + companion object { + const val REQUEST_KEY = "complete_order" + } + + private var didConfirmOrder = false + private val args: PayPalCompleteOrderBottomSheetArgs by navArgs() + + override fun bindAdapter(adapter: DSLSettingsAdapter) { + BadgeDisplay112.register(adapter) + PayPalCompleteOrderPaymentItem.register(adapter) + + adapter.submitList(getConfiguration().toMappingModelList()) + } + + override fun onDismiss(dialog: DialogInterface) { + setFragmentResult(REQUEST_KEY, bundleOf(REQUEST_KEY to didConfirmOrder)) + } + + private fun getConfiguration(): DSLConfiguration { + return configure { + customPref( + BadgeDisplay112.Model( + badge = args.request.badge, + withDisplayText = false + ) + ) + + space(12.dp) + + presentTitleAndSubtitle(requireContext(), args.request) + + space(24.dp) + + customPref(PayPalCompleteOrderPaymentItem.Model()) + + space(82.dp) + + primaryButton( + text = DSLSettingsText.from(R.string.PaypalCompleteOrderBottomSheet__donate), + onClick = { + didConfirmOrder = true + findNavController().popBackStack() + } + ) + + secondaryButtonNoOutline( + text = DSLSettingsText.from(android.R.string.cancel), + onClick = { + findNavController().popBackStack() + } + ) + + space(16.dp) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalCompleteOrderPaymentItem.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalCompleteOrderPaymentItem.kt new file mode 100644 index 0000000000..cf6eb8c886 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalCompleteOrderPaymentItem.kt @@ -0,0 +1,22 @@ +package org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal + +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory +import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter +import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel +import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder.SimpleViewHolder + +/** + * Line item on the PayPal order confirmation screen. + */ +object PayPalCompleteOrderPaymentItem { + fun register(mappingAdapter: MappingAdapter) { + mappingAdapter.registerFactory(Model::class.java, LayoutFactory(::SimpleViewHolder, R.layout.paypal_complete_order_payment_item)) + } + + class Model : MappingModel { + override fun areItemsTheSame(newItem: Model): Boolean = true + + override fun areContentsTheSame(newItem: Model): Boolean = true + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalConfirmationDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalConfirmationDialogFragment.kt index df1c0d8e9b..4386f3b8ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalConfirmationDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalConfirmationDialogFragment.kt @@ -11,6 +11,8 @@ import android.webkit.WebViewClient import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import androidx.fragment.app.setFragmentResult +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.navigation.fragment.navArgs import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R @@ -47,7 +49,9 @@ class PayPalConfirmationDialogFragment : DialogFragment(R.layout.donation_webvie @SuppressLint("SetJavaScriptEnabled") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - binding.webView.webViewClient = PayPalWebClient() + val client = PayPalWebClient() + viewLifecycleOwner.lifecycle.addObserver(client) + binding.webView.webViewClient = client binding.webView.settings.javaScriptEnabled = true binding.webView.settings.cacheMode = WebSettings.LOAD_NO_CACHE binding.webView.loadUrl(args.uri.toString()) @@ -59,21 +63,31 @@ class PayPalConfirmationDialogFragment : DialogFragment(R.layout.donation_webvie setFragmentResult(REQUEST_KEY, result ?: Bundle()) } - private inner class PayPalWebClient : WebViewClient() { + private inner class PayPalWebClient : WebViewClient(), DefaultLifecycleObserver { + + private var isDestroyed = false + + override fun onDestroy(owner: LifecycleOwner) { + isDestroyed = true + } override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - if (!isFinished) { + if (!isDestroyed) { binding.progress.visible = true } } override fun onPageCommitVisible(view: WebView?, url: String?) { - if (!isFinished) { + if (!isDestroyed) { binding.progress.visible = false } } override fun onPageFinished(view: WebView?, url: String?) { + if (isDestroyed) { + return + } + if (url?.startsWith(PayPalRepository.ONE_TIME_RETURN_URL) == true) { val confirmationResult = PayPalConfirmationResult.fromUrl(url) if (confirmationResult != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressFragment.kt index d31c25ff3e..c9d9b86244 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/paypal/PayPalPaymentInProgressFragment.kt @@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorActionResult import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorStage +import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError import org.thoughtcrime.securesms.databinding.DonationInProgressFragmentBinding import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.navigation.safeNavigate @@ -57,7 +58,7 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog viewModel.onBeginNewAction() when (args.action) { DonationProcessorAction.PROCESS_NEW_DONATION -> { - viewModel.processNewDonation(args.request, this::routeToOneTimeConfirmation, this::routeToMonthlyConfirmation) + viewModel.processNewDonation(args.request, this::oneTimeConfirmationPipeline, this::monthlyConfirmationPipeline) } DonationProcessorAction.UPDATE_SUBSCRIPTION -> { viewModel.updateSubscription(args.request) @@ -110,6 +111,18 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog } } + private fun oneTimeConfirmationPipeline(createPaymentIntentResponse: PayPalCreatePaymentIntentResponse): Single { + return routeToOneTimeConfirmation(createPaymentIntentResponse).flatMap { + displayCompleteOrderSheet(it) + } + } + + private fun monthlyConfirmationPipeline(createPaymentIntentResponse: PayPalCreatePaymentMethodResponse): Single { + return routeToMonthlyConfirmation(createPaymentIntentResponse).flatMap { + displayCompleteOrderSheet(it) + } + } + private fun routeToOneTimeConfirmation(createPaymentIntentResponse: PayPalCreatePaymentIntentResponse): Single { return Single.create { emitter -> val listener = FragmentResultListener { _, bundle -> @@ -117,10 +130,11 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog if (result != null) { emitter.onSuccess(result) } else { - emitter.onError(Exception("User did not complete paypal confirmation.")) + emitter.onError(DonationError.PayPalError.UserCancelledPaymentError(args.request.donateToSignalType.toErrorSource())) } } + parentFragmentManager.clearFragmentResult(PayPalConfirmationDialogFragment.REQUEST_KEY) parentFragmentManager.setFragmentResultListener(PayPalConfirmationDialogFragment.REQUEST_KEY, this, listener) findNavController().safeNavigate( @@ -130,6 +144,8 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog ) emitter.setCancellable { + Log.d(TAG, "Clearing one-time confirmation result listener.") + parentFragmentManager.clearFragmentResult(PayPalConfirmationDialogFragment.REQUEST_KEY) parentFragmentManager.clearFragmentResultListener(PayPalConfirmationDialogFragment.REQUEST_KEY) } }.subscribeOn(AndroidSchedulers.mainThread()).observeOn(Schedulers.io()) @@ -138,14 +154,15 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog private fun routeToMonthlyConfirmation(createPaymentIntentResponse: PayPalCreatePaymentMethodResponse): Single { return Single.create { emitter -> val listener = FragmentResultListener { _, bundle -> - val result: Boolean = bundle.getBoolean(REQUEST_KEY) + val result: Boolean = bundle.getBoolean(PayPalConfirmationDialogFragment.REQUEST_KEY) if (result) { emitter.onSuccess(PayPalPaymentMethodId(createPaymentIntentResponse.token)) } else { - emitter.onError(Exception("User did not confirm paypal setup.")) + emitter.onError(DonationError.PayPalError.UserCancelledPaymentError(args.request.donateToSignalType.toErrorSource())) } } + parentFragmentManager.clearFragmentResult(PayPalConfirmationDialogFragment.REQUEST_KEY) parentFragmentManager.setFragmentResultListener(PayPalConfirmationDialogFragment.REQUEST_KEY, this, listener) findNavController().safeNavigate( @@ -155,8 +172,37 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog ) emitter.setCancellable { + Log.d(TAG, "Clearing monthly confirmation result listener.") + parentFragmentManager.clearFragmentResult(PayPalConfirmationDialogFragment.REQUEST_KEY) parentFragmentManager.clearFragmentResultListener(PayPalConfirmationDialogFragment.REQUEST_KEY) } }.subscribeOn(AndroidSchedulers.mainThread()).observeOn(Schedulers.io()) } + + private fun displayCompleteOrderSheet(confirmationData: T): Single { + return Single.create { emitter -> + val listener = FragmentResultListener { _, bundle -> + val result: Boolean = bundle.getBoolean(PayPalCompleteOrderBottomSheet.REQUEST_KEY) + if (result) { + Log.d(TAG, "User confirmed order. Continuing...") + emitter.onSuccess(confirmationData) + } else { + emitter.onError(DonationError.PayPalError.UserCancelledPaymentError(args.request.donateToSignalType.toErrorSource())) + } + } + + parentFragmentManager.clearFragmentResult(PayPalCompleteOrderBottomSheet.REQUEST_KEY) + parentFragmentManager.setFragmentResultListener(PayPalCompleteOrderBottomSheet.REQUEST_KEY, this, listener) + + findNavController().safeNavigate( + PayPalPaymentInProgressFragmentDirections.actionPaypalPaymentInProgressFragmentToPaypalCompleteOrderBottomSheet(args.request) + ) + + emitter.setCancellable { + Log.d(TAG, "Clearing complete order result listener.") + parentFragmentManager.clearFragmentResult(PayPalCompleteOrderBottomSheet.REQUEST_KEY) + parentFragmentManager.clearFragmentResultListener(PayPalCompleteOrderBottomSheet.REQUEST_KEY) + } + }.subscribeOn(AndroidSchedulers.mainThread()).observeOn(Schedulers.io()) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationError.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationError.kt index 37e0e7e514..8747160d66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationError.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/errors/DonationError.kt @@ -19,6 +19,10 @@ sealed class DonationError(val source: DonationErrorSource, cause: Throwable) : class RequestTokenError(source: DonationErrorSource, cause: Throwable) : GooglePayError(source, cause) } + sealed class PayPalError(source: DonationErrorSource, cause: Throwable) : DonationError(source, cause) { + class UserCancelledPaymentError(source: DonationErrorSource) : DonationError(source, Exception("User cancelled payment.")) + } + /** * Gifting recipient validation errors, which occur before the user could be charged for a gift. */ diff --git a/app/src/main/res/drawable/paypal_dark.xml b/app/src/main/res/drawable/paypal_dark.xml new file mode 100644 index 0000000000..d57c004506 --- /dev/null +++ b/app/src/main/res/drawable/paypal_dark.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/rounded_outline_12_surface1.xml b/app/src/main/res/drawable/rounded_outline_12_surface1.xml new file mode 100644 index 0000000000..0e6482b0ee --- /dev/null +++ b/app/src/main/res/drawable/rounded_outline_12_surface1.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/paypal_complete_order_payment_item.xml b/app/src/main/res/layout/paypal_complete_order_payment_item.xml new file mode 100644 index 0000000000..93c3107d79 --- /dev/null +++ b/app/src/main/res/layout/paypal_complete_order_payment_item.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/donate_to_signal.xml b/app/src/main/res/navigation/donate_to_signal.xml index 780a19a3bd..13d940f344 100644 --- a/app/src/main/res/navigation/donate_to_signal.xml +++ b/app/src/main/res/navigation/donate_to_signal.xml @@ -176,6 +176,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/gift_flow.xml b/app/src/main/res/navigation/gift_flow.xml index 9c026a6624..11abb8386e 100644 --- a/app/src/main/res/navigation/gift_flow.xml +++ b/app/src/main/res/navigation/gift_flow.xml @@ -157,6 +157,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/values.xml b/app/src/main/res/values-night/values.xml new file mode 100644 index 0000000000..47d4910687 --- /dev/null +++ b/app/src/main/res/values-night/values.xml @@ -0,0 +1,4 @@ + + + @drawable/paypal_dark + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e7eb129e0b..c4568b870b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5447,4 +5447,8 @@ + + Donate + Payment + diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index ae2db1bc8a..87076bf024 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -3,4 +3,5 @@ true false 1 + @drawable/paypal