Implement donations one-time pending state.

This commit is contained in:
Alex Hart
2023-10-16 12:58:20 -04:00
committed by Cody Henthorne
parent 57135ea2c6
commit 627c47b155
21 changed files with 429 additions and 101 deletions

View File

@@ -14,8 +14,10 @@ import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequestContext
import org.signal.libsignal.zkgroup.receipts.ReceiptSerial
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.PendingOneTimeDonationSerializer.isExpired
import org.thoughtcrime.securesms.database.model.databaseprotos.BadgeList
import org.thoughtcrime.securesms.database.model.databaseprotos.DonationCompletedQueue
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.SubscriptionReceiptRequestResponseJob
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
@@ -29,6 +31,7 @@ import org.whispersystems.signalservice.internal.util.JsonUtil
import java.security.SecureRandom
import java.util.Currency
import java.util.Locale
import java.util.Optional
import java.util.concurrent.TimeUnit
internal class DonationsValues internal constructor(store: KeyValueStore) : SignalStoreValues(store) {
@@ -115,6 +118,12 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
* Popped from whenever we enter the conversation list.
*/
private const val DONATION_COMPLETE_QUEUE = "donation.complete.queue"
/**
* The current one-time donation we are processing, if we are doing so. This is used for showing
* the donation processing / donation pending state in the ManageDonationsFragment.
*/
private const val PENDING_ONE_TIME_DONATION = "pending.one.time.donation"
}
override fun onFirstEverAppLaunch() = Unit
@@ -142,6 +151,14 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
private val oneTimeCurrencyPublisher: Subject<Currency> by lazy { BehaviorSubject.createDefault(getOneTimeCurrency()) }
val observableOneTimeCurrency: Observable<Currency> by lazy { oneTimeCurrencyPublisher }
private var _pendingOneTimeDonation: PendingOneTimeDonation? by protoValue(PENDING_ONE_TIME_DONATION, PendingOneTimeDonation.ADAPTER)
private val pendingOneTimeDonationPublisher: Subject<Optional<PendingOneTimeDonation>> by lazy { BehaviorSubject.createDefault(Optional.ofNullable(_pendingOneTimeDonation)) }
val observablePendingOneTimeDonation: Observable<Optional<PendingOneTimeDonation>> by lazy {
pendingOneTimeDonationPublisher.map { optionalPendingOneTimeDonation ->
optionalPendingOneTimeDonation.filter { !it.isExpired }
}
}
fun getSubscriptionCurrency(): Currency {
val currencyCode = getString(KEY_SUBSCRIPTION_CURRENCY_CODE, null)
val currency: Currency? = if (currencyCode == null) {
@@ -501,6 +518,13 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
}
}
fun getPendingOneTimeDonation(): PendingOneTimeDonation? = _pendingOneTimeDonation.takeUnless { it?.isExpired == true }
fun setPendingOneTimeDonation(pendingOneTimeDonation: PendingOneTimeDonation?) {
this._pendingOneTimeDonation = pendingOneTimeDonation
pendingOneTimeDonationPublisher.onNext(Optional.ofNullable(pendingOneTimeDonation))
}
private fun generateRequestCredential(): ReceiptCredentialRequestContext {
Log.d(TAG, "Generating request credentials context for token redemption...", true)
val secureRandom = SecureRandom()

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.keyvalue
import com.squareup.wire.ProtoAdapter
import org.signal.core.util.LongSerializer
import kotlin.reflect.KProperty
@@ -31,6 +32,10 @@ internal fun <T : Any?> SignalStoreValues.enumValue(key: String, default: T, ser
return KeyValueEnumValue(key, default, serializer, this.store)
}
internal fun <M> SignalStoreValues.protoValue(key: String, adapter: ProtoAdapter<M>): SignalStoreValueDelegate<M?> {
return KeyValueProtoValue(key, adapter, this.store)
}
/**
* Kotlin delegate that serves as a base for all other value types. This allows us to only expose this sealed
* class to callers and protect the individual implementations as private behind the various extension functions.
@@ -109,6 +114,28 @@ private class BlobValue(private val key: String, private val default: ByteArray,
}
}
private class KeyValueProtoValue<M>(
private val key: String,
private val adapter: ProtoAdapter<M>,
store: KeyValueStore
) : SignalStoreValueDelegate<M?>(store) {
override fun getValue(values: KeyValueStore): M? {
return if (values.containsKey(key)) {
adapter.decode(values.getBlob(key, null))
} else {
null
}
}
override fun setValue(values: KeyValueStore, value: M?) {
if (value != null) {
values.beginWrite().putBlob(key, adapter.encode(value)).apply()
} else {
values.beginWrite().remove(key).apply()
}
}
}
private class KeyValueEnumValue<T>(private val key: String, private val default: T, private val serializer: LongSerializer<T>, store: KeyValueStore) : SignalStoreValueDelegate<T>(store) {
override fun getValue(values: KeyValueStore): T {
return if (values.containsKey(key)) {