mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 08:09:12 +01:00
Local backups upgrade UI.
This commit is contained in:
committed by
Greyson Parrelli
parent
2e70ed14dd
commit
6c30f3d573
@@ -37,6 +37,7 @@ class ArchiveFileSystem private constructor(private val context: Context, root:
|
||||
companion object {
|
||||
val TAG = Log.tag(ArchiveFileSystem::class.java)
|
||||
|
||||
const val MAIN_DIRECTORY_NAME = "SignalBackups"
|
||||
const val BACKUP_DIRECTORY_PREFIX: String = "signal-backup"
|
||||
const val TEMP_BACKUP_DIRECTORY_SUFFIX: String = "tmp"
|
||||
|
||||
@@ -75,7 +76,7 @@ class ArchiveFileSystem private constructor(private val context: Context, root:
|
||||
val filesFileSystem: FilesFileSystem
|
||||
|
||||
init {
|
||||
signalBackups = root.mkdirp("SignalBackups") ?: throw IOException("Unable to create main backups directory")
|
||||
signalBackups = root.mkdirp(MAIN_DIRECTORY_NAME) ?: throw IOException("Unable to create main backups directory")
|
||||
val filesDirectory = signalBackups.mkdirp("files") ?: throw IOException("Unable to create files directory")
|
||||
filesFileSystem = FilesFileSystem(context, filesDirectory)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.compose.Nav
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
@@ -159,7 +160,8 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega
|
||||
onRequestSaveToPasswordManager = viewModel::onBackupKeySaveRequested,
|
||||
onConfirmSaveToPasswordManager = viewModel::onBackupKeySaveConfirmed,
|
||||
onSaveToPasswordManagerComplete = viewModel::onBackupKeySaveCompleted,
|
||||
onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) }
|
||||
onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) },
|
||||
notifyKeyIsSameAsOnDeviceBackupKey = SignalStore.backup.newLocalBackupsEnabled
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ package org.thoughtcrime.securesms.backup.v2.ui.subscription
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -17,6 +19,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -29,23 +32,47 @@ import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.horizontalGutters
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.SignalTheme
|
||||
import org.signal.core.ui.R as CoreUiR
|
||||
|
||||
enum class MessageBackupsKeyEducationScreenMode {
|
||||
/**
|
||||
* Displayed when the user is enabling remote backups and does not have unified local backups enabled
|
||||
*/
|
||||
REMOTE_BACKUP_WITH_LOCAL_DISABLED,
|
||||
|
||||
/**
|
||||
* Displayed when the user is upgrading legacy to unified local backup
|
||||
*/
|
||||
LOCAL_BACKUP_UPGRADE,
|
||||
|
||||
/**
|
||||
* Displayed when the user has unified local backup and is enabling remote backups
|
||||
*/
|
||||
REMOTE_BACKUP_WITH_LOCAL_ENABLED
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen detailing how a backups key is used to restore a backup
|
||||
*/
|
||||
@Composable
|
||||
fun MessageBackupsKeyEducationScreen(
|
||||
onNavigationClick: () -> Unit = {},
|
||||
onNextClick: () -> Unit = {}
|
||||
onNextClick: () -> Unit = {},
|
||||
mode: MessageBackupsKeyEducationScreenMode = MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
@@ -71,26 +98,24 @@ fun MessageBackupsKeyEducationScreen(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.MessageBackupsKeyEducationScreen__your_backup_key),
|
||||
text = getTitleText(mode),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
|
||||
InfoRow(
|
||||
R.drawable.symbol_number_24,
|
||||
R.string.MessageBackupsKeyEducationScreen__your_backup_key_is_a
|
||||
)
|
||||
when (mode) {
|
||||
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED -> {
|
||||
RemoteBackupWithLocalDisabledInfo()
|
||||
}
|
||||
|
||||
InfoRow(
|
||||
CoreUiR.drawable.symbol_lock_24,
|
||||
R.string.MessageBackupsKeyEducationScreen__store_your_recovery
|
||||
)
|
||||
|
||||
InfoRow(
|
||||
R.drawable.symbol_error_circle_24,
|
||||
R.string.MessageBackupsKeyEducationScreen__if_you_lose_it
|
||||
)
|
||||
MessageBackupsKeyEducationScreenMode.LOCAL_BACKUP_UPGRADE -> {
|
||||
LocalBackupUpgradeInfo()
|
||||
}
|
||||
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_ENABLED -> {
|
||||
RemoteBackupWithLocalEnabledInfo()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
@@ -117,6 +142,154 @@ fun MessageBackupsKeyEducationScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getTitleText(mode: MessageBackupsKeyEducationScreenMode): String {
|
||||
return when (mode) {
|
||||
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED -> stringResource(R.string.MessageBackupsKeyEducationScreen__your_backup_key)
|
||||
MessageBackupsKeyEducationScreenMode.LOCAL_BACKUP_UPGRADE -> stringResource(R.string.MessageBackupsKeyEducationScreen__your_new_recovery_key)
|
||||
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_ENABLED -> stringResource(R.string.MessageBackupsKeyEducationScreen__your_recovery_key)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LocalBackupUpgradeInfo() {
|
||||
val normalText = stringResource(R.string.MessageBackupsKeyEducationScreen__local_backup_upgrade_description)
|
||||
val boldText = stringResource(R.string.MessageBackupsKeyEducationScreen__local_backup_upgrade_description_bold)
|
||||
|
||||
DescriptionText(
|
||||
normalText = normalText,
|
||||
boldText = boldText
|
||||
)
|
||||
|
||||
UseThisKeyToContainer {
|
||||
UseThisKeyToRow(
|
||||
icon = ImageVector.vectorResource(R.drawable.symbol_folder_24),
|
||||
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_on_device_backup)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
UseThisKeyToRow(
|
||||
icon = ImageVector.vectorResource(CoreUiR.drawable.symbol_backup_24),
|
||||
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_a_signal_secure_backup)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RemoteBackupWithLocalEnabledInfo() {
|
||||
val normalText = stringResource(R.string.MessageBackupsKeyEducationScreen__remote_backup_with_local_enabled_description)
|
||||
val boldText = stringResource(R.string.MessageBackupsKeyEducationScreen__remote_backup_with_local_enabled_description_bold)
|
||||
|
||||
DescriptionText(
|
||||
normalText = normalText,
|
||||
boldText = boldText
|
||||
)
|
||||
|
||||
UseThisKeyToContainer {
|
||||
UseThisKeyToRow(
|
||||
icon = ImageVector.vectorResource(CoreUiR.drawable.symbol_backup_24),
|
||||
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_your_signal_secure_backup)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
UseThisKeyToRow(
|
||||
icon = ImageVector.vectorResource(R.drawable.symbol_folder_24),
|
||||
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_on_device_backup)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DescriptionText(
|
||||
normalText: String,
|
||||
boldText: String
|
||||
) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append(normalText)
|
||||
append(" ")
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(boldText)
|
||||
}
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.horizontalGutters()
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UseThisKeyToContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(top = 28.dp)
|
||||
.horizontalGutters()
|
||||
.fillMaxWidth()
|
||||
.background(color = SignalTheme.colors.colorSurface1, shape = RoundedCornerShape(10.dp))
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.MessageBackupsKeyEducationScreen__use_this_key_to),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 14.dp)
|
||||
)
|
||||
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UseThisKeyToRow(
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RemoteBackupWithLocalDisabledInfo() {
|
||||
InfoRow(
|
||||
R.drawable.symbol_number_24,
|
||||
R.string.MessageBackupsKeyEducationScreen__your_backup_key_is_a
|
||||
)
|
||||
|
||||
InfoRow(
|
||||
CoreUiR.drawable.symbol_lock_24,
|
||||
R.string.MessageBackupsKeyEducationScreen__store_your_recovery
|
||||
)
|
||||
|
||||
InfoRow(
|
||||
R.drawable.symbol_error_circle_24,
|
||||
R.string.MessageBackupsKeyEducationScreen__if_you_lose_it
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoRow(@DrawableRes iconId: Int, @StringRes textId: Int) {
|
||||
Row(
|
||||
@@ -140,8 +313,30 @@ private fun InfoRow(@DrawableRes iconId: Int, @StringRes textId: Int) {
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MessageBackupsKeyEducationScreenPreview() {
|
||||
private fun MessageBackupsKeyEducationScreenRemoteBackupWithLocalDisabledPreview() {
|
||||
Previews.Preview {
|
||||
MessageBackupsKeyEducationScreen()
|
||||
MessageBackupsKeyEducationScreen(
|
||||
mode = MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MessageBackupsKeyEducationScreenLocalBackupUpgradePreview() {
|
||||
Previews.Preview {
|
||||
MessageBackupsKeyEducationScreen(
|
||||
mode = MessageBackupsKeyEducationScreenMode.LOCAL_BACKUP_UPGRADE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MessageBackupsKeyEducationScreenRemoteBackupWithLocalEnabledPreview() {
|
||||
Previews.Preview {
|
||||
MessageBackupsKeyEducationScreen(
|
||||
mode = MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_ENABLED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,13 +58,18 @@ import org.signal.core.ui.compose.Snackbars
|
||||
import org.signal.core.ui.compose.horizontalGutters
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeyCredentialManagerHandler
|
||||
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeySaveState
|
||||
import org.thoughtcrime.securesms.fonts.MonoTypeface
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.storage.AndroidCredentialRepository
|
||||
import org.thoughtcrime.securesms.util.storage.CredentialManagerError
|
||||
import org.thoughtcrime.securesms.util.storage.CredentialManagerResult
|
||||
import org.signal.core.ui.R as CoreUiR
|
||||
|
||||
private const val CLIPBOARD_TIMEOUT_SECONDS = 60
|
||||
|
||||
@Stable
|
||||
sealed interface MessageBackupsKeyRecordMode {
|
||||
data class Next(val onNextClick: () -> Unit) : MessageBackupsKeyRecordMode
|
||||
@@ -76,6 +81,40 @@ sealed interface MessageBackupsKeyRecordMode {
|
||||
) : MessageBackupsKeyRecordMode
|
||||
}
|
||||
|
||||
/**
|
||||
* More self-contained version of [MessageBackupsKeyRecordScreen] to try to improve reusability.
|
||||
* This version is not built to be previewed but covers a lot of the repetitive boilerplate seen
|
||||
* elsewhere.
|
||||
*/
|
||||
@Composable
|
||||
fun MessageBackupsKeyRecordScreen(
|
||||
backupKey: String,
|
||||
keySaveState: BackupKeySaveState?,
|
||||
backupKeyCredentialManagerHandler: BackupKeyCredentialManagerHandler,
|
||||
mode: MessageBackupsKeyRecordMode
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val passwordManagerSettingsIntent = remember {
|
||||
AndroidCredentialRepository.getCredentialManagerSettingsIntent(context)
|
||||
}
|
||||
|
||||
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
|
||||
MessageBackupsKeyRecordScreen(
|
||||
backupKey = backupKey,
|
||||
keySaveState = keySaveState,
|
||||
canOpenPasswordManagerSettings = passwordManagerSettingsIntent != null,
|
||||
onNavigationClick = { onBackPressedDispatcher?.onBackPressed() },
|
||||
onCopyToClipboardClick = { Util.copyToClipboard(context, it, CLIPBOARD_TIMEOUT_SECONDS) },
|
||||
onRequestSaveToPasswordManager = backupKeyCredentialManagerHandler::onBackupKeySaveRequested,
|
||||
onConfirmSaveToPasswordManager = backupKeyCredentialManagerHandler::onBackupKeySaveConfirmed,
|
||||
onSaveToPasswordManagerComplete = backupKeyCredentialManagerHandler::onBackupKeySaveCompleted,
|
||||
mode = mode,
|
||||
onGoToPasswordManagerSettingsClick = { context.startActivity(passwordManagerSettingsIntent) },
|
||||
notifyKeyIsSameAsOnDeviceBackupKey = SignalStore.backup.newLocalBackupsEnabled
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen displaying the backup key allowing the user to write it down
|
||||
* or copy it.
|
||||
@@ -91,7 +130,8 @@ fun MessageBackupsKeyRecordScreen(
|
||||
onConfirmSaveToPasswordManager: () -> Unit = {},
|
||||
onSaveToPasswordManagerComplete: (CredentialManagerResult) -> Unit = {},
|
||||
onGoToPasswordManagerSettingsClick: () -> Unit = {},
|
||||
mode: MessageBackupsKeyRecordMode = MessageBackupsKeyRecordMode.Next(onNextClick = {})
|
||||
mode: MessageBackupsKeyRecordMode = MessageBackupsKeyRecordMode.Next(onNextClick = {}),
|
||||
notifyKeyIsSameAsOnDeviceBackupKey: Boolean = false
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val backupKeyString = remember(backupKey) {
|
||||
@@ -142,8 +182,14 @@ fun MessageBackupsKeyRecordScreen(
|
||||
}
|
||||
|
||||
item {
|
||||
val text = if (notifyKeyIsSameAsOnDeviceBackupKey) {
|
||||
stringResource(R.string.MessageBackupsKeyRecordScreen__this_key_is_the_same_as_your_on_device_backup_key)
|
||||
} else {
|
||||
stringResource(R.string.MessageBackupsKeyRecordScreen__this_key_is_required_to_recover)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.MessageBackupsKeyRecordScreen__this_key_is_required_to_recover),
|
||||
text = text,
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -199,23 +245,14 @@ fun MessageBackupsKeyRecordScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (mode is MessageBackupsKeyRecordMode.Next) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp)
|
||||
) {
|
||||
Buttons.LargeTonal(
|
||||
onClick = mode.onNextClick,
|
||||
modifier = Modifier.align(Alignment.BottomEnd)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.MessageBackupsKeyRecordScreen__next)
|
||||
)
|
||||
}
|
||||
when (mode) {
|
||||
is MessageBackupsKeyRecordMode.Next -> {
|
||||
NextButton(onNextClick = mode.onNextClick)
|
||||
}
|
||||
|
||||
is MessageBackupsKeyRecordMode.CreateNewKey -> {
|
||||
CreateNewKeyButton(mode)
|
||||
}
|
||||
} else if (mode is MessageBackupsKeyRecordMode.CreateNewKey) {
|
||||
CreateNewKeyButton(mode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +296,24 @@ fun MessageBackupsKeyRecordScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NextButton(onNextClick: () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp)
|
||||
) {
|
||||
Buttons.LargeTonal(
|
||||
onClick = onNextClick,
|
||||
modifier = Modifier.align(Alignment.BottomEnd)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.MessageBackupsKeyRecordScreen__next)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CreateNewKeyButton(
|
||||
@@ -505,6 +560,20 @@ private fun MessageBackupsKeyRecordScreenPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun MessageBackupsKeyRecordScreenSameAsOnDeviceKeyPreview() {
|
||||
Previews.Preview {
|
||||
MessageBackupsKeyRecordScreen(
|
||||
backupKey = (0 until 63).map { (('A'..'Z') + ('0'..'9')).random() }.joinToString("") + "0",
|
||||
keySaveState = null,
|
||||
canOpenPasswordManagerSettings = true,
|
||||
mode = MessageBackupsKeyRecordMode.Next(onNextClick = {}),
|
||||
notifyKeyIsSameAsOnDeviceBackupKey = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun SaveKeyConfirmationDialogPreview() {
|
||||
|
||||
@@ -9,8 +9,6 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRec
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRecordScreen
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.storage.AndroidCredentialRepository
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
|
||||
/**
|
||||
@@ -27,17 +25,11 @@ class ForgotBackupKeyFragment : ComposeFragment() {
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val passwordManagerSettingsIntent = AndroidCredentialRepository.getCredentialManagerSettingsIntent(requireContext())
|
||||
|
||||
MessageBackupsKeyRecordScreen(
|
||||
backupKey = SignalStore.account.accountEntropyPool.displayValue,
|
||||
keySaveState = state.keySaveState,
|
||||
canOpenPasswordManagerSettings = passwordManagerSettingsIntent != null,
|
||||
onNavigationClick = { requireActivity().supportFragmentManager.popBackStack() },
|
||||
onCopyToClipboardClick = { Util.copyToClipboard(requireContext(), it, CLIPBOARD_TIMEOUT_SECONDS) },
|
||||
onRequestSaveToPasswordManager = viewModel::onBackupKeySaveRequested,
|
||||
onConfirmSaveToPasswordManager = viewModel::onBackupKeySaveConfirmed,
|
||||
onSaveToPasswordManagerComplete = viewModel::onBackupKeySaveCompleted,
|
||||
backupKeyCredentialManagerHandler = viewModel,
|
||||
mode = remember {
|
||||
MessageBackupsKeyRecordMode.Next(onNextClick = {
|
||||
requireActivity()
|
||||
@@ -47,8 +39,7 @@ class ForgotBackupKeyFragment : ComposeFragment() {
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
})
|
||||
},
|
||||
onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
@@ -27,13 +24,10 @@ import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
|
||||
import org.thoughtcrime.securesms.BiometricDeviceLockContract
|
||||
import org.thoughtcrime.securesms.DevicePinAuthEducationSheet
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.EnterKeyScreen
|
||||
import org.thoughtcrime.securesms.components.compose.rememberBiometricsAuthentication
|
||||
import org.thoughtcrime.securesms.compose.SignalTheme
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
@@ -46,8 +40,6 @@ import kotlin.random.nextInt
|
||||
class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(VerifyBackupKeyActivity::class)
|
||||
|
||||
@JvmStatic
|
||||
fun createIntent(context: Context): Intent {
|
||||
return Intent(context, VerifyBackupKeyActivity::class.java)
|
||||
@@ -56,25 +48,29 @@ class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
|
||||
const val REQUEST_CODE = 1204
|
||||
}
|
||||
|
||||
private lateinit var biometricDeviceAuthentication: BiometricDeviceAuthentication
|
||||
private lateinit var biometricDeviceLockLauncher: ActivityResultLauncher<String>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
SignalTheme {
|
||||
val context = LocalContext.current
|
||||
val biometrics = rememberBiometricsAuthentication(
|
||||
promptTitle = stringResource(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key),
|
||||
educationSheetMessage = stringResource(R.string.RemoteBackupsSettingsFragment__to_view_your_key),
|
||||
onAuthenticationFailed = {
|
||||
// Matches existing behavior: show a generic "authentication required" toast.
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.RemoteBackupsSettingsFragment__authenticatino_required,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
)
|
||||
|
||||
VerifyBackupPinScreen(
|
||||
backupKey = SignalStore.account.accountEntropyPool.displayValue,
|
||||
onForgotKeyClick = {
|
||||
if (biometricDeviceAuthentication.shouldShowEducationSheet(this)) {
|
||||
DevicePinAuthEducationSheet.show(getString(R.string.RemoteBackupsSettingsFragment__to_view_your_key), supportFragmentManager)
|
||||
supportFragmentManager.setFragmentResultListener(DevicePinAuthEducationSheet.REQUEST_KEY, this) { _, _ ->
|
||||
if (!biometricDeviceAuthentication.authenticate(this, true) { biometricDeviceLockLauncher.launch(getString(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key)) }) {
|
||||
displayBackupKey()
|
||||
}
|
||||
}
|
||||
} else if (!biometricDeviceAuthentication.authenticate(this, true) { biometricDeviceLockLauncher.launch(getString(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key)) }) {
|
||||
biometrics.withBiometricsAuthentication {
|
||||
displayBackupKey()
|
||||
}
|
||||
},
|
||||
@@ -88,23 +84,6 @@ class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
initializeBiometricAuth()
|
||||
}
|
||||
|
||||
private fun initializeBiometricAuth() {
|
||||
val biometricPrompt = BiometricPrompt(this, AuthListener())
|
||||
val promptInfo: BiometricPrompt.PromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
|
||||
.setTitle(getString(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key))
|
||||
.build()
|
||||
|
||||
biometricDeviceAuthentication = BiometricDeviceAuthentication(BiometricManager.from(this), biometricPrompt, promptInfo)
|
||||
biometricDeviceLockLauncher = registerForActivityResult(BiometricDeviceLockContract()) { result: Int ->
|
||||
if (result == BiometricDeviceAuthentication.AUTHENTICATED) {
|
||||
displayBackupKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayBackupKey() {
|
||||
@@ -114,23 +93,6 @@ class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
|
||||
private inner class AuthListener : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationFailed() {
|
||||
Log.w(TAG, "onAuthenticationFailed")
|
||||
Toast.makeText(this@VerifyBackupKeyActivity, R.string.RemoteBackupsSettingsFragment__authenticatino_required, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
Log.i(TAG, "onAuthenticationSucceeded")
|
||||
displayBackupKey()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
Log.w(TAG, "onAuthenticationError: $errorCode, $errString")
|
||||
onAuthenticationFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
Reference in New Issue
Block a user