mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Add Delete for Me sync support.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.FragmentManager
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.subjects.CompletableSubject
|
||||
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.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
/**
|
||||
* Show educational info about delete syncing to linked devices. This dialog uses a subject to convey when
|
||||
* it completes and will dismiss itself if that subject is null aka dialog is recreated by OS instead of being
|
||||
* shown by our code.
|
||||
*/
|
||||
class DeleteSyncEducationDialog : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun shouldShow(): Boolean {
|
||||
return TextSecurePreferences.isMultiDevice(ApplicationDependencies.getApplication()) &&
|
||||
!SignalStore.uiHints().hasSeenDeleteSyncEducationSheet &&
|
||||
FeatureFlags.deleteSyncEnabled()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager): Completable {
|
||||
val dialog = DeleteSyncEducationDialog()
|
||||
|
||||
dialog.show(fragmentManager, null)
|
||||
SignalStore.uiHints().hasSeenDeleteSyncEducationSheet = true
|
||||
|
||||
val subject = CompletableSubject.create()
|
||||
dialog.subject = subject
|
||||
|
||||
return subject
|
||||
.onErrorComplete()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
}
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
private var subject: CompletableSubject? = null
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
Sheet(dismiss = this::dismissAllowingStateLoss)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (subject == null || savedInstanceState != null) {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
subject?.onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sheet(
|
||||
dismiss: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.delete_sync),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(top = 48.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.DeleteSyncEducation_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp, bottom = 12.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.DeleteSyncEducation_message),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = dismiss,
|
||||
modifier = Modifier
|
||||
.padding(top = 64.dp)
|
||||
.defaultMinSize(minWidth = 132.dp)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.DeleteSyncEducation_acknowledge_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun SheetPreview() {
|
||||
Previews.Preview {
|
||||
Sheet()
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.integerArrayResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
@@ -65,6 +66,8 @@ import org.thoughtcrime.securesms.database.MediaTable
|
||||
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration
|
||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
|
||||
import org.thoughtcrime.securesms.preferences.widgets.StorageGraphView
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import java.text.NumberFormat
|
||||
@@ -98,6 +101,7 @@ class ManageStorageSettingsFragment : ComposeFragment() {
|
||||
onReviewStorage = { startActivity(MediaOverviewActivity.forAll(requireContext())) },
|
||||
onSetKeepMessages = { navController.navigate("set-keep-messages") },
|
||||
onSetChatLengthLimit = { navController.navigate("set-chat-length-limit") },
|
||||
onSyncTrimThreadDeletes = { viewModel.setSyncTrimDeletes(it) },
|
||||
onDeleteChatHistory = { navController.navigate("confirm-delete-chat-history") }
|
||||
)
|
||||
}
|
||||
@@ -134,7 +138,11 @@ class ManageStorageSettingsFragment : ComposeFragment() {
|
||||
dialog("confirm-delete-chat-history") {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(id = R.string.preferences_storage__delete_message_history),
|
||||
body = stringResource(id = R.string.preferences_storage__this_will_delete_all_message_history_and_media_from_your_device),
|
||||
body = if (TextSecurePreferences.isMultiDevice(LocalContext.current) && FeatureFlags.deleteSyncEnabled()) {
|
||||
stringResource(id = R.string.preferences_storage__this_will_delete_all_message_history_and_media_from_your_device_linked_device)
|
||||
} else {
|
||||
stringResource(id = R.string.preferences_storage__this_will_delete_all_message_history_and_media_from_your_device)
|
||||
},
|
||||
confirm = stringResource(id = R.string.delete),
|
||||
confirmColor = MaterialTheme.colorScheme.error,
|
||||
dismiss = stringResource(id = android.R.string.cancel),
|
||||
@@ -146,7 +154,11 @@ class ManageStorageSettingsFragment : ComposeFragment() {
|
||||
dialog("double-confirm-delete-chat-history", dialogProperties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)) {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(id = R.string.preferences_storage__are_you_sure_you_want_to_delete_all_message_history),
|
||||
body = stringResource(id = R.string.preferences_storage__all_message_history_will_be_permanently_removed_this_action_cannot_be_undone),
|
||||
body = if (TextSecurePreferences.isMultiDevice(LocalContext.current) && FeatureFlags.deleteSyncEnabled()) {
|
||||
stringResource(id = R.string.preferences_storage__all_message_history_will_be_permanently_removed_this_action_cannot_be_undone_linked_device)
|
||||
} else {
|
||||
stringResource(id = R.string.preferences_storage__all_message_history_will_be_permanently_removed_this_action_cannot_be_undone)
|
||||
},
|
||||
confirm = stringResource(id = R.string.preferences_storage__delete_all_now),
|
||||
confirmColor = MaterialTheme.colorScheme.error,
|
||||
dismiss = stringResource(id = android.R.string.cancel),
|
||||
@@ -223,6 +235,7 @@ private fun ManageStorageSettingsScreen(
|
||||
onReviewStorage: () -> Unit = {},
|
||||
onSetKeepMessages: () -> Unit = {},
|
||||
onSetChatLengthLimit: () -> Unit = {},
|
||||
onSyncTrimThreadDeletes: (Boolean) -> Unit = {},
|
||||
onDeleteChatHistory: () -> Unit = {}
|
||||
) {
|
||||
Scaffolds.Settings(
|
||||
@@ -263,6 +276,13 @@ private fun ManageStorageSettingsScreen(
|
||||
onClick = onSetChatLengthLimit
|
||||
)
|
||||
|
||||
Rows.ToggleRow(
|
||||
text = stringResource(id = R.string.ManageStorageSettingsFragment_apply_limits_title),
|
||||
label = stringResource(id = R.string.ManageStorageSettingsFragment_apply_limits_description),
|
||||
checked = state.syncTrimDeletes,
|
||||
onCheckChanged = onSyncTrimThreadDeletes
|
||||
)
|
||||
|
||||
Dividers.Default()
|
||||
|
||||
Rows.TextRow(
|
||||
|
||||
@@ -26,7 +26,8 @@ class ManageStorageSettingsViewModel : ViewModel() {
|
||||
private val store = MutableStateFlow(
|
||||
ManageStorageState(
|
||||
keepMessagesDuration = SignalStore.settings().keepMessagesDuration,
|
||||
lengthLimit = if (SignalStore.settings().isTrimByLengthEnabled) SignalStore.settings().threadTrimLength else ManageStorageState.NO_LIMIT
|
||||
lengthLimit = if (SignalStore.settings().isTrimByLengthEnabled) SignalStore.settings().threadTrimLength else ManageStorageState.NO_LIMIT,
|
||||
syncTrimDeletes = SignalStore.settings().shouldSyncThreadTrimDeletes()
|
||||
)
|
||||
)
|
||||
val state = store.asStateFlow()
|
||||
@@ -82,6 +83,11 @@ class ManageStorageSettingsViewModel : ViewModel() {
|
||||
return isRestrictingLengthLimitChange(newLimit)
|
||||
}
|
||||
|
||||
fun setSyncTrimDeletes(syncTrimDeletes: Boolean) {
|
||||
SignalStore.settings().setSyncThreadTrimDeletes(syncTrimDeletes)
|
||||
store.update { it.copy(syncTrimDeletes = syncTrimDeletes) }
|
||||
}
|
||||
|
||||
private fun isRestrictingLengthLimitChange(newLimit: Int): Boolean {
|
||||
return state.value.lengthLimit == ManageStorageState.NO_LIMIT || (newLimit != ManageStorageState.NO_LIMIT && newLimit < state.value.lengthLimit)
|
||||
}
|
||||
@@ -90,6 +96,7 @@ class ManageStorageSettingsViewModel : ViewModel() {
|
||||
data class ManageStorageState(
|
||||
val keepMessagesDuration: KeepMessagesDuration = KeepMessagesDuration.FOREVER,
|
||||
val lengthLimit: Int = NO_LIMIT,
|
||||
val syncTrimDeletes: Boolean = true,
|
||||
val breakdown: MediaTable.StorageBreakdown? = null
|
||||
) {
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user