diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt index b96b3e4ada..ad1adf818f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt @@ -46,7 +46,8 @@ import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration import kotlin.time.Duration.Companion.seconds class MessageBackupsFlowViewModel( - initialTierSelection: MessageBackupTier? + initialTierSelection: MessageBackupTier?, + startScreen: MessageBackupsStage = if (SignalStore.backup.backupTier == null) MessageBackupsStage.EDUCATION else MessageBackupsStage.TYPE_SELECTION ) : ViewModel() { companion object { @@ -57,7 +58,7 @@ class MessageBackupsFlowViewModel( MessageBackupsFlowState( availableBackupTypes = emptyList(), selectedMessageBackupTier = initialTierSelection ?: SignalStore.backup.backupTier, - startScreen = if (SignalStore.backup.backupTier == null) MessageBackupsStage.EDUCATION else MessageBackupsStage.TYPE_SELECTION + startScreen = startScreen ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt index a2ec724f98..e78e542116 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsTypeSelectionScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -226,7 +227,8 @@ fun MessageBackupsTypeBlock( isSelected: Boolean, onSelected: () -> Unit, modifier: Modifier = Modifier, - enabled: Boolean = true + enabled: Boolean = true, + iconColors: MessageBackupsTypeIconColors = MessageBackupsTypeIconColors.default() ) { val borderColor = if (isSelected) { MaterialTheme.colorScheme.primary @@ -264,9 +266,9 @@ fun MessageBackupsTypeBlock( ) val featureIconTint = if (isSelected) { - MaterialTheme.colorScheme.primary + iconColors.iconColorSelected } else { - MaterialTheme.colorScheme.onSurfaceVariant + iconColors.iconColorNormal } Column( @@ -362,3 +364,22 @@ fun testBackupTypes(): List { ) ) } + +/** + * Feature row iconography coloring + */ +@Immutable +data class MessageBackupsTypeIconColors( + val iconColorNormal: Color, + val iconColorSelected: Color +) { + companion object { + @Composable + fun default(): MessageBackupsTypeIconColors { + return MessageBackupsTypeIconColors( + iconColorNormal = MaterialTheme.colorScheme.onSurfaceVariant, + iconColorSelected = MaterialTheme.colorScheme.primary + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/UpgradeToEnableOptimizedStorageSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToEnableOptimizedStorageSheet.kt similarity index 73% rename from app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/UpgradeToEnableOptimizedStorageSheet.kt rename to app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToEnableOptimizedStorageSheet.kt index 1c72698639..fac18f7a4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/UpgradeToEnableOptimizedStorageSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToEnableOptimizedStorageSheet.kt @@ -3,13 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -package org.thoughtcrime.securesms.components.settings.app.storage +package org.thoughtcrime.securesms.billing.upgrade -import android.os.Bundle -import android.view.View -import androidx.activity.result.ActivityResultLauncher import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -17,7 +15,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -27,45 +24,33 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.fragment.app.viewModels 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.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.backup.v2.MessageBackupTier import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeBlock +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeIconColors import org.thoughtcrime.securesms.backup.v2.ui.subscription.testBackupTypes -import org.thoughtcrime.securesms.components.settings.app.subscription.MessageBackupsCheckoutLauncher.createBackupsCheckoutLauncher -import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment import org.thoughtcrime.securesms.payments.FiatMoneyUtil /** * Sheet describing how users must upgrade to enable optimized storage. */ -class UpgradeToEnableOptimizedStorageSheet : ComposeBottomSheetDialogFragment() { - - override val peekHeightPercentage: Float = 1f - - private val viewModel: UpgradeToEnableOptimizedStorageViewModel by viewModels() - - private lateinit var checkoutLauncher: ActivityResultLauncher - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - checkoutLauncher = createBackupsCheckoutLauncher() - } +class UpgradeToEnableOptimizedStorageSheet : UpgradeToPaidTierBottomSheet() { @Composable - override fun SheetContent() { - val type by viewModel.messageBackupsType + override fun UpgradeSheetContent( + paidBackupType: MessageBackupsType.Paid, + freeBackupType: MessageBackupsType.Free, + isSubscribeEnabled: Boolean, + onSubscribeClick: () -> Unit + ) { UpgradeToEnableOptimizedStorageSheetContent( - messageBackupsType = type, - onUpgradeNowClick = { - checkoutLauncher.launch(MessageBackupTier.PAID) - dismissAllowingStateLoss() - }, + messageBackupsType = paidBackupType, + isSubscribeEnabled = isSubscribeEnabled, + onSubscribeClick = onSubscribeClick, onCancelClick = { dismissAllowingStateLoss() } @@ -75,15 +60,11 @@ class UpgradeToEnableOptimizedStorageSheet : ComposeBottomSheetDialogFragment() @Composable private fun UpgradeToEnableOptimizedStorageSheetContent( - messageBackupsType: MessageBackupsType.Paid?, - onUpgradeNowClick: () -> Unit = {}, + messageBackupsType: MessageBackupsType.Paid, + isSubscribeEnabled: Boolean, + onSubscribeClick: () -> Unit = {}, onCancelClick: () -> Unit = {} ) { - if (messageBackupsType == null) { - // TODO [message-backups] -- network error? - return - } - Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth() @@ -94,7 +75,7 @@ private fun UpgradeToEnableOptimizedStorageSheetContent( painter = painterResource(id = R.drawable.image_signal_backups), contentDescription = null, modifier = Modifier - .padding(top = 8.dp, bottom = 24.dp) + .padding(top = 8.dp, bottom = 16.dp) .size(80.dp) ) @@ -122,15 +103,19 @@ private fun UpgradeToEnableOptimizedStorageSheetContent( isSelected = false, onSelected = {}, enabled = false, + iconColors = MessageBackupsTypeIconColors.default().let { + it.copy(iconColorNormal = it.iconColorSelected) + }, modifier = Modifier .padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter)) .padding(bottom = 50.dp) ) Buttons.LargePrimary( - onClick = onUpgradeNowClick, + enabled = isSubscribeEnabled, + onClick = onSubscribeClick, modifier = Modifier - .fillMaxWidth() + .defaultMinSize(minWidth = 256.dp) .padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter)) .padding(bottom = 8.dp) ) { @@ -145,9 +130,10 @@ private fun UpgradeToEnableOptimizedStorageSheetContent( } TextButton( + enabled = isSubscribeEnabled, onClick = onCancelClick, modifier = Modifier - .fillMaxWidth() + .defaultMinSize(minWidth = 256.dp) .padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter)) .padding(bottom = 16.dp) ) { @@ -163,7 +149,8 @@ private fun UpgradeToEnableOptimizedStorageSheetContent( private fun UpgradeToEnableOptimizedStorageSheetContentPreview() { Previews.BottomSheetPreview { UpgradeToEnableOptimizedStorageSheetContent( - messageBackupsType = testBackupTypes()[1] as MessageBackupsType.Paid? + messageBackupsType = testBackupTypes()[1] as MessageBackupsType.Paid, + isSubscribeEnabled = true ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToPaidTierBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToPaidTierBottomSheet.kt new file mode 100644 index 0000000000..514683879d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToPaidTierBottomSheet.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.billing.upgrade + +import android.os.Bundle +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import kotlinx.coroutines.rx3.asFlowable +import org.signal.core.ui.Dialogs +import org.thoughtcrime.securesms.backup.v2.MessageBackupTier +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsFlowViewModel +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsStage +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType +import org.thoughtcrime.securesms.components.settings.app.subscription.donate.InAppPaymentCheckoutDelegate +import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment +import org.thoughtcrime.securesms.database.InAppPaymentTable +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.util.viewModel + +/** + * BottomSheet that encapsulates the common logic for updating someone to paid tier. + */ +abstract class UpgradeToPaidTierBottomSheet : ComposeBottomSheetDialogFragment(), InAppPaymentCheckoutDelegate.ErrorHandlerCallback { + + companion object { + const val RESULT_KEY = "UpgradeToPaidTierBottomSheet.RESULT_KEY" + } + + private val viewModel: MessageBackupsFlowViewModel by viewModel { + MessageBackupsFlowViewModel( + initialTierSelection = MessageBackupTier.PAID, + startScreen = MessageBackupsStage.TYPE_SELECTION + ) + } + + private val errorHandler = InAppPaymentCheckoutDelegate.ErrorHandler() + + override val peekHeightPercentage: Float = 1f + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + errorHandler.attach( + fragment = this, + errorHandlerCallback = this, + inAppPaymentIdSource = viewModel.stateFlow.asFlowable() + .filter { it.inAppPayment != null } + .map { it.inAppPayment!!.id } + ) + } + + @Composable + override fun SheetContent() { + val state by viewModel.stateFlow.collectAsState() + + val paidBackupType = state.availableBackupTypes.firstOrNull { it.tier == MessageBackupTier.PAID } as? MessageBackupsType.Paid + val freeBackupType = state.availableBackupTypes.firstOrNull { it.tier == MessageBackupTier.FREE } as? MessageBackupsType.Free + + if (paidBackupType != null && freeBackupType != null) { + UpgradeSheetContent( + paidBackupType = paidBackupType, + freeBackupType = freeBackupType, + isSubscribeEnabled = state.stage == MessageBackupsStage.TYPE_SELECTION, + onSubscribeClick = viewModel::goToNextStage + ) + } + + when (state.stage) { + MessageBackupsStage.CREATING_IN_APP_PAYMENT -> Dialogs.IndeterminateProgressDialog() + MessageBackupsStage.PROCESS_PAYMENT -> Dialogs.IndeterminateProgressDialog() + MessageBackupsStage.PROCESS_FREE -> Dialogs.IndeterminateProgressDialog() + else -> Unit + } + + LaunchedEffect(state.stage) { + if (state.stage == MessageBackupsStage.CHECKOUT_SHEET) { + AppDependencies.billingApi.launchBillingFlow(requireActivity()) + } + + if (state.stage == MessageBackupsStage.COMPLETED) { + dismissAllowingStateLoss() + setFragmentResult(RESULT_KEY, bundleOf(RESULT_KEY to true)) + } + } + } + + /** + * This is responsible for displaying the normal upgrade sheet content. + */ + @Composable + abstract fun UpgradeSheetContent( + paidBackupType: MessageBackupsType.Paid, + freeBackupType: MessageBackupsType.Free, + isSubscribeEnabled: Boolean, + onSubscribeClick: () -> Unit + ) + + override fun onUserLaunchedAnExternalApplication() = error("Unsupported.") + + override fun navigateToDonationPending(inAppPayment: InAppPaymentTable.InAppPayment) = error("Unsupported.") + + override fun exitCheckoutFlow() { + dismissAllowingStateLoss() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToStartMediaBackupSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToStartMediaBackupSheet.kt new file mode 100644 index 0000000000..77bc51ad6a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToStartMediaBackupSheet.kt @@ -0,0 +1,155 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.billing.upgrade + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.text.style.TextAlign +import androidx.compose.ui.unit.dp +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.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeBlock +import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsTypeIconColors +import org.thoughtcrime.securesms.backup.v2.ui.subscription.testBackupTypes +import org.thoughtcrime.securesms.payments.FiatMoneyUtil + +/** + * Bottom sheet notifying user that the media they selected is no longer available. This + * can occur when a user had a paid tier in the past and had storage optimization enabled, + * but did not download their media within 30 days of canceling their subscription. + */ +class UpgradeToStartMediaBackupSheet : UpgradeToPaidTierBottomSheet() { + @Composable + override fun UpgradeSheetContent( + paidBackupType: MessageBackupsType.Paid, + freeBackupType: MessageBackupsType.Free, + isSubscribeEnabled: Boolean, + onSubscribeClick: () -> Unit + ) { + UpgradeToStartMediaBackupSheetContent( + paidBackupType = paidBackupType, + freeBackupType = freeBackupType, + isSubscribeEnabled = isSubscribeEnabled, + onSubscribeClick = onSubscribeClick, + onCancelClick = { + dismissAllowingStateLoss() + } + ) + } +} + +@Composable +private fun UpgradeToStartMediaBackupSheetContent( + paidBackupType: MessageBackupsType.Paid, + freeBackupType: MessageBackupsType.Free, + isSubscribeEnabled: Boolean, + onSubscribeClick: () -> Unit = {}, + onCancelClick: () -> Unit = {} +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = dimensionResource(R.dimen.core_ui__gutter)) + ) { + BottomSheets.Handle() + + Image( + painter = painterResource(R.drawable.image_signal_backups_media), + contentDescription = null, + modifier = Modifier + .size(80.dp) + ) + + Text( + text = stringResource(R.string.UpgradeToStartMediaBackupSheet__this_media_is_no_longer_available), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, + modifier = Modifier.padding(vertical = 16.dp) + ) + + Text( + text = pluralStringResource(R.plurals.UpgradeToStartMediaBackupSheet__your_current_signal_backup_plan_includes, freeBackupType.mediaRetentionDays, freeBackupType.mediaRetentionDays), + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + MessageBackupsTypeBlock( + messageBackupsType = paidBackupType, + isCurrent = false, + isSelected = false, + onSelected = {}, + enabled = false, + modifier = Modifier.padding(top = 24.dp, bottom = 32.dp), + iconColors = MessageBackupsTypeIconColors.default().let { + it.copy(iconColorNormal = it.iconColorSelected) + } + ) + + Buttons.LargePrimary( + enabled = isSubscribeEnabled, + onClick = onSubscribeClick, + modifier = Modifier + .defaultMinSize(minWidth = 256.dp) + .padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter)) + .padding(bottom = 8.dp) + ) { + val resources = LocalContext.current.resources + val formattedPrice = remember(paidBackupType.pricePerMonth) { + FiatMoneyUtil.format(resources, paidBackupType.pricePerMonth, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal()) + } + + Text( + text = stringResource(id = R.string.UpgradeToStartMediaBackupSheet__subscribe_for_s_month, formattedPrice) + ) + } + + TextButton( + enabled = isSubscribeEnabled, + onClick = onCancelClick, + modifier = Modifier + .defaultMinSize(minWidth = 256.dp) + .padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter)) + .padding(bottom = 16.dp) + ) { + Text( + text = stringResource(id = android.R.string.cancel) + ) + } + } +} + +@SignalPreview +@Composable +private fun UpgradeToStartMediaBackupSheetContentPreview() { + Previews.Preview { + UpgradeToStartMediaBackupSheetContent( + paidBackupType = testBackupTypes()[1] as MessageBackupsType.Paid, + freeBackupType = testBackupTypes()[0] as MessageBackupsType.Free, + isSubscribeEnabled = true + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt index e51600019b..0cd0d36e02 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt @@ -62,6 +62,7 @@ import org.signal.core.ui.SignalPreview import org.signal.core.ui.Texts import org.signal.core.ui.theme.SignalTheme import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.billing.upgrade.UpgradeToEnableOptimizedStorageSheet import org.thoughtcrime.securesms.compose.ComposeFragment import org.thoughtcrime.securesms.database.MediaTable import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/UpgradeToEnableOptimizedStorageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/UpgradeToEnableOptimizedStorageViewModel.kt deleted file mode 100644 index 6f5ca3f21b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/UpgradeToEnableOptimizedStorageViewModel.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.thoughtcrime.securesms.components.settings.app.storage - -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.thoughtcrime.securesms.backup.v2.BackupRepository -import org.thoughtcrime.securesms.backup.v2.MessageBackupTier -import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType - -class UpgradeToEnableOptimizedStorageViewModel : ViewModel() { - private val internalMessageBackupsType = mutableStateOf(null) - val messageBackupsType: State = internalMessageBackupsType - - init { - viewModelScope.launch { - val backupsType = withContext(Dispatchers.IO) { - BackupRepository.getBackupsType(MessageBackupTier.PAID) as? MessageBackupsType.Paid - } - - withContext(Dispatchers.Main) { - internalMessageBackupsType.value = backupsType - } - } - } -} diff --git a/app/src/main/res/drawable-night/image_signal_backups_media.xml b/app/src/main/res/drawable-night/image_signal_backups_media.xml new file mode 100644 index 0000000000..db200e5dcb --- /dev/null +++ b/app/src/main/res/drawable-night/image_signal_backups_media.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/image_signal_backups_media.xml b/app/src/main/res/drawable/image_signal_backups_media.xml new file mode 100644 index 0000000000..22b747aebc --- /dev/null +++ b/app/src/main/res/drawable/image_signal_backups_media.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a515f60f63..18b8e791fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7365,6 +7365,17 @@ Subscribe for %1$s/month + + + This media is no longer available + + + Your current Signal backup plan includes your most recent %1$d day of media. To start backing up all your media, upgrade now. + Your current Signal backup plan includes your most recent %1$d days of media. To start backing up all your media, upgrade now. + + + Subscribe for %1$s/month + Deleting is now synced across all of your devices