mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
BIT-1031: Add shell for the delete account UI (#241)
This commit is contained in:
parent
028a2caa65
commit
715ff06dfc
18 changed files with 528 additions and 10 deletions
|
@ -0,0 +1,72 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
* Represents a Bitwarden-styled filled [Button] for error scenarios.
|
||||
*
|
||||
* @param label The label for the button.
|
||||
* @param onClick The callback when the button is clicked.
|
||||
* @param modifier The [Modifier] to be applied to the button.
|
||||
* @param isEnabled Whether or not the button is enabled.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenErrorButton(
|
||||
label: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier.semantics(mergeDescendants = true) {},
|
||||
enabled = isEnabled,
|
||||
contentPadding = PaddingValues(
|
||||
vertical = 10.dp,
|
||||
horizontal = 24.dp,
|
||||
),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.error,
|
||||
contentColor = MaterialTheme.colorScheme.onError,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenErrorButton_preview_isEnabled() {
|
||||
BitwardenTheme {
|
||||
BitwardenErrorButton(
|
||||
label = "Label",
|
||||
onClick = {},
|
||||
isEnabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BitwardenErrorButton_preview_isNotEnabled() {
|
||||
BitwardenTheme {
|
||||
BitwardenErrorButton(
|
||||
label = "Label",
|
||||
onClick = {},
|
||||
isEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Represents a Bitwarden-styled filled [OutlinedButton].
|
||||
*
|
||||
* @param label The label for the button.
|
||||
* @param onClick The callback when the button is clicked.
|
||||
* @param modifier The [Modifier] to be applied to the button.
|
||||
* @param isEnabled Whether or not the button is enabled.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenOutlinedButton(
|
||||
label: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.semantics(mergeDescendants = true) { },
|
||||
enabled = isEnabled,
|
||||
contentPadding = PaddingValues(
|
||||
vertical = 10.dp,
|
||||
horizontal = 24.dp,
|
||||
),
|
||||
colors = ButtonDefaults.outlinedButtonColors(),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenOutlinedButton_preview_isEnabled() {
|
||||
BitwardenOutlinedButton(
|
||||
label = "Label",
|
||||
onClick = {},
|
||||
isEnabled = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenOutlinedButton_preview_isNotEnabled() {
|
||||
BitwardenOutlinedButton(
|
||||
label = "Label",
|
||||
onClick = {},
|
||||
isEnabled = false,
|
||||
)
|
||||
}
|
|
@ -27,6 +27,7 @@ private const val SETTINGS_ROUTE: String = "settings"
|
|||
*/
|
||||
fun NavGraphBuilder.settingsGraph(
|
||||
navController: NavController,
|
||||
onNavigateToDeleteAccount: () -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = SETTINGS_ROUTE,
|
||||
|
@ -49,7 +50,10 @@ fun NavGraphBuilder.settingsGraph(
|
|||
)
|
||||
}
|
||||
aboutDestination(onNavigateBack = { navController.popBackStack() })
|
||||
accountSecurityDestination(onNavigateBack = { navController.popBackStack() })
|
||||
accountSecurityDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||
)
|
||||
appearanceDestination(onNavigateBack = { navController.popBackStack() })
|
||||
autoFillDestination(onNavigateBack = { navController.popBackStack() })
|
||||
otherDestination(onNavigateBack = { navController.popBackStack() })
|
||||
|
|
|
@ -13,6 +13,7 @@ private const val ACCOUNT_SECURITY_ROUTE = "settings_account_security"
|
|||
*/
|
||||
fun NavGraphBuilder.accountSecurityDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToDeleteAccount: () -> Unit,
|
||||
) {
|
||||
composable(
|
||||
route = ACCOUNT_SECURITY_ROUTE,
|
||||
|
@ -21,7 +22,10 @@ fun NavGraphBuilder.accountSecurityDestination(
|
|||
popEnterTransition = TransitionProviders.Enter.pushLeft,
|
||||
popExitTransition = TransitionProviders.Exit.pushRight,
|
||||
) {
|
||||
AccountSecurityScreen(onNavigateBack = onNavigateBack)
|
||||
AccountSecurityScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
|||
@Composable
|
||||
fun AccountSecurityScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToDeleteAccount: () -> Unit,
|
||||
viewModel: AccountSecurityViewModel = hiltViewModel(),
|
||||
intentHandler: IntentHandler = IntentHandler(context = LocalContext.current),
|
||||
) {
|
||||
|
@ -62,6 +63,8 @@ fun AccountSecurityScreen(
|
|||
when (event) {
|
||||
AccountSecurityEvent.NavigateBack -> onNavigateBack()
|
||||
|
||||
AccountSecurityEvent.NavigateToDeleteAccount -> onNavigateToDeleteAccount()
|
||||
|
||||
AccountSecurityEvent.NavigateToFingerprintPhrase -> {
|
||||
intentHandler.launchUri("http://bitwarden.com/help/fingerprint-phrase".toUri())
|
||||
}
|
||||
|
|
|
@ -87,8 +87,7 @@ class AccountSecurityViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleDeleteAccountClick() {
|
||||
// TODO BIT-1031: Navigate to delete account
|
||||
sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText()))
|
||||
sendEvent(AccountSecurityEvent.NavigateToDeleteAccount)
|
||||
}
|
||||
|
||||
private fun handleDismissDialog() {
|
||||
|
@ -215,6 +214,11 @@ sealed class AccountSecurityEvent {
|
|||
*/
|
||||
data object NavigateBack : AccountSecurityEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the delete account screen.
|
||||
*/
|
||||
data object NavigateToDeleteAccount : AccountSecurityEvent()
|
||||
|
||||
/**
|
||||
* Navigate to fingerprint phrase information.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import com.x8bit.bitwarden.ui.platform.theme.TransitionProviders
|
||||
|
||||
private const val DELETE_ACCOUNT_ROUTE = "delete_account"
|
||||
|
||||
/**
|
||||
* Add delete account destinations to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.deleteAccountDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composable(
|
||||
route = DELETE_ACCOUNT_ROUTE,
|
||||
enterTransition = TransitionProviders.Enter.slideUp,
|
||||
exitTransition = TransitionProviders.Exit.slideDown,
|
||||
popEnterTransition = TransitionProviders.Enter.slideUp,
|
||||
popExitTransition = TransitionProviders.Exit.slideDown,
|
||||
) {
|
||||
DeleteAccountScreen(onNavigateBack = onNavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the delete account screen.
|
||||
*/
|
||||
fun NavController.navigateToDeleteAccount(navOptions: NavOptions? = null) {
|
||||
navigate(DELETE_ACCOUNT_ROUTE, navOptions)
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
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.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.components.BitwardenErrorButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOutlinedButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
|
||||
/**
|
||||
* Displays the delete account screen.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DeleteAccountScreen(
|
||||
viewModel: DeleteAccountViewModel = hiltViewModel(),
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
DeleteAccountEvent.NavigateBack -> onNavigateBack()
|
||||
|
||||
is DeleteAccountEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message(resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = R.string.delete_account),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||
onNavigationIconClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(DeleteAccountAction.CloseClick) }
|
||||
},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_warning),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.deleting_your_account_is_permanent),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.delete_account_explanation),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BitwardenErrorButton(
|
||||
label = stringResource(id = R.string.delete_account),
|
||||
onClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(DeleteAccountAction.DeleteAccountClick) }
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(id = R.string.cancel),
|
||||
onClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(DeleteAccountAction.CancelClick) }
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount
|
||||
|
||||
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* View model for the account security screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class DeleteAccountViewModel @Inject constructor() :
|
||||
BaseViewModel<Unit, DeleteAccountEvent, DeleteAccountAction>(
|
||||
initialState = Unit,
|
||||
) {
|
||||
|
||||
override fun handleAction(action: DeleteAccountAction) {
|
||||
when (action) {
|
||||
DeleteAccountAction.CancelClick -> handleCancelClick()
|
||||
DeleteAccountAction.CloseClick -> handleCloseClick()
|
||||
DeleteAccountAction.DeleteAccountClick -> handleDeleteAccountClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCancelClick() {
|
||||
sendEvent(DeleteAccountEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleCloseClick() {
|
||||
sendEvent(DeleteAccountEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleDeleteAccountClick() {
|
||||
// TODO: Delete the users account (BIT-1111)
|
||||
sendEvent(DeleteAccountEvent.ShowToast("Not yet implemented.".asText()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models events for the delete account screen.
|
||||
*/
|
||||
sealed class DeleteAccountEvent {
|
||||
/**
|
||||
* Navigates back.
|
||||
*/
|
||||
data object NavigateBack : DeleteAccountEvent()
|
||||
|
||||
/**
|
||||
* Displays the [message] in a toast.
|
||||
*/
|
||||
data class ShowToast(
|
||||
val message: Text,
|
||||
) : DeleteAccountEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the delete account screen.
|
||||
*/
|
||||
sealed class DeleteAccountAction {
|
||||
/**
|
||||
* The user has clicked the cancel button.
|
||||
*/
|
||||
data object CancelClick : DeleteAccountAction()
|
||||
|
||||
/**
|
||||
* The user has clicked the close button.
|
||||
*/
|
||||
data object CloseClick : DeleteAccountAction()
|
||||
|
||||
/**
|
||||
* The user has clicked the delete account button.
|
||||
*/
|
||||
data object DeleteAccountClick : DeleteAccountAction()
|
||||
}
|
|
@ -4,6 +4,8 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navigation
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount.deleteAccountDestination
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount.navigateToDeleteAccount
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.VAULT_UNLOCKED_NAV_BAR_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.vaultUnlockedNavBarDestination
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.navigateToNewSend
|
||||
|
@ -33,7 +35,9 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
|||
vaultUnlockedNavBarDestination(
|
||||
onNavigateToVaultAddItem = { navController.navigateToVaultAddItem() },
|
||||
onNavigateToNewSend = { navController.navigateToNewSend() },
|
||||
onNavigateToDeleteAccount = { navController.navigateToDeleteAccount() },
|
||||
)
|
||||
deleteAccountDestination(onNavigateBack = { navController.popBackStack() })
|
||||
vaultAddItemDestination(onNavigateBack = { navController.popBackStack() })
|
||||
newSendDestination(onNavigateBack = { navController.popBackStack() })
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ fun NavController.navigateToVaultUnlockedNavBar(navOptions: NavOptions? = null)
|
|||
fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
||||
onNavigateToVaultAddItem: () -> Unit,
|
||||
onNavigateToNewSend: () -> Unit,
|
||||
onNavigateToDeleteAccount: () -> Unit,
|
||||
) {
|
||||
composable(
|
||||
route = VAULT_UNLOCKED_NAV_BAR_ROUTE,
|
||||
|
@ -35,6 +36,7 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
|||
VaultUnlockedNavBarScreen(
|
||||
onNavigateToVaultAddItem = onNavigateToVaultAddItem,
|
||||
onNavigateToNewSend = onNavigateToNewSend,
|
||||
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ fun VaultUnlockedNavBarScreen(
|
|||
navController: NavHostController = rememberNavController(),
|
||||
onNavigateToVaultAddItem: () -> Unit,
|
||||
onNavigateToNewSend: () -> Unit,
|
||||
onNavigateToDeleteAccount: () -> Unit,
|
||||
) {
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
navController.apply {
|
||||
|
@ -94,6 +95,7 @@ fun VaultUnlockedNavBarScreen(
|
|||
navController = navController,
|
||||
navigateToVaultAddItem = onNavigateToVaultAddItem,
|
||||
navigateToNewSend = onNavigateToNewSend,
|
||||
navigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||
generatorTabClickedAction = {
|
||||
viewModel.trySendAction(VaultUnlockedNavBarAction.GeneratorTabClick)
|
||||
},
|
||||
|
@ -122,6 +124,7 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
settingsTabClickedAction: () -> Unit,
|
||||
navigateToVaultAddItem: () -> Unit,
|
||||
navigateToNewSend: () -> Unit,
|
||||
navigateToDeleteAccount: () -> Unit,
|
||||
) {
|
||||
var shouldDimNavBar by remember { mutableStateOf(false) }
|
||||
|
||||
|
@ -181,7 +184,10 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
)
|
||||
sendGraph(onNavigateToNewSend = navigateToNewSend)
|
||||
generatorDestination()
|
||||
settingsGraph(navController)
|
||||
settingsGraph(
|
||||
navController = navController,
|
||||
onNavigateToDeleteAccount = navigateToDeleteAccount,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
20
app/src/main/res/drawable/ic_warning.xml
Normal file
20
app/src/main/res/drawable/ic_warning.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportHeight="28"
|
||||
android:viewportWidth="28">
|
||||
<group>
|
||||
<clip-path android:pathData="M0.667,0.667h26.667v26.667h-26.667z" />
|
||||
<path
|
||||
android:fillColor="#BA1A1A"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M16.036,3.434L26.957,20.361C28.069,22.083 26.646,24 24.921,24H3.079C1.354,24 -0.069,22.083 1.043,20.361L11.964,3.434C12.91,1.967 15.09,1.967 16.036,3.434ZM14.635,4.337C14.345,3.888 13.655,3.888 13.365,4.337L2.443,21.264C2.141,21.733 2.498,22.333 3.079,22.333H24.921C25.502,22.333 25.859,21.733 25.557,21.264L14.635,4.337Z" />
|
||||
<path
|
||||
android:fillColor="#BA1A1A"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M14.001,9.499C14.461,9.499 14.834,9.872 14.834,10.332V15.528C14.834,15.989 14.461,16.362 14.001,16.362C13.541,16.362 13.168,15.989 13.168,15.528V10.332C13.168,9.872 13.541,9.499 14.001,9.499Z" />
|
||||
<path
|
||||
android:fillColor="#BA1A1A"
|
||||
android:pathData="M15.126,19.785C15.126,20.406 14.622,20.91 14.001,20.91C13.38,20.91 12.876,20.406 12.876,19.785C12.876,19.163 13.38,18.66 14.001,18.66C14.622,18.66 15.126,19.163 15.126,19.785Z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -33,6 +33,7 @@ import org.junit.Test
|
|||
class AccountSecurityScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
private var onNavigateToDeleteAccountCalled = false
|
||||
|
||||
private val intentHandler = mockk<IntentHandler> {
|
||||
every { launchUri(any()) } just runs
|
||||
|
@ -51,6 +52,7 @@ class AccountSecurityScreenTest : BaseComposeTest() {
|
|||
composeTestRule.setContent {
|
||||
AccountSecurityScreen(
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
onNavigateToDeleteAccount = { onNavigateToDeleteAccountCalled = true },
|
||||
viewModel = viewModel,
|
||||
intentHandler = intentHandler,
|
||||
)
|
||||
|
@ -236,6 +238,12 @@ class AccountSecurityScreenTest : BaseComposeTest() {
|
|||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToDeleteAccount should call onNavigateToDeleteAccountCalled`() {
|
||||
mutableEventFlow.tryEmit(AccountSecurityEvent.NavigateToDeleteAccount)
|
||||
assertTrue(onNavigateToDeleteAccountCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `confirm dialog be shown or hidden according to the state`() {
|
||||
composeTestRule.onNode(isDialog()).assertDoesNotExist()
|
||||
|
|
|
@ -62,14 +62,11 @@ class AccountSecurityViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `on DeleteAccountClick should emit ShowToast`() = runTest {
|
||||
fun `on DeleteAccountClick should emit NavigateToDeleteAccount`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AccountSecurityAction.DeleteAccountClick)
|
||||
assertEquals(
|
||||
AccountSecurityEvent.ShowToast("Not yet implemented.".asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(AccountSecurityEvent.NavigateToDeleteAccount, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class DeleteAccountScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
|
||||
private val mutableEventFlow = MutableSharedFlow<DeleteAccountEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
private val mutableStateFlow = MutableStateFlow(Unit)
|
||||
private val viewModel = mockk<DeleteAccountViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
DeleteAccountScreen(
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(DeleteAccountEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DeleteAccountViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `on CancelClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(DeleteAccountAction.CancelClick)
|
||||
assertEquals(DeleteAccountEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on CloseClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(DeleteAccountAction.CloseClick)
|
||||
assertEquals(DeleteAccountEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on DeleteAccountClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(DeleteAccountAction.DeleteAccountClick)
|
||||
assertEquals(
|
||||
DeleteAccountEvent.ShowToast("Not yet implemented.".asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(): DeleteAccountViewModel = DeleteAccountViewModel()
|
||||
}
|
|
@ -34,6 +34,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("My vault").performClick()
|
||||
|
@ -56,6 +57,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault") }
|
||||
|
@ -79,6 +81,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("Send").performClick()
|
||||
|
@ -101,6 +104,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault") }
|
||||
|
@ -124,6 +128,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("Generator").performClick()
|
||||
|
@ -146,6 +151,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault") }
|
||||
|
@ -169,6 +175,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("Settings").performClick()
|
||||
|
@ -191,6 +198,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault") }
|
||||
|
|
Loading…
Reference in a new issue