From 478b18aed8f7273c093ebe99741133057be727c8 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 28 Aug 2026 23:48:48 -0400 Subject: [PATCH] Implement password manager button in login details screen. --- .../signallogin/SignalLoginViewDetailsAction.kt | 3 +++ .../signallogin/SignalLoginViewDetailsFragment.kt | 10 ++++++++++ .../signallogin/SignalLoginViewDetailsViewModel.kt | 3 +-- .../SignalLoginViewDetailsViewModelTest.kt | 11 +++++++++++ .../org/signal/registration/RegistrationNavigation.kt | 10 ++++++++++ .../SignalLoginViewDetailsScreenActions.kt | 3 +++ .../SignalLoginViewDetailsViewModel.kt | 3 +-- .../SignalLoginViewDetailsViewModelTest.kt | 10 ++++++++++ 8 files changed, 49 insertions(+), 4 deletions(-) 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 3e2fd7dd1b..f487e9301f 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 @@ -16,6 +16,9 @@ sealed interface SignalLoginViewDetailsAction { /** Leave the screen. */ data object NavigateBack : SignalLoginViewDetailsAction + /** Launch the system credential manager UI so the user can store the login in their password manager. */ + data object LaunchSaveToPasswordManager : SignalLoginViewDetailsAction + /** Launch the system document picker so the user can choose where to save the login PDF. */ data object LaunchSaveAsPdf : SignalLoginViewDetailsAction } 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 43c1f662ab..d418e16d40 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.passwordmanager.SignalCredentialManager import org.signal.signallogin.pdf.SignalLoginPdfRenderer import org.signal.signallogin.viewdetails.SignalLoginViewDetailsScreen @@ -54,6 +55,15 @@ class SignalLoginViewDetailsFragment : ComposeFragment() { private fun handleAction(action: SignalLoginViewDetailsAction) { when (action) { SignalLoginViewDetailsAction.NavigateBack -> requireActivity().onBackPressedDispatcher.onBackPressed() + SignalLoginViewDetailsAction.LaunchSaveToPasswordManager -> { + lifecycleScope.launch { + SignalCredentialManager.saveCredential( + activityContext = requireActivity(), + username = viewModel.state.value.accountKey, + password = viewModel.state.value.recoveryKey + ) + } + } SignalLoginViewDetailsAction.LaunchSaveAsPdf -> savePdfLauncher.launch(SignalLoginPdfRenderer.suggestedFileName(requireContext())) } } 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 acc1a430ff..a8fb41fe60 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 @@ -45,8 +45,7 @@ class SignalLoginViewDetailsViewModel( _actions.send(SignalLoginViewDetailsAction.NavigateBack) } SignalLoginViewDetailsScreenEvents.SaveToPasswordManagerClicked -> { - // TODO [phonenumberless] Store the credentials via the credential manager. - Log.i(TAG, "Save to password manager clicked, but the flow isn't implemented yet.") + _actions.send(SignalLoginViewDetailsAction.LaunchSaveToPasswordManager) } SignalLoginViewDetailsScreenEvents.SaveAsPdfClicked -> { _actions.send(SignalLoginViewDetailsAction.LaunchSaveAsPdf) 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 1a834bb8f7..12cc3641d2 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 @@ -77,6 +77,17 @@ class SignalLoginViewDetailsViewModelTest { assertThat(actions).containsExactly(SignalLoginViewDetailsAction.NavigateBack) } + @Test + fun `SaveToPasswordManagerClicked launches the save to password manager flow`() = runTest(testDispatcher) { + val viewModel = SignalLoginViewDetailsViewModel(repository) + val actions = mutableListOf() + backgroundScope.launch { viewModel.actions.toList(actions) } + + viewModel.onEvent(SignalLoginViewDetailsScreenEvents.SaveToPasswordManagerClicked) + + assertThat(actions).containsExactly(SignalLoginViewDetailsAction.LaunchSaveToPasswordManager) + } + @Test fun `SaveAsPdfClicked launches the save as PDF flow`() = runTest(testDispatcher) { val viewModel = SignalLoginViewDetailsViewModel(repository) 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 2022e6f08b..b050ede781 100644 --- a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt +++ b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt @@ -709,6 +709,16 @@ private fun EntryProviderScope.navigationEntries( CollectActions(viewModel.actions) { action -> when (action) { + SignalLoginViewDetailsScreenActions.LaunchSaveToPasswordManager -> { + scope.launch { + SignalCredentialManager.saveCredential( + activityContext = context, + username = viewModel.state.value.accountKey, + password = viewModel.state.value.recoveryKey + ) + } + } + SignalLoginViewDetailsScreenActions.LaunchSaveAsPdf -> savePdfLauncher.launch(SignalLoginPdfRenderer.suggestedFileName(context)) } } 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 f9657749de..7799dbafc4 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 @@ -6,6 +6,9 @@ package org.signal.registration.screens.signallogindetails 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 } 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 257b830510..7afb9408dd 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 @@ -67,8 +67,7 @@ class SignalLoginViewDetailsViewModel( } is SignalLoginViewDetailsScreenEvents.SaveToPasswordManagerClicked -> { - // TODO [phonenumberless] Store the credentials via the credential manager. - Log.i(TAG, "Save to password manager clicked, but the flow isn't implemented yet.") + _actions.trySend(SignalLoginViewDetailsScreenActions.LaunchSaveToPasswordManager) } is SignalLoginViewDetailsScreenEvents.SaveAsPdfClicked -> { 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 660caf1d02..5e40e27034 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 @@ -71,6 +71,16 @@ class SignalLoginViewDetailsViewModelTest { assertThat(parentEvents).containsExactly(RegistrationFlowEvent.NavigateBack) } + @Test + fun `SaveToPasswordManagerClicked launches the save to password manager flow`() = runTest(testDispatcher) { + val actions = mutableListOf() + backgroundScope.launch { viewModel.actions.toList(actions) } + + viewModel.onEvent(SignalLoginViewDetailsScreenEvents.SaveToPasswordManagerClicked) + + assertThat(actions).containsExactly(SignalLoginViewDetailsScreenActions.LaunchSaveToPasswordManager) + } + @Test fun `SaveAsPdfClicked launches the save as PDF flow`() = runTest(testDispatcher) { val actions = mutableListOf()