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 {
|
fun createWhoAmIResponse(aci: ServiceId, pni: ServiceId, e164: String): WhoAmIResponse {
|
||||||
return WhoAmIResponse().apply {
|
return WhoAmIResponse(
|
||||||
this.uuid = aci.toString()
|
aci = aci.toString(),
|
||||||
this.pni = pni.toString()
|
pni = pni.toString(),
|
||||||
this.number = e164
|
number = e164
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account.aciIdentityKey, deviceId: Int): PreKeyResponse {
|
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account.aciIdentityKey, deviceId: Int): PreKeyResponse {
|
||||||
|
|
|
@ -202,6 +202,11 @@ object BackupRepository {
|
||||||
return alertAfter <= now
|
return alertAfter <= now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun shouldDisplayBackupAlreadyRedeemedIndicator(): Boolean {
|
||||||
|
return !(shouldNotDisplayBackupFailedMessaging() || !SignalStore.backup.hasBackupAlreadyRedeemedError)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the "Backup Failed" row should be displayed in settings.
|
* Whether the "Backup Failed" row should be displayed in settings.
|
||||||
* Shown when the initial backup creation has failed
|
* Shown when the initial backup creation has failed
|
||||||
|
@ -226,6 +231,10 @@ object BackupRepository {
|
||||||
return SignalStore.backup.hasBackupBeenUploaded && SignalStore.backup.hasBackupFailure
|
return SignalStore.backup.hasBackupBeenUploaded && SignalStore.backup.hasBackupFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun markBackupAlreadyRedeemedIndicatorClicked() {
|
||||||
|
SignalStore.backup.hasBackupAlreadyRedeemedError = false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the watermark for the indicator display.
|
* 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.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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.Spacer
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -31,11 +36,12 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat
|
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.Buttons
|
||||||
import org.signal.core.ui.Previews
|
import org.signal.core.ui.Previews
|
||||||
import org.signal.core.ui.SignalPreview
|
import org.signal.core.ui.SignalPreview
|
||||||
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
||||||
|
@ -125,6 +132,7 @@ class BackupAlertBottomSheet : UpgradeToPaidTierBottomSheet() {
|
||||||
is BackupAlert.MediaBackupsAreOff -> {
|
is BackupAlert.MediaBackupsAreOff -> {
|
||||||
onSubscribeClick()
|
onSubscribeClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
BackupAlert.MediaWillBeDeletedToday -> {
|
BackupAlert.MediaWillBeDeletedToday -> {
|
||||||
performFullMediaDownload()
|
performFullMediaDownload()
|
||||||
}
|
}
|
||||||
|
@ -132,6 +140,8 @@ class BackupAlertBottomSheet : UpgradeToPaidTierBottomSheet() {
|
||||||
is BackupAlert.DiskFull -> Unit
|
is BackupAlert.DiskFull -> Unit
|
||||||
is BackupAlert.BackupFailed ->
|
is BackupAlert.BackupFailed ->
|
||||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext())
|
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext())
|
||||||
|
|
||||||
|
BackupAlert.CouldNotRedeemBackup -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissAllowingStateLoss()
|
dismissAllowingStateLoss()
|
||||||
|
@ -152,6 +162,7 @@ class BackupAlertBottomSheet : UpgradeToPaidTierBottomSheet() {
|
||||||
}
|
}
|
||||||
// TODO [backups] - Update support URL with backups page
|
// TODO [backups] - Update support URL with backups page
|
||||||
BackupAlert.BackupFailed -> CommunicationActions.openBrowserLink(requireContext(), requireContext().getString(R.string.backup_support_url))
|
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()
|
dismissAllowingStateLoss()
|
||||||
|
@ -224,14 +235,14 @@ private fun BackupAlertSheetContent(
|
||||||
BackupAlert.FailedToRenew, is BackupAlert.MediaBackupsAreOff -> {
|
BackupAlert.FailedToRenew, is BackupAlert.MediaBackupsAreOff -> {
|
||||||
Box {
|
Box {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.image_signal_backups),
|
imageVector = ImageVector.vectorResource(id = R.drawable.image_signal_backups),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(80.dp)
|
.size(80.dp)
|
||||||
.padding(2.dp)
|
.padding(2.dp)
|
||||||
)
|
)
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.symbol_error_circle_fill_24),
|
imageVector = ImageVector.vectorResource(R.drawable.symbol_error_circle_fill_24),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.error,
|
tint = MaterialTheme.colorScheme.error,
|
||||||
modifier = Modifier.align(Alignment.TopEnd)
|
modifier = Modifier.align(Alignment.TopEnd)
|
||||||
|
@ -242,7 +253,7 @@ private fun BackupAlertSheetContent(
|
||||||
else -> {
|
else -> {
|
||||||
val iconColors = rememberBackupsIconColors(backupAlert = backupAlert)
|
val iconColors = rememberBackupsIconColors(backupAlert = backupAlert)
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.symbol_backup_light),
|
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_backup_light),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = iconColors.foreground,
|
tint = iconColors.foreground,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -270,6 +281,7 @@ private fun BackupAlertSheetContent(
|
||||||
BackupAlert.MediaWillBeDeletedToday -> MediaWillBeDeletedTodayBody()
|
BackupAlert.MediaWillBeDeletedToday -> MediaWillBeDeletedTodayBody()
|
||||||
is BackupAlert.DiskFull -> DiskFullBody(requiredSpace = backupAlert.requiredSpace)
|
is BackupAlert.DiskFull -> DiskFullBody(requiredSpace = backupAlert.requiredSpace)
|
||||||
BackupAlert.BackupFailed -> BackupFailedBody()
|
BackupAlert.BackupFailed -> BackupFailedBody()
|
||||||
|
BackupAlert.CouldNotRedeemBackup -> CouldNotRedeemBackup()
|
||||||
}
|
}
|
||||||
|
|
||||||
val secondaryActionResource = rememberSecondaryActionResource(backupAlert = backupAlert)
|
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
|
@Composable
|
||||||
private fun CouldNotCompleteBackup(
|
private fun CouldNotCompleteBackup(
|
||||||
daysSinceLastBackup: Int
|
daysSinceLastBackup: Int
|
||||||
|
@ -390,7 +453,7 @@ private fun rememberBackupsIconColors(backupAlert: BackupAlert): BackupsIconColo
|
||||||
return remember(backupAlert) {
|
return remember(backupAlert) {
|
||||||
when (backupAlert) {
|
when (backupAlert) {
|
||||||
BackupAlert.FailedToRenew, is BackupAlert.MediaBackupsAreOff -> error("Not icon-based options.")
|
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
|
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)
|
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)
|
is BackupAlert.DiskFull -> stringResource(R.string.BackupAlertBottomSheet__free_up_s_on_this_device, backupAlert.requiredSpace)
|
||||||
BackupAlert.BackupFailed -> stringResource(R.string.BackupAlertBottomSheet__backup_failed)
|
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)
|
BackupAlert.MediaWillBeDeletedToday -> stringResource(R.string.BackupAlertBottomSheet__download_media_now)
|
||||||
is BackupAlert.DiskFull -> stringResource(R.string.BackupAlertBottomSheet__got_it)
|
is BackupAlert.DiskFull -> stringResource(R.string.BackupAlertBottomSheet__got_it)
|
||||||
is BackupAlert.BackupFailed -> stringResource(R.string.BackupAlertBottomSheet__check_for_update)
|
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
|
BackupAlert.MediaWillBeDeletedToday -> R.string.BackupAlertBottomSheet__dont_download_media
|
||||||
is BackupAlert.DiskFull -> R.string.BackupAlertBottomSheet__skip_restore
|
is BackupAlert.DiskFull -> R.string.BackupAlertBottomSheet__skip_restore
|
||||||
is BackupAlert.BackupFailed -> R.string.BackupAlertBottomSheet__learn_more
|
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.
|
* 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()
|
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
|
BackupFailureState.NONE -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,8 @@ class AppSettingsViewModel : ViewModel() {
|
||||||
BackupFailureState.COULD_NOT_COMPLETE_BACKUP
|
BackupFailureState.COULD_NOT_COMPLETE_BACKUP
|
||||||
} else if (SignalStore.backup.subscriptionStateMismatchDetected) {
|
} else if (SignalStore.backup.subscriptionStateMismatchDetected) {
|
||||||
BackupFailureState.SUBSCRIPTION_STATE_MISMATCH
|
BackupFailureState.SUBSCRIPTION_STATE_MISMATCH
|
||||||
|
} else if (SignalStore.backup.hasBackupAlreadyRedeemedError) {
|
||||||
|
BackupFailureState.ALREADY_REDEEMED
|
||||||
} else {
|
} else {
|
||||||
BackupFailureState.NONE
|
BackupFailureState.NONE
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,5 +12,6 @@ enum class BackupFailureState {
|
||||||
NONE,
|
NONE,
|
||||||
BACKUP_FAILED,
|
BACKUP_FAILED,
|
||||||
COULD_NOT_COMPLETE_BACKUP,
|
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.ButtonDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
@ -153,7 +154,8 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||||
backupProgress = backupProgress,
|
backupProgress = backupProgress,
|
||||||
backupSize = state.backupSize,
|
backupSize = state.backupSize,
|
||||||
backupState = state.backupState,
|
backupState = state.backupState,
|
||||||
backupRestoreState = restoreState
|
backupRestoreState = restoreState,
|
||||||
|
hasRedemptionError = state.hasRedemptionError
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +250,10 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||||
override fun onRestoreUsingCellularClick(canUseCellular: Boolean) {
|
override fun onRestoreUsingCellularClick(canUseCellular: Boolean) {
|
||||||
viewModel.setCanRestoreUsingCellular(canUseCellular)
|
viewModel.setCanRestoreUsingCellular(canUseCellular)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onRedemptionErrorDetailsClick() {
|
||||||
|
BackupAlertBottomSheet.create(BackupAlert.CouldNotRedeemBackup).show(parentFragmentManager, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayBackupKey() {
|
private fun displayBackupKey() {
|
||||||
|
@ -331,6 +337,7 @@ private interface ContentCallbacks {
|
||||||
fun onContactSupport() = Unit
|
fun onContactSupport() = Unit
|
||||||
fun onLearnMoreAboutBackupFailure() = Unit
|
fun onLearnMoreAboutBackupFailure() = Unit
|
||||||
fun onRestoreUsingCellularClick(canUseCellular: Boolean) = Unit
|
fun onRestoreUsingCellularClick(canUseCellular: Boolean) = Unit
|
||||||
|
fun onRedemptionErrorDetailsClick() = Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -346,7 +353,8 @@ private fun RemoteBackupsSettingsContent(
|
||||||
requestedSnackbar: RemoteBackupsSettingsState.Snackbar,
|
requestedSnackbar: RemoteBackupsSettingsState.Snackbar,
|
||||||
contentCallbacks: ContentCallbacks,
|
contentCallbacks: ContentCallbacks,
|
||||||
backupProgress: ArchiveUploadProgressState?,
|
backupProgress: ArchiveUploadProgressState?,
|
||||||
backupSize: Long
|
backupSize: Long,
|
||||||
|
hasRedemptionError: Boolean
|
||||||
) {
|
) {
|
||||||
val snackbarHostState = remember {
|
val snackbarHostState = remember {
|
||||||
SnackbarHostState()
|
SnackbarHostState()
|
||||||
|
@ -364,6 +372,12 @@ private fun RemoteBackupsSettingsContent(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(it)
|
.padding(it)
|
||||||
) {
|
) {
|
||||||
|
if (hasRedemptionError) {
|
||||||
|
item {
|
||||||
|
RedemptionErrorAlert(onDetailsClick = contentCallbacks::onRedemptionErrorDetailsClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
AnimatedContent(backupState, label = "backup-state-block") { state ->
|
AnimatedContent(backupState, label = "backup-state-block") { state ->
|
||||||
when (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
|
@Composable
|
||||||
private fun BoxCard(content: @Composable () -> Unit) {
|
private fun BoxCard(content: @Composable () -> Unit) {
|
||||||
Box(
|
Box(
|
||||||
|
@ -988,6 +1038,7 @@ private fun getBackupPhaseMessage(state: ArchiveUploadProgressState): String {
|
||||||
(progress.progress * 100).toInt()
|
(progress.progress * 100).toInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> stringResource(R.string.RemoteBackupsSettingsFragment__preparing_backup)
|
else -> stringResource(R.string.RemoteBackupsSettingsFragment__preparing_backup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1272,11 +1323,20 @@ private fun RemoteBackupsSettingsContentPreview() {
|
||||||
backupState = RemoteBackupsSettingsState.BackupState.ActiveFree(
|
backupState = RemoteBackupsSettingsState.BackupState.ActiveFree(
|
||||||
messageBackupsType = MessageBackupsType.Free(mediaRetentionDays = 30)
|
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
|
@SignalPreview
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingCardPreview() {
|
private fun LoadingCardPreview() {
|
||||||
|
|
|
@ -15,6 +15,7 @@ data class RemoteBackupsSettingsState(
|
||||||
val backupsEnabled: Boolean,
|
val backupsEnabled: Boolean,
|
||||||
val canBackUpUsingCellular: Boolean = false,
|
val canBackUpUsingCellular: Boolean = false,
|
||||||
val canRestoreUsingCellular: Boolean = false,
|
val canRestoreUsingCellular: Boolean = false,
|
||||||
|
val hasRedemptionError: Boolean = false,
|
||||||
val backupState: BackupState = BackupState.Loading,
|
val backupState: BackupState = BackupState.Loading,
|
||||||
val backupSize: Long = 0,
|
val backupSize: Long = 0,
|
||||||
val backupsFrequency: BackupFrequency = BackupFrequency.DAILY,
|
val backupsFrequency: BackupFrequency = BackupFrequency.DAILY,
|
||||||
|
|
|
@ -240,6 +240,7 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||||
Log.d(TAG, "Subscription found. Updating UI state with subscription details.")
|
Log.d(TAG, "Subscription found. Updating UI state with subscription details.")
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
hasRedemptionError = lastPurchase?.data?.error?.data_ == "409",
|
||||||
backupState = when {
|
backupState = when {
|
||||||
subscription.isActive -> RemoteBackupsSettingsState.BackupState.ActivePaid(
|
subscription.isActive -> RemoteBackupsSettingsState.BackupState.ActivePaid(
|
||||||
messageBackupsType = type,
|
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.components.settings.app.subscription.thanks.ThanksForYourSupportBottomSheetDialogFragmentArgs
|
||||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
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.DonationErrorValue
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
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".
|
* Handles displaying bottom sheets for in-app payments. The current policy is to "fire and forget".
|
||||||
*/
|
*/
|
||||||
class InAppPaymentsBottomSheetDelegate(
|
class InAppPaymentsBottomSheetDelegate(
|
||||||
private val fragmentManager: FragmentManager,
|
private val fragmentManager: FragmentManager,
|
||||||
private val lifecycleOwner: LifecycleOwner,
|
private val lifecycleOwner: LifecycleOwner
|
||||||
private vararg val supportedTypes: InAppPaymentSubscriberRecord.Type = arrayOf(InAppPaymentSubscriberRecord.Type.DONATION)
|
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -56,13 +55,11 @@ class InAppPaymentsBottomSheetDelegate(
|
||||||
private val badgeRepository = TerminalDonationRepository()
|
private val badgeRepository = TerminalDonationRepository()
|
||||||
|
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
if (InAppPaymentSubscriberRecord.Type.DONATION in supportedTypes) {
|
|
||||||
handleLegacyTerminalDonationSheets()
|
handleLegacyTerminalDonationSheets()
|
||||||
handleLegacyVerifiedMonthlyDonationSheets()
|
handleLegacyVerifiedMonthlyDonationSheets()
|
||||||
handleInAppPaymentDonationSheets()
|
handleInAppPaymentDonationSheets()
|
||||||
}
|
|
||||||
|
|
||||||
if (InAppPaymentSubscriberRecord.Type.BACKUP in supportedTypes) {
|
if (RemoteConfig.messageBackups) {
|
||||||
handleInAppPaymentBackupsSheets()
|
handleInAppPaymentBackupsSheets()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,8 +173,48 @@ class InAppPaymentRecurringContextJob private constructor(
|
||||||
inAppPayment
|
inAppPayment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasEntitlementAlready(inAppPayment, subscription.endOfCurrentPeriod)) {
|
||||||
|
info("Already have entitlement for this badge. Marking complete.")
|
||||||
|
markInAppPaymentCompleted(inAppPayment)
|
||||||
|
} else {
|
||||||
submitAndValidateCredentials(updatedInAppPayment, subscription, requestContext)
|
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> {
|
private fun getAndValidateInAppPayment(): Pair<InAppPaymentTable.InAppPayment, ReceiptCredentialRequestContext> {
|
||||||
val inAppPayment = SignalDatabase.inAppPayments.getById(inAppPaymentId)
|
val inAppPayment = SignalDatabase.inAppPayments.getById(inAppPaymentId)
|
||||||
|
@ -435,18 +475,13 @@ class InAppPaymentRecurringContextJob private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
409 -> {
|
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)
|
warning("Already redeemed this token during new subscription. Failing.", applicationError)
|
||||||
updateInAppPaymentWithGenericRedemptionError(inAppPayment)
|
|
||||||
|
if (inAppPayment.type == InAppPaymentType.RECURRING_BACKUP) {
|
||||||
|
SignalStore.backup.hasBackupAlreadyRedeemedError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateInAppPaymentWithTokenAlreadyRedeemedError(inAppPayment)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -520,6 +555,20 @@ class InAppPaymentRecurringContextJob private constructor(
|
||||||
return isSameLevel && isExpirationAfterSub && isExpiration86400 && isExpirationInTheFuture && isExpirationWithinMax
|
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) {
|
private fun updateInAppPaymentWithGenericRedemptionError(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||||
SignalDatabase.inAppPayments.update(
|
SignalDatabase.inAppPayments.update(
|
||||||
inAppPayment = inAppPayment.copy(
|
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_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_SHEET_SNOOZE_TIME = "backup.failed.sheet.snooze"
|
||||||
private const val KEY_BACKUP_FAIL_SPACE_REMAINING = "backup.failed.space.remaining"
|
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"
|
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 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
|
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
|
* 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"
|
* 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()
|
super.onResume()
|
||||||
SimpleTask.run(viewLifecycleOwner.lifecycle, { Recipient.self() }, ::initializeProfileIcon)
|
SimpleTask.run(viewLifecycleOwner.lifecycle, { Recipient.self() }, ::initializeProfileIcon)
|
||||||
|
|
||||||
_backupsFailedDot.alpha = if (BackupRepository.shouldDisplayBackupFailedIndicator()) {
|
_backupsFailedDot.alpha = if (BackupRepository.shouldDisplayBackupFailedIndicator() || BackupRepository.shouldDisplayBackupAlreadyRedeemedIndicator()) {
|
||||||
1f
|
1f
|
||||||
} else {
|
} else {
|
||||||
0f
|
0f
|
||||||
|
|
|
@ -5015,6 +5015,8 @@
|
||||||
<string name="AppSettingsFragment__renew_your_signal_backups_subscription">Renew your Signal Backups subscription</string>
|
<string name="AppSettingsFragment__renew_your_signal_backups_subscription">Renew your Signal Backups subscription</string>
|
||||||
<!-- String alerting user that backup failed -->
|
<!-- String alerting user that backup failed -->
|
||||||
<string name="AppSettingsFragment__couldnt_complete_backup">Couldn\'t complete backup</string>
|
<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 displayed telling user to invite their friends to Signal -->
|
||||||
<string name="AppSettingsFragment__invite_your_friends">Invite your friends</string>
|
<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 -->
|
<!-- 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>
|
<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 -->
|
<!-- Dialog title when a backup fails to be created -->
|
||||||
<string name="BackupAlertBottomSheet__backup_failed">Backup failed</string>
|
<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 -->
|
<!-- 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>
|
<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 -->
|
<!-- Dialog action button that will allow you to check for any Signal version updates -->
|
||||||
<string name="BackupAlertBottomSheet__check_for_update">Check for update</string>
|
<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 -->
|
<!-- BackupStatus -->
|
||||||
<!-- Status title when user does not have enough free space to download their media. Placeholder is required disk space. -->
|
<!-- 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>
|
<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. -->
|
<!-- 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>
|
<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 -->
|
<!-- SubscriptionNotFoundBottomSheet -->
|
||||||
<!-- Displayed as a bottom sheet title -->
|
<!-- 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