Add whoami check for receipt_credentials.
This commit is contained in:
parent
0dbab7ede0
commit
23f90e070e
16 changed files with 300 additions and 69 deletions
|
@ -61,11 +61,11 @@ object MockProvider {
|
|||
}
|
||||
|
||||
fun createWhoAmIResponse(aci: ServiceId, pni: ServiceId, e164: String): WhoAmIResponse {
|
||||
return WhoAmIResponse().apply {
|
||||
this.uuid = aci.toString()
|
||||
this.pni = pni.toString()
|
||||
this.number = e164
|
||||
}
|
||||
return WhoAmIResponse(
|
||||
aci = aci.toString(),
|
||||
pni = pni.toString(),
|
||||
number = e164
|
||||
)
|
||||
}
|
||||
|
||||
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account.aciIdentityKey, deviceId: Int): PreKeyResponse {
|
||||
|
|
|
@ -202,6 +202,11 @@ object BackupRepository {
|
|||
return alertAfter <= now
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun shouldDisplayBackupAlreadyRedeemedIndicator(): Boolean {
|
||||
return !(shouldNotDisplayBackupFailedMessaging() || !SignalStore.backup.hasBackupAlreadyRedeemedError)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the "Backup Failed" row should be displayed in settings.
|
||||
* Shown when the initial backup creation has failed
|
||||
|
@ -226,6 +231,10 @@ object BackupRepository {
|
|||
return SignalStore.backup.hasBackupBeenUploaded && SignalStore.backup.hasBackupFailure
|
||||
}
|
||||
|
||||
fun markBackupAlreadyRedeemedIndicatorClicked() {
|
||||
SignalStore.backup.hasBackupAlreadyRedeemedError = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the watermark for the indicator display.
|
||||
*/
|
||||
|
|
|
@ -12,11 +12,16 @@ import androidx.compose.foundation.Image
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -31,11 +36,12 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -47,6 +53,7 @@ import org.signal.core.ui.BottomSheets
|
|||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
||||
|
@ -125,6 +132,7 @@ class BackupAlertBottomSheet : UpgradeToPaidTierBottomSheet() {
|
|||
is BackupAlert.MediaBackupsAreOff -> {
|
||||
onSubscribeClick()
|
||||
}
|
||||
|
||||
BackupAlert.MediaWillBeDeletedToday -> {
|
||||
performFullMediaDownload()
|
||||
}
|
||||
|
@ -132,6 +140,8 @@ class BackupAlertBottomSheet : UpgradeToPaidTierBottomSheet() {
|
|||
is BackupAlert.DiskFull -> Unit
|
||||
is BackupAlert.BackupFailed ->
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext())
|
||||
|
||||
BackupAlert.CouldNotRedeemBackup -> Unit
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
|
@ -152,6 +162,7 @@ class BackupAlertBottomSheet : UpgradeToPaidTierBottomSheet() {
|
|||
}
|
||||
// TODO [backups] - Update support URL with backups page
|
||||
BackupAlert.BackupFailed -> CommunicationActions.openBrowserLink(requireContext(), requireContext().getString(R.string.backup_support_url))
|
||||
BackupAlert.CouldNotRedeemBackup -> CommunicationActions.openBrowserLink(requireContext(), requireContext().getString(R.string.backup_support_url)) // TODO [backups] final url
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
|
@ -224,14 +235,14 @@ private fun BackupAlertSheetContent(
|
|||
BackupAlert.FailedToRenew, is BackupAlert.MediaBackupsAreOff -> {
|
||||
Box {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.image_signal_backups),
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.image_signal_backups),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
.padding(2.dp)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_error_circle_fill_24),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_error_circle_fill_24),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.align(Alignment.TopEnd)
|
||||
|
@ -242,7 +253,7 @@ private fun BackupAlertSheetContent(
|
|||
else -> {
|
||||
val iconColors = rememberBackupsIconColors(backupAlert = backupAlert)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.symbol_backup_light),
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_backup_light),
|
||||
contentDescription = null,
|
||||
tint = iconColors.foreground,
|
||||
modifier = Modifier
|
||||
|
@ -270,6 +281,7 @@ private fun BackupAlertSheetContent(
|
|||
BackupAlert.MediaWillBeDeletedToday -> MediaWillBeDeletedTodayBody()
|
||||
is BackupAlert.DiskFull -> DiskFullBody(requiredSpace = backupAlert.requiredSpace)
|
||||
BackupAlert.BackupFailed -> BackupFailedBody()
|
||||
BackupAlert.CouldNotRedeemBackup -> CouldNotRedeemBackup()
|
||||
}
|
||||
|
||||
val secondaryActionResource = rememberSecondaryActionResource(backupAlert = backupAlert)
|
||||
|
@ -297,6 +309,57 @@ private fun BackupAlertSheetContent(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CouldNotRedeemBackup() {
|
||||
Text(
|
||||
text = stringResource(R.string.BackupAlertBottomSheet__too_many_devices_have_tried),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(IntrinsicSize.Min)
|
||||
.padding(horizontal = 35.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(4.dp)
|
||||
.fillMaxHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
.background(color = SignalTheme.colors.colorTransparentInverse2)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.BackupAlertBottomSheet__reregistered_your_signal_account),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(IntrinsicSize.Min)
|
||||
.padding(horizontal = 35.dp)
|
||||
.padding(top = 12.dp, bottom = 40.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(4.dp)
|
||||
.fillMaxHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
.background(color = SignalTheme.colors.colorTransparentInverse2)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.BackupAlertBottomSheet__have_too_many_devices_using_the_same_subscription),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CouldNotCompleteBackup(
|
||||
daysSinceLastBackup: Int
|
||||
|
@ -390,7 +453,7 @@ private fun rememberBackupsIconColors(backupAlert: BackupAlert): BackupsIconColo
|
|||
return remember(backupAlert) {
|
||||
when (backupAlert) {
|
||||
BackupAlert.FailedToRenew, is BackupAlert.MediaBackupsAreOff -> error("Not icon-based options.")
|
||||
is BackupAlert.CouldNotCompleteBackup, BackupAlert.BackupFailed, is BackupAlert.DiskFull -> BackupsIconColors.Warning
|
||||
is BackupAlert.CouldNotCompleteBackup, BackupAlert.BackupFailed, is BackupAlert.DiskFull, BackupAlert.CouldNotRedeemBackup -> BackupsIconColors.Warning
|
||||
BackupAlert.MediaWillBeDeletedToday -> BackupsIconColors.Error
|
||||
}
|
||||
}
|
||||
|
@ -405,6 +468,7 @@ private fun titleString(backupAlert: BackupAlert): String {
|
|||
BackupAlert.MediaWillBeDeletedToday -> stringResource(R.string.BackupAlertBottomSheet__your_media_will_be_deleted_today)
|
||||
is BackupAlert.DiskFull -> stringResource(R.string.BackupAlertBottomSheet__free_up_s_on_this_device, backupAlert.requiredSpace)
|
||||
BackupAlert.BackupFailed -> stringResource(R.string.BackupAlertBottomSheet__backup_failed)
|
||||
BackupAlert.CouldNotRedeemBackup -> stringResource(R.string.BackupAlertBottomSheet__couldnt_redeem_your_backups_subscription)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,6 +484,7 @@ private fun primaryActionString(
|
|||
BackupAlert.MediaWillBeDeletedToday -> stringResource(R.string.BackupAlertBottomSheet__download_media_now)
|
||||
is BackupAlert.DiskFull -> stringResource(R.string.BackupAlertBottomSheet__got_it)
|
||||
is BackupAlert.BackupFailed -> stringResource(R.string.BackupAlertBottomSheet__check_for_update)
|
||||
BackupAlert.CouldNotRedeemBackup -> stringResource(R.string.BackupAlertBottomSheet__got_it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,6 +498,7 @@ private fun rememberSecondaryActionResource(backupAlert: BackupAlert): Int {
|
|||
BackupAlert.MediaWillBeDeletedToday -> R.string.BackupAlertBottomSheet__dont_download_media
|
||||
is BackupAlert.DiskFull -> R.string.BackupAlertBottomSheet__skip_restore
|
||||
is BackupAlert.BackupFailed -> R.string.BackupAlertBottomSheet__learn_more
|
||||
BackupAlert.CouldNotRedeemBackup -> R.string.BackupAlertBottomSheet__learn_more
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -504,6 +570,17 @@ private fun BackupAlertSheetContentPreviewBackupFailed() {
|
|||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun BackupAlertSheetContentPreviewCouldNotRedeemBackup() {
|
||||
Previews.BottomSheetPreview {
|
||||
BackupAlertSheetContent(
|
||||
backupAlert = BackupAlert.CouldNotRedeemBackup,
|
||||
mediaTtl = 60.days
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All necessary information to display the sheet should be handed in through the specific alert.
|
||||
*/
|
||||
|
@ -547,4 +624,9 @@ sealed class BackupAlert : Parcelable {
|
|||
*
|
||||
*/
|
||||
data class DiskFull(val requiredSpace: String) : BackupAlert()
|
||||
|
||||
/**
|
||||
* Too many attempts to redeem the backup subscription have occurred this month.
|
||||
*/
|
||||
data object CouldNotRedeemBackup : BackupAlert()
|
||||
}
|
||||
|
|
|
@ -231,6 +231,22 @@ private fun AppSettingsContent(
|
|||
}
|
||||
}
|
||||
|
||||
BackupFailureState.ALREADY_REDEEMED -> {
|
||||
item {
|
||||
Dividers.Default()
|
||||
|
||||
BackupsWarningRow(
|
||||
text = stringResource(R.string.AppSettingsFragment__couldnt_redeem_your_backups_subscription),
|
||||
onClick = {
|
||||
BackupRepository.markBackupAlreadyRedeemedIndicatorClicked()
|
||||
callbacks.navigate(R.id.action_appSettingsFragment_to_remoteBackupsSettingsFragment)
|
||||
}
|
||||
)
|
||||
|
||||
Dividers.Default()
|
||||
}
|
||||
}
|
||||
|
||||
BackupFailureState.NONE -> Unit
|
||||
}
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ class AppSettingsViewModel : ViewModel() {
|
|||
BackupFailureState.COULD_NOT_COMPLETE_BACKUP
|
||||
} else if (SignalStore.backup.subscriptionStateMismatchDetected) {
|
||||
BackupFailureState.SUBSCRIPTION_STATE_MISMATCH
|
||||
} else if (SignalStore.backup.hasBackupAlreadyRedeemedError) {
|
||||
BackupFailureState.ALREADY_REDEEMED
|
||||
} else {
|
||||
BackupFailureState.NONE
|
||||
}
|
||||
|
|
|
@ -12,5 +12,6 @@ enum class BackupFailureState {
|
|||
NONE,
|
||||
BACKUP_FAILED,
|
||||
COULD_NOT_COMPLETE_BACKUP,
|
||||
SUBSCRIPTION_STATE_MISMATCH
|
||||
SUBSCRIPTION_STATE_MISMATCH,
|
||||
ALREADY_REDEEMED
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import androidx.compose.material3.BasicAlertDialog
|
|||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
|
@ -153,7 +154,8 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
|||
backupProgress = backupProgress,
|
||||
backupSize = state.backupSize,
|
||||
backupState = state.backupState,
|
||||
backupRestoreState = restoreState
|
||||
backupRestoreState = restoreState,
|
||||
hasRedemptionError = state.hasRedemptionError
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -248,6 +250,10 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
|||
override fun onRestoreUsingCellularClick(canUseCellular: Boolean) {
|
||||
viewModel.setCanRestoreUsingCellular(canUseCellular)
|
||||
}
|
||||
|
||||
override fun onRedemptionErrorDetailsClick() {
|
||||
BackupAlertBottomSheet.create(BackupAlert.CouldNotRedeemBackup).show(parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayBackupKey() {
|
||||
|
@ -331,6 +337,7 @@ private interface ContentCallbacks {
|
|||
fun onContactSupport() = Unit
|
||||
fun onLearnMoreAboutBackupFailure() = Unit
|
||||
fun onRestoreUsingCellularClick(canUseCellular: Boolean) = Unit
|
||||
fun onRedemptionErrorDetailsClick() = Unit
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -346,7 +353,8 @@ private fun RemoteBackupsSettingsContent(
|
|||
requestedSnackbar: RemoteBackupsSettingsState.Snackbar,
|
||||
contentCallbacks: ContentCallbacks,
|
||||
backupProgress: ArchiveUploadProgressState?,
|
||||
backupSize: Long
|
||||
backupSize: Long,
|
||||
hasRedemptionError: Boolean
|
||||
) {
|
||||
val snackbarHostState = remember {
|
||||
SnackbarHostState()
|
||||
|
@ -364,6 +372,12 @@ private fun RemoteBackupsSettingsContent(
|
|||
modifier = Modifier
|
||||
.padding(it)
|
||||
) {
|
||||
if (hasRedemptionError) {
|
||||
item {
|
||||
RedemptionErrorAlert(onDetailsClick = contentCallbacks::onRedemptionErrorDetailsClick)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
AnimatedContent(backupState, label = "backup-state-block") { state ->
|
||||
when (state) {
|
||||
|
@ -771,6 +785,42 @@ private fun BackupCard(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RedemptionErrorAlert(
|
||||
onDetailsClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 8.dp, bottom = 4.dp)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(R.color.signal_colorOutline_38),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(vertical = 16.dp)
|
||||
.padding(start = 16.dp, end = 12.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.symbol_backup_error_24),
|
||||
tint = Color(0xFFFF9500),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.AppSettingsFragment__couldnt_redeem_your_backups_subscription),
|
||||
modifier = Modifier.padding(start = 16.dp, end = 4.dp).weight(1f)
|
||||
)
|
||||
|
||||
Buttons.Small(onClick = onDetailsClick) {
|
||||
Text(
|
||||
text = stringResource(R.string.RemoteBackupsSettingsFragment__details)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxCard(content: @Composable () -> Unit) {
|
||||
Box(
|
||||
|
@ -988,6 +1038,7 @@ private fun getBackupPhaseMessage(state: ArchiveUploadProgressState): String {
|
|||
(progress.progress * 100).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
else -> stringResource(R.string.RemoteBackupsSettingsFragment__preparing_backup)
|
||||
}
|
||||
}
|
||||
|
@ -1272,11 +1323,20 @@ private fun RemoteBackupsSettingsContentPreview() {
|
|||
backupState = RemoteBackupsSettingsState.BackupState.ActiveFree(
|
||||
messageBackupsType = MessageBackupsType.Free(mediaRetentionDays = 30)
|
||||
),
|
||||
backupRestoreState = BackupRestoreState.FromBackupStatusData(BackupStatusData.CouldNotCompleteBackup)
|
||||
backupRestoreState = BackupRestoreState.FromBackupStatusData(BackupStatusData.CouldNotCompleteBackup),
|
||||
hasRedemptionError = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun RedemptionErrorAlertPreview() {
|
||||
Previews.Preview {
|
||||
RedemptionErrorAlert { }
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun LoadingCardPreview() {
|
||||
|
|
|
@ -15,6 +15,7 @@ data class RemoteBackupsSettingsState(
|
|||
val backupsEnabled: Boolean,
|
||||
val canBackUpUsingCellular: Boolean = false,
|
||||
val canRestoreUsingCellular: Boolean = false,
|
||||
val hasRedemptionError: Boolean = false,
|
||||
val backupState: BackupState = BackupState.Loading,
|
||||
val backupSize: Long = 0,
|
||||
val backupsFrequency: BackupFrequency = BackupFrequency.DAILY,
|
||||
|
|
|
@ -240,6 +240,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
|||
Log.d(TAG, "Subscription found. Updating UI state with subscription details.")
|
||||
_state.update {
|
||||
it.copy(
|
||||
hasRedemptionError = lastPurchase?.data?.error?.data_ == "409",
|
||||
backupState = when {
|
||||
subscription.isActive -> RemoteBackupsSettingsState.BackupState.ActivePaid(
|
||||
messageBackupsType = type,
|
||||
|
|
|
@ -23,18 +23,17 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.thanks.Th
|
|||
import org.thoughtcrime.securesms.components.settings.app.subscription.thanks.ThanksForYourSupportBottomSheetDialogFragmentArgs
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DonationErrorValue
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
|
||||
/**
|
||||
* Handles displaying bottom sheets for in-app payments. The current policy is to "fire and forget".
|
||||
*/
|
||||
class InAppPaymentsBottomSheetDelegate(
|
||||
private val fragmentManager: FragmentManager,
|
||||
private val lifecycleOwner: LifecycleOwner,
|
||||
private vararg val supportedTypes: InAppPaymentSubscriberRecord.Type = arrayOf(InAppPaymentSubscriberRecord.Type.DONATION)
|
||||
private val lifecycleOwner: LifecycleOwner
|
||||
) : DefaultLifecycleObserver {
|
||||
|
||||
companion object {
|
||||
|
@ -56,13 +55,11 @@ class InAppPaymentsBottomSheetDelegate(
|
|||
private val badgeRepository = TerminalDonationRepository()
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
if (InAppPaymentSubscriberRecord.Type.DONATION in supportedTypes) {
|
||||
handleLegacyTerminalDonationSheets()
|
||||
handleLegacyVerifiedMonthlyDonationSheets()
|
||||
handleInAppPaymentDonationSheets()
|
||||
}
|
||||
handleLegacyTerminalDonationSheets()
|
||||
handleLegacyVerifiedMonthlyDonationSheets()
|
||||
handleInAppPaymentDonationSheets()
|
||||
|
||||
if (InAppPaymentSubscriberRecord.Type.BACKUP in supportedTypes) {
|
||||
if (RemoteConfig.messageBackups) {
|
||||
handleInAppPaymentBackupsSheets()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,7 +173,47 @@ class InAppPaymentRecurringContextJob private constructor(
|
|||
inAppPayment
|
||||
}
|
||||
|
||||
submitAndValidateCredentials(updatedInAppPayment, subscription, requestContext)
|
||||
if (hasEntitlementAlready(inAppPayment, subscription.endOfCurrentPeriod)) {
|
||||
info("Already have entitlement for this badge. Marking complete.")
|
||||
markInAppPaymentCompleted(inAppPayment)
|
||||
} else {
|
||||
submitAndValidateCredentials(updatedInAppPayment, subscription, requestContext)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasEntitlementAlready(
|
||||
inAppPayment: InAppPaymentTable.InAppPayment,
|
||||
endOfCurrentSubscriptionPeriod: Long
|
||||
): Boolean {
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
val whoAmIResponse = AppDependencies.signalServiceAccountManager.getWhoAmI()
|
||||
|
||||
return when (inAppPayment.type) {
|
||||
InAppPaymentType.RECURRING_BACKUP -> {
|
||||
val backupExpirationSeconds = whoAmIResponse.entitlements?.backup?.expirationSeconds ?: return false
|
||||
|
||||
backupExpirationSeconds >= endOfCurrentSubscriptionPeriod
|
||||
}
|
||||
|
||||
InAppPaymentType.RECURRING_DONATION -> {
|
||||
val donationExpirationSeconds = whoAmIResponse.entitlements?.badges?.firstOrNull { it.id == inAppPayment.data.badge?.id }?.expirationSeconds ?: return false
|
||||
|
||||
donationExpirationSeconds >= endOfCurrentSubscriptionPeriod
|
||||
}
|
||||
|
||||
else -> error("Unsupported IAP type ${inAppPayment.type}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun markInAppPaymentCompleted(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
SignalDatabase.inAppPayments.update(
|
||||
inAppPayment = inAppPayment.copy(
|
||||
state = InAppPaymentTable.State.END,
|
||||
data = inAppPayment.data.copy(
|
||||
redemption = InAppPaymentData.RedemptionState(stage = InAppPaymentData.RedemptionState.Stage.REDEEMED)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAndValidateInAppPayment(): Pair<InAppPaymentTable.InAppPayment, ReceiptCredentialRequestContext> {
|
||||
|
@ -435,18 +475,13 @@ class InAppPaymentRecurringContextJob private constructor(
|
|||
}
|
||||
|
||||
409 -> {
|
||||
if (isForKeepAlive) {
|
||||
warning("Already redeemed this token during keep-alive, ignoring.", applicationError)
|
||||
SignalDatabase.inAppPayments.update(
|
||||
inAppPayment.copy(
|
||||
state = InAppPaymentTable.State.END,
|
||||
data = inAppPayment.data.copy(redemption = inAppPayment.data.redemption.copy(stage = InAppPaymentData.RedemptionState.Stage.REDEEMED))
|
||||
)
|
||||
)
|
||||
} else {
|
||||
warning("Already redeemed this token during new subscription. Failing.", applicationError)
|
||||
updateInAppPaymentWithGenericRedemptionError(inAppPayment)
|
||||
warning("Already redeemed this token during new subscription. Failing.", applicationError)
|
||||
|
||||
if (inAppPayment.type == InAppPaymentType.RECURRING_BACKUP) {
|
||||
SignalStore.backup.hasBackupAlreadyRedeemedError = true
|
||||
}
|
||||
|
||||
updateInAppPaymentWithTokenAlreadyRedeemedError(inAppPayment)
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
@ -520,6 +555,20 @@ class InAppPaymentRecurringContextJob private constructor(
|
|||
return isSameLevel && isExpirationAfterSub && isExpiration86400 && isExpirationInTheFuture && isExpirationWithinMax
|
||||
}
|
||||
|
||||
private fun updateInAppPaymentWithTokenAlreadyRedeemedError(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
SignalDatabase.inAppPayments.update(
|
||||
inAppPayment = inAppPayment.copy(
|
||||
state = InAppPaymentTable.State.END,
|
||||
data = inAppPayment.data.copy(
|
||||
error = InAppPaymentData.Error(
|
||||
type = InAppPaymentData.Error.Type.REDEMPTION,
|
||||
data_ = "409"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateInAppPaymentWithGenericRedemptionError(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
SignalDatabase.inAppPayments.update(
|
||||
inAppPayment = inAppPayment.copy(
|
||||
|
|
|
@ -63,6 +63,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
|||
private const val KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_COUNT = "backup.failed.acknowledged.snooze.count"
|
||||
private const val KEY_BACKUP_FAIL_SHEET_SNOOZE_TIME = "backup.failed.sheet.snooze"
|
||||
private const val KEY_BACKUP_FAIL_SPACE_REMAINING = "backup.failed.space.remaining"
|
||||
private const val KEY_BACKUP_ALREADY_REDEEMED = "backup.already.redeemed"
|
||||
|
||||
private const val KEY_USER_MANUALLY_SKIPPED_MEDIA_RESTORE = "backup.user.manually.skipped.media.restore"
|
||||
|
||||
|
@ -209,6 +210,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
|||
val nextBackupFailureSnoozeTime: Duration get() = getLong(KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_TIME, 0L).milliseconds
|
||||
val nextBackupFailureSheetSnoozeTime: Duration get() = getLong(KEY_BACKUP_FAIL_SHEET_SNOOZE_TIME, getNextBackupFailureSheetSnoozeTime(lastBackupTime.milliseconds).inWholeMilliseconds).milliseconds
|
||||
|
||||
var hasBackupAlreadyRedeemedError: Boolean by booleanValue(KEY_BACKUP_ALREADY_REDEEMED, false)
|
||||
|
||||
/**
|
||||
* Denotes how many bytes are still available on the disk for writing. Used to display
|
||||
* the disk full error and sheet. Set when we believe there might be an "out of space"
|
||||
|
|
|
@ -166,7 +166,7 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
|||
super.onResume()
|
||||
SimpleTask.run(viewLifecycleOwner.lifecycle, { Recipient.self() }, ::initializeProfileIcon)
|
||||
|
||||
_backupsFailedDot.alpha = if (BackupRepository.shouldDisplayBackupFailedIndicator()) {
|
||||
_backupsFailedDot.alpha = if (BackupRepository.shouldDisplayBackupFailedIndicator() || BackupRepository.shouldDisplayBackupAlreadyRedeemedIndicator()) {
|
||||
1f
|
||||
} else {
|
||||
0f
|
||||
|
|
|
@ -5015,6 +5015,8 @@
|
|||
<string name="AppSettingsFragment__renew_your_signal_backups_subscription">Renew your Signal Backups subscription</string>
|
||||
<!-- String alerting user that backup failed -->
|
||||
<string name="AppSettingsFragment__couldnt_complete_backup">Couldn\'t complete backup</string>
|
||||
<!-- String alerting user that backup redemption -->
|
||||
<string name="AppSettingsFragment__couldnt_redeem_your_backups_subscription">Couldn\'t redeem your backups subscription</string>
|
||||
<!-- String displayed telling user to invite their friends to Signal -->
|
||||
<string name="AppSettingsFragment__invite_your_friends">Invite your friends</string>
|
||||
<!-- String displayed in a toast when we successfully copy the donations subscriber id to the clipboard -->
|
||||
|
@ -7539,10 +7541,18 @@
|
|||
<string name="BackupAlertBottomSheet__if_you_skip_restore_the">If you skip restore the remaining media and attachments in your backup can be downloaded at a later time when storage space becomes available.</string>
|
||||
<!-- Dialog title when a backup fails to be created -->
|
||||
<string name="BackupAlertBottomSheet__backup_failed">Backup failed</string>
|
||||
<!-- Dialog title when a backup redemption fails -->
|
||||
<string name="BackupAlertBottomSheet__couldnt_redeem_your_backups_subscription">Couldn\'t redeem your backups subscription</string>
|
||||
<!-- Dialog text for when a backup fails to be created and ways to fix it -->
|
||||
<string name="BackupAlertBottomSheet__an_error_occurred">An error occurred and your backup could not be completed. Make sure you\'re on the latest version of Signal and try again. If this problem persists, contact support.</string>
|
||||
<!-- Dialog action button that will allow you to check for any Signal version updates -->
|
||||
<string name="BackupAlertBottomSheet__check_for_update">Check for update</string>
|
||||
<!-- Backup redemption error sheet text line 1 -->
|
||||
<string name="BackupAlertBottomSheet__too_many_devices_have_tried">Too many devices have tried to redeem your subscription this month. You may have:</string>
|
||||
<!-- Backup redemption error sheet bullet point 1 -->
|
||||
<string name="BackupAlertBottomSheet__reregistered_your_signal_account">Re-registered your Signal account too many times.</string>
|
||||
<!-- Backup redemption error sheet bullet point 2 -->
|
||||
<string name="BackupAlertBottomSheet__have_too_many_devices_using_the_same_subscription">Have too many devices using the same subscription.</string>
|
||||
|
||||
<!-- BackupStatus -->
|
||||
<!-- Status title when user does not have enough free space to download their media. Placeholder is required disk space. -->
|
||||
|
@ -7824,6 +7834,8 @@
|
|||
<string name="RemoteBackupsSettingsFragment__a_network_error_occurred">A network error occurred. Please check your internet connection and try again.</string>
|
||||
<!-- Progress message when backup file is being uploaded. First placeholder and second placeholder are formatted byte sizes (2 MB) and third is percent completion. -->
|
||||
<string name="RemoteBackupsSettingsFragment__uploading_s_of_s_d">Uploading: %1$s of %2$s (%3$d%%)</string>
|
||||
<!-- Button label to see more details about redemption error -->
|
||||
<string name="RemoteBackupsSettingsFragment__details">Details</string>
|
||||
|
||||
<!-- SubscriptionNotFoundBottomSheet -->
|
||||
<!-- Displayed as a bottom sheet title -->
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class WhoAmIResponse {
|
||||
@JsonProperty
|
||||
public String uuid;
|
||||
|
||||
@JsonProperty
|
||||
public String pni;
|
||||
|
||||
@JsonProperty
|
||||
public String number;
|
||||
|
||||
@JsonProperty
|
||||
public String usernameHash;
|
||||
|
||||
public String getAci() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getPni() {
|
||||
return pni;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public String getUsernameHash() {
|
||||
return usernameHash;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.whispersystems.signalservice.internal.push
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
/**
|
||||
* Response object for /v1/accounts/whoami
|
||||
*/
|
||||
data class WhoAmIResponse @JsonCreator constructor(
|
||||
@JsonProperty("uuid") val aci: String? = null,
|
||||
@JsonProperty val pni: String? = null,
|
||||
@JsonProperty val number: String,
|
||||
@JsonProperty val usernameHash: String? = null,
|
||||
@JsonProperty val entitlements: Entitlements? = null
|
||||
) {
|
||||
data class Entitlements @JsonCreator constructor(
|
||||
@JsonProperty val badges: List<BadgeEntitlement>? = null,
|
||||
@JsonProperty val backup: BackupEntitlement? = null
|
||||
)
|
||||
|
||||
data class BadgeEntitlement @JsonCreator constructor(
|
||||
@JsonProperty val id: String?,
|
||||
@JsonProperty val visible: Boolean?,
|
||||
@JsonProperty val expirationSeconds: Long?
|
||||
)
|
||||
|
||||
data class BackupEntitlement @JsonCreator constructor(
|
||||
@JsonProperty val backupLevel: Long?,
|
||||
@JsonProperty val expirationSeconds: Long?
|
||||
)
|
||||
}
|
Loading…
Add table
Reference in a new issue