PM-39888: feat: ViewModel controls Environment Selector options (#7135)

This commit is contained in:
David Perez
2026-07-02 18:58:45 +00:00
committed by GitHub
parent 4612f4d2b3
commit 849217c79a
5 changed files with 58 additions and 39 deletions
@@ -235,6 +235,7 @@ private fun LandingScreenContent(
EnvironmentSelector(
labelText = stringResource(id = BitwardenString.logging_in_on_with_colon),
dialogTitle = stringResource(id = BitwardenString.logging_in_on),
options = state.environmentTypeOptions,
selectedOption = state.selectedEnvironmentType,
onOptionSelected = onEnvironmentTypeSelect,
isHelpEnabled = false,
@@ -21,6 +21,8 @@ import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -283,6 +285,12 @@ data class LandingState(
val dialog: DialogState?,
val accountSummaries: List<AccountSummary>,
) : Parcelable {
/**
* The selectable environments.
*/
val environmentTypeOptions: ImmutableList<Environment.Type>
get() = Environment.Type.entries.toImmutableList()
/**
* Determines whether the app bar should be visible based on the presence of account summaries.
*/
@@ -129,11 +129,7 @@ fun StartRegistrationScreen(
},
) {
StartRegistrationContent(
emailInput = state.emailInput,
selectedEnvironmentType = state.selectedEnvironmentType,
nameInput = state.nameInput,
isReceiveMarketingEmailsToggled = state.isReceiveMarketingEmailsToggled,
isContinueButtonEnabled = state.isContinueButtonEnabled,
state = state,
handler = handler,
)
}
@@ -165,11 +161,7 @@ private fun StartRegistrationDialogs(
@Suppress("LongMethod")
@Composable
private fun StartRegistrationContent(
emailInput: String,
selectedEnvironmentType: Environment.Type,
nameInput: String,
isReceiveMarketingEmailsToggled: Boolean,
isContinueButtonEnabled: Boolean,
state: StartRegistrationState,
handler: StartRegistrationHandler,
modifier: Modifier = Modifier,
) {
@@ -195,7 +187,7 @@ private fun StartRegistrationContent(
BitwardenTextField(
label = stringResource(id = BitwardenString.email_address_required),
value = emailInput,
value = state.emailInput,
onValueChange = handler.onEmailInputChange,
keyboardType = KeyboardType.Email,
textFieldTestTag = "EmailAddressEntry",
@@ -204,7 +196,8 @@ private fun StartRegistrationContent(
EnvironmentSelector(
labelText = stringResource(id = BitwardenString.create_account_on_with_colon),
dialogTitle = stringResource(id = BitwardenString.create_account_on),
selectedOption = selectedEnvironmentType,
options = state.environmentTypeOptions,
selectedOption = state.selectedEnvironmentType,
onOptionSelected = handler.onEnvironmentTypeSelect,
onHelpClick = handler.onServerGeologyHelpClick,
modifier = Modifier
@@ -222,7 +215,7 @@ private fun StartRegistrationContent(
BitwardenTextField(
label = stringResource(id = BitwardenString.name),
value = nameInput,
value = state.nameInput,
onValueChange = handler.onNameInputChange,
textFieldTestTag = "NameEntry",
cardStyle = CardStyle.Full,
@@ -231,10 +224,10 @@ private fun StartRegistrationContent(
.standardHorizontalMargin(),
)
if (selectedEnvironmentType != Environment.Type.SELF_HOSTED) {
if (state.selectedEnvironmentType != Environment.Type.SELF_HOSTED) {
Spacer(modifier = Modifier.height(8.dp))
ReceiveMarketingEmailsSwitch(
isChecked = isReceiveMarketingEmailsToggled,
isChecked = state.isReceiveMarketingEmailsToggled,
onCheckedChange = handler.onReceiveMarketingEmailsToggle,
onUnsubscribeClick = handler.onUnsubscribeMarketingEmailsClick,
modifier = Modifier
@@ -248,7 +241,7 @@ private fun StartRegistrationContent(
BitwardenFilledButton(
label = stringResource(id = BitwardenString.continue_text),
onClick = handler.onContinueClick,
isEnabled = isContinueButtonEnabled,
isEnabled = state.isContinueButtonEnabled,
modifier = Modifier
.testTag("ContinueButton")
.standardHorizontalMargin()
@@ -365,11 +358,14 @@ private fun ReceiveMarketingEmailsSwitch(
private fun StartRegistrationContentFilledOut_preview() {
BitwardenTheme {
StartRegistrationContent(
emailInput = "e@mail.com",
selectedEnvironmentType = Environment.Type.US,
nameInput = "Test User",
isReceiveMarketingEmailsToggled = true,
isContinueButtonEnabled = true,
state = StartRegistrationState(
emailInput = "e@mail.com",
selectedEnvironmentType = Environment.Type.US,
nameInput = "Test User",
isReceiveMarketingEmailsToggled = true,
isContinueButtonEnabled = true,
dialog = null,
),
handler = StartRegistrationHandler(
onEmailInputChange = {},
onNameInputChange = {},
@@ -391,11 +387,14 @@ private fun StartRegistrationContentFilledOut_preview() {
private fun StartRegistrationContentEmpty_preview() {
BitwardenTheme {
StartRegistrationContent(
emailInput = "",
selectedEnvironmentType = Environment.Type.US,
nameInput = "",
isReceiveMarketingEmailsToggled = false,
isContinueButtonEnabled = false,
state = StartRegistrationState(
emailInput = "",
selectedEnvironmentType = Environment.Type.US,
nameInput = "",
isReceiveMarketingEmailsToggled = false,
isContinueButtonEnabled = false,
dialog = null,
),
handler = StartRegistrationHandler(
onEmailInputChange = {},
onNameInputChange = {},
@@ -4,7 +4,6 @@ import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.data.repository.model.Environment
import com.bitwarden.data.repository.model.Environment.Type
import com.bitwarden.ui.platform.base.BackgroundEvent
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.base.util.isValidEmail
@@ -19,6 +18,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResul
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -40,15 +41,17 @@ class StartRegistrationViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val environmentRepository: EnvironmentRepository,
) : BaseViewModel<StartRegistrationState, StartRegistrationEvent, StartRegistrationAction>(
initialState = savedStateHandle[KEY_STATE]
?: StartRegistrationState(
initialState = savedStateHandle[KEY_STATE] ?: run {
val environmentType = environmentRepository.environment.type
StartRegistrationState(
emailInput = "",
nameInput = "",
isReceiveMarketingEmailsToggled = environmentRepository.environment.type == Type.US,
isReceiveMarketingEmailsToggled = environmentType == Environment.Type.US,
isContinueButtonEnabled = false,
selectedEnvironmentType = environmentRepository.environment.type,
selectedEnvironmentType = environmentType,
dialog = null,
),
)
},
) {
init {
@@ -115,9 +118,9 @@ class StartRegistrationViewModel @Inject constructor(
private fun handleEnvironmentTypeSelect(action: StartRegistrationAction.EnvironmentTypeSelect) {
val environment = when (action.environmentType) {
Type.US -> Environment.Us
Type.EU -> Environment.Eu
Type.SELF_HOSTED -> {
Environment.Type.US -> Environment.Us
Environment.Type.EU -> Environment.Eu
Environment.Type.SELF_HOSTED -> {
// Launch the self-hosted screen and select the full environment details there.
sendEvent(StartRegistrationEvent.NavigateToEnvironment)
return
@@ -286,9 +289,15 @@ data class StartRegistrationState(
val nameInput: String,
val isReceiveMarketingEmailsToggled: Boolean,
val isContinueButtonEnabled: Boolean,
val selectedEnvironmentType: Type,
val selectedEnvironmentType: Environment.Type,
val dialog: StartRegistrationDialog?,
) : Parcelable
) : Parcelable {
/**
* The selectable environments.
*/
val environmentTypeOptions: ImmutableList<Environment.Type>
get() = Environment.Type.entries.toImmutableList()
}
/**
* Models dialogs that can be displayed on the start registration screen.
@@ -397,7 +406,7 @@ sealed class StartRegistrationAction {
* Indicates that the selection from the region drop down has changed.
*/
data class EnvironmentTypeSelect(
val environmentType: Type,
val environmentType: Environment.Type,
) : StartRegistrationAction()
/**
@@ -29,6 +29,7 @@ import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.util.displayLabel
import kotlinx.collections.immutable.ImmutableList
/**
* A dropdown selector UI component specific to region url selection.
@@ -39,6 +40,7 @@ import com.x8bit.bitwarden.ui.platform.util.displayLabel
*
* @param labelText The text displayed near the selector button.
* @param dialogTitle The title displayed in the selection dialog.
* @param options The options displayed in the selection dialog.
* @param selectedOption The currently selected environment option.
* @param onOptionSelected A callback that gets invoked when an environment option is selected
* and passes the selected option as an argument.
@@ -51,13 +53,13 @@ import com.x8bit.bitwarden.ui.platform.util.displayLabel
fun EnvironmentSelector(
labelText: String,
dialogTitle: String,
options: ImmutableList<Environment.Type>,
selectedOption: Environment.Type,
onOptionSelected: (Environment.Type) -> Unit,
onHelpClick: () -> Unit,
modifier: Modifier = Modifier,
isHelpEnabled: Boolean = true,
) {
val options = Environment.Type.entries.toTypedArray()
var shouldShowDialog by rememberSaveable { mutableStateOf(false) }
Row(