From bd3c4af8e43f0c413bc60e95e737bc34f9f8f9d0 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 4 Sep 2026 16:04:44 -0400 Subject: [PATCH] Improve backup onboarding for numberless accounts. --- .../MessageBackupsCheckoutActivityTest.kt | 2 +- .../MessageBackupsConfirmRecoveryKeyScreen.kt | 217 ++++++++++++++++++ .../MessageBackupsEducationScreen.kt | 25 +- .../MessageBackupsFlowFragment.kt | 78 ++++++- .../subscription/MessageBackupsFlowState.kt | 3 + .../MessageBackupsFlowViewModel.kt | 34 ++- .../MessageBackupsKeyVerifyScreen.kt | 20 +- .../v2/ui/subscription/MessageBackupsStage.kt | 4 + .../upgrade/UpgradeToPaidTierBottomSheet.kt | 2 + .../image_signal_backups_clock.xml | 61 +++++ .../image_signal_backups_plans.xml | 118 +++++----- .../drawable/image_signal_backups_clock.xml | 61 +++++ .../drawable/image_signal_backups_plans.xml | 118 +++++----- app/src/main/res/values-af/strings.xml | 4 - app/src/main/res/values-ar/strings.xml | 4 - app/src/main/res/values-az/strings.xml | 4 - app/src/main/res/values-be/strings.xml | 4 - app/src/main/res/values-bg/strings.xml | 4 - app/src/main/res/values-bn/strings.xml | 4 - app/src/main/res/values-bs/strings.xml | 4 - app/src/main/res/values-ca/strings.xml | 4 - app/src/main/res/values-cs/strings.xml | 4 - app/src/main/res/values-da/strings.xml | 4 - app/src/main/res/values-de/strings.xml | 4 - app/src/main/res/values-el/strings.xml | 4 - app/src/main/res/values-es/strings.xml | 4 - app/src/main/res/values-et/strings.xml | 4 - app/src/main/res/values-eu/strings.xml | 4 - app/src/main/res/values-fa/strings.xml | 4 - app/src/main/res/values-fi/strings.xml | 4 - app/src/main/res/values-fr/strings.xml | 4 - app/src/main/res/values-ga/strings.xml | 4 - app/src/main/res/values-gl/strings.xml | 4 - app/src/main/res/values-gu/strings.xml | 4 - app/src/main/res/values-hi/strings.xml | 4 - app/src/main/res/values-hr/strings.xml | 4 - app/src/main/res/values-hu/strings.xml | 4 - app/src/main/res/values-in/strings.xml | 4 - app/src/main/res/values-it/strings.xml | 4 - app/src/main/res/values-iw/strings.xml | 4 - app/src/main/res/values-ja/strings.xml | 4 - app/src/main/res/values-ka/strings.xml | 4 - app/src/main/res/values-kk/strings.xml | 4 - app/src/main/res/values-km/strings.xml | 4 - app/src/main/res/values-kn/strings.xml | 4 - app/src/main/res/values-ko/strings.xml | 4 - app/src/main/res/values-ky/strings.xml | 4 - app/src/main/res/values-lt/strings.xml | 4 - app/src/main/res/values-lv/strings.xml | 4 - app/src/main/res/values-mk/strings.xml | 4 - app/src/main/res/values-ml/strings.xml | 4 - app/src/main/res/values-mr/strings.xml | 4 - app/src/main/res/values-ms/strings.xml | 4 - app/src/main/res/values-my/strings.xml | 4 - app/src/main/res/values-nb/strings.xml | 4 - app/src/main/res/values-nl/strings.xml | 4 - app/src/main/res/values-pa/strings.xml | 4 - app/src/main/res/values-pl/strings.xml | 4 - app/src/main/res/values-pt-rBR/strings.xml | 4 - app/src/main/res/values-pt/strings.xml | 4 - app/src/main/res/values-ro/strings.xml | 4 - app/src/main/res/values-ru/strings.xml | 4 - app/src/main/res/values-sk/strings.xml | 4 - app/src/main/res/values-sl/strings.xml | 4 - app/src/main/res/values-sq/strings.xml | 4 - app/src/main/res/values-sr/strings.xml | 4 - app/src/main/res/values-sv/strings.xml | 4 - app/src/main/res/values-sw/strings.xml | 4 - app/src/main/res/values-ta/strings.xml | 4 - app/src/main/res/values-te/strings.xml | 4 - app/src/main/res/values-th/strings.xml | 4 - app/src/main/res/values-tl/strings.xml | 4 - app/src/main/res/values-tr/strings.xml | 4 - app/src/main/res/values-ug/strings.xml | 4 - app/src/main/res/values-uk/strings.xml | 4 - app/src/main/res/values-ur/strings.xml | 4 - app/src/main/res/values-vi/strings.xml | 4 - app/src/main/res/values-yue/strings.xml | 4 - app/src/main/res/values-zh-rCN/strings.xml | 4 - app/src/main/res/values-zh-rHK/strings.xml | 4 - app/src/main/res/values-zh-rTW/strings.xml | 4 - app/src/main/res/values/strings.xml | 26 ++- ...sageBackupsConfirmRecoveryKeyScreenTest.kt | 165 +++++++++++++ .../MessageBackupsFlowViewModelTest.kt | 164 +++++++++++++ .../image_signal_login_keys.xml | 0 .../res/drawable/image_signal_login_keys.xml | 0 86 files changed, 945 insertions(+), 425 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreen.kt create mode 100644 app/src/main/res/drawable-night/image_signal_backups_clock.xml create mode 100644 app/src/main/res/drawable/image_signal_backups_clock.xml create mode 100644 app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreenTest.kt create mode 100644 app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModelTest.kt rename {feature/registration => lib/signal-login}/src/main/res/drawable-night/image_signal_login_keys.xml (100%) rename {feature/registration => lib/signal-login}/src/main/res/drawable/image_signal_login_keys.xml (100%) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt index 4c5c750372..690be544d5 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsCheckoutActivityTest.kt @@ -136,7 +136,7 @@ class MessageBackupsCheckoutActivityTest { // Backup education screen composeTestRule.onNodeWithText(context.getString(R.string.RemoteBackupsSettingsFragment__signal_backups)).assertIsDisplayed() - composeTestRule.onNodeWithText(context.getString(R.string.MessageBackupsEducationScreen__enable_backups)).performClick() + composeTestRule.onNodeWithText(context.getString(R.string.MessageBackupsEducationScreen__continue)).performClick() // Key education screen composeTestRule.onNodeWithText(context.getString(R.string.MessageBackupsKeyEducationScreen__your_backup_key)).assertIsDisplayed() diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreen.kt new file mode 100644 index 0000000000..8bd2dce31e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreen.kt @@ -0,0 +1,217 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.ui.subscription + +import android.content.Context +import android.widget.Toast +import androidx.annotation.UiContext +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +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 kotlinx.coroutines.launch +import org.signal.core.models.AccountEntropyPool +import org.signal.core.models.ServiceId +import org.signal.core.ui.compose.Buttons +import org.signal.core.ui.compose.DayNightPreviews +import org.signal.core.ui.compose.Dialogs +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.theme.SignalTheme +import org.signal.passwordmanager.SignalCredentialManager +import org.signal.signallogin.card.SignalLoginCard +import org.thoughtcrime.securesms.R +import java.util.UUID +import org.signal.core.ui.R as CoreUiR +import org.signal.signallogin.R as SignalLoginR + +object MessageBackupsConfirmRecoveryKeyTestTags { + const val CONFIRM_BUTTON = "confirm-recovery-key-screen-confirm-button" + const val ENTER_MANUALLY_BUTTON = "confirm-recovery-key-screen-enter-manually-button" +} + +/** + * Asks a user who already has a Signal Login to confirm the recovery key that came with it, by pulling the credential + * back out of their password manager. Shown in place of the record-and-verify screens, since the key already exists and + * was already presented to them during registration. + */ +@Composable +fun MessageBackupsConfirmRecoveryKeyScreen( + aci: ServiceId.ACI, + aep: AccountEntropyPool, + onNavigationClick: () -> Unit = {}, + onViewDetailsClick: () -> Unit = {}, + onConfirmed: () -> Unit = {}, + onEnterManuallyClick: () -> Unit = {} +) { + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + val successMessage = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__recovery_key_confirmed) + + var displayConfirmationFailure by remember { mutableStateOf(false) } + if (displayConfirmationFailure) { + ConfirmationFailureDialog( + onEnterManuallyClick = onEnterManuallyClick, + onDismiss = { displayConfirmationFailure = false } + ) + } + + Scaffolds.Settings( + title = "", + navigationIcon = SignalIcons.ArrowStart.imageVector, + onNavigationClick = onNavigationClick + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = dimensionResource(CoreUiR.dimen.gutter)) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + Image( + painter = painterResource(SignalLoginR.drawable.image_signal_login_keys), + contentDescription = null, + modifier = Modifier + .padding(top = 12.dp) + .size(96.dp) + ) + + Text( + text = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__confirm_your_recovery_key), + style = MaterialTheme.typography.headlineMedium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 24.dp) + ) + + Text( + text = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__your_recovery_key_is_a_64_character_code), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 12.dp) + ) + + SignalLoginCard( + aci = aci, + aep = aep, + onViewDetailsClicked = onViewDetailsClick, + modifier = Modifier.padding(top = 32.dp) + ) + + Spacer(modifier = Modifier.height(24.dp)) + } + + Text( + text = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__you_will_be_prompted_with_your_saved_password), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) + + Buttons.LargeTonal( + onClick = { + coroutineScope.launch { + if (getKeyFromCredentialManager(context, aci) == aep.displayValue) { + Toast.makeText(context, successMessage, Toast.LENGTH_SHORT).show() + onConfirmed() + } else { + displayConfirmationFailure = true + } + } + }, + modifier = Modifier + .fillMaxWidth() + .testTag(MessageBackupsConfirmRecoveryKeyTestTags.CONFIRM_BUTTON) + ) { + Text(text = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__confirm_recovery_key)) + } + + Buttons.LargeTonal( + onClick = onEnterManuallyClick, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = SignalTheme.colors.colorSurface2, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ), + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, bottom = 16.dp) + .testTag(MessageBackupsConfirmRecoveryKeyTestTags.ENTER_MANUALLY_BUTTON) + ) { + Text(text = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__enter_manually)) + } + } + } +} + +@Composable +private fun ConfirmationFailureDialog( + onEnterManuallyClick: () -> Unit, + onDismiss: () -> Unit +) { + Dialogs.SimpleAlertDialog( + title = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__error_confirming_recovery_key), + body = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__your_recovery_key_could_not_be_confirmed), + confirm = stringResource(R.string.MessageBackupsConfirmRecoveryKeyScreen__enter_manually), + dismiss = stringResource(android.R.string.cancel), + onConfirm = onEnterManuallyClick, + onDismiss = onDismiss + ) +} + +/** + * The password manager stores the Signal Login under the account key, so that is the credential id we ask for. + */ +private suspend fun getKeyFromCredentialManager( + @UiContext activityContext: Context, + aci: ServiceId.ACI +): String? { + return SignalCredentialManager.getCredential(activityContext, aci.toString().uppercase())?.password +} + +@DayNightPreviews +@Composable +private fun MessageBackupsConfirmRecoveryKeyScreenPreview() { + Previews.Preview { + MessageBackupsConfirmRecoveryKeyScreen( + aci = ServiceId.ACI.from(UUID.fromString("a6b28482-2e32-83d0-7f23-91360a4c2b91")), + aep = AccountEntropyPool("uy38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t") + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsEducationScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsEducationScreen.kt index 009e9db9a5..a417f9823a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsEducationScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsEducationScreen.kt @@ -14,10 +14,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ButtonDefaults 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.ui.Alignment import androidx.compose.ui.Modifier @@ -32,18 +32,19 @@ 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.theme.SignalTheme import org.thoughtcrime.securesms.R import org.signal.core.ui.R as CoreUiR /** * Educational content which allows user to proceed to set up automatic backups - * or navigate to a support page to learn more. + * or leave the flow without enabling them. */ @Composable fun MessageBackupsEducationScreen( onNavigationClick: () -> Unit, onEnableBackups: () -> Unit, - onLearnMore: () -> Unit + onNotNow: () -> Unit ) { Scaffolds.Settings( onNavigationClick = onNavigationClick, @@ -65,7 +66,7 @@ fun MessageBackupsEducationScreen( ) { item { Image( - painter = painterResource(id = R.drawable.image_signal_backups), + painter = painterResource(id = R.drawable.image_signal_backups_clock), contentDescription = null, modifier = Modifier .padding(top = 24.dp) @@ -119,18 +120,22 @@ fun MessageBackupsEducationScreen( modifier = Modifier.fillMaxWidth() ) { Text( - text = stringResource(id = R.string.MessageBackupsEducationScreen__enable_backups) + text = stringResource(id = R.string.MessageBackupsEducationScreen__continue) ) } - TextButton( - onClick = onLearnMore, + Buttons.LargeTonal( + onClick = onNotNow, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = SignalTheme.colors.colorSurface2, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ), modifier = Modifier .fillMaxWidth() - .padding(bottom = 16.dp) + .padding(top = 16.dp, bottom = 16.dp) ) { Text( - text = stringResource(id = R.string.MessageBackupsEducationScreen__learn_more) + text = stringResource(id = R.string.MessageBackupsEducationScreen__not_now) ) } } @@ -144,7 +149,7 @@ private fun MessageBackupsEducationSheetPreview() { MessageBackupsEducationScreen( onNavigationClick = {}, onEnableBackups = {}, - onLearnMore = {} + onNotNow = {} ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt index 021798dd7f..32bf54bb68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowFragment.kt @@ -6,10 +6,12 @@ package org.thoughtcrime.securesms.backup.v2.ui.subscription import android.app.Activity +import android.net.Uri import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -18,6 +20,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.core.os.bundleOf +import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -29,15 +32,21 @@ import com.google.android.gms.common.GoogleApiAvailability import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.rx3.asFlowable +import org.signal.core.ui.compose.CollectActions import org.signal.core.ui.compose.ComposeFragment import org.signal.core.ui.compose.Dialogs +import org.signal.core.util.Result import org.signal.core.util.Util import org.signal.core.util.concurrent.SignalDispatchers import org.signal.core.util.getSerializableCompat import org.signal.passwordmanager.SignalCredentialManager +import org.signal.signallogin.pdf.SignalLoginPdfRenderer +import org.signal.signallogin.viewdetails.SignalLoginViewDetailsScreen import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.backup.DeletionState import org.thoughtcrime.securesms.backup.v2.MessageBackupTier +import org.thoughtcrime.securesms.components.settings.app.account.signallogin.SignalLoginViewDetailsAction +import org.thoughtcrime.securesms.components.settings.app.account.signallogin.SignalLoginViewDetailsViewModel import org.thoughtcrime.securesms.components.settings.app.subscription.donate.InAppPaymentCheckoutDelegate import org.thoughtcrime.securesms.compose.Nav import org.thoughtcrime.securesms.database.InAppPaymentTable @@ -58,6 +67,8 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega const val TIER = "tier" const val CLIPBOARD_TIMEOUT_SECONDS = 60 + private const val PDF_MIME_TYPE = "application/pdf" + fun create(messageBackupTier: MessageBackupTier?): MessageBackupsFlowFragment { return MessageBackupsFlowFragment().apply { arguments = bundleOf(TIER to messageBackupTier) @@ -69,10 +80,25 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega MessageBackupsFlowViewModel( initialTierSelection = requireArguments().getSerializableCompat(TIER, MessageBackupTier::class.java), googlePlayApiAvailability = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(requireContext()), - isCredentialManagerSupported = SignalCredentialManager.isSupported(requireContext()) + isCredentialManagerSupported = SignalCredentialManager.isSupported(requireContext()), + isPhoneNumberless = SignalStore.account.isPhoneNumberless ) } + private val signalLoginViewDetailsViewModel: SignalLoginViewDetailsViewModel by viewModels() + + private val savePdfLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument(PDF_MIME_TYPE)) { uri: Uri? -> + if (uri != null) { + val context = requireContext().applicationContext + lifecycleScope.launch { + val result = SignalLoginPdfRenderer.renderTo(context, uri, signalLoginViewDetailsViewModel.state.value) + if (result is Result.Failure) { + Toast.makeText(context, result.failure.userMessageRes, Toast.LENGTH_LONG).show() + } + } + } + } + private val errorHandler = InAppPaymentCheckoutDelegate.ErrorHandler() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -134,9 +160,7 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega MessageBackupsEducationScreen( onNavigationClick = viewModel::goToPreviousStage, onEnableBackups = viewModel::goToNextStage, - onLearnMore = { - CommunicationActions.openBrowserLink(requireContext(), getString(R.string.remote_backup_support_url)) - } + onNotNow = viewModel::goToPreviousStage ) } @@ -201,7 +225,34 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega MessageBackupsKeyVerifyScreen( backupKey = state.accountEntropyPool.displayValue, onNavigationClick = viewModel::goToPreviousStage, - onNextClick = viewModel::goToNextStage + onNextClick = viewModel::goToNextStage, + mode = if (state.isPhoneNumberless) { + MessageBackupsKeyVerifyScreenMode.SIGNAL_LOGIN + } else { + MessageBackupsKeyVerifyScreenMode.DEFAULT + } + ) + } + + composable(route = MessageBackupsStage.Route.CONFIRM_RECOVERY_KEY.name) { + MessageBackupsConfirmRecoveryKeyScreen( + aci = state.aci, + aep = state.accountEntropyPool, + onNavigationClick = viewModel::goToPreviousStage, + onViewDetailsClick = viewModel::goToSignalLoginViewDetails, + onConfirmed = viewModel::onRecoveryKeyConfirmed, + onEnterManuallyClick = viewModel::goToEnterRecoveryKeyManually + ) + } + + composable(route = MessageBackupsStage.Route.SIGNAL_LOGIN_VIEW_DETAILS.name) { + val signalLoginState by signalLoginViewDetailsViewModel.state.collectAsStateWithLifecycle() + + CollectActions(signalLoginViewDetailsViewModel.actions) { action -> handleSignalLoginViewDetailsAction(action) } + + SignalLoginViewDetailsScreen( + state = signalLoginState, + onEvent = signalLoginViewDetailsViewModel::onEvent ) } @@ -273,6 +324,23 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega } } + private fun handleSignalLoginViewDetailsAction(action: SignalLoginViewDetailsAction) { + when (action) { + SignalLoginViewDetailsAction.NavigateBack -> viewModel.goToPreviousStage() + SignalLoginViewDetailsAction.LaunchSaveToPasswordManager -> { + lifecycleScope.launch { + SignalCredentialManager.saveCredential( + activityContext = requireActivity(), + username = signalLoginViewDetailsViewModel.state.value.accountKey, + password = signalLoginViewDetailsViewModel.state.value.recoveryKey + ) + } + } + SignalLoginViewDetailsAction.LaunchSaveAsPdf -> savePdfLauncher.launch(SignalLoginPdfRenderer.suggestedFileName(requireContext())) + is SignalLoginViewDetailsAction.CopyTextToClipboard -> Util.copyToClipboard(requireContext(), action.text, CLIPBOARD_TIMEOUT_SECONDS) + } + } + override fun onUserLaunchedAnExternalApplication() = error("Not supported by this fragment.") override fun navigateToDonationPending(inAppPayment: InAppPaymentTable.InAppPayment) = error("Not supported by this fragment.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt index dcbabc7e38..db689d2a54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowState.kt @@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.backup.v2.ui.subscription import androidx.compose.runtime.Immutable import org.signal.core.models.AccountEntropyPool +import org.signal.core.models.ServiceId import org.signal.core.util.billing.BillingResponseCode import org.thoughtcrime.securesms.backup.v2.MessageBackupTier import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeySaveState @@ -24,6 +25,8 @@ data class MessageBackupsFlowState( val startScreen: MessageBackupsStage, val stage: MessageBackupsStage = startScreen, val accountEntropyPool: AccountEntropyPool = SignalStore.account.accountEntropyPool, + val aci: ServiceId.ACI = SignalStore.account.requireAci(), + val isPhoneNumberless: Boolean = false, val failure: Throwable? = null, val paymentReadyState: PaymentReadyState = PaymentReadyState.NOT_READY, val backupKeySaveState: BackupKeySaveState? = null diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt index 18a6efbd8e..80c4b3a769 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt @@ -54,6 +54,7 @@ class MessageBackupsFlowViewModel( private val initialTierSelection: MessageBackupTier?, googlePlayApiAvailability: Int, private val isCredentialManagerSupported: Boolean, + private val isPhoneNumberless: Boolean, startScreen: MessageBackupsStage = if (SignalStore.backup.backupTier == null) MessageBackupsStage.EDUCATION else MessageBackupsStage.TYPE_SELECTION ) : ViewModel(), BackupKeyCredentialManagerHandler { @@ -68,7 +69,8 @@ class MessageBackupsFlowViewModel( googlePlayApiAvailability = GooglePlayServicesAvailability.fromCode(googlePlayApiAvailability), currentMessageBackupTier = SignalStore.backup.backupTier, selectedMessageBackupTier = resolveSelectedTier(initialTierSelection, SignalStore.backup.backupTier), - startScreen = startScreen + startScreen = startScreen, + isPhoneNumberless = isPhoneNumberless ) ) @@ -237,11 +239,13 @@ class MessageBackupsFlowViewModel( internalStateFlow.update { when (it.stage) { MessageBackupsStage.CANCEL -> error("Unsupported state transition from terminal state CANCEL") - MessageBackupsStage.EDUCATION -> it.copy(stage = MessageBackupsStage.BACKUP_KEY_EDUCATION) + MessageBackupsStage.EDUCATION -> it.copy(stage = if (isPhoneNumberless) MessageBackupsStage.CONFIRM_RECOVERY_KEY else MessageBackupsStage.BACKUP_KEY_EDUCATION) MessageBackupsStage.BACKUP_KEY_EDUCATION -> it.copy(stage = if (isCredentialManagerSupported) MessageBackupsStage.BACKUP_KEY_RECORD else MessageBackupsStage.BACKUP_KEY_RECORD_MANUALLY) MessageBackupsStage.BACKUP_KEY_RECORD -> it.copy(stage = MessageBackupsStage.TYPE_SELECTION) MessageBackupsStage.BACKUP_KEY_RECORD_MANUALLY -> it.copy(stage = MessageBackupsStage.BACKUP_KEY_VERIFY) MessageBackupsStage.BACKUP_KEY_VERIFY -> it.copy(stage = MessageBackupsStage.TYPE_SELECTION) + MessageBackupsStage.CONFIRM_RECOVERY_KEY -> it.copy(stage = MessageBackupsStage.TYPE_SELECTION) + MessageBackupsStage.SIGNAL_LOGIN_VIEW_DETAILS -> error("Unsupported state transition from SIGNAL_LOGIN_VIEW_DETAILS") MessageBackupsStage.TYPE_SELECTION -> validateTypeAndUpdateState(it) MessageBackupsStage.CHECKOUT_SHEET -> it.copy(stage = MessageBackupsStage.PROCESS_PAYMENT) MessageBackupsStage.CREATING_IN_APP_PAYMENT -> error("This is driven by an async coroutine.") @@ -264,8 +268,10 @@ class MessageBackupsFlowViewModel( MessageBackupsStage.BACKUP_KEY_EDUCATION -> MessageBackupsStage.EDUCATION MessageBackupsStage.BACKUP_KEY_RECORD -> MessageBackupsStage.BACKUP_KEY_EDUCATION MessageBackupsStage.BACKUP_KEY_RECORD_MANUALLY -> if (isCredentialManagerSupported) MessageBackupsStage.BACKUP_KEY_RECORD else MessageBackupsStage.BACKUP_KEY_EDUCATION - MessageBackupsStage.BACKUP_KEY_VERIFY -> MessageBackupsStage.BACKUP_KEY_RECORD_MANUALLY - MessageBackupsStage.TYPE_SELECTION -> MessageBackupsStage.BACKUP_KEY_RECORD + MessageBackupsStage.BACKUP_KEY_VERIFY -> if (isPhoneNumberless) MessageBackupsStage.CONFIRM_RECOVERY_KEY else MessageBackupsStage.BACKUP_KEY_RECORD_MANUALLY + MessageBackupsStage.CONFIRM_RECOVERY_KEY -> MessageBackupsStage.EDUCATION + MessageBackupsStage.SIGNAL_LOGIN_VIEW_DETAILS -> MessageBackupsStage.CONFIRM_RECOVERY_KEY + MessageBackupsStage.TYPE_SELECTION -> if (isPhoneNumberless) MessageBackupsStage.CONFIRM_RECOVERY_KEY else MessageBackupsStage.BACKUP_KEY_RECORD MessageBackupsStage.CHECKOUT_SHEET -> MessageBackupsStage.TYPE_SELECTION MessageBackupsStage.CREATING_IN_APP_PAYMENT -> MessageBackupsStage.CREATING_IN_APP_PAYMENT MessageBackupsStage.PROCESS_PAYMENT -> MessageBackupsStage.PROCESS_PAYMENT @@ -290,6 +296,26 @@ class MessageBackupsFlowViewModel( } } + /** The user asked to see the full keys that make up their Signal Login. */ + fun goToSignalLoginViewDetails() { + internalStateFlow.update { + it.copy(stage = MessageBackupsStage.SIGNAL_LOGIN_VIEW_DETAILS) + } + } + + /** The user chose to type their recovery key in by hand instead of pulling it from their password manager. */ + fun goToEnterRecoveryKeyManually() { + internalStateFlow.update { + it.copy(stage = MessageBackupsStage.BACKUP_KEY_VERIFY) + } + } + + /** The recovery key stored in the user's password manager matched the one on this device. */ + fun onRecoveryKeyConfirmed() { + SignalStore.backup.lastVerifyKeyTime = System.currentTimeMillis() + goToNextStage() + } + fun onMessageBackupTierUpdated(messageBackupTier: MessageBackupTier) { internalStateFlow.update { it.copy( diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt index d5b6876951..51f5e2e6d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsKeyVerifyScreen.kt @@ -43,6 +43,18 @@ import kotlin.random.Random import kotlin.random.nextInt import org.signal.core.ui.R as CoreUiR +enum class MessageBackupsKeyVerifyScreenMode { + /** + * Displayed when the user recorded their recovery key earlier in this same flow. + */ + DEFAULT, + + /** + * Displayed when the user's recovery key came with their Signal Login rather than being recorded in this flow. + */ + SIGNAL_LOGIN +} + /** * Prompt user to re-enter backup key (AEP) to confirm they have it still. */ @@ -51,7 +63,8 @@ import org.signal.core.ui.R as CoreUiR fun MessageBackupsKeyVerifyScreen( backupKey: String, onNavigationClick: () -> Unit = {}, - onNextClick: () -> Unit = {} + onNextClick: () -> Unit = {}, + mode: MessageBackupsKeyVerifyScreenMode = MessageBackupsKeyVerifyScreenMode.DEFAULT ) { val coroutineScope = rememberCoroutineScope() val sheetState = rememberModalBottomSheetState( @@ -72,7 +85,10 @@ fun MessageBackupsKeyVerifyScreen( }, captionContent = { Text( - text = stringResource(R.string.MessageBackupsKeyVerifyScreen__enter_the_backup_key_that_you_just_recorded), + text = when (mode) { + MessageBackupsKeyVerifyScreenMode.DEFAULT -> stringResource(R.string.MessageBackupsKeyVerifyScreen__enter_the_backup_key_that_you_just_recorded) + MessageBackupsKeyVerifyScreenMode.SIGNAL_LOGIN -> stringResource(R.string.MessageBackupsKeyVerifyScreen__enter_the_recovery_key_from_your_signal_login) + }, style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurfaceVariant) ) }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt index fac97a4478..791c74ef6f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsStage.kt @@ -17,6 +17,8 @@ enum class MessageBackupsStage( BACKUP_KEY_RECORD(route = Route.BACKUP_KEY_RECORD), BACKUP_KEY_RECORD_MANUALLY(route = Route.BACKUP_KEY_RECORD_MANUALLY), BACKUP_KEY_VERIFY(route = Route.BACKUP_KEY_VERIFY), + CONFIRM_RECOVERY_KEY(route = Route.CONFIRM_RECOVERY_KEY), + SIGNAL_LOGIN_VIEW_DETAILS(route = Route.SIGNAL_LOGIN_VIEW_DETAILS), TYPE_SELECTION(route = Route.TYPE_SELECTION), CREATING_IN_APP_PAYMENT(route = Route.TYPE_SELECTION), CHECKOUT_SHEET(route = Route.TYPE_SELECTION), @@ -32,6 +34,8 @@ enum class MessageBackupsStage( CANCEL, EDUCATION, BACKUP_KEY_EDUCATION, + CONFIRM_RECOVERY_KEY, + SIGNAL_LOGIN_VIEW_DETAILS, BACKUP_KEY_RECORD, BACKUP_KEY_RECORD_MANUALLY, BACKUP_KEY_VERIFY, diff --git a/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToPaidTierBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToPaidTierBottomSheet.kt index e5f972553f..66bce4177b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToPaidTierBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/billing/upgrade/UpgradeToPaidTierBottomSheet.kt @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType import org.thoughtcrime.securesms.components.settings.app.subscription.donate.InAppPaymentCheckoutDelegate import org.thoughtcrime.securesms.database.InAppPaymentTable import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.viewModel /** @@ -63,6 +64,7 @@ abstract class UpgradeToPaidTierBottomSheet : ComposeBottomSheetDialogFragment() initialTierSelection = MessageBackupTier.PAID, googlePlayApiAvailability = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(requireContext()), isCredentialManagerSupported = SignalCredentialManager.isSupported(requireContext()), + isPhoneNumberless = SignalStore.account.isPhoneNumberless, startScreen = MessageBackupsStage.TYPE_SELECTION ) } diff --git a/app/src/main/res/drawable-night/image_signal_backups_clock.xml b/app/src/main/res/drawable-night/image_signal_backups_clock.xml new file mode 100644 index 0000000000..20181a2ab9 --- /dev/null +++ b/app/src/main/res/drawable-night/image_signal_backups_clock.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-night/image_signal_backups_plans.xml b/app/src/main/res/drawable-night/image_signal_backups_plans.xml index e95ef99b3e..ad883efc1f 100644 --- a/app/src/main/res/drawable-night/image_signal_backups_plans.xml +++ b/app/src/main/res/drawable-night/image_signal_backups_plans.xml @@ -1,73 +1,63 @@ + android:width="86.5623dp" + android:height="81.6602dp" + android:viewportWidth="86.5623" + android:viewportHeight="81.6602"> - - - - - - - + android:fillColor="#FFC1C4FA" + android:pathData="M17.39 18.71C17.39 12.16 17.39 8.89 18.66 6.39 19.78 4.19 21.57 2.4 23.77 1.27 26.27 0 29.55 0 36.1 0H49.71C56.26 0 59.53 0 62.04 1.27 64.24 2.4 66.03 4.19 67.15 6.39 68.42 8.89 68.42 12.16 68.42 18.71V62.95C68.42 69.5 68.42 72.77 67.15 75.27 66.03 77.47 64.24 79.26 62.04 80.39 59.53 81.66 56.26 81.66 49.71 81.66H36.1C29.55 81.66 26.27 81.66 23.77 80.39 21.57 79.26 19.78 77.47 18.66 75.27 17.39 72.77 17.39 69.5 17.39 62.95V18.71Z"/> + + + + + + android:strokeColor="#FF8C90CB" + android:strokeWidth="2" + android:pathData="M36.1 1H49.71C53 1 55.41 1 57.31 1.16 59.2 1.31 60.5 1.61 61.58 2.17 63.6 3.19 65.23 4.83 66.26 6.84 66.81 7.93 67.11 9.22 67.27 11.11 67.42 13.01 67.42 15.42 67.42 18.71V62.95C67.42 66.24 67.42 68.65 67.27 70.55 67.11 72.44 66.81 73.73 66.26 74.82 65.23 76.83 63.6 78.47 61.58 79.49 60.5 80.05 59.2 80.35 57.31 80.5 55.41 80.66 53 80.66 49.71 80.66H36.1C32.81 80.66 30.4 80.66 28.5 80.5 26.61 80.35 25.31 80.05 24.23 79.49 22.21 78.47 20.58 76.83 19.55 74.82 19 73.73 18.7 72.44 18.54 70.55 18.39 68.65 18.39 66.24 18.39 62.95V18.71C18.39 15.42 18.39 13.01 18.54 11.11 18.7 9.22 19 7.93 19.55 6.84 20.58 4.83 22.21 3.19 24.23 2.17 25.31 1.61 26.61 1.31 28.5 1.16 30.4 1 32.81 1 36.1 1Z"/> + android:fillColor="#FFC0CBE2" + android:strokeColor="#FF929AAC" + android:strokeWidth="1.5" + android:pathData="M17.3 35.84C26.05 35.84 33.14 42.93 33.14 51.67 33.14 60.42 26.05 67.51 17.3 67.51 14.77 67.51 12.37 66.92 10.25 65.86L9.83 65.64 9.75 65.6C9.7 65.58 9.65 65.57 9.6 65.56L9.52 65.55 9.43 65.54C9.42 65.55 9.41 65.55 9.4 65.55 9.4 65.55 9.4 65.55 9.39 65.55H9.38L9.38 65.55H9.38L9.29 65.56 9.2 65.59 2.03 68.17C1.27 68.44 0.54 67.71 0.81 66.95L3.39 59.78 3.42 59.69 3.43 59.6V59.59C3.43 59.58 3.43 59.58 3.43 59.58 3.43 59.57 3.44 59.56 3.44 59.55L3.43 59.46 3.42 59.38C3.41 59.3 3.38 59.23 3.34 59.15 2.15 56.93 1.47 54.38 1.47 51.67 1.47 42.93 8.56 35.84 17.3 35.84Z"/> + + + + + + android:strokeColor="#FF929AAC" + android:strokeWidth="2" + android:pathData="M17.3 36.09C25.91 36.09 32.89 43.07 32.89 51.67 32.89 60.28 25.91 67.26 17.3 67.26 14.64 67.26 12.14 66.59 9.95 65.42L9.85 65.37C9.78 65.35 9.71 65.33 9.64 65.31L9.54 65.3 9.43 65.29C9.41 65.3 9.4 65.3 9.39 65.3 9.38 65.3 9.38 65.3 9.37 65.3H9.36L9.36 65.3H9.36L9.45 66.29 9.36 65.3 9.23 65.31 9.11 65.35 1.94 67.93C1.38 68.14 0.84 67.6 1.04 67.04L3.63 59.87 3.67 59.75 3.68 59.62V59.62H3.68V59.61C3.68 59.61 3.68 59.61 3.68 59.61 3.68 59.6 3.68 59.6 3.68 59.59 3.68 59.58 3.68 59.57 3.68 59.55 3.69 59.53 3.69 59.49 3.68 59.44 3.67 59.3 3.63 59.16 3.56 59.03 2.38 56.84 1.72 54.34 1.72 51.67 1.72 43.07 8.7 36.09 17.3 36.09Z"/> + android:fillColor="#FFD9E0EE" + android:strokeColor="#FF929AAC" + android:strokeWidth="1.5" + android:pathData="M60.56 21.41H79.56C83.01 21.41 85.81 24.21 85.81 27.66V40.66C85.81 44.11 83.01 46.91 79.56 46.91H60.56C57.11 46.91 54.31 44.11 54.31 40.66V27.66C54.31 24.21 57.11 21.41 60.56 21.41Z"/> + + + + + android:strokeColor="#FF929AAC" + android:strokeWidth="2" + android:pathData="M60.56 21.66H79.56C82.88 21.66 85.56 24.35 85.56 27.66V40.66C85.56 43.97 82.88 46.66 79.56 46.66H60.56C57.25 46.66 54.56 43.97 54.56 40.66V27.66C54.56 24.35 57.25 21.66 60.56 21.66Z"/> - - - - - - - + android:fillColor="#FF929AAC" + android:pathData="M59.86 28.79C59.86 27.06 61.27 25.66 63 25.66 64.74 25.66 66.14 27.06 66.14 28.79 66.14 30.52 64.74 31.93 63 31.93 61.27 31.93 59.86 30.52 59.86 28.79Z"/> diff --git a/app/src/main/res/drawable/image_signal_backups_clock.xml b/app/src/main/res/drawable/image_signal_backups_clock.xml new file mode 100644 index 0000000000..f32a1969fb --- /dev/null +++ b/app/src/main/res/drawable/image_signal_backups_clock.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/image_signal_backups_plans.xml b/app/src/main/res/drawable/image_signal_backups_plans.xml index 9e73d6c801..8cd0e6073c 100644 --- a/app/src/main/res/drawable/image_signal_backups_plans.xml +++ b/app/src/main/res/drawable/image_signal_backups_plans.xml @@ -1,73 +1,63 @@ + android:width="86.5623dp" + android:height="81.6602dp" + android:viewportWidth="86.5623" + android:viewportHeight="81.6602"> - - - - - - - + android:fillColor="#FFC1C4FA" + android:pathData="M17.39 18.71C17.39 12.16 17.39 8.89 18.66 6.39 19.78 4.19 21.57 2.4 23.77 1.27 26.27 0 29.55 0 36.1 0H49.71C56.26 0 59.53 0 62.04 1.27 64.24 2.4 66.03 4.19 67.15 6.39 68.42 8.89 68.42 12.16 68.42 18.71V62.95C68.42 69.5 68.42 72.77 67.15 75.27 66.03 77.47 64.24 79.26 62.04 80.39 59.53 81.66 56.26 81.66 49.71 81.66H36.1C29.55 81.66 26.27 81.66 23.77 80.39 21.57 79.26 19.78 77.47 18.66 75.27 17.39 72.77 17.39 69.5 17.39 62.95V18.71Z"/> + + + + + + android:strokeColor="#FF4046A9" + android:strokeWidth="2" + android:pathData="M36.1 1H49.71C53 1 55.41 1 57.31 1.16 59.2 1.31 60.5 1.61 61.58 2.17 63.6 3.19 65.23 4.83 66.26 6.84 66.81 7.93 67.11 9.22 67.27 11.11 67.42 13.01 67.42 15.42 67.42 18.71V62.95C67.42 66.24 67.42 68.65 67.27 70.55 67.11 72.44 66.81 73.73 66.26 74.82 65.23 76.83 63.6 78.47 61.58 79.49 60.5 80.05 59.2 80.35 57.31 80.5 55.41 80.66 53 80.66 49.71 80.66H36.1C32.81 80.66 30.4 80.66 28.5 80.5 26.61 80.35 25.31 80.05 24.23 79.49 22.21 78.47 20.58 76.83 19.55 74.82 19 73.73 18.7 72.44 18.54 70.55 18.39 68.65 18.39 66.24 18.39 62.95V18.71C18.39 15.42 18.39 13.01 18.54 11.11 18.7 9.22 19 7.93 19.55 6.84 20.58 4.83 22.21 3.19 24.23 2.17 25.31 1.61 26.61 1.31 28.5 1.16 30.4 1 32.81 1 36.1 1Z"/> + android:fillColor="#FFC0CBE2" + android:strokeColor="#FF4A5775" + android:strokeWidth="1.5" + android:pathData="M17.3 35.84C26.05 35.84 33.14 42.93 33.14 51.67 33.14 60.42 26.05 67.51 17.3 67.51 14.77 67.51 12.37 66.92 10.25 65.86L9.83 65.64 9.75 65.6C9.7 65.58 9.65 65.57 9.6 65.56L9.52 65.55 9.43 65.54C9.42 65.55 9.41 65.55 9.4 65.55 9.4 65.55 9.4 65.55 9.39 65.55H9.38L9.38 65.55H9.38L9.29 65.56 9.2 65.59 2.03 68.17C1.27 68.44 0.54 67.71 0.81 66.95L3.39 59.78 3.42 59.69 3.43 59.6V59.59C3.43 59.58 3.43 59.58 3.43 59.58 3.43 59.57 3.44 59.56 3.44 59.55L3.43 59.46 3.42 59.38C3.41 59.3 3.38 59.23 3.34 59.15 2.15 56.93 1.47 54.38 1.47 51.67 1.47 42.93 8.56 35.84 17.3 35.84Z"/> + + + + + + android:strokeColor="#FF4A5775" + android:strokeWidth="2" + android:pathData="M17.3 36.09C25.91 36.09 32.89 43.07 32.89 51.67 32.89 60.28 25.91 67.26 17.3 67.26 14.64 67.26 12.14 66.59 9.95 65.42L9.85 65.37C9.78 65.35 9.71 65.33 9.64 65.31L9.54 65.3 9.43 65.29C9.41 65.3 9.4 65.3 9.39 65.3 9.38 65.3 9.38 65.3 9.37 65.3H9.36L9.36 65.3H9.36L9.45 66.29 9.36 65.3 9.23 65.31 9.11 65.35 1.94 67.93C1.38 68.14 0.84 67.6 1.04 67.04L3.63 59.87 3.67 59.75 3.68 59.62V59.62H3.68V59.61C3.68 59.61 3.68 59.61 3.68 59.61 3.68 59.6 3.68 59.6 3.68 59.59 3.68 59.58 3.68 59.57 3.68 59.55 3.69 59.53 3.69 59.49 3.68 59.44 3.67 59.3 3.63 59.16 3.56 59.03 2.38 56.84 1.72 54.34 1.72 51.67 1.72 43.07 8.7 36.09 17.3 36.09Z"/> + android:fillColor="#FFD9E0EE" + android:strokeColor="#FF4A5775" + android:strokeWidth="1.5" + android:pathData="M60.56 21.41H79.56C83.01 21.41 85.81 24.21 85.81 27.66V40.66C85.81 44.11 83.01 46.91 79.56 46.91H60.56C57.11 46.91 54.31 44.11 54.31 40.66V27.66C54.31 24.21 57.11 21.41 60.56 21.41Z"/> + + + + + android:strokeColor="#FF4A5775" + android:strokeWidth="2" + android:pathData="M60.56 21.66H79.56C82.88 21.66 85.56 24.35 85.56 27.66V40.66C85.56 43.97 82.88 46.66 79.56 46.66H60.56C57.25 46.66 54.56 43.97 54.56 40.66V27.66C54.56 24.35 57.25 21.66 60.56 21.66Z"/> - - - - - - - + android:fillColor="#FF4A5775" + android:pathData="M59.86 28.79C59.86 27.06 61.27 25.66 63 25.66 64.74 25.66 66.14 27.06 66.14 28.79 66.14 30.52 64.74 31.93 63 31.93 61.27 31.93 59.86 30.52 59.86 28.79Z"/> diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index 2bfbd89928..c3b73e60d4 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -9016,10 +9016,6 @@ Opsioneel, altyd Skrap jou rugsteun te eniger tyd - - Aktiveer rugsteun - - Vind meer uit diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index f0c19ac1c4..6414bfbfd9 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -9808,10 +9808,6 @@ اختيارية، دائمًا احذف نسختك الاحتياطية في أي وقت - - تفعيل النسخ الاحتياطية - - اعرف المزيد diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 3cb5415057..719c3de802 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -9016,10 +9016,6 @@ Hər zaman ixtiyari Ehtiyat nüsxəni istədiyiniz zaman silin - - Nüsxələməni fəallaşdır - - Daha ətraflı diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 52bf3db5bd..06791dd4e4 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -9412,10 +9412,6 @@ Неабавязкова, як заўсёды Выдаліць рэзервовую копію ў любы час - - Уключыць рэзервовыя копіі - - Даведацца больш diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 15640bc4d3..c98c5afe44 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -9016,10 +9016,6 @@ По желание, винаги Можете да изтриете резервното копие по всяко време - - Активиране на архиви - - Научете повече diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index c2b447e68f..88bcbb05f0 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -9016,10 +9016,6 @@ ঐচ্ছিক, সবসময় যেকোনো সময় আপনার ব্যাকআপ মুছে ফেলুন - - ব্যাকআপ সক্ষম করুন - - আরো জানুন diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml index f337416d02..f6b06d0425 100644 --- a/app/src/main/res/values-bs/strings.xml +++ b/app/src/main/res/values-bs/strings.xml @@ -9412,10 +9412,6 @@ Nije obavezno Izbrišite sigurnosnu kopiju bilo kada - - Aktiviraj - - Saznaj više diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 2f8ab38011..8a760c8fbe 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -9016,10 +9016,6 @@ Opcional, sempre Elimina la teva còpia de seguretat en qualsevol moment - - Activa les còpies de seguretat - - Més informació diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 5b1517b7e9..2571d7518e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -9412,10 +9412,6 @@ Volitelné, vždy Zálohu můžete kdykoli odstranit - - Povolit zálohování - - Zjistit více diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 408531ad9f..f3c4e69815 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -9016,10 +9016,6 @@ Altid valgfrit Slet din sikkerhedskopi når som helst - - Aktivér sikkerhedskopier - - Få mere at vide diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9407f39e06..0868897b82 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -9016,10 +9016,6 @@ Optional Lösche dein Backup jederzeit - - Backups aktivieren - - Mehr erfahren diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d2fb2ba3c3..8e8b1c17ad 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -9016,10 +9016,6 @@ Προαιρετικά, πάντα Διαγραφή αντιγράφων ασφαλείας ανά πάσα στιγμή - - Ενεργοποίηση αντίγραφων ασφαλείας - - Μάθε περισσότερα diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index cb13825f84..83049088aa 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -9016,10 +9016,6 @@ Servicio opcional, en todo momento Copias de seguridad que puedes eliminar en cualquier momento - - Habilitar copias de seguridad - - Más información diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 318070f55b..ad4d8d239e 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -9016,10 +9016,6 @@ Alati vabatahtlik Võid oma varukoopia igal ajal kustutada - - Luba varundamine - - Rohkem teavet diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 26f6f60ef8..84132e7f1b 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -9016,10 +9016,6 @@ Aukerakoa, beti Ezabatu babeskopia nahi duzunean - - Gaitu babeskopiak - - Informazio gehiago diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index e1bc238ae3..7e2f4b2ae4 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -9016,10 +9016,6 @@ اختیاری، همیشه هر موقع خواستید، نسخه پشتیبان خود را حذف کنید - - فعال‌سازی پشتیبان‌ها - - اطلاعات بیشتر diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index cbb1867b29..17cf47aa46 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -9016,10 +9016,6 @@ Aina valinnainen Poista varmuuskopio milloin tahansa - - Ota varmuuskopiot käyttöön - - Lue lisää diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 00ef522cd1..798993f587 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -9016,10 +9016,6 @@ La liberté de choisir un service en option Des sauvegardes que vous pouvez supprimer à tout moment - - Activer les sauvegardes - - En savoir plus diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index 87c8c6dbf2..2fc0c44d2f 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -9610,10 +9610,6 @@ Roghnach, i gcónaí Scrios do chúltaca ag am ar bith - - Cumasaigh cúltacaithe - - Tuilleadh faisnéise diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index d07360f3b1..e0a935f581 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -9016,10 +9016,6 @@ Escolle usalo cando queiras Elimina a copia de seguranza cando queiras - - Activar copias de seguranza - - Máis información diff --git a/app/src/main/res/values-gu/strings.xml b/app/src/main/res/values-gu/strings.xml index ff4dda6e98..84f9a00ea9 100644 --- a/app/src/main/res/values-gu/strings.xml +++ b/app/src/main/res/values-gu/strings.xml @@ -9016,10 +9016,6 @@ વૈકલ્પિક, હંમેશા તમારા બેકઅપને કોઈ પણ સમયે ડિલીટ કરો - - બેકઅપ સક્ષમ કરો - - વધુ જાણો diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 1a45ce1a9e..cda5695bf8 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -9016,10 +9016,6 @@ वैकल्पिक, हमेशा अपना बैकअप कभी भी डिलीट करें - - बैकअप की सुविधा चालू करें - - और जानें diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 31cfd7c5f4..d31f7befa3 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -9412,10 +9412,6 @@ Neobavezno Brisanje sigurnosne kopije moguće je u bilo kojem trenutku - - Omogući sigurnosne kopije - - Saznajte više diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index ede3dc2e16..0c60cc1cbe 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -9016,10 +9016,6 @@ Sosem kötelező A biztonsági másolatok bármikor törölhetők - - Biztonsági mentések bekapcsolása - - Tudj meg többet diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index d85be5331f..4040674166 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -8818,10 +8818,6 @@ Opsional, selalu Hapus cadangan Anda kapan saja - - Aktifkan pencadangan - - Pelajari selengkapnya diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 432708c709..de136fccce 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -9016,10 +9016,6 @@ Sempre facoltativo Elimina il tuo backup quando vuoi - - Abilita i backup - - Scopri di più diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 8543a8e6a7..36e5161756 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -9412,10 +9412,6 @@ אופציונלי, תמיד אפשר למחוק את הגיבוי בכל עת - - אפשר גיבויים - - למידע נוסף diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 972adfa041..3d001a7d26 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -8818,10 +8818,6 @@ 任意でいつでも利用できます バックアップはいつでも消去可能です - - バックアップを有効にする - - 詳しく見る diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 6844809e3b..f75a3a920d 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -9016,10 +9016,6 @@ ყოველთვის ნებაყოფლობითი წაშალე შენი სარეზერვო კოპიები ნებისმიერ დროს - - სარეზერვო კოპირების ჩართვა - - გაიგე მეტი diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index 5d76ad52cd..6237065805 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -9016,10 +9016,6 @@ Міндетті емес, әрдайым Резервтік көшірмеңізді кез келген уақытты жойыңыз - - Резервтік көшірмелерді қосу - - Толық ақпарат diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml index fe6f301928..462376d538 100644 --- a/app/src/main/res/values-km/strings.xml +++ b/app/src/main/res/values-km/strings.xml @@ -8818,10 +8818,6 @@ ជាជម្រើសជានិច្ច លុបការបម្រុងទុករបស់អ្នកនៅពេលណាក៏បាន - - បើកការបម្រុងទុក - - ស្វែងយល់បន្ថែម diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 9257638487..b8111daf4e 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -9016,10 +9016,6 @@ ಐಚ್ಛಿಕ, ಯಾವಾಗಲೂ ನಿಮ್ಮ ಬ್ಯಾಕಪ್ ಅನ್ನು ಯಾವಾಗ ಬೇಕಾದರೂ ಅಳಿಸಿ - - ಬ್ಯಾಕಪ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ - - ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 1a4162c775..774ea5328c 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -8818,10 +8818,6 @@ 필수가 아닌 선택 사항 언제나 백업 삭제 가능 - - 백업 활성화 - - 자세히 알아보기 diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml index 6790aadcce..2aa9132cb2 100644 --- a/app/src/main/res/values-ky/strings.xml +++ b/app/src/main/res/values-ky/strings.xml @@ -9016,10 +9016,6 @@ Ар дайым өзүңүз каалагандай Камдык көчүрмөлөрдү каалаган учурда өчүрүп коесуз - - Камдык көчүрмөлөрдү иштетүү - - Кененирээк маалымат diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index f9d8b4b8ef..27590b72be 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -9412,10 +9412,6 @@ Neprivaloma Atsarginę kopiją galima bet kada ištrinti - - Įjungti atsargines kopijas - - Sužinoti daugiau diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index a0d6f92685..a19d61b5a2 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -9214,10 +9214,6 @@ Pēc izvēles Dzēsiet rezerves kopiju jebkurā laikā - - Iespējot rezerves kopēšanu - - Uzzināt vairāk diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 96d6fa8093..a4891ba6ba 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -9016,10 +9016,6 @@ Секогаш незадолжително Можете да ја избришете вашата резерва копија во секое време - - Вклучи резервна копија - - Дознајте повеќе diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 7aa227a16b..a70b0bea62 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -9016,10 +9016,6 @@ ഓപ്ഷണൽ, എപ്പോഴും എപ്പോൾ വേണമെങ്കിലും നിങ്ങളുടെ ബാക്കപ്പ് ഇല്ലാതാക്കുക - - ബാക്കപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കുക - - കൂടുതലറിയുക diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml index f72da659db..bf26712e6b 100644 --- a/app/src/main/res/values-mr/strings.xml +++ b/app/src/main/res/values-mr/strings.xml @@ -9016,10 +9016,6 @@ वैकल्पिक, नेहमी आपला बॅकअप कधीही हटवा - - बॅकअप सक्षम करा - - अधिक जाणून घ्या diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 6699ec6225..96e278d5d6 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -8818,10 +8818,6 @@ Terpulang kepada anda Padamkan sandaran anda pada bila-bila masa - - Mendayakan sandaran - - Ketahui lebih lanjut diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index a70d1fec9b..a2bd76933a 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -8818,10 +8818,6 @@ အမြဲတမ်း ရွေးချယ်နိုင်သည် သင်၏ဘက်ခ်အပ်ကို အချိန်မရွေး ဖျက်နိုင်သည် - - ဘက်ခ်အပ် လုပ်ဆောင်မည် - - ပိုမိုလေ့လာရန် diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 09ec1fca91..bddf40ca78 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -9016,10 +9016,6 @@ Alltid valgfritt Slett sikkerhetskopien når som helst - - Slå på sikkerhetskopiering - - Les mer diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1fae4fb8c7..fc7d7d324e 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -9016,10 +9016,6 @@ Optioneel Verwijder je back-up wanneer je wilt - - Back-ups inschakelen - - Meer informatie diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 0d1413bcb8..90bc7c6f12 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -9016,10 +9016,6 @@ ਵਿਕਲਪਿਕ, ਹਮੇਸ਼ਾ ਕਿਸੇ ਵੀ ਸਮੇਂ ਆਪਣਾ ਬੈਕਅੱਪ ਮਿਟਾਓ - - ਬੈਕਅੱਪ ਨੂੰ ਸਮਰੱਥ ਕਰੋ - - ਹੋਰ ਜਾਣੋ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c3d58ec4ca..78b1115932 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -9412,10 +9412,6 @@ Dla chętnych (jak zwykle) Możliwość usunięcia kopii zapasowej w dowolnym momencie - - Włącz kopie zapasowe - - Dowiedz się więcej diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index c1f738065a..86fee8e6b2 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -9016,10 +9016,6 @@ Sempre opcional Exclua seu backup quando quiser - - Habilitar backups - - Saiba mais diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 48a5f47b0b..038c5aaf75 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -9016,10 +9016,6 @@ Opcional, sempre Elimine a sua cópia de segurança a qualquer altura - - Ativar cópias de segurança - - Saber mais diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 1ebca18eed..0ac6f68ed0 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -9214,10 +9214,6 @@ Opțional, întotdeauna Șterge oricând copia ta de rezervă - - Activează backup-urile - - Află mai multe diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a547b67ead..89ec800a55 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -9412,10 +9412,6 @@ Всегда можно отключить Удаляйте резервные копии в любое время - - Включить резервные копии - - Узнать больше diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index fd4581df95..3ff40102d1 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -9412,10 +9412,6 @@ Voliteľné, zakaždým Kedykoľvek môžete zálohu vymazať - - Povoliť zálohovanie - - Zistiť viac diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index db65aa85e7..f74e5d0dae 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -9412,10 +9412,6 @@ Neobvezno, vedno Kadarkoli izbrišite varnostno kopijo - - Dovoli varnostno kopiranje - - Preberite več diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index 35400e6ed4..08df5b2200 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -9016,10 +9016,6 @@ Opsionale, gjithmonë Fshij kopjeruajtjen në çdo kohë - - Aktivizoji kopjeruajtjet - - Mëso më shumë diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 459486a5b3..7f2c3963cd 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -9016,10 +9016,6 @@ Увек опционо Избришите резервну копију када год пожелите - - Укључи резервне копије - - Сазнајте више diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 1a1090e430..1aa8b220f9 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -9016,10 +9016,6 @@ Alltid valfritt Ta bort din säkerhetskopia när som helst - - Aktivera säkerhetskopior - - Läs mer diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml index 42a9578dfd..c0fa8d491b 100644 --- a/app/src/main/res/values-sw/strings.xml +++ b/app/src/main/res/values-sw/strings.xml @@ -9016,10 +9016,6 @@ Ya hiari, mara zote Futa nakala yako muda wowote ule - - Wezesha nakalahifadhi - - Jifunze zaidi diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 2ff74d60c2..a83f46bb91 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -9016,10 +9016,6 @@ விருப்பத்தேர்வுக்குரியது, எப்போதும் உங்கள் காப்புப்பிரதியை எப்போது வேண்டுமானாலும் அழிக்கலாம் - - காப்புப்பிரதிகளை இயக்கு - - மேலும் அறிக diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 5fb8de70a9..bca8f5660a 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -9016,10 +9016,6 @@ ఐచ్ఛికం, ఎల్లప్పుడు మీ బ్యాకప్ ఎప్పుడైనా తొలగించండి - - ప్రత్యామ్నాయ ప్రారంభించు - - మరింత తెలుసుకోండి diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 78145bba32..42d2904878 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -8818,10 +8818,6 @@ เลือกสมัครใช้บริการได้ตลอด เลือกลบข้อมูลสำรองได้ทุกเมื่อ - - เปิดใช้การสำรองข้อมูล - - เรียนรู้เพิ่มเติม diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 7157360958..5824b98d44 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -9016,10 +9016,6 @@ Palaging optional Burahin ang backup mo anumang oras - - I-enable ang mga backup? - - Matuto pa diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 612ad6115b..da0999b7dc 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -9016,10 +9016,6 @@ İsteğe bağlı, daima Yedeklemeni istediğin zaman sil - - Yedekleri etkinleştir - - Daha fazlasını öğren diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml index 8e332b7b0f..695bbc304c 100644 --- a/app/src/main/res/values-ug/strings.xml +++ b/app/src/main/res/values-ug/strings.xml @@ -8818,10 +8818,6 @@ ئىختىيارىي ، ھەرقاچان زاپاس مەلۇماتلار خالىغان ۋاقىتتا ئۆچۈرەلەيسىز - - زاپاسلارنى قوزغات - - تەپسىلاتى diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 0af5e85bcb..85a4d50915 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -9412,10 +9412,6 @@ Ніколи не буде обов\'язковим Резервну копію можна видалити коли завгодно - - Увімкнути - - Дізнатися більше diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 9fe282e98d..4623d70857 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -9016,10 +9016,6 @@ اختیاری، ہمیشہ اپنا بیک اپ کسی بھی وقت حذف کریں - - بیک اپ فعال کریں - - مزید جانیں diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index a26d28342c..c7558777b4 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -8818,10 +8818,6 @@ Lựa chọn không bắt buộc Xóa bản sao lưu bất kỳ lúc nào - - Bật sao lưu - - Tìm hiểu thêm diff --git a/app/src/main/res/values-yue/strings.xml b/app/src/main/res/values-yue/strings.xml index b1ac23d478..b7dd53f37d 100644 --- a/app/src/main/res/values-yue/strings.xml +++ b/app/src/main/res/values-yue/strings.xml @@ -8818,10 +8818,6 @@ 幾時都有得揀 隨時刪除備份 - - 啟用備份 - - 了解詳情 diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ce18ec277a..c387cd9dd5 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -8818,10 +8818,6 @@ 始终任选 可随时删除备份 - - 启用备份 - - 了解详情 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 844a53480d..a14af34f4f 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -8818,10 +8818,6 @@ 永遠可自由選擇 隨時刪除你的備份 - - 啟用備份 - - 了解更多 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 123d75cd9d..90cd6a523c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -8818,10 +8818,6 @@ 永遠可自由選擇 隨時刪除你的備份 - - 啟用備份 - - 了解更多 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 466e4042a1..c13963df9b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9024,9 +9024,9 @@ Delete your backup anytime - Enable backups - - Learn more + Continue + + Not now @@ -9160,6 +9160,26 @@ See key again Next + + Enter the recovery key from your Signal Login + + + + Confirm your recovery key + + Your recovery key is a 64-character code you received with your Signal Login. It\'s required to restore your backup. If you forget your key, you will not be able to recover your account. + + You will be prompted with your saved password in the next step. + + Confirm recovery key + + Enter manually + + Error confirming recovery key + + Your recovery key could not be confirmed. Make sure you\'ve saved your Signal Login in your password manager, or enter your recovery key manually. + + Recovery key confirmed Enter your recovery key diff --git a/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreenTest.kt b/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreenTest.kt new file mode 100644 index 0000000000..7a90a5818c --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsConfirmRecoveryKeyScreenTest.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.ui.subscription + +import android.app.Application +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo +import androidx.test.core.app.ApplicationProvider +import assertk.assertThat +import assertk.assertions.isFalse +import assertk.assertions.isTrue +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockkObject +import io.mockk.unmockkAll +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.signal.core.models.AccountEntropyPool +import org.signal.core.models.ServiceId +import org.signal.core.ui.CoreUiDependenciesRule +import org.signal.core.ui.compose.Dialogs +import org.signal.core.ui.compose.theme.SignalTheme +import org.signal.passwordmanager.SignalCredentialManager +import org.signal.passwordmanager.UsernamePasswordCredential +import org.signal.signallogin.SignalLoginTestTags +import java.util.UUID + +/** + * Checks that the screen only advances when the password manager hands back the recovery key this device already has, + * and that every other path leaves the user a way to enter it manually. + */ +@RunWith(RobolectricTestRunner::class) +@Config(application = Application::class) +class MessageBackupsConfirmRecoveryKeyScreenTest { + + companion object { + private val ACI = ServiceId.ACI.from(UUID.fromString("a6b28482-2e32-83d0-7f23-91360a4c2b91")) + private val AEP = AccountEntropyPool("uy38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t") + private const val OTHER_KEY = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + + @get:Rule + val composeTestRule = createComposeRule() + + @get:Rule + val coreUiDependenciesRule = CoreUiDependenciesRule(ApplicationProvider.getApplicationContext()) + + private var confirmed = false + private var enteredManually = false + private var viewedDetails = false + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `confirming a matching saved key advances the flow`() { + stubCredentialManager(AEP.displayValue) + setContent() + + composeTestRule.onNodeWithTag(MessageBackupsConfirmRecoveryKeyTestTags.CONFIRM_BUTTON).performClick() + composeTestRule.waitForIdle() + + assertThat(confirmed).isTrue() + } + + @Test + fun `confirming a key that does not match shows the failure dialog instead of advancing`() { + stubCredentialManager(OTHER_KEY) + setContent() + + composeTestRule.onNodeWithTag(MessageBackupsConfirmRecoveryKeyTestTags.CONFIRM_BUTTON).performClick() + composeTestRule.waitForIdle() + + assertThat(confirmed).isFalse() + composeTestRule.onNodeWithTag(Dialogs.TEST_TAG_ALERT_DIALOG_CONFIRM_BUTTON).assertIsDisplayed() + } + + @Test + fun `confirming with no saved credential shows the failure dialog instead of advancing`() { + stubCredentialManager(null) + setContent() + + composeTestRule.onNodeWithTag(MessageBackupsConfirmRecoveryKeyTestTags.CONFIRM_BUTTON).performClick() + composeTestRule.waitForIdle() + + assertThat(confirmed).isFalse() + composeTestRule.onNodeWithTag(Dialogs.TEST_TAG_ALERT_DIALOG_CONFIRM_BUTTON).assertIsDisplayed() + } + + @Test + fun `the failure dialog sends the user to manual entry`() { + stubCredentialManager(OTHER_KEY) + setContent() + + composeTestRule.onNodeWithTag(MessageBackupsConfirmRecoveryKeyTestTags.CONFIRM_BUTTON).performClick() + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag(Dialogs.TEST_TAG_ALERT_DIALOG_CONFIRM_BUTTON).performClick() + + assertThat(enteredManually).isTrue() + } + + @Test + fun `enter manually skips the password manager entirely`() { + stubCredentialManager(AEP.displayValue) + setContent() + + composeTestRule.onNodeWithTag(MessageBackupsConfirmRecoveryKeyTestTags.ENTER_MANUALLY_BUTTON).performClick() + + assertThat(enteredManually).isTrue() + assertThat(confirmed).isFalse() + } + + @Test + fun `view details on the card opens the full login`() { + setContent() + + composeTestRule.onNodeWithTag(SignalLoginTestTags.CARD_VIEW_DETAILS_BUTTON).performScrollTo().performClick() + + assertThat(viewedDetails).isTrue() + } + + @Test + fun `the credential is requested under the account key`() { + stubCredentialManager(AEP.displayValue) + setContent() + + composeTestRule.onNodeWithTag(MessageBackupsConfirmRecoveryKeyTestTags.CONFIRM_BUTTON).performClick() + composeTestRule.waitForIdle() + + coVerify { SignalCredentialManager.getCredential(any(), ACI.toString().uppercase()) } + } + + private fun stubCredentialManager(password: String?) { + mockkObject(SignalCredentialManager) + coEvery { SignalCredentialManager.getCredential(any(), any()) } returns password?.let { + UsernamePasswordCredential(username = ACI.toString().uppercase(), password = it) + } + } + + private fun setContent() { + composeTestRule.setContent { + SignalTheme { + MessageBackupsConfirmRecoveryKeyScreen( + aci = ACI, + aep = AEP, + onViewDetailsClick = { viewedDetails = true }, + onConfirmed = { confirmed = true }, + onEnterManuallyClick = { enteredManually = true } + ) + } + } + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModelTest.kt new file mode 100644 index 0000000000..9741967d85 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModelTest.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.ui.subscription + +import android.app.Application +import assertk.assertThat +import assertk.assertions.isEqualTo +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkAll +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.signal.core.models.AccountEntropyPool +import org.signal.core.models.ServiceId +import org.thoughtcrime.securesms.backup.v2.BackupRepository +import org.thoughtcrime.securesms.keyvalue.BackupValues +import org.thoughtcrime.securesms.testing.CoroutineDispatcherRule +import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule +import org.thoughtcrime.securesms.testutil.MockSignalStoreRule +import java.util.UUID + +/** + * Covers the stage machine, and in particular how it forks for accounts that already have a Signal Login and therefore + * already know their recovery key. + */ +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = Application::class) +class MessageBackupsFlowViewModelTest { + + companion object { + private val ACI = ServiceId.ACI.from(UUID.fromString("a6b28482-2e32-83d0-7f23-91360a4c2b91")) + private val AEP = AccountEntropyPool("uy38jh2778hjjhj8lk19ga61s672jsj089r023s6a57809bap92j2yh5t326vv7t") + } + + @get:Rule + val appDependencies = MockAppDependenciesRule() + + @get:Rule + val signalStore = MockSignalStoreRule(relaxed = setOf(BackupValues::class)) + + @get:Rule + val dispatcherRule = CoroutineDispatcherRule(StandardTestDispatcher()) + + @Before + fun setUp() { + every { signalStore.backup.backupTier } returns null + every { signalStore.backup.deletionStateFlow } returns emptyFlow() + every { signalStore.account.accountEntropyPool } returns AEP + every { signalStore.account.requireAci() } returns ACI + + mockkObject(BackupRepository) + coEvery { BackupRepository.getBackupTypes(any()) } returns emptyList() + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `an account with a Signal Login confirms its existing key instead of recording a new one`() { + val viewModel = createViewModel(isPhoneNumberless = true) + + viewModel.goToNextStage() + + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.CONFIRM_RECOVERY_KEY) + } + + @Test + fun `an account with a phone number still records a new key`() { + val viewModel = createViewModel(isPhoneNumberless = false) + + viewModel.goToNextStage() + + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.BACKUP_KEY_EDUCATION) + } + + @Test + fun `confirming the recovery key goes straight to plan selection`() { + val viewModel = createViewModel(isPhoneNumberless = true) + viewModel.goToNextStage() + + viewModel.onRecoveryKeyConfirmed() + + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.TYPE_SELECTION) + } + + @Test + fun `entering the recovery key manually leads to plan selection by way of the verify screen`() { + val viewModel = createViewModel(isPhoneNumberless = true) + viewModel.goToNextStage() + + viewModel.goToEnterRecoveryKeyManually() + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.BACKUP_KEY_VERIFY) + + viewModel.goToNextStage() + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.TYPE_SELECTION) + } + + @Test + fun `backing out of the verify screen returns to the confirmation screen`() { + val viewModel = createViewModel(isPhoneNumberless = true) + viewModel.goToNextStage() + viewModel.goToEnterRecoveryKeyManually() + + viewModel.goToPreviousStage() + + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.CONFIRM_RECOVERY_KEY) + } + + @Test + fun `viewing the full login details is a detour off the confirmation screen`() { + val viewModel = createViewModel(isPhoneNumberless = true) + viewModel.goToNextStage() + + viewModel.goToSignalLoginViewDetails() + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.SIGNAL_LOGIN_VIEW_DETAILS) + + viewModel.goToPreviousStage() + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.CONFIRM_RECOVERY_KEY) + } + + @Test + fun `backing out of the confirmation screen returns to education`() { + val viewModel = createViewModel(isPhoneNumberless = true) + viewModel.goToNextStage() + + viewModel.goToPreviousStage() + + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.EDUCATION) + } + + @Test + fun `backing out of plan selection returns to the confirmation screen`() { + val viewModel = createViewModel(isPhoneNumberless = true) + viewModel.goToNextStage() + viewModel.onRecoveryKeyConfirmed() + + viewModel.goToPreviousStage() + + assertThat(viewModel.stateFlow.value.stage).isEqualTo(MessageBackupsStage.CONFIRM_RECOVERY_KEY) + } + + private fun createViewModel(isPhoneNumberless: Boolean): MessageBackupsFlowViewModel { + return MessageBackupsFlowViewModel( + initialTierSelection = null, + googlePlayApiAvailability = 0, + isCredentialManagerSupported = true, + isPhoneNumberless = isPhoneNumberless, + startScreen = MessageBackupsStage.EDUCATION + ) + } +} diff --git a/feature/registration/src/main/res/drawable-night/image_signal_login_keys.xml b/lib/signal-login/src/main/res/drawable-night/image_signal_login_keys.xml similarity index 100% rename from feature/registration/src/main/res/drawable-night/image_signal_login_keys.xml rename to lib/signal-login/src/main/res/drawable-night/image_signal_login_keys.xml diff --git a/feature/registration/src/main/res/drawable/image_signal_login_keys.xml b/lib/signal-login/src/main/res/drawable/image_signal_login_keys.xml similarity index 100% rename from feature/registration/src/main/res/drawable/image_signal_login_keys.xml rename to lib/signal-login/src/main/res/drawable/image_signal_login_keys.xml