mirror of
https://github.com/bitwarden/android.git
synced 2026-06-07 23:58:03 -05:00
BIT-725: Replace "region" concept with Environment (#152)
This commit is contained in:
committed by
Álison Fernandes
parent
e4ab70a106
commit
2472648434
@@ -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].
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user