mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 19:29:54 +01:00
Basic settings functionality for message backup.
This commit is contained in:
@@ -650,3 +650,8 @@ class BackupMetadata(
|
||||
val usedSpace: Long,
|
||||
val mediaCount: Long
|
||||
)
|
||||
|
||||
enum class MessageBackupTier {
|
||||
FREE,
|
||||
PAID
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2
|
||||
|
||||
class BackupV2Event(val type: Type, val count: Long, val estimatedTotalCount: Long) {
|
||||
enum class Type {
|
||||
PROGRESS_MESSAGES, PROGRESS_ATTACHMENTS, FINISHED
|
||||
}
|
||||
}
|
||||
@@ -30,23 +30,20 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.GooglePayButton
|
||||
import org.thoughtcrime.securesms.databinding.PaypalButtonBinding
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MessageBackupsCheckoutSheet(
|
||||
messageBackupsType: MessageBackupsType,
|
||||
messageBackupTier: MessageBackupTier,
|
||||
availablePaymentGateways: List<GatewayResponse.Gateway>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onPaymentGatewaySelected: (GatewayResponse.Gateway) -> Unit
|
||||
@@ -57,7 +54,7 @@ fun MessageBackupsCheckoutSheet(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
) {
|
||||
SheetContent(
|
||||
messageBackupsType = messageBackupsType,
|
||||
messageBackupTier = messageBackupTier,
|
||||
availablePaymentGateways = availablePaymentGateways,
|
||||
onPaymentGatewaySelected = onPaymentGatewaySelected
|
||||
)
|
||||
@@ -66,13 +63,16 @@ fun MessageBackupsCheckoutSheet(
|
||||
|
||||
@Composable
|
||||
private fun SheetContent(
|
||||
messageBackupsType: MessageBackupsType,
|
||||
messageBackupTier: MessageBackupTier,
|
||||
availablePaymentGateways: List<GatewayResponse.Gateway>,
|
||||
onPaymentGatewaySelected: (GatewayResponse.Gateway) -> Unit
|
||||
) {
|
||||
val resources = LocalContext.current.resources
|
||||
val formattedPrice = remember(messageBackupsType.pricePerMonth) {
|
||||
FiatMoneyUtil.format(resources, messageBackupsType.pricePerMonth, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||
val backupTypeDetails = remember(messageBackupTier) {
|
||||
getTierDetails(messageBackupTier)
|
||||
}
|
||||
val formattedPrice = remember(backupTypeDetails.pricePerMonth) {
|
||||
FiatMoneyUtil.format(resources, backupTypeDetails.pricePerMonth, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||
}
|
||||
|
||||
Text(
|
||||
@@ -88,7 +88,7 @@ private fun SheetContent(
|
||||
)
|
||||
|
||||
MessageBackupsTypeBlock(
|
||||
messageBackupsType = messageBackupsType,
|
||||
messageBackupsType = backupTypeDetails,
|
||||
isSelected = false,
|
||||
onSelected = {},
|
||||
enabled = false,
|
||||
@@ -221,29 +221,6 @@ private fun CreditOrDebitCardButton(
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MessageBackupsCheckoutSheetPreview() {
|
||||
val paidTier = MessageBackupsType(
|
||||
pricePerMonth = FiatMoney(BigDecimal.valueOf(3), Currency.getInstance("USD")),
|
||||
title = "Text + All your media",
|
||||
features = persistentListOf(
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "Full text message backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||
label = "Full media backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "1TB of storage (~250K photos)"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_heart_compact_bold_16,
|
||||
label = "Thanks for supporting Signal!"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val availablePaymentGateways = GatewayResponse.Gateway.values().toList()
|
||||
|
||||
Previews.Preview {
|
||||
@@ -252,7 +229,7 @@ private fun MessageBackupsCheckoutSheetPreview() {
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
) {
|
||||
SheetContent(
|
||||
messageBackupsType = paidTier,
|
||||
messageBackupTier = MessageBackupTier.PAID,
|
||||
availablePaymentGateways = availablePaymentGateways,
|
||||
onPaymentGatewaySelected = {}
|
||||
)
|
||||
|
||||
@@ -32,6 +32,10 @@ class MessageBackupsFlowActivity : PassphraseRequiredActivity() {
|
||||
|
||||
fun MessageBackupsScreen.next() {
|
||||
val nextScreen = viewModel.goToNextScreen(this)
|
||||
if (nextScreen == MessageBackupsScreen.COMPLETED) {
|
||||
finishAfterTransition()
|
||||
return
|
||||
}
|
||||
if (nextScreen != this) {
|
||||
navController.navigate(nextScreen.name)
|
||||
}
|
||||
@@ -88,9 +92,9 @@ class MessageBackupsFlowActivity : PassphraseRequiredActivity() {
|
||||
|
||||
composable(route = MessageBackupsScreen.TYPE_SELECTION.name) {
|
||||
MessageBackupsTypeSelectionScreen(
|
||||
selectedBackupsType = state.selectedMessageBackupsType,
|
||||
availableBackupsTypes = state.availableBackupsTypes,
|
||||
onMessageBackupsTypeSelected = viewModel::onMessageBackupsTypeUpdated,
|
||||
selectedBackupTier = state.selectedMessageBackupTier,
|
||||
availableBackupTiers = state.availableBackupTiers,
|
||||
onMessageBackupsTierSelected = viewModel::onMessageBackupTierUpdated,
|
||||
onNavigationClick = navController::popOrFinish,
|
||||
onReadMoreClicked = {},
|
||||
onNextClicked = { MessageBackupsScreen.TYPE_SELECTION.next() }
|
||||
@@ -99,7 +103,7 @@ class MessageBackupsFlowActivity : PassphraseRequiredActivity() {
|
||||
|
||||
dialog(route = MessageBackupsScreen.CHECKOUT_SHEET.name) {
|
||||
MessageBackupsCheckoutSheet(
|
||||
messageBackupsType = state.selectedMessageBackupsType!!,
|
||||
messageBackupTier = state.selectedMessageBackupTier!!,
|
||||
availablePaymentGateways = state.availablePaymentGateways,
|
||||
onDismissRequest = navController::popOrFinish,
|
||||
onPaymentGatewaySelected = {
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.ui.subscription
|
||||
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
|
||||
data class MessageBackupsFlowState(
|
||||
val selectedMessageBackupsType: MessageBackupsType? = null,
|
||||
val availableBackupsTypes: List<MessageBackupsType> = emptyList(),
|
||||
val selectedMessageBackupTier: MessageBackupTier? = null,
|
||||
val availableBackupTiers: List<MessageBackupTier> = emptyList(),
|
||||
val selectedPaymentGateway: GatewayResponse.Gateway? = null,
|
||||
val availablePaymentGateways: List<GatewayResponse.Gateway> = emptyList(),
|
||||
val pin: String = "",
|
||||
|
||||
@@ -5,14 +5,28 @@
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.ui.subscription
|
||||
|
||||
import android.text.TextUtils
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
import org.thoughtcrime.securesms.lock.v2.SvrConstants
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.whispersystems.signalservice.api.kbs.PinHashUtil.verifyLocalPinHash
|
||||
|
||||
class MessageBackupsFlowViewModel : ViewModel() {
|
||||
private val internalState = mutableStateOf(MessageBackupsFlowState())
|
||||
private val internalState = mutableStateOf(
|
||||
MessageBackupsFlowState(
|
||||
availableBackupTiers = if (!FeatureFlags.messageBackups()) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(MessageBackupTier.FREE, MessageBackupTier.PAID)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val state: State<MessageBackupsFlowState> = internalState
|
||||
|
||||
@@ -40,16 +54,27 @@ class MessageBackupsFlowViewModel : ViewModel() {
|
||||
internalState.value = state.value.copy(selectedPaymentGateway = gateway)
|
||||
}
|
||||
|
||||
fun onMessageBackupsTypeUpdated(messageBackupsType: MessageBackupsType) {
|
||||
internalState.value = state.value.copy(selectedMessageBackupsType = messageBackupsType)
|
||||
fun onMessageBackupTierUpdated(messageBackupTier: MessageBackupTier) {
|
||||
internalState.value = state.value.copy(selectedMessageBackupTier = messageBackupTier)
|
||||
}
|
||||
|
||||
private fun validatePinAndUpdateState(): MessageBackupsScreen {
|
||||
val pinHash = SignalStore.svr().localPinHash
|
||||
val pin = state.value.pin
|
||||
|
||||
if (pinHash == null || TextUtils.isEmpty(pin) || pin.length < SvrConstants.MINIMUM_PIN_LENGTH) return MessageBackupsScreen.PIN_CONFIRMATION
|
||||
|
||||
if (!verifyLocalPinHash(pinHash, pin)) {
|
||||
return MessageBackupsScreen.PIN_CONFIRMATION
|
||||
}
|
||||
return MessageBackupsScreen.TYPE_SELECTION
|
||||
}
|
||||
|
||||
private fun validateTypeAndUpdateState(): MessageBackupsScreen {
|
||||
return MessageBackupsScreen.CHECKOUT_SHEET
|
||||
SignalStore.backup().canReadWriteToArchiveCdn = state.value.selectedMessageBackupTier == MessageBackupTier.PAID
|
||||
SignalStore.backup().areBackupsEnabled = true
|
||||
return MessageBackupsScreen.COMPLETED
|
||||
// return MessageBackupsScreen.CHECKOUT_SHEET TODO [message-backups] Switch back to payment flow
|
||||
}
|
||||
|
||||
private fun validateGatewayAndUpdateState(): MessageBackupsScreen {
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
@@ -32,6 +33,7 @@ import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -52,93 +54,95 @@ fun MessageBackupsPinConfirmationScreen(
|
||||
onNextClick: () -> Unit
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
) {
|
||||
LazyColumn(
|
||||
Surface {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = "Enter your PIN", // TODO [message-backups] Finalized copy
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.padding(top = 40.dp)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Text(
|
||||
text = "Enter your Signal PIN to enable backups", // TODO [message-backups] Finalized copy
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
// TODO [message-backups] Confirm default focus state
|
||||
val keyboardType = remember(pinKeyboardType) {
|
||||
when (pinKeyboardType) {
|
||||
PinKeyboardType.NUMERIC -> KeyboardType.NumberPassword
|
||||
PinKeyboardType.ALPHA_NUMERIC -> KeyboardType.Password
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = "Enter your PIN", // TODO [message-backups] Finalized copy
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.padding(top = 40.dp)
|
||||
)
|
||||
}
|
||||
|
||||
TextField(
|
||||
value = pin,
|
||||
onValueChange = onPinChanged,
|
||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { onNextClick() }
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = keyboardType
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(top = 72.dp)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
)
|
||||
item {
|
||||
Text(
|
||||
text = "Enter your Signal PIN to enable backups", // TODO [message-backups] Finalized copy
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
// TODO [message-backups] Confirm default focus state
|
||||
val keyboardType = remember(pinKeyboardType) {
|
||||
when (pinKeyboardType) {
|
||||
PinKeyboardType.NUMERIC -> KeyboardType.NumberPassword
|
||||
PinKeyboardType.ALPHA_NUMERIC -> KeyboardType.Password
|
||||
}
|
||||
}
|
||||
|
||||
TextField(
|
||||
value = pin,
|
||||
onValueChange = onPinChanged,
|
||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { onNextClick() }
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = keyboardType
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(top = 72.dp)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
visualTransformation = PasswordVisualTransformation()
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 48.dp)
|
||||
) {
|
||||
PinKeyboardTypeToggle(
|
||||
pinKeyboardType = pinKeyboardType,
|
||||
onPinKeyboardTypeSelected = onPinKeyboardTypeSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 48.dp)
|
||||
Box(
|
||||
contentAlignment = Alignment.BottomEnd,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
Buttons.LargeTonal(
|
||||
onClick = onNextClick
|
||||
) {
|
||||
PinKeyboardTypeToggle(
|
||||
pinKeyboardType = pinKeyboardType,
|
||||
onPinKeyboardTypeSelected = onPinKeyboardTypeSelected
|
||||
Text(
|
||||
text = "Next" // TODO [message-backups] Finalized copy
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.BottomEnd,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
Buttons.LargeTonal(
|
||||
onClick = onNextClick
|
||||
) {
|
||||
Text(
|
||||
text = "Next" // TODO [message-backups] Finalized copy
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,22 +96,22 @@ fun MessageBackupsPinEducationScreen(
|
||||
}
|
||||
|
||||
Buttons.LargePrimary(
|
||||
onClick = onGeneratePinClick,
|
||||
onClick = onUseCurrentPinClick,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "Generate a new $recommendedPinSize-digit PIN" // TODO [message-backups] Finalized copy
|
||||
text = "Use current Signal PIN" // TODO [message-backups] Finalized copy
|
||||
)
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = onUseCurrentPinClick,
|
||||
onClick = onGeneratePinClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Use current Signal PIN" // TODO [message-backups] Finalized copy
|
||||
text = "Generate a new $recommendedPinSize-digit PIN" // TODO [message-backups] Finalized copy
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,16 +39,17 @@ import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withAnnotation
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.signal.core.ui.Buttons
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.Scaffolds
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
@@ -59,9 +60,9 @@ import java.util.Currency
|
||||
@OptIn(ExperimentalTextApi::class)
|
||||
@Composable
|
||||
fun MessageBackupsTypeSelectionScreen(
|
||||
selectedBackupsType: MessageBackupsType?,
|
||||
availableBackupsTypes: List<MessageBackupsType>,
|
||||
onMessageBackupsTypeSelected: (MessageBackupsType) -> Unit,
|
||||
selectedBackupTier: MessageBackupTier?,
|
||||
availableBackupTiers: List<MessageBackupTier>,
|
||||
onMessageBackupsTierSelected: (MessageBackupTier) -> Unit,
|
||||
onNavigationClick: () -> Unit,
|
||||
onReadMoreClicked: () -> Unit,
|
||||
onNextClicked: () -> Unit
|
||||
@@ -128,13 +129,16 @@ fun MessageBackupsTypeSelectionScreen(
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
availableBackupsTypes,
|
||||
{ _, item -> item.title }
|
||||
availableBackupTiers,
|
||||
{ _, item -> item }
|
||||
) { index, item ->
|
||||
val type = remember(item) {
|
||||
getTierDetails(item)
|
||||
}
|
||||
MessageBackupsTypeBlock(
|
||||
messageBackupsType = item,
|
||||
isSelected = item == selectedBackupsType,
|
||||
onSelected = { onMessageBackupsTypeSelected(item) },
|
||||
messageBackupsType = type,
|
||||
isSelected = item == selectedBackupTier,
|
||||
onSelected = { onMessageBackupsTierSelected(item) },
|
||||
modifier = Modifier.padding(top = if (index == 0) 20.dp else 18.dp)
|
||||
)
|
||||
}
|
||||
@@ -154,54 +158,16 @@ fun MessageBackupsTypeSelectionScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun MessageBackupsTypeSelectionScreenPreview() {
|
||||
val freeTier = MessageBackupsType(
|
||||
pricePerMonth = FiatMoney(BigDecimal.ZERO, Currency.getInstance("USD")),
|
||||
title = "Text + 30 days of media",
|
||||
features = persistentListOf(
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "Full text message backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||
label = "Last 30 days of media"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val paidTier = MessageBackupsType(
|
||||
pricePerMonth = FiatMoney(BigDecimal.valueOf(3), Currency.getInstance("USD")),
|
||||
title = "Text + All your media",
|
||||
features = persistentListOf(
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "Full text message backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||
label = "Full media backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "1TB of storage (~250K photos)"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_heart_compact_bold_16,
|
||||
label = "Thanks for supporting Signal!"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
var selectedBackupsType by remember { mutableStateOf(freeTier) }
|
||||
var selectedBackupsType by remember { mutableStateOf(MessageBackupTier.FREE) }
|
||||
|
||||
Previews.Preview {
|
||||
MessageBackupsTypeSelectionScreen(
|
||||
selectedBackupsType = selectedBackupsType,
|
||||
availableBackupsTypes = listOf(freeTier, paidTier),
|
||||
onMessageBackupsTypeSelected = { selectedBackupsType = it },
|
||||
selectedBackupTier = MessageBackupTier.FREE,
|
||||
availableBackupTiers = listOf(MessageBackupTier.FREE, MessageBackupTier.PAID),
|
||||
onMessageBackupsTierSelected = { selectedBackupsType = it },
|
||||
onNavigationClick = {},
|
||||
onReadMoreClicked = {},
|
||||
onNextClicked = {}
|
||||
@@ -272,7 +238,51 @@ private fun formatCostPerMonth(pricePerMonth: FiatMoney): String {
|
||||
|
||||
@Stable
|
||||
data class MessageBackupsType(
|
||||
val tier: MessageBackupTier,
|
||||
val pricePerMonth: FiatMoney,
|
||||
val title: String,
|
||||
val features: ImmutableList<MessageBackupsTypeFeature>
|
||||
)
|
||||
|
||||
fun getTierDetails(tier: MessageBackupTier): MessageBackupsType {
|
||||
return when (tier) {
|
||||
MessageBackupTier.FREE -> MessageBackupsType(
|
||||
tier = MessageBackupTier.FREE,
|
||||
pricePerMonth = FiatMoney(BigDecimal.ZERO, Currency.getInstance("USD")),
|
||||
title = "Text + 30 days of media",
|
||||
features = persistentListOf(
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "Full text message backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||
label = "Last 30 days of media"
|
||||
)
|
||||
)
|
||||
)
|
||||
MessageBackupTier.PAID -> MessageBackupsType(
|
||||
tier = MessageBackupTier.PAID,
|
||||
pricePerMonth = FiatMoney(BigDecimal.valueOf(3), Currency.getInstance("USD")),
|
||||
title = "Text + All your media",
|
||||
features = persistentListOf(
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "Full text message backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_album_compact_bold_16,
|
||||
label = "Full media backup"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
|
||||
label = "1TB of storage (~250K photos)"
|
||||
),
|
||||
MessageBackupsTypeFeature(
|
||||
iconResourceId = R.drawable.symbol_heart_compact_bold_16,
|
||||
label = "Thanks for supporting Signal!"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user