Display thanks sheet if we resume activity before iDEAL is redeemed.

This commit is contained in:
Alex Hart
2025-02-14 13:22:20 -04:00
committed by GitHub
parent 16309d87cd
commit 6a1032577c
8 changed files with 97 additions and 25 deletions

View File

@@ -75,11 +75,7 @@ class InAppPaymentsBottomSheetDelegate(
TerminalDonationBottomSheet.show(fragmentManager, donation)
} else if (donation.error != null) {
lifecycleDisposable += badgeRepository.getBadge(donation).observeOn(AndroidSchedulers.mainThread()).subscribe { badge ->
val args = ThanksForYourSupportBottomSheetDialogFragmentArgs.Builder(badge).build().toBundle()
val sheet = ThanksForYourSupportBottomSheetDialogFragment()
sheet.arguments = args
sheet.show(fragmentManager, null)
ThanksForYourSupportBottomSheetDialogFragment.create(badge).show(fragmentManager, ThanksForYourSupportBottomSheetDialogFragment.SHEET_TAG)
}
}
}

View File

@@ -24,9 +24,9 @@ object ExternalNavigationHelper {
private val TAG = Log.tag(ExternalNavigationHelper::class)
fun maybeLaunchExternalNavigationIntent(context: Context, webRequestUri: Uri?, launchIntent: (Intent) -> Unit): Boolean {
fun maybeLaunchExternalNavigationIntent(context: Context, webRequestUri: Uri?, force: Boolean = false, launchIntent: (Intent) -> Unit): Boolean {
val url = webRequestUri ?: return false
if (url.scheme?.startsWith("http") == true || url.scheme == StripeApi.RETURN_URL_SCHEME) {
if (!force && (url.scheme?.startsWith("http") == true || url.scheme == StripeApi.RETURN_URL_SCHEME)) {
return false
}

View File

@@ -91,7 +91,8 @@ class Stripe3DSDialogFragment : DialogFragment(R.layout.donation_webview_fragmen
setOnClickListener {
ExternalNavigationHelper.maybeLaunchExternalNavigationIntent(
context,
args.uri
args.uri,
force = true
) {
handleLaunchExternal(it)
}
@@ -125,7 +126,11 @@ class Stripe3DSDialogFragment : DialogFragment(R.layout.donation_webview_fragmen
private inner class Stripe3DSWebClient : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
return ExternalNavigationHelper.maybeLaunchExternalNavigationIntent(requireContext(), request?.url, this@Stripe3DSDialogFragment::handleLaunchExternal)
return ExternalNavigationHelper.maybeLaunchExternalNavigationIntent(
context = requireContext(),
webRequestUri = request?.url,
launchIntent = this@Stripe3DSDialogFragment::handleLaunchExternal
)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {

View File

@@ -7,9 +7,14 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.signal.core.util.dp
import org.signal.core.util.money.FiatMoney
import org.signal.donations.InAppPaymentType
@@ -25,6 +30,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.DonationS
import org.thoughtcrime.securesms.components.settings.app.subscription.completed.InAppPaymentsBottomSheetDelegate
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.CheckoutFlowActivity
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
import org.thoughtcrime.securesms.components.settings.app.subscription.thanks.ThanksForYourSupportBottomSheetDialogFragment
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle
import org.thoughtcrime.securesms.database.model.databaseprotos.DonationErrorValue
@@ -82,6 +88,14 @@ class ManageDonationsFragment :
if (savedInstanceState == null && args.directToCheckoutType != InAppPaymentType.UNKNOWN) {
launcher.launch(args.directToCheckoutType)
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
viewModel.displayThanksBottomSheetPulse.collectLatest {
ThanksForYourSupportBottomSheetDialogFragment.create(it).show(parentFragmentManager, ThanksForYourSupportBottomSheetDialogFragment.SHEET_TAG)
}
}
}
}
override fun onResume() {

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.subscription.manage
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import org.thoughtcrime.securesms.database.DatabaseObserver.InAppPaymentObserver
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.AppDependencies
object ManageDonationsRepository {
/**
* Emits any time we see a successfully completed IDEAL payment that we've not notified the user about.
*/
fun consumeSuccessfulIdealPayments(): Flow<InAppPaymentTable.InAppPayment> {
return callbackFlow {
val observer = InAppPaymentObserver {
if (it.state == InAppPaymentTable.State.END &&
it.data.error == null &&
it.data.paymentMethodType == InAppPaymentData.PaymentMethodType.IDEAL &&
!it.notified
) {
trySendBlocking(it)
SignalDatabase.inAppPayments.update(
it.copy(notified = true)
)
}
}
AppDependencies.databaseObserver.registerInAppPaymentObserver(observer)
awaitClose { AppDependencies.databaseObserver.unregisterObserver(observer) }
}
}
}

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.manage
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
@@ -9,9 +10,15 @@ import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.signal.core.util.logging.Log
import org.signal.core.util.orNull
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
import org.thoughtcrime.securesms.database.SignalDatabase
@@ -31,6 +38,9 @@ class ManageDonationsViewModel : ViewModel() {
private val networkDisposable: Disposable
val state: LiveData<ManageDonationsState> = store.stateLiveData
private val internalDisplayThanksBottomSheetPulse = MutableSharedFlow<Badge>()
val displayThanksBottomSheetPulse: SharedFlow<Badge> = internalDisplayThanksBottomSheetPulse
init {
store.update(Recipient.self().live().liveDataResolved) { self, state ->
@@ -45,6 +55,13 @@ class ManageDonationsViewModel : ViewModel() {
retry()
}
}
viewModelScope.launch {
ManageDonationsRepository.consumeSuccessfulIdealPayments()
.collectLatest {
internalDisplayThanksBottomSheetPulse.emit(Badges.fromDatabaseBadge(it.data.badge!!))
}
}
}
override fun onCleared() {

View File

@@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.animation.AnimationCompleteListener
import org.thoughtcrime.securesms.badges.BadgeImageView
import org.thoughtcrime.securesms.badges.BadgeRepository
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -30,6 +31,19 @@ import org.thoughtcrime.securesms.util.visible
class ThanksForYourSupportBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
companion object {
private val TAG = Log.tag(ThanksForYourSupportBottomSheetDialogFragment::class.java)
const val SHEET_TAG = "ThanksForYourSupportBottomSheet"
fun create(badge: Badge): ThanksForYourSupportBottomSheetDialogFragment {
val args = ThanksForYourSupportBottomSheetDialogFragmentArgs.Builder(badge).build().toBundle()
val sheet = ThanksForYourSupportBottomSheetDialogFragment()
sheet.arguments = args
return sheet
}
}
override val peekHeightPercentage: Float = 1f
private lateinit var switch: MaterialSwitch
@@ -156,10 +170,6 @@ class ThanksForYourSupportBottomSheetDialogFragment : FixedRoundedCornerBottomSh
}
}
companion object {
private val TAG = Log.tag(ThanksForYourSupportBottomSheetDialogFragment::class.java)
}
interface Callback {
fun onBoostThanksSheetDismissed()
}