Implement PayPal confirm donation sheet.

main
Alex Hart 2022-12-07 09:45:32 -04:00
rodzic e686a09ce4
commit 961057f620
17 zmienionych plików z 379 dodań i 72 usunięć

Wyświetl plik

@ -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<PayPalConfirmPaymentIntentResponse> {
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())
}

Wyświetl plik

@ -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
}
}
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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))
)
)
}
}
}

Wyświetl plik

@ -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)
}
}
}

Wyświetl plik

@ -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<Model> {
override fun areItemsTheSame(newItem: Model): Boolean = true
override fun areContentsTheSame(newItem: Model): Boolean = true
}
}

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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<PayPalConfirmationResult> {
return routeToOneTimeConfirmation(createPaymentIntentResponse).flatMap {
displayCompleteOrderSheet(it)
}
}
private fun monthlyConfirmationPipeline(createPaymentIntentResponse: PayPalCreatePaymentMethodResponse): Single<PayPalPaymentMethodId> {
return routeToMonthlyConfirmation(createPaymentIntentResponse).flatMap {
displayCompleteOrderSheet(it)
}
}
private fun routeToOneTimeConfirmation(createPaymentIntentResponse: PayPalCreatePaymentIntentResponse): Single<PayPalConfirmationResult> {
return Single.create<PayPalConfirmationResult> { 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<PayPalPaymentMethodId> {
return Single.create<PayPalPaymentMethodId> { 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 <T : Any> displayCompleteOrderSheet(confirmationData: T): Single<T> {
return Single.create<T> { 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())
}
}

Wyświetl plik

@ -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.
*/

Wyświetl plik

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="92dp"
android:height="24dp"
android:viewportWidth="92"
android:viewportHeight="24">
<path
android:pathData="M34.179,5.992H29.121C28.774,5.992 28.48,6.244 28.426,6.586L26.38,19.557C26.34,19.812 26.538,20.043 26.797,20.043H29.212C29.558,20.043 29.853,19.792 29.907,19.449L30.458,15.951C30.512,15.608 30.807,15.357 31.152,15.357H32.754C36.086,15.357 38.009,13.744 38.511,10.549C38.737,9.151 38.521,8.053 37.866,7.283C37.147,6.439 35.872,5.992 34.179,5.992M34.763,10.729C34.486,12.545 33.099,12.545 31.758,12.545H30.995L31.53,9.155C31.562,8.95 31.74,8.799 31.947,8.799H32.296C33.21,8.799 34.072,8.799 34.517,9.319C34.782,9.63 34.864,10.092 34.763,10.729Z"
android:fillColor="#F3F3F3"/>
<path
android:pathData="M49.3,10.67H46.877C46.672,10.67 46.493,10.821 46.461,11.026L46.354,11.704L46.185,11.458C45.661,10.698 44.491,10.442 43.324,10.442C40.647,10.442 38.361,12.47 37.916,15.313C37.684,16.733 38.014,18.089 38.818,19.035C39.556,19.905 40.612,20.267 41.869,20.267C44.025,20.267 45.221,18.881 45.221,18.881L45.113,19.554C45.073,19.811 45.271,20.042 45.529,20.042H47.711C48.058,20.042 48.351,19.791 48.406,19.448L49.715,11.158C49.757,10.902 49.559,10.67 49.3,10.67M45.924,15.385C45.69,16.768 44.591,17.698 43.191,17.698C42.488,17.698 41.925,17.472 41.564,17.045C41.206,16.62 41.07,16.016 41.184,15.342C41.403,13.971 42.519,13.012 43.899,13.012C44.587,13.012 45.145,13.24 45.514,13.671C45.883,14.107 46.03,14.715 45.924,15.385"
android:fillColor="#F3F3F3"/>
<path
android:pathData="M62.2,10.671H59.766C59.534,10.671 59.315,10.787 59.184,10.98L55.827,15.925L54.403,11.173C54.314,10.875 54.039,10.671 53.728,10.671H51.336C51.046,10.671 50.845,10.956 50.937,11.229L53.618,19.097L51.097,22.656C50.899,22.937 51.099,23.321 51.441,23.321H53.872C54.103,23.321 54.319,23.209 54.45,23.02L62.546,11.333C62.74,11.054 62.541,10.671 62.2,10.671"
android:fillColor="#F3F3F3"/>
<path
android:pathData="M70.26,5.992H65.201C64.855,5.992 64.561,6.244 64.507,6.586L62.461,19.557C62.421,19.812 62.619,20.043 62.877,20.043H65.473C65.714,20.043 65.921,19.868 65.958,19.627L66.539,15.951C66.592,15.608 66.888,15.357 67.233,15.357H68.834C72.166,15.357 74.088,13.744 74.591,10.549C74.818,9.151 74.601,8.053 73.945,7.283C73.227,6.439 71.953,5.992 70.26,5.992M70.844,10.729C70.568,12.545 69.181,12.545 67.84,12.545H67.076L67.613,9.155C67.644,8.95 67.821,8.799 68.028,8.799H68.378C69.291,8.799 70.154,8.799 70.599,9.319C70.864,9.63 70.945,10.092 70.844,10.729"
android:fillColor="#F3F3F3"/>
<path
android:pathData="M85.379,10.67H82.958C82.751,10.67 82.573,10.821 82.542,11.026L82.435,11.704L82.265,11.458C81.741,10.698 80.572,10.442 79.405,10.442C76.729,10.442 74.443,12.47 73.998,15.313C73.767,16.733 74.094,18.089 74.899,19.035C75.639,19.905 76.694,20.267 77.95,20.267C80.107,20.267 81.303,18.881 81.303,18.881L81.195,19.554C81.154,19.811 81.352,20.042 81.612,20.042H83.794C84.138,20.042 84.433,19.791 84.487,19.448L85.797,11.158C85.837,10.902 85.638,10.67 85.379,10.67M82.003,15.385C81.771,16.768 80.671,17.698 79.27,17.698C78.568,17.698 78.004,17.472 77.643,17.045C77.285,16.62 77.15,16.016 77.263,15.342C77.482,13.971 78.598,13.012 79.978,13.012C80.666,13.012 81.224,13.24 81.593,13.671C81.963,14.107 82.11,14.715 82.003,15.385"
android:fillColor="#F3F3F3"/>
<path
android:pathData="M88.235,6.347L86.159,19.556C86.118,19.812 86.316,20.042 86.574,20.042H88.662C89.009,20.042 89.303,19.791 89.356,19.449L91.404,6.478C91.445,6.222 91.247,5.99 90.988,5.99H88.651C88.445,5.99 88.267,6.143 88.235,6.347"
android:fillColor="#F3F3F3"/>
<path
android:pathData="M5.374,22.564L5.761,20.107L4.899,20.086H0.785L3.644,1.956C3.654,1.901 3.682,1.85 3.724,1.814C3.766,1.777 3.821,1.758 3.877,1.758H10.814C13.117,1.758 14.707,2.237 15.537,3.183C15.926,3.627 16.174,4.09 16.293,4.601C16.419,5.136 16.422,5.776 16.299,6.557L16.289,6.613V7.113L16.679,7.334C17.007,7.508 17.267,7.707 17.467,7.935C17.8,8.314 18.015,8.796 18.106,9.367C18.2,9.956 18.169,10.655 18.015,11.448C17.837,12.359 17.55,13.152 17.163,13.802C16.806,14.4 16.352,14.897 15.813,15.281C15.298,15.647 14.686,15.924 13.995,16.101C13.325,16.276 12.561,16.364 11.722,16.364H11.182C10.797,16.364 10.421,16.503 10.127,16.753C9.833,17.007 9.637,17.354 9.577,17.735L9.536,17.955L8.852,22.286L8.822,22.445C8.814,22.495 8.8,22.521 8.779,22.537C8.76,22.553 8.734,22.564 8.708,22.564H5.374V22.564Z"
android:fillColor="#253B80"/>
<path
android:pathData="M17.047,6.671C17.026,6.804 17.003,6.939 16.975,7.078C16.061,11.775 12.931,13.398 8.933,13.398H6.898C6.409,13.398 5.996,13.753 5.92,14.236L4.878,20.845L4.584,22.719C4.534,23.035 4.778,23.32 5.097,23.32H8.708C9.135,23.32 9.498,23.01 9.566,22.588L9.6,22.405L10.281,18.091L10.324,17.855C10.391,17.431 10.755,17.121 11.182,17.121H11.722C15.22,17.121 17.958,15.7 18.758,11.591C19.092,9.874 18.92,8.441 18.035,7.433C17.767,7.129 17.435,6.877 17.047,6.671"
android:fillColor="#B6B6B6"/>
<path
android:pathData="M16.089,6.289C15.949,6.249 15.805,6.211 15.657,6.178C15.509,6.145 15.356,6.116 15.199,6.092C14.651,6.003 14.049,5.96 13.405,5.96H7.967C7.834,5.96 7.706,5.99 7.592,6.046C7.342,6.166 7.155,6.404 7.11,6.695L5.953,14.021L5.919,14.235C5.996,13.752 6.408,13.397 6.897,13.397H8.932C12.93,13.397 16.06,11.774 16.975,7.078C17.003,6.939 17.025,6.803 17.047,6.671C16.815,6.548 16.564,6.443 16.294,6.354C16.227,6.331 16.159,6.31 16.089,6.289"
android:fillColor="#E9E9E9"/>
<path
android:pathData="M7.111,6.695C7.156,6.404 7.343,6.166 7.594,6.047C7.708,5.992 7.835,5.961 7.968,5.961H13.406C14.05,5.961 14.651,6.004 15.2,6.093C15.357,6.117 15.509,6.146 15.658,6.179C15.806,6.212 15.95,6.25 16.09,6.29C16.16,6.311 16.228,6.332 16.296,6.354C16.565,6.443 16.817,6.549 17.048,6.671C17.32,4.935 17.046,3.753 16.107,2.683C15.073,1.504 13.205,1 10.815,1H3.877C3.389,1 2.973,1.355 2.897,1.838L0.007,20.156C-0.049,20.518 0.23,20.845 0.596,20.845H4.879L5.954,14.022L7.111,6.695V6.695Z"
android:fillColor="#F3F3F3"/>
</vector>

Wyświetl plik

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/signal_colorSurface1" />
<stroke android:color="@color/signal_colorOutline" android:width="1dp" />
<corners android:radius="12dp" />
</shape>

Wyświetl plik

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:background="@drawable/rounded_outline_12_surface1"
tools:viewBindingIgnore="true">
<TextView
android:id="@+id/paypal_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/PaypalCompleteOrderBottomSheet__payment"
android:textAppearance="@style/Signal.Text.BodyLarge"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/paypal_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/paypal_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/paypal_themed" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -176,6 +176,9 @@
<action
android:id="@+id/action_paypalPaymentInProgressFragment_to_paypalConfirmationFragment"
app:destination="@id/paypalConfirmationFragment" />
<action
android:id="@+id/action_paypalPaymentInProgressFragment_to_paypalCompleteOrderBottomSheet"
app:destination="@id/paypalCompleteOrderBottomSheet" />
</dialog>
<dialog
@ -191,4 +194,16 @@
</dialog>
<dialog
android:id="@+id/paypalCompleteOrderBottomSheet"
android:name="org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal.PayPalCompleteOrderBottomSheet"
android:label="paypal_complete_order_bottom_sheet"
tools:layout="@layout/dsl_settings_bottom_sheet">
<argument
android:name="request"
app:argType="org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest"
app:nullable="false" />
</dialog>
</navigation>

Wyświetl plik

@ -157,6 +157,9 @@
<action
android:id="@+id/action_paypalPaymentInProgressFragment_to_paypalConfirmationFragment"
app:destination="@id/paypalConfirmationFragment" />
<action
android:id="@+id/action_paypalPaymentInProgressFragment_to_paypalCompleteOrderBottomSheet"
app:destination="@id/paypalCompleteOrderBottomSheet" />
</dialog>
<dialog
@ -171,4 +174,16 @@
app:nullable="false" />
</dialog>
<dialog
android:id="@+id/paypalCompleteOrderBottomSheet"
android:name="org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal.PayPalCompleteOrderBottomSheet"
android:label="paypal_complete_order_bottom_sheet"
tools:layout="@layout/dsl_settings_bottom_sheet">
<argument
android:name="request"
app:argType="org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest"
app:nullable="false" />
</dialog>
</navigation>

Wyświetl plik

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<drawable name="paypal_themed">@drawable/paypal_dark</drawable>
</resources>

Wyświetl plik

@ -5447,4 +5447,8 @@
<!-- EOF -->
<!-- PaypalCompleteOrderBottomSheet -->
<string name="PaypalCompleteOrderBottomSheet__donate">Donate</string>
<string name="PaypalCompleteOrderBottomSheet__payment">Payment</string>
</resources>

Wyświetl plik

@ -3,4 +3,5 @@
<bool name="enable_alarm_manager">true</bool>
<bool name="enable_job_service">false</bool>
<integer name="image_scale_flip">1</integer>
<drawable name="paypal_themed">@drawable/paypal</drawable>
</resources>