mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Add optimize storage setting and sheet.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7217,6 +7217,20 @@
|
||||
<string name="ManageStorageSettingsFragment_apply_limits_title">Apply limits to linked devices</string>
|
||||
<!-- Setting description for syncing automated chat limit trimming (deleting messages automatically by length or date) to linked devices -->
|
||||
<string name="ManageStorageSettingsFragment_apply_limits_description">When enabled, chat limits will also delete messages from your linked devices.</string>
|
||||
<!-- Setting section title header for storage optimization -->
|
||||
<string name="ManageStorageSettingsFragment__on_device_storage">On-device storage</string>
|
||||
<!-- Setting row title for storage optimization -->
|
||||
<string name="ManageStorageSettingsFragment__optimize_on_device_storage">Optimize on-device storage</string>
|
||||
<!-- Setting row explanation for storage optimization -->
|
||||
<string name="ManageStorageSettingsFragment__unused_media_will_be_offloaded">Unused media will be offloaded, but can be downloaded from your backup anytime.</string>
|
||||
|
||||
<!-- UpgradeToEnableOptimizedStorageSheet -->
|
||||
<!-- Title on a bottom sheet, detailing that the user must upgrade to enable storage optimization -->
|
||||
<string name="UpgradeToEnableOptimizedStorageSheet__upgrade_to_enable_this_feature">Upgrade to enable this feature</string>
|
||||
<!-- Subtitle of bottom sheet detailing that the user must upgrade to enable storage optimization -->
|
||||
<string name="UpgradeToEnableOptimizedStorageSheet__storage_optimization_can_only_be_used">Storage optimization can only be used with the paid tier of Signal Backups. Upgrade your backup plan to start using this feature.</string>
|
||||
<!-- Primary action to launch upgrade flow -->
|
||||
<string name="UpgradeToEnableOptimizedStorageSheet__upgrade_now">Upgrade now</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>
|
||||
|
||||
Reference in New Issue
Block a user