BIT-725: Replace "region" concept with Environment (#152)

This commit is contained in:
Brian Yencho
2023-10-24 11:17:10 -05:00
committed by Álison Fernandes
parent e4ab70a106
commit 2472648434
10 changed files with 120 additions and 75 deletions

View File

@@ -27,12 +27,6 @@ interface AuthRepository {
*/
var rememberedEmailAddress: String?
/**
* The currently selected region label (`null` if not set).
*/
// TODO replace this with a more robust selected region object BIT-725
var selectedRegionLabel: String
/**
* Attempt to login with the given email and password. Updated access token will be reflected
* in [authStateFlow].

View File

@@ -77,9 +77,6 @@ class AuthRepositoryImpl @Inject constructor(
authDiskSource.rememberedEmailAddress = value
}
// TODO Handle selected region functionality BIT-725
override var selectedRegionLabel: String = "bitwarden.us"
override suspend fun login(
email: String,
password: String,

View File

@@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
@@ -132,10 +133,10 @@ fun LandingScreen(
Spacer(modifier = Modifier.height(10.dp))
RegionSelector(
selectedOption = state.selectedRegion,
EnvironmentSelector(
selectedOption = state.selectedEnvironment.type,
onOptionSelected = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.RegionOptionSelect(it)) }
{ viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(it)) }
},
modifier = Modifier
.semantics { testTag = "RegionSelectorDropdown" }
@@ -208,19 +209,19 @@ fun LandingScreen(
* from a list of options. When an option is selected, it invokes the provided callback
* and displays the currently selected region on the UI.
*
* @param selectedOption The currently selected region option.
* @param onOptionSelected A callback that gets invoked when a region option is selected
* @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.
* @param modifier A [Modifier] for the composable.
*
*/
@Composable
private fun RegionSelector(
selectedOption: LandingState.RegionOption,
onOptionSelected: (LandingState.RegionOption) -> Unit,
private fun EnvironmentSelector(
selectedOption: Environment.Type,
onOptionSelected: (Environment.Type) -> Unit,
modifier: Modifier,
) {
val options = LandingState.RegionOption.values().toList()
val options = Environment.Type.values()
var expanded by remember { mutableStateOf(false) }
Box(modifier = modifier) {
@@ -238,7 +239,7 @@ private fun RegionSelector(
modifier = Modifier.padding(end = 12.dp),
)
Text(
text = selectedOption.label,
text = selectedOption.label(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 8.dp),
@@ -256,7 +257,7 @@ private fun RegionSelector(
) {
options.forEach { optionString ->
DropdownMenuItem(
text = { Text(text = optionString.label) },
text = { Text(text = optionString.label()) },
onClick = {
expanded = false
onOptionSelected(optionString)

View File

@@ -5,6 +5,8 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.isValidEmail
@@ -24,6 +26,7 @@ private const val KEY_STATE = "state"
@HiltViewModel
class LandingViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val environmentRepository: EnvironmentRepository,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<LandingState, LandingEvent, LandingAction>(
initialState = savedStateHandle[KEY_STATE]
@@ -31,15 +34,20 @@ class LandingViewModel @Inject constructor(
emailInput = authRepository.rememberedEmailAddress.orEmpty(),
isContinueButtonEnabled = authRepository.rememberedEmailAddress != null,
isRememberMeEnabled = authRepository.rememberedEmailAddress != null,
selectedRegion = LandingState.RegionOption.BITWARDEN_US,
selectedEnvironment = environmentRepository.environment,
errorDialogState = BasicDialogState.Hidden,
),
) {
init {
// As state updates, write to saved state handle:
// As state updates:
// - write to saved state handle
// - updated selected environment
stateFlow
.onEach { savedStateHandle[KEY_STATE] = it }
.onEach {
savedStateHandle[KEY_STATE] = it
environmentRepository.environment = it.selectedEnvironment
}
.launchIn(viewModelScope)
}
@@ -50,7 +58,7 @@ class LandingViewModel @Inject constructor(
is LandingAction.ErrorDialogDismiss -> handleErrorDialogDismiss()
is LandingAction.RememberMeToggle -> handleRememberMeToggled(action)
is LandingAction.EmailInputChanged -> handleEmailInputUpdated(action)
is LandingAction.RegionOptionSelect -> handleRegionSelect(action)
is LandingAction.EnvironmentTypeSelect -> handleEnvironmentTypeSelect(action)
}
}
@@ -82,8 +90,6 @@ class LandingViewModel @Inject constructor(
// Update the remembered email address
authRepository.rememberedEmailAddress = email.takeUnless { !isRememberMeEnabled }
// Update the selected region selectedRegionLabel
authRepository.selectedRegionLabel = mutableStateFlow.value.selectedRegion.label
sendEvent(LandingEvent.NavigateToLogin(email))
}
@@ -102,10 +108,21 @@ class LandingViewModel @Inject constructor(
mutableStateFlow.update { it.copy(isRememberMeEnabled = action.isChecked) }
}
private fun handleRegionSelect(action: LandingAction.RegionOptionSelect) {
private fun handleEnvironmentTypeSelect(action: LandingAction.EnvironmentTypeSelect) {
val environment = when (action.environmentType) {
Environment.Type.US -> Environment.Us
Environment.Type.EU -> Environment.Eu
Environment.Type.SELF_HOSTED -> {
// TODO Show dialog for setting selected environment (BIT-330)
Environment.SelfHosted(
environmentUrlData = Environment.Us.environmentUrlData,
)
}
}
mutableStateFlow.update {
it.copy(
selectedRegion = action.regionOption,
selectedEnvironment = environment,
)
}
}
@@ -119,18 +136,9 @@ data class LandingState(
val emailInput: String,
val isContinueButtonEnabled: Boolean,
val isRememberMeEnabled: Boolean,
val selectedRegion: RegionOption,
val selectedEnvironment: Environment,
val errorDialogState: BasicDialogState,
) : Parcelable {
/**
* Enumerates the possible region options with their corresponding labels.
*/
enum class RegionOption(val label: String) {
BITWARDEN_US("bitwarden.com"),
BITWARDEN_EU("bitwarden.eu"),
SELF_HOSTED("Self-hosted"),
}
}
) : Parcelable
/**
* Models events for the landing screen.
@@ -185,7 +193,7 @@ sealed class LandingAction {
/**
* Indicates that the selection from the region drop down has changed.
*/
data class RegionOptionSelect(
val regionOption: LandingState.RegionOption,
data class EnvironmentTypeSelect(
val environmentType: Environment.Type,
) : LandingAction()
}

View File

@@ -147,12 +147,12 @@ fun LoginScreen(
.padding(bottom = 24.dp),
isEnabled = state.isLoginButtonEnabled,
)
// TODO Get the "login target" from a dropdown (BIT-202)
Text(
text = stringResource(
id = R.string.logging_in_as_x_on_y,
state.emailAddress,
state.region,
state.environmentLabel(),
),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,

View File

@@ -11,7 +11,9 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
@@ -31,6 +33,7 @@ private const val KEY_STATE = "state"
@HiltViewModel
class LoginViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val environmentRepository: EnvironmentRepository,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<LoginState, LoginEvent, LoginAction>(
initialState = savedStateHandle[KEY_STATE]
@@ -38,7 +41,7 @@ class LoginViewModel @Inject constructor(
emailAddress = LoginArgs(savedStateHandle).emailAddress,
isLoginButtonEnabled = true,
passwordInput = "",
region = authRepository.selectedRegionLabel,
environmentLabel = environmentRepository.environment.label,
loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden,
captchaToken = LoginArgs(savedStateHandle).captchaToken,
@@ -193,7 +196,7 @@ data class LoginState(
val passwordInput: String,
val emailAddress: String,
val captchaToken: String?,
val region: String,
val environmentLabel: Text,
val isLoginButtonEnabled: Boolean,
val loadingDialogState: LoadingDialogState,
val errorDialogState: BasicDialogState,