From d079b46aa0658a05c5d255c8f8d796044fe17562 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 3 Sep 2026 00:14:00 -0400 Subject: [PATCH] Make text copy-pastable in signal login details. --- .../SignalLoginViewDetailsAction.kt | 9 +++ .../SignalLoginViewDetailsFragment.kt | 6 ++ .../SignalLoginViewDetailsViewModel.kt | 6 ++ .../SignalLoginViewDetailsViewModelTest.kt | 23 ++++++ .../registration/RegistrationNavigation.kt | 4 + .../SignalLoginViewDetailsScreenActions.kt | 9 +++ .../SignalLoginViewDetailsViewModel.kt | 8 ++ .../SignalLoginViewDetailsScreenTest.kt | 77 +++++++++++++++++++ .../SignalLoginViewDetailsViewModelTest.kt | 21 +++++ .../signal/signallogin/SignalLoginTestTags.kt | 2 + .../SignalLoginViewDetailsScreen.kt | 25 ++++-- .../SignalLoginViewDetailsScreenEvents.kt | 16 ++++ .../SignalLoginViewDetailsViewModel.kt | 10 +++ 13 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenTest.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsAction.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsAction.kt index f487e9301f..c3aead03c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsAction.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsAction.kt @@ -5,6 +5,8 @@ package org.thoughtcrime.securesms.components.settings.app.account.signallogin +import org.signal.core.util.censor + /** * One-shot side effects that need an Activity or the nav graph, and therefore have to be carried out by * [SignalLoginViewDetailsFragment] rather than the screen itself. @@ -21,4 +23,11 @@ sealed interface SignalLoginViewDetailsAction { /** Launch the system document picker so the user can choose where to save the login PDF. */ data object LaunchSaveAsPdf : SignalLoginViewDetailsAction + + /** Copy the specified text to the clipboard */ + data class CopyTextToClipboard(val text: String) : SignalLoginViewDetailsAction { + override fun toString(): String { + return "CopyTextToClipboard(text=${text.censor()})" + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt index d418e16d40..964b5843b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsFragment.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.launch import org.signal.core.ui.compose.CollectActions import org.signal.core.ui.compose.ComposeFragment import org.signal.core.util.Result +import org.signal.core.util.Util import org.signal.passwordmanager.SignalCredentialManager import org.signal.signallogin.pdf.SignalLoginPdfRenderer import org.signal.signallogin.viewdetails.SignalLoginViewDetailsScreen @@ -26,6 +27,10 @@ import org.signal.signallogin.viewdetails.SignalLoginViewDetailsScreen */ class SignalLoginViewDetailsFragment : ComposeFragment() { + companion object { + private const val CLIPBOARD_TIMEOUT_SECONDS = 60 + } + private val viewModel: SignalLoginViewDetailsViewModel by viewModels() private val savePdfLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("application/pdf")) { uri: Uri? -> @@ -65,6 +70,7 @@ class SignalLoginViewDetailsFragment : ComposeFragment() { } } SignalLoginViewDetailsAction.LaunchSaveAsPdf -> savePdfLauncher.launch(SignalLoginPdfRenderer.suggestedFileName(requireContext())) + is SignalLoginViewDetailsAction.CopyTextToClipboard -> Util.copyToClipboard(requireContext(), action.text, CLIPBOARD_TIMEOUT_SECONDS) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModel.kt index a8fb41fe60..5f12a464e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModel.kt @@ -50,6 +50,12 @@ class SignalLoginViewDetailsViewModel( SignalLoginViewDetailsScreenEvents.SaveAsPdfClicked -> { _actions.send(SignalLoginViewDetailsAction.LaunchSaveAsPdf) } + is SignalLoginViewDetailsScreenEvents.AccountIdLongClicked -> { + _actions.send(SignalLoginViewDetailsAction.CopyTextToClipboard(event.aci)) + } + is SignalLoginViewDetailsScreenEvents.RecoveryKeyLongClicked -> { + _actions.send(SignalLoginViewDetailsAction.CopyTextToClipboard(event.aep)) + } } } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModelTest.kt index 12cc3641d2..5fddec2a10 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/signallogin/SignalLoginViewDetailsViewModelTest.kt @@ -98,4 +98,27 @@ class SignalLoginViewDetailsViewModelTest { assertThat(actions).containsExactly(SignalLoginViewDetailsAction.LaunchSaveAsPdf) } + + @Test + fun `AccountIdLongClicked copies the account key to the clipboard`() = runTest(testDispatcher) { + val viewModel = SignalLoginViewDetailsViewModel(repository) + val actions = mutableListOf() + backgroundScope.launch { viewModel.actions.toList(actions) } + + viewModel.onEvent(SignalLoginViewDetailsScreenEvents.AccountIdLongClicked("A6B28482-2E32-83D0-7F23-91360A4C2B91")) + + assertThat(actions).containsExactly(SignalLoginViewDetailsAction.CopyTextToClipboard("A6B28482-2E32-83D0-7F23-91360A4C2B91")) + } + + @Test + fun `RecoveryKeyLongClicked copies the recovery key to the clipboard`() = runTest(testDispatcher) { + val recoveryKey = AccountEntropyPool.generate().displayValue + val viewModel = SignalLoginViewDetailsViewModel(repository) + val actions = mutableListOf() + backgroundScope.launch { viewModel.actions.toList(actions) } + + viewModel.onEvent(SignalLoginViewDetailsScreenEvents.RecoveryKeyLongClicked(recoveryKey)) + + assertThat(actions).containsExactly(SignalLoginViewDetailsAction.CopyTextToClipboard(recoveryKey)) + } } diff --git a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt index aa3775c93a..ee10d70765 100644 --- a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt +++ b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt @@ -49,6 +49,7 @@ import org.signal.core.ui.navigation.TransitionSpecs import org.signal.core.util.LinkActions import org.signal.core.util.LinkActions.OpenUrlError import org.signal.core.util.Result +import org.signal.core.util.Util import org.signal.core.util.censor import org.signal.core.util.serialization.AccountEntropyPoolSerializer import org.signal.network.api.RegistrationApiV2.SessionMetadata @@ -387,6 +388,7 @@ private const val LOCAL_BACKUP_RESTORE_RESULT = "local_backup_restore_result" private const val PHONE_NUMBER_DISCOVERABILITY_RESULT = "phone_number_discoverability_result" private const val TWO_FACTOR_CODE_RESULT = "two_factor_code_result" private const val PIN_LEARN_MORE_URL = "https://support.signal.org/hc/articles/360007059792" +private const val CLIPBOARD_TIMEOUT_SECONDS = 60 // TODO [phonenumberless] Point at the real support article once it exists. private const val SIGNAL_LOGIN_LEARN_MORE_URL = "https://support.signal.org/" @@ -767,6 +769,8 @@ private fun EntryProviderScope.navigationEntries( } SignalLoginViewDetailsScreenActions.LaunchSaveAsPdf -> savePdfLauncher.launch(SignalLoginPdfRenderer.suggestedFileName(context)) + + is SignalLoginViewDetailsScreenActions.CopyTextToClipboard -> Util.copyToClipboard(context, action.text, CLIPBOARD_TIMEOUT_SECONDS) } } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenActions.kt b/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenActions.kt index 7799dbafc4..27d3ca800c 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenActions.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenActions.kt @@ -5,10 +5,19 @@ package org.signal.registration.screens.signallogindetails +import org.signal.core.util.censor + sealed interface SignalLoginViewDetailsScreenActions { /** Launch the system credential manager UI so the user can store the login in their password manager. */ data object LaunchSaveToPasswordManager : SignalLoginViewDetailsScreenActions /** Launch the system document picker so the user can choose where to save the login PDF. */ data object LaunchSaveAsPdf : SignalLoginViewDetailsScreenActions + + /** Copy the specified text to the clipboard. */ + data class CopyTextToClipboard(val text: String) : SignalLoginViewDetailsScreenActions { + override fun toString(): String { + return "CopyTextToClipboard(text=${text.censor()})" + } + } } diff --git a/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModel.kt b/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModel.kt index 7afb9408dd..a49be6fc4d 100644 --- a/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModel.kt +++ b/feature/registration/src/main/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModel.kt @@ -73,6 +73,14 @@ class SignalLoginViewDetailsViewModel( is SignalLoginViewDetailsScreenEvents.SaveAsPdfClicked -> { _actions.trySend(SignalLoginViewDetailsScreenActions.LaunchSaveAsPdf) } + + is SignalLoginViewDetailsScreenEvents.AccountIdLongClicked -> { + _actions.trySend(SignalLoginViewDetailsScreenActions.CopyTextToClipboard(event.aci)) + } + + is SignalLoginViewDetailsScreenEvents.RecoveryKeyLongClicked -> { + _actions.trySend(SignalLoginViewDetailsScreenActions.CopyTextToClipboard(event.aep)) + } } } diff --git a/feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenTest.kt b/feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenTest.kt new file mode 100644 index 0000000000..6ad48b8fba --- /dev/null +++ b/feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsScreenTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.registration.screens.signallogindetails + +import android.app.Application +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.longClick +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performTouchInput +import androidx.test.core.app.ApplicationProvider +import assertk.assertThat +import assertk.assertions.contains +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.ui.CoreUiDependenciesRule +import org.signal.core.ui.compose.theme.SignalTheme +import org.signal.signallogin.SignalLoginTestTags +import org.signal.signallogin.viewdetails.SignalLoginViewDetailsScreen +import org.signal.signallogin.viewdetails.SignalLoginViewDetailsScreenEvents +import org.signal.signallogin.viewdetails.SignalLoginViewDetailsState + +@RunWith(RobolectricTestRunner::class) +@Config(application = Application::class) +class SignalLoginViewDetailsScreenTest { + + companion object { + private const val ACCOUNT_KEY = "A6B28482-2E32-83D0-7F23-91360A4C2B91" + private const val RECOVERY_KEY = "UY38JH2778HJJHJ8LK19GA61S672JSJ089R023S6A57809BAP92J2YH5T326VV7T" + } + + @get:Rule + val composeTestRule = createComposeRule() + + @get:Rule + val coreUiDependenciesRule = CoreUiDependenciesRule(ApplicationProvider.getApplicationContext()) + + private val events = mutableListOf() + + @Test + fun `when the account key is long clicked, AccountIdLongClicked is emitted`() { + setContent() + + composeTestRule.onNodeWithTag(SignalLoginTestTags.VIEW_DETAILS_ACCOUNT_KEY_BLOCK).performScrollTo().performTouchInput { longClick() } + + assertThat(events).contains(SignalLoginViewDetailsScreenEvents.AccountIdLongClicked(ACCOUNT_KEY)) + } + + @Test + fun `when the recovery key is long clicked, RecoveryKeyLongClicked is emitted`() { + setContent() + + composeTestRule.onNodeWithTag(SignalLoginTestTags.VIEW_DETAILS_RECOVERY_KEY_BLOCK).performScrollTo().performTouchInput { longClick() } + + assertThat(events).contains(SignalLoginViewDetailsScreenEvents.RecoveryKeyLongClicked(RECOVERY_KEY)) + } + + private fun setContent() { + composeTestRule.setContent { + SignalTheme { + SignalLoginViewDetailsScreen( + state = SignalLoginViewDetailsState( + accountKey = ACCOUNT_KEY, + recoveryKey = RECOVERY_KEY + ), + onEvent = { events += it } + ) + } + } + } +} diff --git a/feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModelTest.kt b/feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModelTest.kt index 5e40e27034..94387d66a8 100644 --- a/feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModelTest.kt +++ b/feature/registration/src/test/java/org/signal/registration/screens/signallogindetails/SignalLoginViewDetailsViewModelTest.kt @@ -90,4 +90,25 @@ class SignalLoginViewDetailsViewModelTest { assertThat(actions).containsExactly(SignalLoginViewDetailsScreenActions.LaunchSaveAsPdf) } + + @Test + fun `AccountIdLongClicked copies the account key to the clipboard`() = runTest(testDispatcher) { + val actions = mutableListOf() + backgroundScope.launch { viewModel.actions.toList(actions) } + + viewModel.onEvent(SignalLoginViewDetailsScreenEvents.AccountIdLongClicked("A6B28482-2E32-83D0-7F23-91360A4C2B91")) + + assertThat(actions).containsExactly(SignalLoginViewDetailsScreenActions.CopyTextToClipboard("A6B28482-2E32-83D0-7F23-91360A4C2B91")) + } + + @Test + fun `RecoveryKeyLongClicked copies the recovery key to the clipboard`() = runTest(testDispatcher) { + val recoveryKey = AccountEntropyPool.generate().displayValue + val actions = mutableListOf() + backgroundScope.launch { viewModel.actions.toList(actions) } + + viewModel.onEvent(SignalLoginViewDetailsScreenEvents.RecoveryKeyLongClicked(recoveryKey)) + + assertThat(actions).containsExactly(SignalLoginViewDetailsScreenActions.CopyTextToClipboard(recoveryKey)) + } } diff --git a/lib/signal-login/src/main/java/org/signal/signallogin/SignalLoginTestTags.kt b/lib/signal-login/src/main/java/org/signal/signallogin/SignalLoginTestTags.kt index 7f25fe4bf8..0f3547b5d7 100644 --- a/lib/signal-login/src/main/java/org/signal/signallogin/SignalLoginTestTags.kt +++ b/lib/signal-login/src/main/java/org/signal/signallogin/SignalLoginTestTags.kt @@ -14,4 +14,6 @@ object SignalLoginTestTags { const val VIEW_DETAILS_SCREEN = "signal_login_view_details_screen" const val VIEW_DETAILS_SAVE_TO_PASSWORD_MANAGER_BUTTON = "signal_login_view_details_save_to_password_manager_button" const val VIEW_DETAILS_SAVE_AS_PDF_BUTTON = "signal_login_view_details_save_as_pdf_button" + const val VIEW_DETAILS_ACCOUNT_KEY_BLOCK = "signal_login_view_details_account_key_block" + const val VIEW_DETAILS_RECOVERY_KEY_BLOCK = "signal_login_view_details_recovery_key_block" } diff --git a/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreen.kt b/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreen.kt index f5c80ae447..d8db4f6db0 100644 --- a/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreen.kt +++ b/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreen.kt @@ -7,6 +7,7 @@ package org.signal.signallogin.viewdetails import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints @@ -103,11 +104,19 @@ fun SignalLoginViewDetailsScreen( Texts.SectionHeader(text = stringResource(R.string.SignalLoginViewDetailsScreen__account_key)) - KeyBlock(text = state.accountKey) + KeyBlock( + text = state.accountKey, + onEvent = onEvent, + modifier = Modifier.testTag(SignalLoginTestTags.VIEW_DETAILS_ACCOUNT_KEY_BLOCK) + ) Texts.SectionHeader(text = stringResource(R.string.SignalLoginViewDetailsScreen__recovery_key)) - RecoveryKeyBlock(groups = state.recoveryKeyGroups) + RecoveryKeyBlock( + groups = state.recoveryKeyGroups, + onEvent = onEvent, + modifier = Modifier.testTag(SignalLoginTestTags.VIEW_DETAILS_RECOVERY_KEY_BLOCK) + ) } Footer(onEvent = onEvent) @@ -136,9 +145,10 @@ private fun MiniCard(modifier: Modifier = Modifier) { @Composable private fun KeyBlock( text: String, + onEvent: (SignalLoginViewDetailsScreenEvents) -> Unit, modifier: Modifier = Modifier ) { - Box(modifier = modifier.keyBlockSurface()) { + Box(modifier = modifier.keyBlockSurface(onLongClick = { onEvent(SignalLoginViewDetailsScreenEvents.AccountIdLongClicked(text)) })) { Text( text = text, style = keyTextStyle() @@ -154,9 +164,10 @@ private fun KeyBlock( @Composable private fun RecoveryKeyBlock( groups: List, + onEvent: (SignalLoginViewDetailsScreenEvents) -> Unit, modifier: Modifier = Modifier ) { - BoxWithConstraints(modifier = modifier.keyBlockSurface()) { + BoxWithConstraints(modifier = modifier.keyBlockSurface(onLongClick = { onEvent(SignalLoginViewDetailsScreenEvents.RecoveryKeyLongClicked(groups.joinToString(separator = ""))) })) { val style = keyTextStyle() val textMeasurer = rememberTextMeasurer() val maxWidth = constraints.maxWidth @@ -196,12 +207,16 @@ private fun RecoveryKeyBlock( } @Composable -private fun Modifier.keyBlockSurface(): Modifier { +private fun Modifier.keyBlockSurface(onLongClick: () -> Unit): Modifier { return this .horizontalGutters() .fillMaxWidth() .clip(RoundedCornerShape(18.dp)) .background(SignalTheme.colors.colorSurface2) + .combinedClickable( + onLongClick = onLongClick, + onClick = {} + ) .padding(horizontal = 28.dp, vertical = 20.dp) } diff --git a/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreenEvents.kt b/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreenEvents.kt index 82346a12e0..167fb4e42d 100644 --- a/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreenEvents.kt +++ b/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsScreenEvents.kt @@ -5,6 +5,8 @@ package org.signal.signallogin.viewdetails +import org.signal.core.util.censor + sealed class SignalLoginViewDetailsScreenEvents { /** The user tapped the back arrow. */ data object BackClicked : SignalLoginViewDetailsScreenEvents() @@ -14,4 +16,18 @@ sealed class SignalLoginViewDetailsScreenEvents { /** The user chose to save the credentials as a PDF. */ data object SaveAsPdfClicked : SignalLoginViewDetailsScreenEvents() + + /** User long clicked the account ID field. */ + data class AccountIdLongClicked(val aci: String) : SignalLoginViewDetailsScreenEvents() { + override fun toString(): String { + return "AccountIdLongClicked(aci=${aci.censor()})" + } + } + + /** User long clicked the recovery key field. */ + data class RecoveryKeyLongClicked(val aep: String) : SignalLoginViewDetailsScreenEvents() { + override fun toString(): String { + return "RecoveryKeyLongClicked(aep=${aep.censor()})" + } + } } diff --git a/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsViewModel.kt b/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsViewModel.kt index a755fea890..c8a7ac1e0d 100644 --- a/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsViewModel.kt +++ b/lib/signal-login/src/main/java/org/signal/signallogin/viewdetails/SignalLoginViewDetailsViewModel.kt @@ -64,6 +64,16 @@ class SignalLoginViewDetailsViewModel( // TODO [phonenumberless] Render the credentials to a PDF and hand it to the user. Log.i(TAG, "Save as PDF clicked, but the flow isn't implemented yet.") } + + is SignalLoginViewDetailsScreenEvents.AccountIdLongClicked -> { + // TODO [phonenumberless] Copy the account key to the clipboard. + Log.i(TAG, "Account key long clicked, but the copy flow isn't implemented yet.") + } + + is SignalLoginViewDetailsScreenEvents.RecoveryKeyLongClicked -> { + // TODO [phonenumberless] Copy the recovery key to the clipboard. + Log.i(TAG, "Recovery key long clicked, but the copy flow isn't implemented yet.") + } } }