Hide some settings for numberless accounts.

This commit is contained in:
Greyson Parrelli
2026-09-02 16:11:23 -03:00
committed by Alex Hart
parent c4b0f95f72
commit d069ffb6d5
6 changed files with 87 additions and 62 deletions
@@ -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
@@ -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(),
@@ -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<String>() == 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)
@@ -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 = {}
@@ -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,
@@ -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
)