Add base and subclassed upgrade sheets.

This commit is contained in:
Alex Hart
2024-10-09 16:40:32 -03:00
committed by Greyson Parrelli
parent 7abe76f76a
commit 7cc425fa7b
10 changed files with 412 additions and 78 deletions

View File

@@ -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
)
)

View File

@@ -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<MessageBackupsType> {
)
)
}
/**
* 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
)
}
}
}

View File

@@ -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<MessageBackupTier?>
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
)
}
}

View File

@@ -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()
}
}

View File

@@ -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
)
}
}

View File

@@ -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

View File

@@ -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<MessageBackupsType.Paid?>(null)
val messageBackupsType: State<MessageBackupsType.Paid?> = internalMessageBackupsType
init {
viewModelScope.launch {
val backupsType = withContext(Dispatchers.IO) {
BackupRepository.getBackupsType(MessageBackupTier.PAID) as? MessageBackupsType.Paid
}
withContext(Dispatchers.Main) {
internalMessageBackupsType.value = backupsType
}
}
}
}

View File

@@ -0,0 +1,40 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="80dp"
android:height="80dp"
android:viewportWidth="80"
android:viewportHeight="80">
<path
android:pathData="M11 35.2c0-6.72 0-10.08 1.3-12.65 1.16-2.26 3-4.1 5.25-5.24C20.12 16 23.48 16 30.2 16h19.6c6.72 0 10.08 0 12.65 1.3 2.26 1.16 4.1 3 5.24 5.25C69 25.12 69 28.48 69 35.2v9.6c0 6.72 0 10.08-1.3 12.65-1.16 2.26-3 4.1-5.25 5.24C59.88 64 56.52 64 49.8 64H30.2c-6.72 0-10.08 0-12.65-1.3-2.26-1.16-4.1-3-5.24-5.25C11 54.88 11 51.52 11 44.8v-9.6Z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="40"
android:startY="16"
android:endX="40"
android:endY="64">
<item
android:color="#FFD2D8FE"
android:offset="0"/>
<item
android:color="#FFC1C7FE"
android:offset="1"/>
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFE3E8FE"
android:pathData="M15.65 61.77L10 56.17l15.34-14.4 8.89 5.6 16.95-13.6 16.96 13.6-0.8 5.6-1.62 6.4-6.46 3.2c-3.23 0.27-9.85 0.8-10.5 0.8-0.64 0-22.34-1.07-33.1-1.6Z"/>
<path
android:fillColor="#FF666EE5"
android:fillType="evenOdd"
android:pathData="M55.34 37.37c-1.5-1.5-3.93-1.5-5.43 0L37.85 49.44c-0.78 0.78-2.05 0.78-2.83 0l-4.35-4.36c-1.5-1.5-3.93-1.5-5.42 0L12.4 57.91 9.6 55.1l12.83-12.84c3.06-3.06 8.02-3.06 11.08 0l2.94 2.94 10.65-10.65c3.06-3.06 8.01-3.06 11.07 0L71 47.38l-2.83 2.83-12.83-12.84Z"/>
<path
android:fillColor="#FF666EE5"
android:fillType="evenOdd"
android:pathData="M8.59 9.59c0.78-0.79 2.04-0.79 2.82 0L69.4 67.57c0.78 0.78 0.78 2.05 0 2.83-0.78 0.78-2.05 0.78-2.83 0L8.59 12.4c-0.79-0.78-0.79-2.04 0-2.82Z"/>
<path
android:fillColor="#FF666EE5"
android:fillType="evenOdd"
android:pathData="M30.11 14H49.9c3.28 0 5.87 0 7.95 0.17 2.13 0.17 3.9 0.53 5.52 1.36 2.63 1.34 4.77 3.48 6.11 6.11 0.83 1.62 1.19 3.4 1.36 5.52C71 29.24 71 31.83 71 35.1v9.78c0 3.28 0 5.87-0.17 7.95-0.17 2.13-0.53 3.9-1.36 5.52-1.34 2.63-3.48 4.77-6.11 6.11-1.62 0.83-3.4 1.19-5.52 1.36C55.76 66 53.17 66 49.9 66H30.1c-3.28 0-5.87 0-7.95-0.17-2.13-0.17-3.9-0.53-5.52-1.36-2.63-1.34-4.77-3.48-6.11-6.11-0.83-1.62-1.19-3.4-1.36-5.52C9 50.76 9 48.17 9 44.9V35.1c0-3.28 0-5.87 0.17-7.95 0.17-2.13 0.53-3.9 1.36-5.52 1.34-2.63 3.48-4.77 6.11-6.11 1.62-0.83 3.4-1.19 5.52-1.36C24.24 14 26.83 14 30.1 14Zm-7.63 4.16c-1.88 0.15-3.07 0.44-4.02 0.93-1.88 0.96-3.41 2.49-4.37 4.37-0.49 0.95-0.78 2.14-0.93 4.02C13 29.38 13 31.81 13 35.2v9.6c0 3.4 0 5.82 0.16 7.72 0.15 1.88 0.44 3.07 0.93 4.02 0.96 1.88 2.49 3.41 4.37 4.37 0.95 0.49 2.14 0.78 4.02 0.93C24.38 62 26.81 62 30.2 62h19.6c3.4 0 5.82 0 7.72-0.16 1.88-0.15 3.07-0.44 4.02-0.93 1.88-0.96 3.41-2.49 4.37-4.37 0.49-0.95 0.78-2.14 0.93-4.02C67 50.62 67 48.19 67 44.8v-9.6c0-3.4 0-5.82-0.16-7.72-0.15-1.88-0.44-3.07-0.93-4.02-0.96-1.88-2.49-3.41-4.37-4.37-0.95-0.49-2.14-0.78-4.02-0.93C55.62 18 53.19 18 49.8 18H30.2c-3.4 0-5.82 0-7.72 0.16Z"/>
</vector>

View File

@@ -0,0 +1,40 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="80dp"
android:height="80dp"
android:viewportWidth="80"
android:viewportHeight="80">
<path
android:pathData="M11 35.2c0-6.72 0-10.08 1.3-12.65 1.16-2.26 3-4.1 5.25-5.24C20.12 16 23.48 16 30.2 16h19.6c6.72 0 10.08 0 12.65 1.3 2.26 1.16 4.1 3 5.24 5.25C69 25.12 69 28.48 69 35.2v9.6c0 6.72 0 10.08-1.3 12.65-1.16 2.26-3 4.1-5.25 5.24C59.88 64 56.52 64 49.8 64H30.2c-6.72 0-10.08 0-12.65-1.3-2.26-1.16-4.1-3-5.24-5.25C11 54.88 11 51.52 11 44.8v-9.6Z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="42.9"
android:startY="14.5"
android:endX="44.01"
android:endY="63.97">
<item
android:color="#FFD2D8FE"
android:offset="0"/>
<item
android:color="#FFB0BCFC"
android:offset="1"/>
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFE0E5FF"
android:pathData="M15.65 61.77L10 56.17l15.34-14.4 8.89 5.6 16.95-13.6 16.96 13.6-0.8 5.6-1.62 6.4-6.46 3.2c-3.23 0.27-9.85 0.8-10.5 0.8-0.64 0-22.34-1.07-33.1-1.6Z"/>
<path
android:fillColor="#FF3B45FD"
android:fillType="evenOdd"
android:pathData="M55.34 37.37c-1.5-1.5-3.93-1.5-5.43 0L37.85 49.44c-0.78 0.78-2.05 0.78-2.83 0l-4.35-4.36c-1.5-1.5-3.93-1.5-5.42 0L12.4 57.91 9.6 55.1l12.83-12.84c3.06-3.06 8.02-3.06 11.08 0l2.94 2.94 10.65-10.65c3.06-3.06 8.01-3.06 11.07 0L71 47.38l-2.83 2.83-12.83-12.84Z"/>
<path
android:fillColor="#FF3B45FD"
android:fillType="evenOdd"
android:pathData="M8.59 9.59c0.78-0.79 2.04-0.79 2.82 0L69.4 67.57c0.78 0.78 0.78 2.05 0 2.83-0.78 0.78-2.05 0.78-2.83 0L8.59 12.4c-0.79-0.78-0.79-2.04 0-2.82Z"/>
<path
android:fillColor="#FF3B45FD"
android:fillType="evenOdd"
android:pathData="M30.11 14H49.9c3.28 0 5.87 0 7.95 0.17 2.13 0.17 3.9 0.53 5.52 1.36 2.63 1.34 4.77 3.48 6.11 6.11 0.83 1.62 1.19 3.4 1.36 5.52C71 29.24 71 31.83 71 35.1v9.78c0 3.28 0 5.87-0.17 7.95-0.17 2.13-0.53 3.9-1.36 5.52-1.34 2.63-3.48 4.77-6.11 6.11-1.62 0.83-3.4 1.19-5.52 1.36C55.76 66 53.17 66 49.9 66H30.1c-3.28 0-5.87 0-7.95-0.17-2.13-0.17-3.9-0.53-5.52-1.36-2.63-1.34-4.77-3.48-6.11-6.11-0.83-1.62-1.19-3.4-1.36-5.52C9 50.76 9 48.17 9 44.9V35.1c0-3.28 0-5.87 0.17-7.95 0.17-2.13 0.53-3.9 1.36-5.52 1.34-2.63 3.48-4.77 6.11-6.11 1.62-0.83 3.4-1.19 5.52-1.36C24.24 14 26.83 14 30.1 14Zm-7.63 4.16c-1.88 0.15-3.07 0.44-4.02 0.93-1.88 0.96-3.41 2.49-4.37 4.37-0.49 0.95-0.78 2.14-0.93 4.02C13 29.38 13 31.81 13 35.2v9.6c0 3.4 0 5.82 0.16 7.72 0.15 1.88 0.44 3.07 0.93 4.02 0.96 1.88 2.49 3.41 4.37 4.37 0.95 0.49 2.14 0.78 4.02 0.93C24.38 62 26.81 62 30.2 62h19.6c3.4 0 5.82 0 7.72-0.16 1.88-0.15 3.07-0.44 4.02-0.93 1.88-0.96 3.41-2.49 4.37-4.37 0.49-0.95 0.78-2.14 0.93-4.02C67 50.62 67 48.19 67 44.8v-9.6c0-3.4 0-5.82-0.16-7.72-0.15-1.88-0.44-3.07-0.93-4.02-0.96-1.88-2.49-3.41-4.37-4.37-0.95-0.49-2.14-0.78-4.02-0.93C55.62 18 53.19 18 49.8 18H30.2c-3.4 0-5.82 0-7.72 0.16Z"/>
</vector>

View File

@@ -7365,6 +7365,17 @@
<!-- Primary action to launch upgrade flow. Placeholder is formatted price. -->
<string name="UpgradeToEnableOptimizedStorageSheet__subscribe_for_s_month">Subscribe for %1$s/month</string>
<!-- UpgradeToStartMediaBackupSheet -->
<!-- Title on a bottom sheet, detailing that in order to start backing up media, they need to upgrade to paid backup storage -->
<string name="UpgradeToStartMediaBackupSheet__this_media_is_no_longer_available">This media is no longer available</string>
<!-- Subtitle of bottom sheet detailing that with their current plan, there is a limitation to how many days media is stored for. Placeholder is number of days. -->
<plurals name="UpgradeToStartMediaBackupSheet__your_current_signal_backup_plan_includes">
<item quantity="one">Your current Signal backup plan includes your most recent %1$d day of media. To start backing up all your media, upgrade now.</item>
<item quantity="other">Your current Signal backup plan includes your most recent %1$d days of media. To start backing up all your media, upgrade now.</item>
</plurals>
<!-- Primary action to launch upgrade flow. Placeholder is formatted price. -->
<string name="UpgradeToStartMediaBackupSheet__subscribe_for_s_month">Subscribe for %1$s/month</string>
<!-- Educational bottom sheet dialog title shown to notify about delete syncs causing deletes to happen across all devices -->
<string name="DeleteSyncEducation_title">Deleting is now synced across all of your devices</string>
<!-- Educational bottom sheet dialog message shown to notify about delete syncs causing deletes to happen across all devices -->