[PM-11270] Hide all new UI behind onboarding flow flag. (#3810)

This commit is contained in:
Dave Severns 2024-08-22 16:06:54 -04:00 committed by GitHub
parent b56a21b6e5
commit 82d3b44712
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 476 additions and 51 deletions

View file

@ -81,6 +81,9 @@ fun NavGraphBuilder.authGraph(
) )
checkEmailDestination( checkEmailDestination(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },
onNavigateBackToLanding = {
navController.popBackStack(route = LANDING_ROUTE, inclusive = false)
},
) )
completeRegistrationDestination( completeRegistrationDestination(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },

View file

@ -36,6 +36,7 @@ data class CheckEmailArgs(
*/ */
fun NavGraphBuilder.checkEmailDestination( fun NavGraphBuilder.checkEmailDestination(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateBackToLanding: () -> Unit,
) { ) {
composableWithSlideTransitions( composableWithSlideTransitions(
route = CHECK_EMAIL_ROUTE, route = CHECK_EMAIL_ROUTE,
@ -45,6 +46,7 @@ fun NavGraphBuilder.checkEmailDestination(
) { ) {
CheckEmailScreen( CheckEmailScreen(
onNavigateBack = onNavigateBack, onNavigateBack = onNavigateBack,
onNavigateBackToLanding = onNavigateBackToLanding,
) )
} }
} }

View file

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -20,13 +21,17 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -35,6 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.auth.feature.checkemail.handlers.rememberCheckEmailHandler
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.createAnnotatedString import com.x8bit.bitwarden.ui.platform.base.util.createAnnotatedString
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
@ -47,6 +53,8 @@ import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
private const val TAG_URL = "URL"
/** /**
* Top level composable for the check email screen. * Top level composable for the check email screen.
*/ */
@ -55,19 +63,23 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
@Composable @Composable
fun CheckEmailScreen( fun CheckEmailScreen(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateBackToLanding: () -> Unit,
intentManager: IntentManager = LocalIntentManager.current, intentManager: IntentManager = LocalIntentManager.current,
viewModel: CheckEmailViewModel = hiltViewModel(), viewModel: CheckEmailViewModel = hiltViewModel(),
) { ) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle() val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val handler = rememberCheckEmailHandler(viewModel = viewModel)
EventsEffect(viewModel) { event -> EventsEffect(viewModel) { event ->
when (event) { when (event) {
is CheckEmailEvent.NavigateBack -> { is CheckEmailEvent.NavigateBack -> {
onNavigateBack.invoke() onNavigateBack()
} }
is CheckEmailEvent.NavigateToEmailApp -> { is CheckEmailEvent.NavigateToEmailApp -> {
intentManager.startDefaultEmailApplication() intentManager.startDefaultEmailApplication()
} }
CheckEmailEvent.NavigateBackToLanding -> onNavigateBackToLanding()
} }
} }
@ -82,9 +94,7 @@ fun CheckEmailScreen(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = rememberVectorPainter(id = R.drawable.ic_back), navigationIcon = rememberVectorPainter(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back), navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) { onNavigationIconClick = handler.onBackClick,
{ viewModel.trySendAction(CheckEmailAction.BackClick) }
},
) )
}, },
) { innerPadding -> ) { innerPadding ->
@ -95,20 +105,21 @@ fun CheckEmailScreen(
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),
) { ) {
if (state.showNewOnboardingUi) {
CheckEmailContent( CheckEmailContent(
email = state.email, email = state.email,
onOpenEmailAppClick = remember(viewModel) { onOpenEmailAppClick = handler.onOpenEmailAppClick,
{ onChangeEmailClick = handler.onChangeEmailClick,
viewModel.trySendAction(CheckEmailAction.OpenEmailClick)
}
},
onChangeEmailClick = remember(viewModel) {
{
viewModel.trySendAction(CheckEmailAction.ChangeEmailClick)
}
},
modifier = Modifier.standardHorizontalMargin(), modifier = Modifier.standardHorizontalMargin(),
) )
} else {
CheckEmailLegacyContent(
email = state.email,
onOpenEmailAppClick = handler.onOpenEmailAppClick,
onChangeEmailClick = handler.onChangeEmailClick,
onLoginClick = handler.onLoginClick,
)
}
Spacer(modifier = Modifier.navigationBarsPadding()) Spacer(modifier = Modifier.navigationBarsPadding())
} }
} }
@ -198,9 +209,136 @@ private fun CheckEmailContent(
} }
} }
@Suppress("LongMethod")
@Composable
private fun CheckEmailLegacyContent(
email: String,
onOpenEmailAppClick: () -> Unit,
onChangeEmailClick: () -> Unit,
onLoginClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = rememberVectorPainter(id = R.drawable.email_check),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary),
contentDescription = null,
contentScale = ContentScale.FillHeight,
modifier = Modifier
.padding(horizontal = 16.dp)
.height(112.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(32.dp))
Text(
text = stringResource(id = R.string.check_your_email),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.padding(horizontal = 24.dp)
.wrapContentHeight()
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(16.dp))
@Suppress("MaxLineLength")
val descriptionAnnotatedString = createAnnotatedString(
mainString = stringResource(
id = R.string.follow_the_instructions_in_the_email_sent_to_x_to_continue_creating_your_account,
email,
),
highlights = listOf(email),
highlightStyle = SpanStyle(
color = MaterialTheme.colorScheme.onSurface,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
fontWeight = FontWeight.Bold,
),
tag = "EMAIL",
)
Text(
text = descriptionAnnotatedString,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(horizontal = 24.dp)
.fillMaxWidth()
.wrapContentHeight(),
)
Spacer(modifier = Modifier.height(32.dp))
BitwardenFilledButton(
label = stringResource(id = R.string.open_email_app),
onClick = onOpenEmailAppClick,
modifier = Modifier
.testTag("OpenEmailApp")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(32.dp))
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val goBackAnnotatedString = createAnnotatedString(
mainString = stringResource(
id = R.string.no_email_go_back_to_edit_your_email_address,
),
highlights = listOf(stringResource(id = R.string.go_back)),
tag = TAG_URL,
)
ClickableText(
text = goBackAnnotatedString,
onClick = {
goBackAnnotatedString
.getStringAnnotations(TAG_URL, it, it)
.firstOrNull()?.let {
onChangeEmailClick()
}
},
modifier = Modifier.semantics {
role = Role.Button
onClick {
onChangeEmailClick()
true
}
},
)
Spacer(modifier = Modifier.height(32.dp))
val logInAnnotatedString = createAnnotatedString(
mainString = stringResource(
id = R.string.or_log_in_you_may_already_have_an_account,
),
highlights = listOf(stringResource(id = R.string.log_in)),
tag = TAG_URL,
)
ClickableText(
text = logInAnnotatedString,
onClick = {
logInAnnotatedString
.getStringAnnotations(TAG_URL, it, it)
.firstOrNull()?.let {
onLoginClick()
}
},
modifier = Modifier.semantics {
role = Role.Button
onClick {
onLoginClick()
true
}
},
)
}
}
}
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
private fun CheckEmailScreenPreview() { private fun CheckEmailScreenNewUi_preview() {
BitwardenTheme { BitwardenTheme {
CheckEmailContent( CheckEmailContent(
email = "email@fake.com", email = "email@fake.com",
@ -210,3 +348,16 @@ private fun CheckEmailScreenPreview() {
) )
} }
} }
@Preview(showBackground = true)
@Composable
private fun CheckEmailScreenLegacy_preview() {
BitwardenTheme {
CheckEmailLegacyContent(
email = "email@fake.com",
onOpenEmailAppClick = { },
onChangeEmailClick = { },
onLoginClick = {},
)
}
}

View file

@ -3,10 +3,14 @@ package com.x8bit.bitwarden.ui.auth.feature.checkemail
import android.os.Parcelable import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import javax.inject.Inject import javax.inject.Inject
@ -17,11 +21,13 @@ private const val KEY_STATE = "state"
*/ */
@HiltViewModel @HiltViewModel
class CheckEmailViewModel @Inject constructor( class CheckEmailViewModel @Inject constructor(
featureFlagManager: FeatureFlagManager,
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
) : BaseViewModel<CheckEmailState, CheckEmailEvent, CheckEmailAction>( ) : BaseViewModel<CheckEmailState, CheckEmailEvent, CheckEmailAction>(
initialState = savedStateHandle[KEY_STATE] initialState = savedStateHandle[KEY_STATE]
?: CheckEmailState( ?: CheckEmailState(
email = CheckEmailArgs(savedStateHandle).emailAddress, email = CheckEmailArgs(savedStateHandle).emailAddress,
showNewOnboardingUi = featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow),
), ),
) { ) {
init { init {
@ -29,6 +35,14 @@ class CheckEmailViewModel @Inject constructor(
stateFlow stateFlow
.onEach { savedStateHandle[KEY_STATE] = it } .onEach { savedStateHandle[KEY_STATE] = it }
.launchIn(viewModelScope) .launchIn(viewModelScope)
// Listen for changes on the onboarding feature flag.
featureFlagManager
.getFeatureFlagFlow(FlagKey.OnboardingFlow)
.map {
CheckEmailAction.Internal.OnboardingFeatureFlagUpdated(it)
}
.onEach(::sendAction)
.launchIn(viewModelScope)
} }
override fun handleAction(action: CheckEmailAction) { override fun handleAction(action: CheckEmailAction) {
@ -36,6 +50,23 @@ class CheckEmailViewModel @Inject constructor(
CheckEmailAction.BackClick -> handleBackClick() CheckEmailAction.BackClick -> handleBackClick()
CheckEmailAction.OpenEmailClick -> handleOpenEmailClick() CheckEmailAction.OpenEmailClick -> handleOpenEmailClick()
CheckEmailAction.ChangeEmailClick -> handleChangeEmailClick() CheckEmailAction.ChangeEmailClick -> handleChangeEmailClick()
is CheckEmailAction.Internal.OnboardingFeatureFlagUpdated -> {
handleOnboardingFeatureFlagUpdated(action)
}
CheckEmailAction.LoginClick -> handleLoginClick()
}
}
private fun handleLoginClick() {
sendEvent(CheckEmailEvent.NavigateBackToLanding)
}
private fun handleOnboardingFeatureFlagUpdated(
action: CheckEmailAction.Internal.OnboardingFeatureFlagUpdated,
) {
mutableStateFlow.update {
it.copy(showNewOnboardingUi = action.newValue)
} }
} }
@ -52,6 +83,7 @@ class CheckEmailViewModel @Inject constructor(
@Parcelize @Parcelize
data class CheckEmailState( data class CheckEmailState(
val email: String, val email: String,
val showNewOnboardingUi: Boolean,
) : Parcelable ) : Parcelable
/** /**
@ -68,6 +100,11 @@ sealed class CheckEmailEvent {
* Navigate to email app. * Navigate to email app.
*/ */
data object NavigateToEmailApp : CheckEmailEvent() data object NavigateToEmailApp : CheckEmailEvent()
/**
* Navigate back to Landing
*/
data object NavigateBackToLanding : CheckEmailEvent()
} }
/** /**
@ -88,4 +125,19 @@ sealed class CheckEmailAction {
* User clicked open email. * User clicked open email.
*/ */
data object OpenEmailClick : CheckEmailAction() data object OpenEmailClick : CheckEmailAction()
/**
* User clicked log in.
*/
data object LoginClick : CheckEmailAction()
/**
* Denotes an internal action.
*/
sealed class Internal : CheckEmailAction() {
/**
* Indicates updated value for onboarding feature flag.
*/
data class OnboardingFeatureFlagUpdated(val newValue: Boolean) : Internal()
}
} }

View file

@ -0,0 +1,37 @@
package com.x8bit.bitwarden.ui.auth.feature.checkemail.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.auth.feature.checkemail.CheckEmailAction
import com.x8bit.bitwarden.ui.auth.feature.checkemail.CheckEmailViewModel
/**
* Handler for [CheckEmailScreen] actions.
*/
class CheckEmailHandler(
val onOpenEmailAppClick: () -> Unit,
val onChangeEmailClick: () -> Unit,
val onBackClick: () -> Unit,
val onLoginClick: () -> Unit,
) {
companion object {
/**
* Create [CheckEmailHandler] with the given [viewModel] to send actions to.
*/
fun create(viewModel: CheckEmailViewModel) = CheckEmailHandler(
onChangeEmailClick = { viewModel.trySendAction(CheckEmailAction.ChangeEmailClick) },
onOpenEmailAppClick = { viewModel.trySendAction(CheckEmailAction.OpenEmailClick) },
onLoginClick = { viewModel.trySendAction(CheckEmailAction.LoginClick) },
onBackClick = { viewModel.trySendAction(CheckEmailAction.BackClick) },
)
}
}
/**
* Remember [CheckEmailHandler] with the given [viewModel] within a [Composable] scope.
*/
@Composable
fun rememberCheckEmailHandler(viewModel: CheckEmailViewModel) =
remember(viewModel) {
CheckEmailHandler.create(viewModel)
}

View file

@ -194,6 +194,7 @@ fun StartRegistrationScreen(
nameInput = state.nameInput, nameInput = state.nameInput,
isReceiveMarketingEmailsToggled = state.isReceiveMarketingEmailsToggled, isReceiveMarketingEmailsToggled = state.isReceiveMarketingEmailsToggled,
isContinueButtonEnabled = state.isContinueButtonEnabled, isContinueButtonEnabled = state.isContinueButtonEnabled,
isNewOnboardingUiEnabled = state.showNewOnboardingUi,
handler = handler, handler = handler,
) )
Spacer(modifier = Modifier.navigationBarsPadding()) Spacer(modifier = Modifier.navigationBarsPadding())
@ -210,10 +211,12 @@ private fun StartRegistrationContent(
isReceiveMarketingEmailsToggled: Boolean, isReceiveMarketingEmailsToggled: Boolean,
isContinueButtonEnabled: Boolean, isContinueButtonEnabled: Boolean,
handler: StartRegistrationHandler, handler: StartRegistrationHandler,
isNewOnboardingUiEnabled: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
if (isNewOnboardingUiEnabled) {
Image( Image(
painter = rememberVectorPainter(id = R.drawable.vault), painter = rememberVectorPainter(id = R.drawable.vault),
contentDescription = null, contentDescription = null,
@ -222,6 +225,7 @@ private fun StartRegistrationContent(
.align(Alignment.CenterHorizontally), .align(Alignment.CenterHorizontally),
) )
Spacer(modifier = Modifier.height(48.dp)) Spacer(modifier = Modifier.height(48.dp))
}
BitwardenTextField( BitwardenTextField(
label = stringResource(id = R.string.name), label = stringResource(id = R.string.name),
value = nameInput, value = nameInput,
@ -233,13 +237,9 @@ private fun StartRegistrationContent(
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
BitwardenTextField( BitwardenTextField(
label = if (emailInput.isEmpty()) { label = stringResource(
stringResource(R.string.email_address_required)
} else {
stringResource(
id = R.string.email_address, id = R.string.email_address,
) ),
},
placeholder = stringResource(R.string.email_address_required), placeholder = stringResource(R.string.email_address_required),
value = emailInput, value = emailInput,
onValueChange = handler.onEmailInputChange, onValueChange = handler.onEmailInputChange,
@ -263,6 +263,7 @@ private fun StartRegistrationContent(
modifier = Modifier modifier = Modifier
.testTag("RegionSelectorDropdown"), .testTag("RegionSelectorDropdown"),
) )
if (isNewOnboardingUiEnabled) {
IconButton( IconButton(
onClick = handler.onServerGeologyHelpClick, onClick = handler.onServerGeologyHelpClick,
// Align with design but keep accessible touch target of IconButton. // Align with design but keep accessible touch target of IconButton.
@ -276,6 +277,7 @@ private fun StartRegistrationContent(
) )
} }
} }
}
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
if (selectedEnvironmentType != Environment.Type.SELF_HOSTED) { if (selectedEnvironmentType != Environment.Type.SELF_HOSTED) {
@ -474,7 +476,7 @@ private fun ReceiveMarketingEmailsSwitch(
@PreviewScreenSizes @PreviewScreenSizes
@Composable @Composable
private fun StartRegistrationContentPreview_filledout() { private fun StartRegistrationContentFilledOut_preview() {
BitwardenTheme { BitwardenTheme {
StartRegistrationContent( StartRegistrationContent(
emailInput = "e@mail.com", emailInput = "e@mail.com",
@ -482,6 +484,7 @@ private fun StartRegistrationContentPreview_filledout() {
nameInput = "Test User", nameInput = "Test User",
isReceiveMarketingEmailsToggled = true, isReceiveMarketingEmailsToggled = true,
isContinueButtonEnabled = true, isContinueButtonEnabled = true,
isNewOnboardingUiEnabled = false,
handler = StartRegistrationHandler( handler = StartRegistrationHandler(
onEmailInputChange = {}, onEmailInputChange = {},
onNameInputChange = {}, onNameInputChange = {},
@ -500,7 +503,7 @@ private fun StartRegistrationContentPreview_filledout() {
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
private fun StartRegistrationContentPreview_empty() { private fun StartRegistrationContentEmpty_preview() {
BitwardenTheme { BitwardenTheme {
StartRegistrationContent( StartRegistrationContent(
emailInput = "", emailInput = "",
@ -508,6 +511,34 @@ private fun StartRegistrationContentPreview_empty() {
nameInput = "", nameInput = "",
isReceiveMarketingEmailsToggled = false, isReceiveMarketingEmailsToggled = false,
isContinueButtonEnabled = false, isContinueButtonEnabled = false,
isNewOnboardingUiEnabled = false,
handler = StartRegistrationHandler(
onEmailInputChange = {},
onNameInputChange = {},
onEnvironmentTypeSelect = {},
onContinueClick = {},
onTermsClick = {},
onPrivacyPolicyClick = {},
onReceiveMarketingEmailsToggle = {},
onUnsubscribeMarketingEmailsClick = {},
onServerGeologyHelpClick = {},
onBackClick = {},
),
)
}
}
@Preview(showBackground = true)
@Composable
private fun StartRegistrationContentNewOnboardingUi_preview() {
BitwardenTheme {
StartRegistrationContent(
emailInput = "",
selectedEnvironmentType = Environment.Type.US,
nameInput = "",
isReceiveMarketingEmailsToggled = false,
isContinueButtonEnabled = false,
isNewOnboardingUiEnabled = true,
handler = StartRegistrationHandler( handler = StartRegistrationHandler(
onEmailInputChange = {}, onEmailInputChange = {},
onNameInputChange = {}, onNameInputChange = {},

View file

@ -7,6 +7,8 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.repository.model.Environment.Type import com.x8bit.bitwarden.data.platform.repository.model.Environment.Type
@ -15,6 +17,7 @@ import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAc
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.EmailInputChange import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.EmailInputChange
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.EnvironmentTypeSelect import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.EnvironmentTypeSelect
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.ErrorDialogDismiss import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.ErrorDialogDismiss
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.Internal.OnboardingFeatureFlagUpdated
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.Internal.ReceiveSendVerificationEmailResult import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.Internal.ReceiveSendVerificationEmailResult
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.Internal.UpdatedEnvironmentReceive import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.Internal.UpdatedEnvironmentReceive
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.NameInputChange import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.NameInputChange
@ -29,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.isValidEmail
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -44,6 +48,7 @@ private const val KEY_STATE = "state"
@HiltViewModel @HiltViewModel
class StartRegistrationViewModel @Inject constructor( class StartRegistrationViewModel @Inject constructor(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
featureFlagManager: FeatureFlagManager,
private val authRepository: AuthRepository, private val authRepository: AuthRepository,
private val environmentRepository: EnvironmentRepository, private val environmentRepository: EnvironmentRepository,
) : BaseViewModel<StartRegistrationState, StartRegistrationEvent, StartRegistrationAction>( ) : BaseViewModel<StartRegistrationState, StartRegistrationEvent, StartRegistrationAction>(
@ -55,6 +60,7 @@ class StartRegistrationViewModel @Inject constructor(
isContinueButtonEnabled = false, isContinueButtonEnabled = false,
selectedEnvironmentType = environmentRepository.environment.type, selectedEnvironmentType = environmentRepository.environment.type,
dialog = null, dialog = null,
showNewOnboardingUi = featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow),
), ),
) { ) {
@ -73,6 +79,14 @@ class StartRegistrationViewModel @Inject constructor(
) )
} }
.launchIn(viewModelScope) .launchIn(viewModelScope)
// Listen for changes on the onboarding feature flag.
featureFlagManager
.getFeatureFlagFlow(FlagKey.OnboardingFlow)
.map {
OnboardingFeatureFlagUpdated(it)
}
.onEach(::sendAction)
.launchIn(viewModelScope)
} }
override fun handleAction(action: StartRegistrationAction) { override fun handleAction(action: StartRegistrationAction) {
@ -99,6 +113,13 @@ class StartRegistrationViewModel @Inject constructor(
} }
ServerGeologyHelpClick -> handleServerGeologyHelpClick() ServerGeologyHelpClick -> handleServerGeologyHelpClick()
is OnboardingFeatureFlagUpdated -> handleOnboardingFeatureFlagUpdated(action)
}
}
private fun handleOnboardingFeatureFlagUpdated(action: OnboardingFeatureFlagUpdated) {
mutableStateFlow.update {
it.copy(showNewOnboardingUi = action.newValue)
} }
} }
@ -269,6 +290,7 @@ data class StartRegistrationState(
val isContinueButtonEnabled: Boolean, val isContinueButtonEnabled: Boolean,
val selectedEnvironmentType: Type, val selectedEnvironmentType: Type,
val dialog: StartRegistrationDialog?, val dialog: StartRegistrationDialog?,
val showNewOnboardingUi: Boolean,
) : Parcelable ) : Parcelable
/** /**
@ -422,5 +444,10 @@ sealed class StartRegistrationAction {
data class UpdatedEnvironmentReceive( data class UpdatedEnvironmentReceive(
val environment: Environment, val environment: Environment,
) : Internal() ) : Internal()
/**
* Indicates updated value for onboarding feature flag.
*/
data class OnboardingFeatureFlagUpdated(val newValue: Boolean) : Internal()
} }
} }

View file

@ -1,9 +1,11 @@
package com.x8bit.bitwarden.ui.auth.feature.checkemail package com.x8bit.bitwarden.ui.auth.feature.checkemail
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performSemanticsAction
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
@ -12,8 +14,8 @@ import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.runs import io.mockk.runs
import io.mockk.verify import io.mockk.verify
import junit.framework.TestCase
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -22,6 +24,7 @@ class CheckEmailScreenTest : BaseComposeTest() {
every { startDefaultEmailApplication() } just runs every { startDefaultEmailApplication() } just runs
} }
private var onNavigateBackCalled = false private var onNavigateBackCalled = false
private var onNavigateToLandingCalled = false
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
private val mutableEventFlow = bufferedMutableSharedFlow<CheckEmailEvent>() private val mutableEventFlow = bufferedMutableSharedFlow<CheckEmailEvent>()
@ -35,6 +38,7 @@ class CheckEmailScreenTest : BaseComposeTest() {
composeTestRule.setContent { composeTestRule.setContent {
CheckEmailScreen( CheckEmailScreen(
onNavigateBack = { onNavigateBackCalled = true }, onNavigateBack = { onNavigateBackCalled = true },
onNavigateBackToLanding = { onNavigateToLandingCalled = true },
viewModel = viewModel, viewModel = viewModel,
intentManager = intentManager, intentManager = intentManager,
) )
@ -63,10 +67,16 @@ class CheckEmailScreenTest : BaseComposeTest() {
} }
} }
@Test
fun `login button click should send LoginTap action`() {
mutableEventFlow.tryEmit(CheckEmailEvent.NavigateBackToLanding)
assertTrue(onNavigateToLandingCalled)
}
@Test @Test
fun `NavigateBack should call onNavigateBack`() { fun `NavigateBack should call onNavigateBack`() {
mutableEventFlow.tryEmit(CheckEmailEvent.NavigateBack) mutableEventFlow.tryEmit(CheckEmailEvent.NavigateBack)
TestCase.assertTrue(onNavigateBackCalled) assertTrue(onNavigateBackCalled)
} }
@Test @Test
@ -77,8 +87,31 @@ class CheckEmailScreenTest : BaseComposeTest() {
} }
} }
@Test
fun `go back and update email text click should send ChangeEmailClick action`() {
mutableStateFlow.value = DEFAULT_STATE.copy(showNewOnboardingUi = false)
composeTestRule
.onNodeWithText("No email? Go back to edit your email address.")
.performScrollTo()
.performSemanticsAction(SemanticsActions.OnClick)
verify { viewModel.trySendAction(CheckEmailAction.ChangeEmailClick) }
}
@Test
fun `already have account text click should send ChangeEmailClick action`() {
mutableStateFlow.value = DEFAULT_STATE.copy(showNewOnboardingUi = false)
composeTestRule
.onNodeWithText("Or log in, you may already have an account.")
.performScrollTo()
.performSemanticsAction(SemanticsActions.OnClick)
verify { viewModel.trySendAction(CheckEmailAction.LoginClick) }
}
@Test @Test
fun `change email button click should send ChangeEmailClick action`() { fun `change email button click should send ChangeEmailClick action`() {
mutableStateFlow.value = DEFAULT_STATE.copy(showNewOnboardingUi = true)
composeTestRule composeTestRule
.onNodeWithText("Change email address") .onNodeWithText("Change email address")
.performScrollTo() .performScrollTo()
@ -91,6 +124,7 @@ class CheckEmailScreenTest : BaseComposeTest() {
private const val EMAIL = "test@gmail.com" private const val EMAIL = "test@gmail.com"
private val DEFAULT_STATE = CheckEmailState( private val DEFAULT_STATE = CheckEmailState(
email = EMAIL, email = EMAIL,
showNewOnboardingUi = false,
) )
} }
} }

View file

@ -2,12 +2,23 @@ package com.x8bit.bitwarden.ui.auth.feature.checkemail
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test import app.cash.turbine.test
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class CheckEmailViewModelTest : BaseViewModelTest() { class CheckEmailViewModelTest : BaseViewModelTest() {
private val mutableFeatureFlagFlow = MutableStateFlow(false)
private val featureFlagManager = mockk<FeatureFlagManager>(relaxed = true) {
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
every { getFeatureFlagFlow(FlagKey.OnboardingFlow) } returns mutableFeatureFlagFlow
}
@Test @Test
fun `initial state should be correct`() = runTest { fun `initial state should be correct`() = runTest {
val viewModel = createViewModel() val viewModel = createViewModel()
@ -63,8 +74,31 @@ class CheckEmailViewModelTest : BaseViewModelTest() {
} }
} }
@Test
fun `OnboardingFeatureFlagUpdated should update showNewOnboardingUi in state`() {
val viewModel = createViewModel()
mutableFeatureFlagFlow.value = true
val expectedState = DEFAULT_STATE.copy(
showNewOnboardingUi = true,
)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@Test
fun `OnLoginClick action should send NavigateToLanding event`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(CheckEmailAction.LoginClick)
assertEquals(
CheckEmailEvent.NavigateBackToLanding,
awaitItem(),
)
}
}
private fun createViewModel(state: CheckEmailState? = null): CheckEmailViewModel = private fun createViewModel(state: CheckEmailState? = null): CheckEmailViewModel =
CheckEmailViewModel( CheckEmailViewModel(
featureFlagManager = featureFlagManager,
savedStateHandle = SavedStateHandle().also { savedStateHandle = SavedStateHandle().also {
it["email"] = EMAIL it["email"] = EMAIL
it["state"] = state it["state"] = state
@ -75,6 +109,7 @@ class CheckEmailViewModelTest : BaseViewModelTest() {
private const val EMAIL = "test@gmail.com" private const val EMAIL = "test@gmail.com"
private val DEFAULT_STATE = CheckEmailState( private val DEFAULT_STATE = CheckEmailState(
email = EMAIL, email = EMAIL,
showNewOnboardingUi = false,
) )
} }
} }

View file

@ -134,7 +134,7 @@ class StartRegistrationScreenTest : BaseComposeTest() {
@Test @Test
fun `email input change should send EmailInputChange action`() { fun `email input change should send EmailInputChange action`() {
composeTestRule.onNodeWithText("Email address (required)").performTextInput(TEST_INPUT) composeTestRule.onNodeWithText("Email address").performTextInput(TEST_INPUT)
verify { viewModel.trySendAction(EmailInputChange(TEST_INPUT)) } verify { viewModel.trySendAction(EmailInputChange(TEST_INPUT)) }
} }
@ -180,6 +180,7 @@ class StartRegistrationScreenTest : BaseComposeTest() {
@Test @Test
fun `clicking the server tool tip should send ServerGeologyHelpClickAction`() { fun `clicking the server tool tip should send ServerGeologyHelpClickAction`() {
mutableStateFlow.value = DEFAULT_STATE.copy(showNewOnboardingUi = true)
composeTestRule composeTestRule
.onNodeWithContentDescription("Help with server geolocations.") .onNodeWithContentDescription("Help with server geolocations.")
.performScrollTo() .performScrollTo()
@ -188,6 +189,14 @@ class StartRegistrationScreenTest : BaseComposeTest() {
verify { viewModel.trySendAction(StartRegistrationAction.ServerGeologyHelpClick) } verify { viewModel.trySendAction(StartRegistrationAction.ServerGeologyHelpClick) }
} }
@Test
fun `server tool tip should not exist if not in new onboarding ui`() {
mutableStateFlow.value = DEFAULT_STATE.copy(showNewOnboardingUi = false)
composeTestRule
.onNodeWithContentDescription("Help with server geolocations.")
.assertDoesNotExist()
}
@Test @Test
fun `when NavigateToServerSelectionInfo is observed event should invoke intent manager`() { fun `when NavigateToServerSelectionInfo is observed event should invoke intent manager`() {
mutableEventFlow.tryEmit(StartRegistrationEvent.NavigateToServerSelectionInfo) mutableEventFlow.tryEmit(StartRegistrationEvent.NavigateToServerSelectionInfo)
@ -279,6 +288,7 @@ class StartRegistrationScreenTest : BaseComposeTest() {
isContinueButtonEnabled = false, isContinueButtonEnabled = false,
selectedEnvironmentType = Environment.Type.US, selectedEnvironmentType = Environment.Type.US,
dialog = null, dialog = null,
showNewOnboardingUi = false,
) )
} }
} }

View file

@ -8,6 +8,8 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.BackClick import com.x8bit.bitwarden.ui.auth.feature.startregistration.StartRegistrationAction.BackClick
@ -33,6 +35,7 @@ import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkStatic import io.mockk.unmockkStatic
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
@ -42,7 +45,11 @@ import org.junit.jupiter.api.Test
@Suppress("LargeClass") @Suppress("LargeClass")
class StartRegistrationViewModelTest : BaseViewModelTest() { class StartRegistrationViewModelTest : BaseViewModelTest() {
private val mutableFeatureFlagFlow = MutableStateFlow(false)
private val featureFlagManager = mockk<FeatureFlagManager>(relaxed = true) {
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
every { getFeatureFlagFlow(FlagKey.OnboardingFlow) } returns mutableFeatureFlagFlow
}
/** /**
* Saved state handle that has valid inputs. Useful for tests that want to test things * Saved state handle that has valid inputs. Useful for tests that want to test things
* after the user has entered all valid inputs. * after the user has entered all valid inputs.
@ -71,6 +78,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
} }
@ -84,12 +92,14 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
isContinueButtonEnabled = false, isContinueButtonEnabled = false,
selectedEnvironmentType = Environment.Type.US, selectedEnvironmentType = Environment.Type.US,
dialog = null, dialog = null,
showNewOnboardingUi = false,
) )
val handle = SavedStateHandle(mapOf("state" to savedState)) val handle = SavedStateHandle(mapOf("state" to savedState))
val viewModel = StartRegistrationViewModel( val viewModel = StartRegistrationViewModel(
savedStateHandle = handle, savedStateHandle = handle,
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
assertEquals(savedState, viewModel.stateFlow.value) assertEquals(savedState, viewModel.stateFlow.value)
} }
@ -100,6 +110,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
val input = "a" val input = "a"
viewModel.trySendAction(EmailInputChange(input)) viewModel.trySendAction(EmailInputChange(input))
@ -125,6 +136,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
val input = " " val input = " "
viewModel.trySendAction(EmailInputChange(input)) viewModel.trySendAction(EmailInputChange(input))
@ -162,6 +174,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = validInputHandle, savedStateHandle = validInputHandle,
authRepository = repo, authRepository = repo,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
turbineScope { turbineScope {
val stateFlow = viewModel.stateFlow.testIn(backgroundScope) val stateFlow = viewModel.stateFlow.testIn(backgroundScope)
@ -197,6 +210,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = validInputHandle, savedStateHandle = validInputHandle,
authRepository = repo, authRepository = repo,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.stateFlow.test { viewModel.stateFlow.test {
assertEquals(VALID_INPUT_STATE, awaitItem()) assertEquals(VALID_INPUT_STATE, awaitItem())
@ -243,6 +257,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = validInputHandle, savedStateHandle = validInputHandle,
authRepository = repo, authRepository = repo,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(ContinueClick) viewModel.trySendAction(ContinueClick)
@ -280,6 +295,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = validInputHandle, savedStateHandle = validInputHandle,
authRepository = repo, authRepository = repo,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(ContinueClick) viewModel.trySendAction(ContinueClick)
@ -298,6 +314,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(BackClick) viewModel.trySendAction(BackClick)
@ -311,6 +328,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(PrivacyPolicyClick) viewModel.trySendAction(PrivacyPolicyClick)
@ -324,6 +342,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(TermsClick) viewModel.trySendAction(TermsClick)
@ -337,6 +356,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(UnsubscribeMarketingEmailsClick) viewModel.trySendAction(UnsubscribeMarketingEmailsClick)
@ -351,6 +371,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.stateFlow.test { viewModel.stateFlow.test {
awaitItem() awaitItem()
@ -373,6 +394,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction( viewModel.trySendAction(
@ -393,6 +415,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.trySendAction(EmailInputChange("input")) viewModel.trySendAction(EmailInputChange("input"))
viewModel.stateFlow.test { viewModel.stateFlow.test {
@ -412,6 +435,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.trySendAction(NameInputChange("input")) viewModel.trySendAction(NameInputChange("input"))
viewModel.stateFlow.test { viewModel.stateFlow.test {
@ -425,6 +449,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.trySendAction(ReceiveMarketingEmailsToggle(false)) viewModel.trySendAction(ReceiveMarketingEmailsToggle(false))
viewModel.stateFlow.test { viewModel.stateFlow.test {
@ -438,6 +463,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository, authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository, environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
@ -446,6 +472,21 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
} }
} }
@Test
fun `OnboardingFeatureFlagUpdated should update showNewOnboardingUi in state`() {
val viewModel = StartRegistrationViewModel(
savedStateHandle = SavedStateHandle(),
authRepository = mockAuthRepository,
environmentRepository = fakeEnvironmentRepository,
featureFlagManager = featureFlagManager,
)
mutableFeatureFlagFlow.value = true
assertEquals(
DEFAULT_STATE.copy(showNewOnboardingUi = true),
viewModel.stateFlow.value,
)
}
companion object { companion object {
private const val EMAIL = "test@test.com" private const val EMAIL = "test@test.com"
private const val NAME = "name" private const val NAME = "name"
@ -456,6 +497,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
isContinueButtonEnabled = false, isContinueButtonEnabled = false,
selectedEnvironmentType = Environment.Type.US, selectedEnvironmentType = Environment.Type.US,
dialog = null, dialog = null,
showNewOnboardingUi = false,
) )
private val VALID_INPUT_STATE = StartRegistrationState( private val VALID_INPUT_STATE = StartRegistrationState(
emailInput = EMAIL, emailInput = EMAIL,
@ -464,6 +506,7 @@ class StartRegistrationViewModelTest : BaseViewModelTest() {
isContinueButtonEnabled = true, isContinueButtonEnabled = true,
selectedEnvironmentType = Environment.Type.US, selectedEnvironmentType = Environment.Type.US,
dialog = null, dialog = null,
showNewOnboardingUi = false,
) )
} }
} }