Stub out MoreOptionsSheet and RestoreFromBackupFragment.

This commit is contained in:
Alex Hart 2024-03-21 17:17:05 -03:00 committed by Nicholas Tinsley
parent 7802448b24
commit 5f5a80dcbe
13 changed files with 756 additions and 39 deletions

View file

@ -0,0 +1,55 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.ui
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
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.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
/**
* Represents a "Feature" included for a specify tier of message backups
*/
data class MessageBackupsTypeFeature(
val iconResourceId: Int,
val label: String
)
/**
* Renders a "feature row" for a given feature.
*/
@Composable
fun MessageBackupsTypeFeatureRow(
messageBackupsTypeFeature: MessageBackupsTypeFeature,
iconTint: Color = LocalContentColor.current,
modifier: Modifier = Modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier.fillMaxWidth()
) {
Icon(
painter = painterResource(id = messageBackupsTypeFeature.iconResourceId),
contentDescription = null,
tint = iconTint,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = messageBackupsTypeFeature.label,
style = MaterialTheme.typography.bodyLarge
)
}
}

View file

@ -10,7 +10,6 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -19,7 +18,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -271,32 +269,8 @@ private fun formatCostPerMonth(pricePerMonth: FiatMoney): String {
}
}
@Composable
private fun MessageBackupsTypeFeatureRow(messageBackupsTypeFeature: MessageBackupsTypeFeature) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Icon(
painter = painterResource(id = messageBackupsTypeFeature.iconResourceId),
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = messageBackupsTypeFeature.label,
style = MaterialTheme.typography.bodyLarge
)
}
}
data class MessageBackupsType(
val pricePerMonth: FiatMoney,
val title: String,
val features: ImmutableList<MessageBackupsTypeFeature>
)
data class MessageBackupsTypeFeature(
val iconResourceId: Int,
val label: String
)

View file

@ -0,0 +1,179 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.ui.restore
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
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.theme.SignalTheme
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.ui.MessageBackupsTypeFeature
import org.thoughtcrime.securesms.backup.v2.ui.MessageBackupsTypeFeatureRow
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.devicetransfer.moreoptions.MoreTransferOrRestoreOptionsMode
import org.thoughtcrime.securesms.util.navigation.safeNavigate
/**
* Fragment which facilitates restoring from a backup during
* registration.
*/
class RestoreFromBackupFragment : ComposeFragment() {
private val navArgs: RestoreFromBackupFragmentArgs by navArgs()
@Composable
override fun FragmentContent() {
RestoreFromBackupContent(
features = persistentListOf(),
onRestoreBackupClick = {
// TODO [message-backups] Restore backup.
},
onCancelClick = {
findNavController()
.popBackStack()
},
onMoreOptionsClick = {
findNavController()
.safeNavigate(RestoreFromBackupFragmentDirections.actionRestoreFromBacakupFragmentToMoreOptions(MoreTransferOrRestoreOptionsMode.SELECTION))
},
cancelable = navArgs.cancelable
)
}
}
@Preview
@Composable
private fun RestoreFromBackupContentPreview() {
Previews.Preview {
RestoreFromBackupContent(
features = persistentListOf(
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_thread_compact_bold_16,
label = "Your last 30 days of media"
),
MessageBackupsTypeFeature(
iconResourceId = R.drawable.symbol_recent_compact_bold_16,
label = "All of your text messages"
)
),
onRestoreBackupClick = {},
onCancelClick = {},
onMoreOptionsClick = {},
true
)
}
}
@Composable
private fun RestoreFromBackupContent(
features: ImmutableList<MessageBackupsTypeFeature>,
onRestoreBackupClick: () -> Unit,
onCancelClick: () -> Unit,
onMoreOptionsClick: () -> Unit,
cancelable: Boolean
) {
Column(
modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
.padding(top = 40.dp, bottom = 24.dp)
) {
Text(
text = "Restore from backup", // TODO [message-backups] Finalized copy.
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 12.dp)
)
val yourLastBackupText = buildAnnotatedString {
append("Your last backup was made on March 5, 2024 at 9:00am.") // TODO [message-backups] Finalized copy.
append(" ")
withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) {
append("Only media sent or received in the past 30 days is included.") // TODO [message-backups] Finalized copy.
}
}
Text(
text = yourLastBackupText,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(bottom = 28.dp)
)
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = SignalTheme.colors.colorSurface2, shape = RoundedCornerShape(18.dp))
.padding(horizontal = 20.dp)
.padding(top = 20.dp, bottom = 18.dp)
) {
Text(
text = "Your backup includes:", // TODO [message-backups] Finalized copy.
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 6.dp)
)
features.forEach {
MessageBackupsTypeFeatureRow(
messageBackupsTypeFeature = it,
iconTint = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = 16.dp, top = 6.dp)
)
}
}
Spacer(modifier = Modifier.weight(1f))
Buttons.LargeTonal(
onClick = onRestoreBackupClick,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Restore backup" // TODO [message-backups] Finalized copy.
)
}
if (cancelable) {
TextButton(
onClick = onCancelClick,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = android.R.string.cancel)
)
}
} else {
TextButton(
onClick = onMoreOptionsClick,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.TransferOrRestoreFragment__more_options)
)
}
}
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.devicetransfer.moreoptions
/**
* Allows component opening sheet to specify mode
*/
enum class MoreTransferOrRestoreOptionsMode {
/**
* Only display the option to log in without transferring. Selection
* will be disabled.
*/
SKIP_ONLY,
/**
* Display transfer/restore local/skip as well as a next and cancel button
*/
SELECTION
}

View file

@ -0,0 +1,338 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.devicetransfer.moreoptions
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.signal.core.ui.BottomSheets
import org.signal.core.ui.Buttons
import org.signal.core.ui.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.devicetransfer.newdevice.BackupRestorationType
/**
* Lists a set of options the user can choose from for restoring backup or skipping restoration
*/
class MoreTransferOrRestoreOptionsSheet : ComposeBottomSheetDialogFragment() {
private val args by navArgs<MoreTransferOrRestoreOptionsSheetArgs>()
@Composable
override fun SheetContent() {
var selectedOption by remember {
mutableStateOf<BackupRestorationType?>(null)
}
MoreOptionsSheetContent(
mode = args.mode,
selectedOption = selectedOption,
onOptionSelected = { selectedOption = it },
onCancelClick = { findNavController().popBackStack() },
onNextClick = {
this.onNextClicked(selectedOption ?: BackupRestorationType.NONE)
}
)
}
private fun onNextClicked(selectedOption: BackupRestorationType) {
// TODO [message-requests] -- Launch next screen based off user choice
}
}
@Preview
@Composable
private fun MoreOptionsSheetContentPreview() {
Previews.BottomSheetPreview {
MoreOptionsSheetContent(
mode = MoreTransferOrRestoreOptionsMode.SKIP_ONLY,
selectedOption = null,
onOptionSelected = {},
onCancelClick = {},
onNextClick = {}
)
}
}
@Preview
@Composable
private fun MoreOptionsSheetSelectableContentPreview() {
Previews.BottomSheetPreview {
MoreOptionsSheetContent(
mode = MoreTransferOrRestoreOptionsMode.SELECTION,
selectedOption = null,
onOptionSelected = {},
onCancelClick = {},
onNextClick = {}
)
}
}
@Composable
private fun MoreOptionsSheetContent(
mode: MoreTransferOrRestoreOptionsMode,
selectedOption: BackupRestorationType?,
onOptionSelected: (BackupRestorationType) -> Unit,
onCancelClick: () -> Unit,
onNextClick: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
) {
BottomSheets.Handle()
Spacer(modifier = Modifier.size(42.dp))
if (mode == MoreTransferOrRestoreOptionsMode.SELECTION) {
TransferFromAndroidDeviceOption(
selectedOption = selectedOption,
onOptionSelected = onOptionSelected
)
Spacer(modifier = Modifier.size(16.dp))
RestoreLocalBackupOption(
selectedOption = selectedOption,
onOptionSelected = onOptionSelected
)
Spacer(modifier = Modifier.size(16.dp))
}
LogInWithoutTransferringOption(
selectedOption = selectedOption,
onOptionSelected = when (mode) {
MoreTransferOrRestoreOptionsMode.SKIP_ONLY -> { _ -> onNextClick() }
MoreTransferOrRestoreOptionsMode.SELECTION -> onOptionSelected
}
)
if (mode == MoreTransferOrRestoreOptionsMode.SELECTION) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 30.dp, bottom = 24.dp)
) {
TextButton(
onClick = onCancelClick
) {
Text(text = stringResource(id = android.R.string.cancel))
}
Spacer(modifier = Modifier.weight(1f))
Buttons.LargeTonal(
enabled = selectedOption != null,
onClick = onNextClick
) {
Text(text = stringResource(id = R.string.RegistrationActivity_next))
}
}
} else {
Spacer(modifier = Modifier.size(45.dp))
}
}
}
@Preview
@Composable
private fun LogInWithoutTransferringOptionPreview() {
Previews.BottomSheetPreview {
LogInWithoutTransferringOption(
selectedOption = null,
onOptionSelected = {}
)
}
}
@Composable
private fun LogInWithoutTransferringOption(
selectedOption: BackupRestorationType?,
onOptionSelected: (BackupRestorationType) -> Unit
) {
Option(
icon = {
Box(
modifier = Modifier.padding(horizontal = 18.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_backup_light), // TODO [message-backups] Finalized asset.
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(36.dp)
)
}
},
isSelected = selectedOption == BackupRestorationType.NONE,
title = "Log in without transferring", // TODO [message-backups] Finalized copy.
subtitle = "Continue without transferring your messages and media", // TODO [message-backups] Finalized copy.
onClick = { onOptionSelected(BackupRestorationType.NONE) }
)
}
@Preview
@Composable
private fun TransferFromAndroidDeviceOptionPreview() {
Previews.BottomSheetPreview {
TransferFromAndroidDeviceOption(
selectedOption = null,
onOptionSelected = {}
)
}
}
@Composable
private fun TransferFromAndroidDeviceOption(
selectedOption: BackupRestorationType?,
onOptionSelected: (BackupRestorationType) -> Unit
) {
Option(
icon = {
Box(
modifier = Modifier.padding(horizontal = 18.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_backup_light), // TODO [message-backups] Finalized asset.
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(36.dp)
)
}
},
isSelected = selectedOption == BackupRestorationType.DEVICE_TRANSFER,
title = "Transfer from Android device", // TODO [message-backups] Finalized copy.
subtitle = "Transfer your account and messages from your old device.", // TODO [message-backups] Finalized copy.
onClick = { onOptionSelected(BackupRestorationType.DEVICE_TRANSFER) }
)
}
@Preview
@Composable
private fun RestoreLocalBackupOptionPreview() {
Previews.BottomSheetPreview {
RestoreLocalBackupOption(
selectedOption = null,
onOptionSelected = {}
)
}
}
@Composable
private fun RestoreLocalBackupOption(
selectedOption: BackupRestorationType?,
onOptionSelected: (BackupRestorationType) -> Unit
) {
Option(
icon = {
Box(
modifier = Modifier.padding(horizontal = 18.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_backup_light), // TODO [message-backups] Finalized asset.
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(36.dp)
)
}
},
isSelected = selectedOption == BackupRestorationType.LOCAL_BACKUP,
title = "Restore local backup", // TODO [message-backups] Finalized copy.
subtitle = "Restore your messages from a backup file you saved on your device.", // TODO [message-backups] Finalized copy.
onClick = { onOptionSelected(BackupRestorationType.LOCAL_BACKUP) }
)
}
@Preview
@Composable
private fun OptionPreview() {
Previews.BottomSheetPreview {
Option(
icon = {
Box(
modifier = Modifier.padding(horizontal = 18.dp)
) {
Icon(
painter = painterResource(id = R.drawable.symbol_backup_light), // TODO [message-backups] Finalized asset.
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(36.dp)
)
}
},
isSelected = false,
title = "Log in without transferring", // TODO [message-backups] Finalized copy.
subtitle = "Continue without transferring your messages and media", // TODO [message-backups] Finalized copy.
onClick = {}
)
}
}
@Composable
private fun Option(
icon: @Composable () -> Unit,
isSelected: Boolean,
title: String,
subtitle: String,
onClick: () -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(12.dp)
)
.border(
width = if (isSelected) 2.dp else 0.dp,
color = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent
)
.clip(RoundedCornerShape(12.dp))
.clickable { onClick() }
.padding(vertical = 21.dp)
) {
icon()
Column {
Text(
text = title,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.devicetransfer.newdevice
/**
* What kind of backup restore the user wishes to perform.
*/
enum class BackupRestorationType {
DEVICE_TRANSFER,
LOCAL_BACKUP,
REMOTE_BACKUP,
NONE
}

View file

@ -12,6 +12,7 @@ import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.databinding.FragmentTransferRestoreBinding;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
@ -36,7 +37,13 @@ public final class TransferOrRestoreFragment extends LoggingFragment {
binding.transferOrRestoreFragmentTransfer.setOnClickListener(v -> viewModel.onTransferFromAndroidDeviceSelected());
binding.transferOrRestoreFragmentRestore.setOnClickListener(v -> viewModel.onRestoreFromLocalBackupSelected());
binding.transferOrRestoreFragmentRestoreRemote.setOnClickListener(v -> viewModel.onRestoreFromRemoteBackupSelected());
binding.transferOrRestoreFragmentNext.setOnClickListener(v -> launchSelection(viewModel.getStateSnapshot()));
binding.transferOrRestoreFragmentMoreOptions.setOnClickListener(v -> SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_transferOrRestore_to_moreOptions));
int visibility = FeatureFlags.messageBackups() ? View.VISIBLE : View.GONE;
binding.transferOrRestoreFragmentRestoreRemoteCard.setVisibility(visibility);
binding.transferOrRestoreFragmentMoreOptions.setVisibility(visibility);
String description = getString(R.string.TransferOrRestoreFragment__transfer_your_account_and_messages_from_your_old_android_device);
String toBold = getString(R.string.TransferOrRestoreFragment__you_need_access_to_your_old_device);
@ -47,15 +54,18 @@ public final class TransferOrRestoreFragment extends LoggingFragment {
lifecycleDisposable.add(viewModel.getState().subscribe(this::updateSelection));
}
private void updateSelection(TransferOrRestoreViewModel.RestorationType restorationType) {
binding.transferOrRestoreFragmentTransferCard.setSelected(restorationType == TransferOrRestoreViewModel.RestorationType.DEVICE_TRANSFER);
binding.transferOrRestoreFragmentRestoreCard.setSelected(restorationType == TransferOrRestoreViewModel.RestorationType.LOCAL_BACKUP);
private void updateSelection(BackupRestorationType restorationType) {
binding.transferOrRestoreFragmentTransferCard.setSelected(restorationType == BackupRestorationType.DEVICE_TRANSFER);
binding.transferOrRestoreFragmentRestoreCard.setSelected(restorationType == BackupRestorationType.LOCAL_BACKUP);
binding.transferOrRestoreFragmentRestoreRemoteCard.setSelected(restorationType == BackupRestorationType.REMOTE_BACKUP);
}
private void launchSelection(TransferOrRestoreViewModel.RestorationType restorationType) {
private void launchSelection(BackupRestorationType restorationType) {
switch (restorationType) {
case DEVICE_TRANSFER -> SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_new_device_transfer_instructions);
case LOCAL_BACKUP -> SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_choose_backup);
case REMOTE_BACKUP -> {}
default -> throw new IllegalArgumentException();
}
}
}

View file

@ -15,21 +15,20 @@ import io.reactivex.rxjava3.processors.BehaviorProcessor
*/
class TransferOrRestoreViewModel : ViewModel() {
private val internalState = BehaviorProcessor.createDefault(RestorationType.DEVICE_TRANSFER)
private val internalState = BehaviorProcessor.createDefault(BackupRestorationType.DEVICE_TRANSFER)
val state: Flowable<RestorationType> = internalState.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread())
val stateSnapshot: RestorationType get() = internalState.value!!
val state: Flowable<BackupRestorationType> = internalState.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread())
val stateSnapshot: BackupRestorationType get() = internalState.value!!
fun onTransferFromAndroidDeviceSelected() {
internalState.onNext(RestorationType.DEVICE_TRANSFER)
internalState.onNext(BackupRestorationType.DEVICE_TRANSFER)
}
fun onRestoreFromLocalBackupSelected() {
internalState.onNext(RestorationType.LOCAL_BACKUP)
internalState.onNext(BackupRestorationType.LOCAL_BACKUP)
}
enum class RestorationType {
DEVICE_TRANSFER,
LOCAL_BACKUP
fun onRestoreFromRemoteBackupSelected() {
internalState.onNext(BackupRestorationType.REMOTE_BACKUP)
}
}

View file

@ -126,6 +126,7 @@ public final class FeatureFlags {
private static final String CDSI_LIBSIGNAL_NET = "android.cds.libsignal.2";
private static final String RX_MESSAGE_SEND = "android.rxMessageSend";
private static final String LINKED_DEVICE_LIFESPAN_SECONDS = "android.linkedDeviceLifespanSeconds";
private static final String MESSAGE_BACKUPS = "android.messageBackups";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -208,7 +209,7 @@ public final class FeatureFlags {
);
@VisibleForTesting
static final Set<String> NOT_REMOTE_CAPABLE = SetUtil.newHashSet();
static final Set<String> NOT_REMOTE_CAPABLE = SetUtil.newHashSet(MESSAGE_BACKUPS);
/**
* Values in this map will take precedence over any value. This should only be used for local
@ -731,6 +732,14 @@ public final class FeatureFlags {
return TimeUnit.SECONDS.toMillis(seconds);
}
/**
* Enable Message Backups UI
* Note: This feature is in active development and is not intended to currently function.
*/
public static boolean messageBackups() {
return getBoolean(MESSAGE_BACKUPS, false);
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View file

@ -92,6 +92,67 @@
</org.thoughtcrime.securesms.components.ClippedCardView>
<org.thoughtcrime.securesms.components.ClippedCardView
android:id="@+id/transfer_or_restore_fragment_restore_remote_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:cardBackgroundColor="@color/signal_colorSurface2"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:strokeColor="@color/transfer_option_stroke_color_selector"
app:strokeWidth="2dp"
tools:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/transfer_or_restore_fragment_restore_remote"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="16dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/transfer_or_restore_fragment_restore_remote_icon"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="18dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/symbol_backup_light"
app:tint="@color/signal_colorPrimary" />
<TextView
android:id="@+id/transfer_or_restore_fragment_restore_remote_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginEnd="16dp"
android:text="@string/TransferOrRestoreFragment__restore_from_backup"
android:textAppearance="@style/Signal.Text.Body"
app:layout_constraintBottom_toTopOf="@+id/transfer_or_restore_fragment_restore_remote_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/transfer_or_restore_fragment_restore_remote_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/transfer_or_restore_fragment_restore_remote_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/TransferOrRestoreFragment__restore_your_messages_from_a_local_backup"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@+id/transfer_or_restore_fragment_restore_remote_header"
app:layout_constraintTop_toBottomOf="@+id/transfer_or_restore_fragment_restore_remote_header" />
</androidx.constraintlayout.widget.ConstraintLayout>
</org.thoughtcrime.securesms.components.ClippedCardView>
<org.thoughtcrime.securesms.components.ClippedCardView
android:id="@+id/transfer_or_restore_fragment_restore_card"
android:layout_width="match_parent"
@ -160,6 +221,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/transfer_or_restore_fragment_more_options"
style="@style/Widget.Signal.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/TransferOrRestoreFragment__more_options"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/transfer_or_restore_fragment_next"
style="@style/Signal.Widget.Button.Large.Tonal"

View file

@ -368,8 +368,35 @@
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_transferOrRestore_to_moreOptions"
app:destination="@+id/transferOrRestoreMoreOptionsDialog" />
</fragment>
<fragment
android:id="@+id/restoreFromBackupFragment"
android:name="org.thoughtcrime.securesms.backup.v2.ui.restore.RestoreFromBackupFragment">
<action
android:id="@+id/action_restoreFromBacakupFragment_to_moreOptions"
app:destination="@+id/transferOrRestoreMoreOptionsDialog" />
<argument
android:name="cancelable"
app:argType="boolean" />
</fragment>
<dialog
android:id="@+id/transferOrRestoreMoreOptionsDialog"
android:name="org.thoughtcrime.securesms.devicetransfer.moreoptions.MoreTransferOrRestoreOptionsSheet">
<argument
android:name="mode"
app:argType="org.thoughtcrime.securesms.devicetransfer.moreoptions.MoreTransferOrRestoreOptionsMode" />
</dialog>
<fragment
android:id="@+id/newDeviceTransferInstructions"
android:name="org.thoughtcrime.securesms.devicetransfer.newdevice.NewDeviceTransferInstructionsFragment"

View file

@ -3972,6 +3972,8 @@
<string name="TransferOrRestoreFragment__you_need_access_to_your_old_device">You need access to your old device.</string>
<string name="TransferOrRestoreFragment__restore_from_backup">Restore from backup</string>
<string name="TransferOrRestoreFragment__restore_your_messages_from_a_local_backup">Restore your messages from a local backup. If you dont restore now, you won\'t be able to restore later.</string>
<!-- Button label for more options -->
<string name="TransferOrRestoreFragment__more_options">More options</string>
<!-- NewDeviceTransferInstructionsFragment -->
<string name="NewDeviceTransferInstructions__open_signal_on_your_old_android_phone">Open Signal on your old Android phone</string>

View file

@ -5,8 +5,11 @@
package org.signal.core.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import org.signal.core.ui.theme.SignalTheme
object Previews {
@ -20,4 +23,17 @@ object Previews {
}
}
}
@Composable
fun BottomSheetPreview(
content: @Composable () -> Unit
) {
SignalTheme {
Surface {
Box(modifier = Modifier.background(color = SignalTheme.colors.colorSurface1)) {
content()
}
}
}
}
}