Add optimize storage setting and sheet.

This commit is contained in:
Alex Hart
2024-08-01 15:52:51 -03:00
committed by mtang-signal
parent 2677665069
commit 1d1ea01cc1
6 changed files with 288 additions and 7 deletions

View File

@@ -261,6 +261,12 @@ fun MessageBackupsTypeBlock(
style = MaterialTheme.typography.titleMedium
)
val featureIconTint = if (isSelected) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurfaceVariant
}
Column(
verticalArrangement = spacedBy(4.dp),
modifier = Modifier
@@ -268,7 +274,7 @@ fun MessageBackupsTypeBlock(
.padding(horizontal = 16.dp)
) {
messageBackupsType.features.forEach {
MessageBackupsTypeFeatureRow(messageBackupsTypeFeature = it)
MessageBackupsTypeFeatureRow(messageBackupsTypeFeature = it, iconTint = featureIconTint)
}
}
}
@@ -292,7 +298,7 @@ private fun formatCostPerMonth(pricePerMonth: FiatMoney): String {
}
}
private fun testBackupTypes(): List<MessageBackupsType> {
fun testBackupTypes(): List<MessageBackupsType> {
return listOf(
MessageBackupsType(
tier = MessageBackupTier.FREE,

View File

@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -67,6 +68,7 @@ import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
import org.thoughtcrime.securesms.preferences.widgets.StorageGraphView
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.viewModel
@@ -79,6 +81,7 @@ class ManageStorageSettingsFragment : ComposeFragment() {
private val viewModel by viewModel<ManageStorageSettingsViewModel> { ManageStorageSettingsViewModel() }
@ExperimentalMaterial3Api
@Composable
override fun FragmentContent() {
val state by viewModel.state.collectAsState()
@@ -102,7 +105,14 @@ class ManageStorageSettingsFragment : ComposeFragment() {
onSetKeepMessages = { navController.navigate("set-keep-messages") },
onSetChatLengthLimit = { navController.navigate("set-chat-length-limit") },
onSyncTrimThreadDeletes = { viewModel.setSyncTrimDeletes(it) },
onDeleteChatHistory = { navController.navigate("confirm-delete-chat-history") }
onDeleteChatHistory = { navController.navigate("confirm-delete-chat-history") },
onToggleOnDeviceStorageOptimization = {
if (state.onDeviceStorageOptimizationState == ManageStorageSettingsViewModel.OnDeviceStorageOptimizationState.REQUIRES_PAID_TIER) {
UpgradeToEnableOptimizedStorageSheet().show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
} else {
viewModel.setOptimizeStorage(it)
}
}
)
}
@@ -236,7 +246,8 @@ private fun ManageStorageSettingsScreen(
onSetKeepMessages: () -> Unit = {},
onSetChatLengthLimit: () -> Unit = {},
onSyncTrimThreadDeletes: (Boolean) -> Unit = {},
onDeleteChatHistory: () -> Unit = {}
onDeleteChatHistory: () -> Unit = {},
onToggleOnDeviceStorageOptimization: (Boolean) -> Unit = {}
) {
Scaffolds.Settings(
title = stringResource(id = R.string.preferences__storage),
@@ -252,6 +263,19 @@ private fun ManageStorageSettingsScreen(
StorageOverview(state.breakdown, onReviewStorage)
if (state.onDeviceStorageOptimizationState > ManageStorageSettingsViewModel.OnDeviceStorageOptimizationState.FEATURE_NOT_AVAILABLE) {
Dividers.Default()
Texts.SectionHeader(text = stringResource(id = R.string.ManageStorageSettingsFragment__on_device_storage))
Rows.ToggleRow(
checked = state.onDeviceStorageOptimizationState == ManageStorageSettingsViewModel.OnDeviceStorageOptimizationState.ENABLED,
text = stringResource(id = R.string.ManageStorageSettingsFragment__optimize_on_device_storage),
label = stringResource(id = R.string.ManageStorageSettingsFragment__unused_media_will_be_offloaded),
onCheckChanged = onToggleOnDeviceStorageOptimization
)
}
Dividers.Default()
Texts.SectionHeader(text = stringResource(id = R.string.ManageStorageSettingsFragment_chat_limit))
@@ -510,7 +534,8 @@ private fun ManageStorageSettingsScreenPreview() {
ManageStorageSettingsScreen(
state = ManageStorageSettingsViewModel.ManageStorageState(
keepMessagesDuration = KeepMessagesDuration.FOREVER,
lengthLimit = ManageStorageSettingsViewModel.ManageStorageState.NO_LIMIT
lengthLimit = ManageStorageSettingsViewModel.ManageStorageState.NO_LIMIT,
onDeviceStorageOptimizationState = ManageStorageSettingsViewModel.OnDeviceStorageOptimizationState.DISABLED
)
)
}

View File

@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.database.MediaTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.media
@@ -20,6 +21,7 @@ import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.RemoteConfig
class ManageStorageSettingsViewModel : ViewModel() {
@@ -27,7 +29,8 @@ class ManageStorageSettingsViewModel : ViewModel() {
ManageStorageState(
keepMessagesDuration = SignalStore.settings.keepMessagesDuration,
lengthLimit = if (SignalStore.settings.isTrimByLengthEnabled) SignalStore.settings.threadTrimLength else ManageStorageState.NO_LIMIT,
syncTrimDeletes = SignalStore.settings.shouldSyncThreadTrimDeletes()
syncTrimDeletes = SignalStore.settings.shouldSyncThreadTrimDeletes(),
onDeviceStorageOptimizationState = getOnDeviceStorageOptimizationState()
)
)
val state = store.asStateFlow()
@@ -88,16 +91,56 @@ class ManageStorageSettingsViewModel : ViewModel() {
store.update { it.copy(syncTrimDeletes = syncTrimDeletes) }
}
fun setOptimizeStorage(enabled: Boolean) {
val storageState = getOnDeviceStorageOptimizationState()
if (storageState >= OnDeviceStorageOptimizationState.DISABLED) {
SignalStore.backup.optimizeStorage = enabled
store.update { it.copy(onDeviceStorageOptimizationState = if (enabled) OnDeviceStorageOptimizationState.ENABLED else OnDeviceStorageOptimizationState.DISABLED) }
}
}
private fun isRestrictingLengthLimitChange(newLimit: Int): Boolean {
return state.value.lengthLimit == ManageStorageState.NO_LIMIT || (newLimit != ManageStorageState.NO_LIMIT && newLimit < state.value.lengthLimit)
}
private fun getOnDeviceStorageOptimizationState(): OnDeviceStorageOptimizationState {
return when {
!RemoteConfig.messageBackups -> OnDeviceStorageOptimizationState.FEATURE_NOT_AVAILABLE
!SignalStore.backup.areBackupsEnabled || SignalStore.backup.backupTier != MessageBackupTier.PAID -> OnDeviceStorageOptimizationState.REQUIRES_PAID_TIER
SignalStore.backup.optimizeStorage -> OnDeviceStorageOptimizationState.ENABLED
else -> OnDeviceStorageOptimizationState.DISABLED
}
}
enum class OnDeviceStorageOptimizationState {
/**
* The entire feature is not available and the option should not be displayed to the user.
*/
FEATURE_NOT_AVAILABLE,
/**
* The feature is available, but the user is not on the paid backups plan.
*/
REQUIRES_PAID_TIER,
/**
* The user is on the paid backups plan but optimized storage is disabled.
*/
DISABLED,
/**
* The user is on the paid backups plan and optimized storage is enabled.
*/
ENABLED
}
@Immutable
data class ManageStorageState(
val keepMessagesDuration: KeepMessagesDuration = KeepMessagesDuration.FOREVER,
val lengthLimit: Int = NO_LIMIT,
val syncTrimDeletes: Boolean = true,
val breakdown: MediaTable.StorageBreakdown? = null
val breakdown: MediaTable.StorageBreakdown? = null,
val onDeviceStorageOptimizationState: OnDeviceStorageOptimizationState
) {
companion object {
const val NO_LIMIT = 0

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.storage
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
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.Icons
import org.signal.core.ui.Previews
import org.signal.core.ui.SignalPreview
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.ui.BackupsIconColors
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.testBackupTypes
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.CheckoutFlowActivity
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
/**
* Sheet describing how users must upgrade to enable optimized storage.
*/
class UpgradeToEnableOptimizedStorageSheet : ComposeBottomSheetDialogFragment() {
override val peekHeightPercentage: Float = 1f
private val viewModel: UpgradeToEnableOptimizedStorageViewModel by viewModels()
@Composable
override fun SheetContent() {
val type by viewModel.messageBackupsType
UpgradeToEnableOptimizedStorageSheetContent(
messageBackupsType = type,
onUpgradeNowClick = {
startActivity(CheckoutFlowActivity.createIntent(requireContext(), InAppPaymentType.RECURRING_BACKUP))
dismissAllowingStateLoss()
},
onCancelClick = {
dismissAllowingStateLoss()
}
)
}
}
@Composable
private fun UpgradeToEnableOptimizedStorageSheetContent(
messageBackupsType: MessageBackupsType?,
onUpgradeNowClick: () -> Unit = {},
onCancelClick: () -> Unit = {}
) {
if (messageBackupsType == null) {
// TODO [message-backups] -- network error?
return
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
BottomSheets.Handle()
Icons.BrushedForeground(
painter = painterResource(id = R.drawable.symbol_backup_light),
contentDescription = null,
foregroundBrush = BackupsIconColors.Normal.foreground,
modifier = Modifier
.padding(top = 8.dp, bottom = 12.dp)
.size(88.dp)
.background(
color = BackupsIconColors.Normal.background,
shape = CircleShape
)
.padding(20.dp)
)
Text(
text = stringResource(id = R.string.UpgradeToEnableOptimizedStorageSheet__upgrade_to_enable_this_feature),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
)
Text(
text = stringResource(id = R.string.UpgradeToEnableOptimizedStorageSheet__storage_optimization_can_only_be_used),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp, bottom = 28.dp)
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
)
MessageBackupsTypeBlock(
messageBackupsType = messageBackupsType,
isCurrent = false,
isSelected = false,
onSelected = {},
enabled = false,
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
.padding(bottom = 50.dp)
)
Buttons.LargePrimary(
onClick = onUpgradeNowClick,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
.padding(bottom = 8.dp)
) {
Text(
text = stringResource(id = R.string.UpgradeToEnableOptimizedStorageSheet__upgrade_now)
)
}
TextButton(
onClick = onCancelClick,
modifier = Modifier
.fillMaxWidth()
.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 UpgradeToEnableOptimizedStorageSheetContentPreview() {
Previews.BottomSheetPreview {
UpgradeToEnableOptimizedStorageSheetContent(
messageBackupsType = testBackupTypes()[1]
)
}
}

View File

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