mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-20 00:35:47 +01:00
Improve re-register flow for numberless account.
This commit is contained in:
+24
-1
@@ -82,6 +82,7 @@ import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.util.Util
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.registration.PendingRestoreOption
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.RegistrationDependencies
|
||||
import org.signal.registration.screens.OnePaneRegistrationScaffold
|
||||
@@ -413,7 +414,15 @@ private fun NextButton(
|
||||
enabled = !state.showSpinner,
|
||||
modifier = Modifier.testTag(TestTags.PHONE_NUMBER_REGISTER_WITHOUT_NUMBER_BUTTON)
|
||||
) {
|
||||
Text(stringResource(R.string.RegistrationActivity_register_without_number))
|
||||
Text(
|
||||
stringResource(
|
||||
if (state.hasExistingAccount) {
|
||||
R.string.RegistrationActivity_use_account_id
|
||||
} else {
|
||||
R.string.RegistrationActivity_register_without_number
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
@@ -646,6 +655,20 @@ private fun PhoneNumberScreenRegisterWithoutNumberPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun PhoneNumberScreenUseAccountIdPreview() {
|
||||
Previews.Preview {
|
||||
PhoneNumberScreen(
|
||||
state = PhoneNumberEntryState(
|
||||
isPhoneNumberlessRegistrationAvailable = true,
|
||||
pendingRestoreOption = PendingRestoreOption.RemoteBackup
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun PhoneNumberScreenAccountIdPreview() {
|
||||
|
||||
+4
@@ -52,6 +52,10 @@ data class PhoneNumberEntryState(
|
||||
null
|
||||
}
|
||||
|
||||
/** Whether the user already told us they have an account to restore, which lets us skip past the Signal Login purchase screen. */
|
||||
val hasExistingAccount: Boolean
|
||||
get() = pendingRestoreOption != null
|
||||
|
||||
/** Whether what has been entered is complete enough to submit, be it a phone number or an account ID. */
|
||||
val isNextEnabled: Boolean
|
||||
get() {
|
||||
|
||||
+5
-1
@@ -151,7 +151,11 @@ class PhoneNumberEntryViewModel(
|
||||
parentEventEmitter.navigateTo(RegistrationRoute.LinkAccount())
|
||||
}
|
||||
is PhoneNumberEntryScreenEvents.RegisterWithoutNumber -> {
|
||||
parentEventEmitter.navigateTo(RegistrationRoute.SignalLoginPayment)
|
||||
if (state.hasExistingAccount) {
|
||||
parentEventEmitter.navigateTo(RegistrationRoute.SignalLoginCredentialEntry())
|
||||
} else {
|
||||
parentEventEmitter.navigateTo(RegistrationRoute.SignalLoginPayment)
|
||||
}
|
||||
}
|
||||
is PhoneNumberEntryScreenEvents.CaptchaCompleted -> {
|
||||
stateEmitter(applyCaptchaCompleted(state, event.token, parentEventEmitter))
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
<string name="RegistrationActivity_next">Next</string>
|
||||
<!-- Button on the phone number screen that starts registering with a purchased Signal Login instead of a phone number -->
|
||||
<string name="RegistrationActivity_register_without_number">Register without number</string>
|
||||
<!-- Button on the phone number screen, shown when the user already said they have an account to restore, that goes straight to entering their existing Signal Login -->
|
||||
<string name="RegistrationActivity_use_account_id">Use account ID</string>
|
||||
<!-- Dialog title to confirm phone number -->
|
||||
<string name="RegistrationActivity_is_the_phone_number">Is the phone number below correct?</string>
|
||||
<!-- Dialog body to confirm phone number -->
|
||||
|
||||
+32
@@ -1840,6 +1840,38 @@ class RegistrationEndToEndTest {
|
||||
assert(storageController.restoreDecision == RestoreDecision.SKIPPED) { "Expected SKIPPED restore decision but was ${storageController.restoreDecision}" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a pending restore turns the numberless button into a direct jump to signal login entry`() {
|
||||
enableSignalLoginRegistration()
|
||||
val login = signalLoginFor(reregistration = true)
|
||||
|
||||
var registrationComplete = false
|
||||
launchRegistrationFlow(onRegistrationComplete = { registrationComplete = true })
|
||||
|
||||
startManualRestore()
|
||||
chooseRestoreOption(TestTags.ARCHIVE_RESTORE_SELECTION_FROM_SIGNAL_BACKUPS)
|
||||
|
||||
// The user already said they have an account to restore, so the purchase screen is skipped entirely
|
||||
waitForTag(TestTags.PHONE_NUMBER_SCREEN)
|
||||
composeTestRule.onNodeWithTag(TestTags.PHONE_NUMBER_REGISTER_WITHOUT_NUMBER_BUTTON).performClick()
|
||||
waitForTag(TestTags.SIGNAL_LOGIN_CREDENTIAL_ENTRY_SCREEN)
|
||||
assert(composeTestRule.onAllNodesWithTag(TestTags.SIGNAL_LOGIN_PAYMENT_SCREEN).fetchSemanticsNodes().isEmpty()) {
|
||||
"Expected the Signal Login payment screen to be skipped for a user with a pending restore"
|
||||
}
|
||||
|
||||
enterSignalLogin(login)
|
||||
|
||||
chooseRestoreOption(TestTags.ARCHIVE_RESTORE_SELECTION_NONE)
|
||||
waitForTag(Dialogs.TEST_TAG_ALERT_DIALOG_CONFIRM_BUTTON)
|
||||
composeTestRule.onNodeWithTag(Dialogs.TEST_TAG_ALERT_DIALOG_CONFIRM_BUTTON).performClick()
|
||||
|
||||
waitFor("registration to complete") { registrationComplete }
|
||||
|
||||
assert(purchaseApi.launchCount == 0) { "A user with an existing login should never hit Google Play" }
|
||||
assert(networkController.lastCreateSessionE164 == null) { "Expected no verification session for a numberless login" }
|
||||
assert(networkController.lastRegisterAccountRequest?.aci == login.aci) { "Expected the entered login to be reclaimed but was ${networkController.lastRegisterAccountRequest}" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `typing an account id into the phone number field goes straight to signal login entry with it filled in`() {
|
||||
enableSignalLoginRegistration()
|
||||
|
||||
+33
@@ -46,6 +46,7 @@ import org.signal.network.api.RegistrationApiV2.SvrCredentials
|
||||
import org.signal.network.api.RegistrationApiV2.ThirdPartyServiceErrorResponse
|
||||
import org.signal.network.api.RegistrationApiV2.UpdateSessionError
|
||||
import org.signal.registration.KeyMaterial
|
||||
import org.signal.registration.PendingRestoreOption
|
||||
import org.signal.registration.PreExistingRegistrationData
|
||||
import org.signal.registration.RegisteredAccountData
|
||||
import org.signal.registration.RegistrationFlowEvent
|
||||
@@ -449,6 +450,38 @@ class PhoneNumberEntryViewModelTest {
|
||||
.isInstanceOf<RegistrationRoute.LinkAccount>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `RegisterWithoutNumber navigates to the Signal Login payment screen`() = runTest {
|
||||
viewModel.applyEvent(
|
||||
PhoneNumberEntryState(),
|
||||
PhoneNumberEntryScreenEvents.RegisterWithoutNumber,
|
||||
parentEventEmitter,
|
||||
stateEmitter
|
||||
)
|
||||
|
||||
assertThat(emittedEvents).hasSize(1)
|
||||
assertThat(emittedEvents.first())
|
||||
.isInstanceOf<RegistrationFlowEvent.NavigateToScreen>()
|
||||
.prop(RegistrationFlowEvent.NavigateToScreen::route)
|
||||
.isInstanceOf<RegistrationRoute.SignalLoginPayment>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `RegisterWithoutNumber skips payment and goes straight to credential entry when a restore is pending`() = runTest {
|
||||
viewModel.applyEvent(
|
||||
PhoneNumberEntryState(pendingRestoreOption = PendingRestoreOption.RemoteBackup),
|
||||
PhoneNumberEntryScreenEvents.RegisterWithoutNumber,
|
||||
parentEventEmitter,
|
||||
stateEmitter
|
||||
)
|
||||
|
||||
assertThat(emittedEvents).hasSize(1)
|
||||
assertThat(emittedEvents.first())
|
||||
.isInstanceOf<RegistrationFlowEvent.NavigateToScreen>()
|
||||
.prop(RegistrationFlowEvent.NavigateToScreen::route)
|
||||
.isEqualTo(RegistrationRoute.SignalLoginCredentialEntry())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state reflects repository link and sync availability`() = runTest {
|
||||
every { mockRepository.isLinkAndSyncAvailable } returns true
|
||||
|
||||
+37
@@ -25,6 +25,7 @@ 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.registration.PendingRestoreOption
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.screens.shared.AccountIdError
|
||||
import org.signal.registration.test.TestTags
|
||||
@@ -326,6 +327,42 @@ class PhoneNumberScreenTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the numberless button offers to register without a number when there is no pending restore`() {
|
||||
// Given
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
PhoneNumberScreen(
|
||||
state = PhoneNumberEntryState(isPhoneNumberlessRegistrationAvailable = true),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
composeTestRule.onNodeWithText(context.getString(R.string.RegistrationActivity_register_without_number)).assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the numberless button offers to use an account ID when a restore is pending`() {
|
||||
// Given
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
PhoneNumberScreen(
|
||||
state = PhoneNumberEntryState(
|
||||
isPhoneNumberlessRegistrationAvailable = true,
|
||||
pendingRestoreOption = PendingRestoreOption.RemoteBackup
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
composeTestRule.onNodeWithText(context.getString(R.string.RegistrationActivity_use_account_id)).assertExists()
|
||||
composeTestRule.onNodeWithText(context.getString(R.string.RegistrationActivity_register_without_number)).assertDoesNotExist()
|
||||
}
|
||||
|
||||
private fun accountIdState(accountId: String = "a6b284822e3283d07f2391360a4c2b91") = PhoneNumberEntryState(
|
||||
accountId = accountId,
|
||||
formattedNumber = accountId,
|
||||
|
||||
Reference in New Issue
Block a user