mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 19:28:44 +03:00
PM-10620 prevent account lockout tips screen (#3711)
This commit is contained in:
parent
5e643e11fd
commit
2b13151bd1
8 changed files with 338 additions and 2 deletions
|
@ -23,6 +23,7 @@ import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.navigateToLoginWithDe
|
|||
import com.x8bit.bitwarden.ui.auth.feature.masterpasswordguidance.masterPasswordGuidanceDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.masterPasswordHintDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.navigateToMasterPasswordHint
|
||||
import com.x8bit.bitwarden.ui.auth.feature.preventaccountlockout.preventAccountLockoutDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.navigateToSetPassword
|
||||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.setPasswordDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.navigateToTwoFactorLogin
|
||||
|
@ -130,6 +131,9 @@ fun NavGraphBuilder.authGraph(
|
|||
// TODO [PM-10619](https://bitwarden.atlassian.net/browse/PM-10619)
|
||||
},
|
||||
)
|
||||
preventAccountLockoutDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,6 @@ fun MasterPasswordGuidanceScreen(
|
|||
onNavigateToGeneratePassword: () -> Unit,
|
||||
viewModel: MasterPasswordGuidanceViewModel = hiltViewModel(),
|
||||
) {
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
MasterPasswordGuidanceEvent.NavigateBack -> onNavigateBack()
|
||||
|
@ -118,7 +117,7 @@ fun MasterPasswordGuidanceScreen(
|
|||
),
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.preventaccountlockout
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
|
||||
private const val PREVENT_ACCOUNT_LOCKOUT = "prevent_account_lockout"
|
||||
|
||||
/**
|
||||
* Navigate to prevent account lockout screen.
|
||||
*/
|
||||
fun NavController.navigateToPreventAccountLockout(navOptions: NavOptions? = null) {
|
||||
this.navigate(PREVENT_ACCOUNT_LOCKOUT, navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the prevent account lockout screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.preventAccountLockoutDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = PREVENT_ACCOUNT_LOCKOUT,
|
||||
) {
|
||||
PreventAccountLockoutScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.preventaccountlockout
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
* Top level screen component for the prevent account lockout info screen.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun PreventAccountLockoutScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
viewModel: PreventAccountLockoutViewModel = hiltViewModel(),
|
||||
) {
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
PreventAccountLockoutEvent.NavigateBack -> onNavigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(R.string.prevent_account_lockout),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = rememberVectorPainter(id = R.drawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||
onNavigationIconClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(PreventAccountLockoutAction.CloseClickAction)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
NeverLoseAccessContent()
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NeverLoseAccessContent(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(size = 4.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerLowest),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.never_lose_access_to_your_vault),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.the_best_way_to_make_sure_you_can_always_access_your_account,
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
AccountRecoveryTipRow(
|
||||
title = stringResource(R.string.create_a_hint),
|
||||
description = stringResource(
|
||||
R.string.your_hint_will_be_send_to_you_via_email_when_you_request_it,
|
||||
),
|
||||
icon = rememberVectorPainter(id = R.drawable.ic_light_bulb),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outline)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
AccountRecoveryTipRow(
|
||||
title = stringResource(R.string.write_your_password_down),
|
||||
description = stringResource(R.string.keep_it_secret_keep_it_safe),
|
||||
icon = rememberVectorPainter(id = R.drawable.ic_edit),
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountRecoveryTipRow(
|
||||
title: String,
|
||||
description: String,
|
||||
icon: Painter,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.clearAndSetSemantics { },
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = description,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreventAccountLockoutScreenPreview() {
|
||||
BitwardenTheme {
|
||||
PreventAccountLockoutScreen(onNavigateBack = {})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.preventaccountlockout
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* ViewModel for the [PreventAccountLockoutScreen].
|
||||
*/
|
||||
@HiltViewModel
|
||||
class PreventAccountLockoutViewModel @Inject constructor() :
|
||||
BaseViewModel<Unit, PreventAccountLockoutEvent, PreventAccountLockoutAction>(
|
||||
initialState = Unit,
|
||||
) {
|
||||
|
||||
override fun handleAction(action: PreventAccountLockoutAction) {
|
||||
when (action) {
|
||||
PreventAccountLockoutAction.CloseClickAction -> handleCloseClickAction()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCloseClickAction() = sendEvent(PreventAccountLockoutEvent.NavigateBack)
|
||||
}
|
||||
|
||||
/**
|
||||
* Model events to send to the [PreventAccountLockoutScreen].
|
||||
*/
|
||||
sealed class PreventAccountLockoutEvent {
|
||||
|
||||
/**
|
||||
* Navigates to the previous screen.
|
||||
*/
|
||||
data object NavigateBack : PreventAccountLockoutEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Model actions to be handled in the [PreventAccountLockoutViewModel].
|
||||
*/
|
||||
sealed class PreventAccountLockoutAction {
|
||||
|
||||
/**
|
||||
* Close button has been clicked.
|
||||
*/
|
||||
data object CloseClickAction : PreventAccountLockoutAction()
|
||||
}
|
|
@ -952,4 +952,11 @@ Do you want to switch to this account?</string>
|
|||
<string name="you_can_return_to_complete_this_step_anytime_from_account_security_in_settings">You can return to complete this step anytime from Account Security in Settings.</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="set_up_biometrics_or_choose_a_pin_code_to_quickly_access_your_vault_and_autofill_your_logins">Set up biometrics or choose a PIN code to quickly access your vault and AutoFill your logins.</string>
|
||||
<string name="never_lose_access_to_your_vault">Never lose access to your vault</string>
|
||||
<string name="the_best_way_to_make_sure_you_can_always_access_your_account">The best way to make sure you can always access your account is to set up safeguards from the start.</string>
|
||||
<string name="create_a_hint">Create a hint</string>
|
||||
<string name="your_hint_will_be_send_to_you_via_email_when_you_request_it">Your hint will be sent to you via email when you request it.</string>
|
||||
<string name="write_your_password_down">Write your password down</string>
|
||||
<string name="keep_it_secret_keep_it_safe">Be careful to keep your written password somewhere secret and safe.</string>
|
||||
<string name="prevent_account_lockout">Prevent account lockout</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.preventaccountlockout
|
||||
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class PreventAccountLockoutScreenTest : BaseComposeTest() {
|
||||
private var onBackHasBeenInvoked = false
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<PreventAccountLockoutEvent>()
|
||||
private val viewModel = mockk<PreventAccountLockoutViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
PreventAccountLockoutScreen(
|
||||
onNavigateBack = { onBackHasBeenInvoked = true },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When navigation button is clicked CloseClickAction is sent`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Close")
|
||||
.performClick()
|
||||
|
||||
verify { viewModel.trySendAction(PreventAccountLockoutAction.CloseClickAction) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateBackEvent from ViewModel invokes onBackNavigation lambda`() {
|
||||
mutableEventFlow.tryEmit(PreventAccountLockoutEvent.NavigateBack)
|
||||
|
||||
assertTrue(onBackHasBeenInvoked)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.preventaccountlockout
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class PreventAccountLockoutViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `When handling CloseClickAction a NavigateBack event is emitted`() = runTest {
|
||||
val viewModel = PreventAccountLockoutViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(PreventAccountLockoutAction.CloseClickAction)
|
||||
assertEquals(PreventAccountLockoutEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue