mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-817: Update storage of Environment on Landing Screen (#203)
This commit is contained in:
parent
a8de4b10aa
commit
9a4e3af27c
6 changed files with 103 additions and 24 deletions
|
@ -1,18 +1,15 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.RawValue
|
||||
|
||||
/**
|
||||
* A higher-level wrapper around [EnvironmentUrlDataJson] that provides type-safety, enumerability,
|
||||
* and human-readable labels.
|
||||
*/
|
||||
sealed class Environment : Parcelable {
|
||||
sealed class Environment {
|
||||
/**
|
||||
* The [Type] of the environment.
|
||||
*/
|
||||
|
@ -31,7 +28,6 @@ sealed class Environment : Parcelable {
|
|||
/**
|
||||
* The default US environment.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Us : Environment() {
|
||||
override val type: Type get() = Type.US
|
||||
override val environmentUrlData: EnvironmentUrlDataJson
|
||||
|
@ -41,7 +37,6 @@ sealed class Environment : Parcelable {
|
|||
/**
|
||||
* The default EU environment.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Eu : Environment() {
|
||||
override val type: Type get() = Type.EU
|
||||
override val environmentUrlData: EnvironmentUrlDataJson
|
||||
|
@ -51,9 +46,8 @@ sealed class Environment : Parcelable {
|
|||
/**
|
||||
* A custom self-hosted environment with a fully configurable [environmentUrlData].
|
||||
*/
|
||||
@Parcelize
|
||||
data class SelfHosted(
|
||||
override val environmentUrlData: @RawValue EnvironmentUrlDataJson,
|
||||
override val environmentUrlData: EnvironmentUrlDataJson,
|
||||
) : Environment() {
|
||||
override val type: Type get() = Type.SELF_HOSTED
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ fun LandingScreen(
|
|||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
EnvironmentSelector(
|
||||
selectedOption = state.selectedEnvironment.type,
|
||||
selectedOption = state.selectedEnvironmentType,
|
||||
onOptionSelected = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(it)) }
|
||||
},
|
||||
|
|
|
@ -34,7 +34,7 @@ class LandingViewModel @Inject constructor(
|
|||
emailInput = authRepository.rememberedEmailAddress.orEmpty(),
|
||||
isContinueButtonEnabled = authRepository.rememberedEmailAddress != null,
|
||||
isRememberMeEnabled = authRepository.rememberedEmailAddress != null,
|
||||
selectedEnvironment = environmentRepository.environment,
|
||||
selectedEnvironmentType = environmentRepository.environment.type,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
),
|
||||
) {
|
||||
|
@ -42,11 +42,19 @@ class LandingViewModel @Inject constructor(
|
|||
init {
|
||||
// As state updates:
|
||||
// - write to saved state handle
|
||||
// - updated selected environment
|
||||
stateFlow
|
||||
.onEach {
|
||||
savedStateHandle[KEY_STATE] = it
|
||||
environmentRepository.environment = it.selectedEnvironment
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
// Listen for changes in environment triggered both by this VM and externally.
|
||||
environmentRepository
|
||||
.environmentStateFlow
|
||||
.onEach { environment ->
|
||||
sendAction(
|
||||
LandingAction.Internal.UpdatedEnvironmentReceive(environment = environment),
|
||||
)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
@ -59,6 +67,9 @@ class LandingViewModel @Inject constructor(
|
|||
is LandingAction.RememberMeToggle -> handleRememberMeToggled(action)
|
||||
is LandingAction.EmailInputChanged -> handleEmailInputUpdated(action)
|
||||
is LandingAction.EnvironmentTypeSelect -> handleEnvironmentTypeSelect(action)
|
||||
is LandingAction.Internal.UpdatedEnvironmentReceive -> {
|
||||
handleUpdatedEnvironmentReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,9 +130,17 @@ class LandingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// Update the environment in the repo; the VM state will update accordingly because it is
|
||||
// listening for changes.
|
||||
environmentRepository.environment = environment
|
||||
}
|
||||
|
||||
private fun handleUpdatedEnvironmentReceive(
|
||||
action: LandingAction.Internal.UpdatedEnvironmentReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
selectedEnvironment = environment,
|
||||
selectedEnvironmentType = action.environment.type,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +154,7 @@ data class LandingState(
|
|||
val emailInput: String,
|
||||
val isContinueButtonEnabled: Boolean,
|
||||
val isRememberMeEnabled: Boolean,
|
||||
val selectedEnvironment: Environment,
|
||||
val selectedEnvironmentType: Environment.Type,
|
||||
val errorDialogState: BasicDialogState,
|
||||
) : Parcelable
|
||||
|
||||
|
@ -200,4 +219,17 @@ sealed class LandingAction {
|
|||
data class EnvironmentTypeSelect(
|
||||
val environmentType: Environment.Type,
|
||||
) : LandingAction()
|
||||
|
||||
/**
|
||||
* Actions for internal use by the ViewModel.
|
||||
*/
|
||||
sealed class Internal : LandingAction() {
|
||||
|
||||
/**
|
||||
* Indicates that there has been a change in [environment].
|
||||
*/
|
||||
data class UpdatedEnvironmentReceive(
|
||||
val environment: Environment,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* A faked implementation of [EnvironmentRepository] based on in-memory caching.
|
||||
*/
|
||||
class FakeEnvironmentRepository : EnvironmentRepository {
|
||||
override var environment: Environment
|
||||
get() = mutableEnvironmentStateFlow.value
|
||||
set(value) {
|
||||
mutableEnvironmentStateFlow.value = value
|
||||
}
|
||||
override val environmentStateFlow: StateFlow<Environment>
|
||||
get() = mutableEnvironmentStateFlow.asStateFlow()
|
||||
|
||||
private val mutableEnvironmentStateFlow = MutableStateFlow<Environment>(Environment.Us)
|
||||
}
|
|
@ -366,7 +366,7 @@ class LandingScreenTest : BaseComposeTest() {
|
|||
emailInput = "",
|
||||
isContinueButtonEnabled = true,
|
||||
isRememberMeEnabled = false,
|
||||
selectedEnvironment = Environment.Us,
|
||||
selectedEnvironmentType = Environment.Type.US,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
|
@ -14,6 +15,9 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
|||
import org.junit.jupiter.api.Test
|
||||
|
||||
class LandingViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val fakeEnvironmentRepository = FakeEnvironmentRepository()
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when there is no remembered email`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -146,14 +150,44 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `EnvironmentTypeSelect should update value of selected region`() = runTest {
|
||||
val inputEnvironment = Environment.Eu
|
||||
fun `external environment updates should update the selected environment type`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
|
||||
fakeEnvironmentRepository.environment = Environment.Eu
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
selectedEnvironmentType = Environment.Type.EU,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `EnvironmentTypeSelect should update value of selected region for US or EU`() = runTest {
|
||||
val inputEnvironmentType = Environment.Type.EU
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
awaitItem()
|
||||
viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(inputEnvironment.type))
|
||||
viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(inputEnvironmentType))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(selectedEnvironment = Environment.Eu),
|
||||
DEFAULT_STATE.copy(selectedEnvironmentType = Environment.Type.EU),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `EnvironmentTypeSelect should emit NavigateToEnvironment for self-hosted`() = runTest {
|
||||
val inputEnvironmentType = Environment.Type.SELF_HOSTED
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(inputEnvironmentType))
|
||||
assertEquals(
|
||||
LandingEvent.NavigateToEnvironment,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
|
@ -163,15 +197,12 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
private fun createViewModel(
|
||||
rememberedEmail: String? = null,
|
||||
environment: Environment = Environment.Us,
|
||||
savedStateHandle: SavedStateHandle = SavedStateHandle(),
|
||||
): LandingViewModel = LandingViewModel(
|
||||
authRepository = mockk(relaxed = true) {
|
||||
every { rememberedEmailAddress } returns rememberedEmail
|
||||
},
|
||||
environmentRepository = mockk(relaxed = true) {
|
||||
every { this@mockk.environment } returns environment
|
||||
},
|
||||
environmentRepository = fakeEnvironmentRepository,
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
|
||||
|
@ -182,7 +213,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
emailInput = "",
|
||||
isContinueButtonEnabled = false,
|
||||
isRememberMeEnabled = false,
|
||||
selectedEnvironment = Environment.Us,
|
||||
selectedEnvironmentType = Environment.Type.US,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue