mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1081: Add log out ability to Vault Unlock screen (#285)
This commit is contained in:
parent
107ee1c08c
commit
04609b9a86
6 changed files with 138 additions and 20 deletions
|
@ -38,11 +38,14 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLogoutConfirmationDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
|
@ -76,6 +79,7 @@ fun VaultUnlockScreen(
|
|||
canScroll = { !accountMenuVisible },
|
||||
)
|
||||
|
||||
// Dynamic dialogs
|
||||
when (val dialog = state.dialog) {
|
||||
is VaultUnlockState.VaultUnlockDialog.Error -> BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
|
@ -94,6 +98,23 @@ fun VaultUnlockScreen(
|
|||
null -> Unit
|
||||
}
|
||||
|
||||
// Static dialogs
|
||||
var showLogoutConfirmationDialog by remember { mutableStateOf(false) }
|
||||
if (showLogoutConfirmationDialog) {
|
||||
BitwardenLogoutConfirmationDialog(
|
||||
onDismissRequest = { showLogoutConfirmationDialog = false },
|
||||
onConfirmClick = remember(viewModel) {
|
||||
{
|
||||
showLogoutConfirmationDialog = false
|
||||
viewModel.trySendAction(
|
||||
VaultUnlockAction.ConfirmLogoutClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Content
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
@ -109,7 +130,14 @@ fun VaultUnlockScreen(
|
|||
color = state.avatarColor,
|
||||
onClick = { accountMenuVisible = !accountMenuVisible },
|
||||
)
|
||||
BitwardenOverflowActionItem()
|
||||
BitwardenOverflowActionItem(
|
||||
menuItemDataList = persistentListOf(
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.log_out),
|
||||
onClick = { showLogoutConfirmationDialog = true },
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
|
@ -71,6 +71,7 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
when (action) {
|
||||
VaultUnlockAction.AddAccountClick -> handleAddAccountClick()
|
||||
VaultUnlockAction.DismissDialog -> handleDismissDialog()
|
||||
VaultUnlockAction.ConfirmLogoutClick -> handleConfirmLogoutClick()
|
||||
is VaultUnlockAction.PasswordInputChanged -> handlePasswordInputChanged(action)
|
||||
is VaultUnlockAction.SwitchAccountClick -> handleSwitchAccountClick(action)
|
||||
VaultUnlockAction.UnlockClick -> handleUnlockClick()
|
||||
|
@ -88,6 +89,10 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
mutableStateFlow.update { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
private fun handleConfirmLogoutClick() {
|
||||
authRepository.logout()
|
||||
}
|
||||
|
||||
private fun handlePasswordInputChanged(action: VaultUnlockAction.PasswordInputChanged) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(passwordInput = action.passwordInput)
|
||||
|
@ -213,6 +218,11 @@ sealed class VaultUnlockAction {
|
|||
*/
|
||||
data object DismissDialog : VaultUnlockAction()
|
||||
|
||||
/**
|
||||
* The user has clicked on the logout confirmation button.
|
||||
*/
|
||||
data object ConfirmLogoutClick : VaultUnlockAction()
|
||||
|
||||
/**
|
||||
* The user has modified the password input.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.x8bit.bitwarden.R
|
||||
|
||||
/**
|
||||
* A reusable dialog for confirming whether or not the user wants to log out.
|
||||
*
|
||||
* @param onDismissRequest A callback for when the dialog is requesting dismissal.
|
||||
* @param onConfirmClick A callback for when the log out confirmation button is clicked.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenLogoutConfirmationDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirmClick: () -> Unit,
|
||||
) {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(id = R.string.log_out),
|
||||
message = stringResource(id = R.string.logout_confirmation),
|
||||
confirmButtonText = stringResource(id = R.string.yes),
|
||||
onConfirmClick = onConfirmClick,
|
||||
dismissButtonText = stringResource(id = R.string.cancel),
|
||||
onDismissClick = onDismissRequest,
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
}
|
|
@ -31,6 +31,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLogoutConfirmationDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenExternalLinkRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
|
@ -39,7 +40,6 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionRow
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||
|
@ -76,8 +76,8 @@ fun AccountSecurityScreen(
|
|||
}
|
||||
|
||||
when (state.dialog) {
|
||||
AccountSecurityDialog.ConfirmLogout -> ConfirmLogoutDialog(
|
||||
onDismiss = remember(viewModel) {
|
||||
AccountSecurityDialog.ConfirmLogout -> BitwardenLogoutConfirmationDialog(
|
||||
onDismissRequest = remember(viewModel) {
|
||||
{ viewModel.trySendAction(AccountSecurityAction.DismissDialog) }
|
||||
},
|
||||
onConfirmClick = remember(viewModel) {
|
||||
|
@ -286,22 +286,6 @@ fun AccountSecurityScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConfirmLogoutDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirmClick: () -> Unit,
|
||||
) {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(id = R.string.log_out),
|
||||
message = stringResource(id = R.string.logout_confirmation),
|
||||
confirmButtonText = stringResource(id = R.string.yes),
|
||||
onConfirmClick = onConfirmClick,
|
||||
dismissButtonText = stringResource(id = R.string.cancel),
|
||||
onDismissClick = onDismiss,
|
||||
onDismissRequest = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FingerPrintPhraseDialog(
|
||||
fingerprintPhrase: Text,
|
||||
|
|
|
@ -3,6 +3,12 @@ package com.x8bit.bitwarden.ui.auth.feature.vaultunlock
|
|||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.filterToOne
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.isPopup
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
|
@ -77,6 +83,58 @@ class VaultUnlockScreenTest : BaseComposeTest() {
|
|||
composeTestRule.onNodeWithText("Add account").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout click in the overflow menu should show the logout confirmation dialog`() {
|
||||
// Confirm neither the popup nor the dialog are showing
|
||||
composeTestRule.onNode(isPopup()).assertDoesNotExist()
|
||||
composeTestRule.onNode(isDialog()).assertDoesNotExist()
|
||||
|
||||
// Expand the overflow menu
|
||||
composeTestRule.onNodeWithContentDescription("More").performClick()
|
||||
composeTestRule.onNode(isPopup()).assertIsDisplayed()
|
||||
composeTestRule.onNode(isDialog()).assertDoesNotExist()
|
||||
|
||||
// Click on the logout item
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Log out")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.performClick()
|
||||
|
||||
// Check for the dialog
|
||||
composeTestRule
|
||||
.onNode(isDialog())
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Log out")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Are you sure you want to log out?")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Yes click in the logout confirmation dialog should send the ConfirmLogoutClick action`() {
|
||||
// Expand the overflow menu
|
||||
composeTestRule.onNodeWithContentDescription("More").performClick()
|
||||
|
||||
// Click on the logout item to display the dialog
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Log out")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.performClick()
|
||||
composeTestRule.onNode(isDialog()).assertIsDisplayed()
|
||||
|
||||
// Click on the Yes button in the dialog
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Yes")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify { viewModel.trySendAction(VaultUnlockAction.ConfirmLogoutClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email state change should update logged in as text`() {
|
||||
val newEmail = "david@bitwarden.com"
|
||||
|
|
|
@ -17,7 +17,10 @@ import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
|||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
@ -28,6 +31,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
private val environmentRepository = FakeEnvironmentRepository()
|
||||
private val authRepository = mockk<AuthRepository>() {
|
||||
every { userStateFlow } returns MutableStateFlow(DEFAULT_USER_STATE)
|
||||
every { logout() } just runs
|
||||
}
|
||||
private val vaultRepository = mockk<VaultRepository>()
|
||||
|
||||
|
@ -80,6 +84,13 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ConfirmLogoutClick should call logout on the AuthRepository`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultUnlockAction.ConfirmLogoutClick)
|
||||
verify { authRepository.logout() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on PasswordInputChanged should update the password input state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
|
Loading…
Reference in a new issue