From d069ffb6d595958caa2210d45a64df6317fc4428 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 27 Aug 2026 13:41:41 -0400 Subject: [PATCH] Hide some settings for numberless accounts. --- .../app/account/AccountSettingsRepository.kt | 3 +- .../app/account/AccountSettingsViewModel.kt | 5 +- .../account/AccountSettingsViewModelTest.kt | 14 ++- .../account/AccountSettingsScreen.kt | 103 +++++++++--------- .../account/AccountSettingsState.kt | 5 +- .../account/AccountSettingsScreenTest.kt | 19 ++++ 6 files changed, 87 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsRepository.kt index 43727688cd..bf4d5c3e21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsRepository.kt @@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.lock.v2.PinKeyboardType import org.thoughtcrime.securesms.pin.SvrRepository -import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.signalservice.api.kbs.PinHashUtil import java.io.IOException @@ -47,7 +46,7 @@ class AccountSettingsRepository { fun getPinKeyboardType(): PinKeyboardType = SignalStore.pin.keyboardType - fun isPhoneNumberlessRegistrationEnabled(): Boolean = Environment.PHONENUMBERLESS_REGISTRATION + fun isPhoneNumberless(): Boolean = SignalStore.account.isPhoneNumberless fun getAuthenticatorAppCount(): Int = authenticatorRepository.getAuthenticatorApps().size diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt index c127f79b0c..027f071cf8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt @@ -148,6 +148,8 @@ class AccountSettingsViewModel( } private fun refresh() { + val isPhoneNumberless = repository.isPhoneNumberless() + _state.update { it.copy( hasPin = repository.hasPin(), @@ -156,7 +158,8 @@ class AccountSettingsViewModel( registrationLockEnabled = repository.isRegistrationLockEnabled(), userUnregistered = repository.isUserUnregistered(), clientDeprecated = repository.isClientDeprecated(), - signalLogin = if (repository.isPhoneNumberlessRegistrationEnabled()) { + isPhoneNumberless = isPhoneNumberless, + signalLogin = if (isPhoneNumberless) { AccountSettingsState.SignalLogin( keyCount = MOCK_SIGNAL_LOGIN_KEY_COUNT, authenticatorAppCount = repository.getAuthenticatorAppCount(), diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt index 3e5262da78..b5e053ce3f 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModelTest.kt @@ -62,7 +62,7 @@ class AccountSettingsViewModelTest { every { repository.isUserUnregistered() } returns false every { repository.isClientDeprecated() } returns false every { repository.getPinKeyboardType() } returns PinKeyboardType.NUMERIC - every { repository.isPhoneNumberlessRegistrationEnabled() } returns false + every { repository.isPhoneNumberless() } returns false every { repository.getAuthenticatorAppCount() } returns 0 every { repository.getPasskeyCount() } returns 0 every { repository.verifyLocalPin(any()) } answers { firstArg() == CORRECT_PIN } @@ -293,27 +293,29 @@ class AccountSettingsViewModelTest { } @Test - fun `the Signal Login section is left out when phone-numberless registration is off`() = runTest(testDispatcher) { + fun `the Signal Login section is left out when the account has a phone number`() = runTest(testDispatcher) { val viewModel = createViewModel() + assertThat(viewModel.state.value.isPhoneNumberless).isFalse() assertThat(viewModel.state.value.signalLogin).isNull() } @Test - fun `the Signal Login section is filled in when phone-numberless registration is on`() = runTest(testDispatcher) { - every { repository.isPhoneNumberlessRegistrationEnabled() } returns true + fun `the Signal Login section is filled in when the account is phone-numberless`() = runTest(testDispatcher) { + every { repository.isPhoneNumberless() } returns true every { repository.getAuthenticatorAppCount() } returns 2 every { repository.getPasskeyCount() } returns 8 val viewModel = createViewModel() + assertThat(viewModel.state.value.isPhoneNumberless).isTrue() assertThat(viewModel.state.value.signalLogin?.authenticatorAppCount).isEqualTo(2) assertThat(viewModel.state.value.signalLogin?.passkeyCount).isEqualTo(8) } @Test fun `AuthenticatorAppClicked opens the authenticator apps screen`() = runTest(testDispatcher) { - every { repository.isPhoneNumberlessRegistrationEnabled() } returns true + every { repository.isPhoneNumberless() } returns true val viewModel = createViewModel() val actions = collectActions(viewModel.actions) @@ -325,7 +327,7 @@ class AccountSettingsViewModelTest { @Test fun `PasskeysClicked opens the passkeys screen`() = runTest(testDispatcher) { - every { repository.isPhoneNumberlessRegistrationEnabled() } returns true + every { repository.isPhoneNumberless() } returns true val viewModel = createViewModel() val actions = collectActions(viewModel.actions) diff --git a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsScreen.kt b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsScreen.kt index 7888c715dd..0f30dedb04 100644 --- a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsScreen.kt +++ b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsScreen.kt @@ -159,60 +159,62 @@ fun AccountSettingsScreen( } } - item { - Texts.SectionHeader( - text = stringResource(R.string.preferences_app_protection__signal_pin) - ) - } - - item { - @StringRes val textId = if (state.hasPin || state.hasRestoredAep) { - R.string.preferences_app_protection__change_your_pin - } else { - R.string.preferences_app_protection__create_a_pin + if (!state.isPhoneNumberless) { + item { + Texts.SectionHeader( + text = stringResource(R.string.preferences_app_protection__signal_pin) + ) } - Rows.TextRow( - text = stringResource(textId), - enabled = state.isNotDeprecatedOrUnregistered, - onClick = { onEvent(AccountSettingsEvent.ModifyPinClicked) }, - modifier = Modifier.testTag(AccountSettingsTestTags.ROW_MODIFY_PIN) - ) - } + item { + @StringRes val textId = if (state.hasPin || state.hasRestoredAep) { + R.string.preferences_app_protection__change_your_pin + } else { + R.string.preferences_app_protection__create_a_pin + } - item { - Rows.ToggleRow( - text = stringResource(R.string.preferences_app_protection__pin_reminders), - label = stringResource(R.string.AccountSettingsFragment__youll_be_asked_less_frequently), - checked = state.hasPin && state.pinRemindersEnabled, - enabled = state.hasPin && state.isNotDeprecatedOrUnregistered, - onCheckChanged = { onEvent(AccountSettingsEvent.PinRemindersToggled(it)) }, - modifier = Modifier.testTag(AccountSettingsTestTags.ROW_PIN_REMINDER) - ) - } + Rows.TextRow( + text = stringResource(textId), + enabled = state.isNotDeprecatedOrUnregistered, + onClick = { onEvent(AccountSettingsEvent.ModifyPinClicked) }, + modifier = Modifier.testTag(AccountSettingsTestTags.ROW_MODIFY_PIN) + ) + } - item { - Rows.ToggleRow( - text = stringResource(R.string.preferences_app_protection__registration_lock), - label = stringResource(R.string.AccountSettingsFragment__require_your_signal_pin), - checked = state.registrationLockEnabled, - enabled = state.hasPin && state.isNotDeprecatedOrUnregistered, - onCheckChanged = { onEvent(AccountSettingsEvent.RegistrationLockToggled(it)) }, - modifier = Modifier.testTag(AccountSettingsTestTags.ROW_REGISTRATION_LOCK) - ) - } + item { + Rows.ToggleRow( + text = stringResource(R.string.preferences_app_protection__pin_reminders), + label = stringResource(R.string.AccountSettingsFragment__youll_be_asked_less_frequently), + checked = state.hasPin && state.pinRemindersEnabled, + enabled = state.hasPin && state.isNotDeprecatedOrUnregistered, + onCheckChanged = { onEvent(AccountSettingsEvent.PinRemindersToggled(it)) }, + modifier = Modifier.testTag(AccountSettingsTestTags.ROW_PIN_REMINDER) + ) + } - item { - Rows.TextRow( - text = stringResource(R.string.preferences__advanced_pin_settings), - enabled = state.isNotDeprecatedOrUnregistered, - onClick = { onEvent(AccountSettingsEvent.AdvancedPinSettingsClicked) }, - modifier = Modifier.testTag(AccountSettingsTestTags.ROW_ADVANCED_PIN_SETTINGS) - ) - } + item { + Rows.ToggleRow( + text = stringResource(R.string.preferences_app_protection__registration_lock), + label = stringResource(R.string.AccountSettingsFragment__require_your_signal_pin), + checked = state.registrationLockEnabled, + enabled = state.hasPin && state.isNotDeprecatedOrUnregistered, + onCheckChanged = { onEvent(AccountSettingsEvent.RegistrationLockToggled(it)) }, + modifier = Modifier.testTag(AccountSettingsTestTags.ROW_REGISTRATION_LOCK) + ) + } - item { - Dividers.Default() + item { + Rows.TextRow( + text = stringResource(R.string.preferences__advanced_pin_settings), + enabled = state.isNotDeprecatedOrUnregistered, + onClick = { onEvent(AccountSettingsEvent.AdvancedPinSettingsClicked) }, + modifier = Modifier.testTag(AccountSettingsTestTags.ROW_ADVANCED_PIN_SETTINGS) + ) + } + + item { + Dividers.Default() + } } item { @@ -221,7 +223,7 @@ fun AccountSettingsScreen( ) } - if (!state.userUnregistered) { + if (!state.userUnregistered && !state.isPhoneNumberless) { item { Rows.TextRow( text = stringResource(R.string.AccountSettingsFragment__change_phone_number), @@ -567,8 +569,7 @@ private fun AccountSettingsScreenSignalLoginPreview() { Previews.Preview { AccountSettingsScreen( state = AccountSettingsState( - hasPin = true, - pinRemindersEnabled = true, + isPhoneNumberless = true, signalLogin = AccountSettingsState.SignalLogin(keyCount = 2, authenticatorAppCount = 2, passkeyCount = 8) ), onEvent = {} diff --git a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsState.kt b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsState.kt index f2cca555f5..3238fd61f3 100644 --- a/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsState.kt +++ b/feature/app-settings/src/main/java/org/signal/appsettings/account/AccountSettingsState.kt @@ -13,6 +13,7 @@ data class AccountSettingsState( val userUnregistered: Boolean = false, val clientDeprecated: Boolean = false, val canTransferWhileUnregistered: Boolean = true, + val isPhoneNumberless: Boolean = false, val signalLogin: SignalLogin? = null, val dialog: Dialog = Dialog.None ) { @@ -21,8 +22,8 @@ data class AccountSettingsState( get() = !(userUnregistered || clientDeprecated) /** - * The Signal Login and two-factor authentication sections, which only exist when phone-numberless registration is - * enabled. Null means the sections aren't shown at all. + * The Signal Login and two-factor authentication sections, which only exist for phone-numberless accounts. Null means + * the sections aren't shown at all. */ data class SignalLogin( val keyCount: Int, diff --git a/feature/app-settings/src/test/java/org/signal/appsettings/account/AccountSettingsScreenTest.kt b/feature/app-settings/src/test/java/org/signal/appsettings/account/AccountSettingsScreenTest.kt index 5e5ccb3121..86db50e90a 100644 --- a/feature/app-settings/src/test/java/org/signal/appsettings/account/AccountSettingsScreenTest.kt +++ b/feature/app-settings/src/test/java/org/signal/appsettings/account/AccountSettingsScreenTest.kt @@ -319,6 +319,23 @@ class AccountSettingsScreenTest { assertThat(events).contains(AccountSettingsEvent.PasskeysClicked) } + @Test + fun givenAPhoneNumberlessAccount_whenScreenDisplayed_thenPinSectionIsAbsent() { + setContent(createState(isPhoneNumberless = true)) + + composeTestRule.onNodeWithTag(AccountSettingsTestTags.ROW_MODIFY_PIN).assertDoesNotExist() + composeTestRule.onNodeWithTag(AccountSettingsTestTags.ROW_PIN_REMINDER).assertDoesNotExist() + composeTestRule.onNodeWithTag(AccountSettingsTestTags.ROW_REGISTRATION_LOCK).assertDoesNotExist() + composeTestRule.onNodeWithTag(AccountSettingsTestTags.ROW_ADVANCED_PIN_SETTINGS).assertDoesNotExist() + } + + @Test + fun givenAPhoneNumberlessAccount_whenScreenDisplayed_thenChangePhoneNumberIsAbsent() { + setContent(createState(isPhoneNumberless = true)) + + composeTestRule.onNodeWithTag(AccountSettingsTestTags.ROW_CHANGE_PHONE_NUMBER).assertDoesNotExist() + } + private fun setContent(state: AccountSettingsState) { composeTestRule.setContent { AccountSettingsScreen( @@ -341,6 +358,7 @@ class AccountSettingsScreenTest { userUnregistered: Boolean = false, clientDeprecated: Boolean = false, canTransferWhileUnregistered: Boolean = true, + isPhoneNumberless: Boolean = false, signalLogin: AccountSettingsState.SignalLogin? = null, dialog: Dialog = Dialog.None ): AccountSettingsState { @@ -352,6 +370,7 @@ class AccountSettingsScreenTest { userUnregistered = userUnregistered, clientDeprecated = clientDeprecated, canTransferWhileUnregistered = canTransferWhileUnregistered, + isPhoneNumberless = isPhoneNumberless, signalLogin = signalLogin, dialog = dialog )