mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Create initial vault item shell (#253)
This commit is contained in:
parent
f912eb14ef
commit
dd6e7639b5
13 changed files with 332 additions and 0 deletions
|
@ -10,6 +10,8 @@ import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.VAULT_UNLOCKE
|
||||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.vaultUnlockedNavBarDestination
|
import com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar.vaultUnlockedNavBarDestination
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.send.navigateToNewSend
|
import com.x8bit.bitwarden.ui.tools.feature.send.navigateToNewSend
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.send.newSendDestination
|
import com.x8bit.bitwarden.ui.tools.feature.send.newSendDestination
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.vault.item.navigateToVaultItem
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.vault.item.vaultItemDestination
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.navigateToVaultAddItem
|
import com.x8bit.bitwarden.ui.vault.feature.vault.navigateToVaultAddItem
|
||||||
import com.x8bit.bitwarden.ui.vault.feature.vault.vaultAddItemDestination
|
import com.x8bit.bitwarden.ui.vault.feature.vault.vaultAddItemDestination
|
||||||
|
|
||||||
|
@ -34,11 +36,13 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
||||||
) {
|
) {
|
||||||
vaultUnlockedNavBarDestination(
|
vaultUnlockedNavBarDestination(
|
||||||
onNavigateToVaultAddItem = { navController.navigateToVaultAddItem() },
|
onNavigateToVaultAddItem = { navController.navigateToVaultAddItem() },
|
||||||
|
onNavigateToVaultItem = { navController.navigateToVaultItem(it) },
|
||||||
onNavigateToNewSend = { navController.navigateToNewSend() },
|
onNavigateToNewSend = { navController.navigateToNewSend() },
|
||||||
onNavigateToDeleteAccount = { navController.navigateToDeleteAccount() },
|
onNavigateToDeleteAccount = { navController.navigateToDeleteAccount() },
|
||||||
)
|
)
|
||||||
deleteAccountDestination(onNavigateBack = { navController.popBackStack() })
|
deleteAccountDestination(onNavigateBack = { navController.popBackStack() })
|
||||||
vaultAddItemDestination(onNavigateBack = { navController.popBackStack() })
|
vaultAddItemDestination(onNavigateBack = { navController.popBackStack() })
|
||||||
|
vaultItemDestination(onNavigateBack = { navController.popBackStack() })
|
||||||
newSendDestination(onNavigateBack = { navController.popBackStack() })
|
newSendDestination(onNavigateBack = { navController.popBackStack() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ fun NavController.navigateToVaultUnlockedNavBar(navOptions: NavOptions? = null)
|
||||||
*/
|
*/
|
||||||
fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
||||||
onNavigateToVaultAddItem: () -> Unit,
|
onNavigateToVaultAddItem: () -> Unit,
|
||||||
|
onNavigateToVaultItem: (vaultItemId: String) -> Unit,
|
||||||
onNavigateToNewSend: () -> Unit,
|
onNavigateToNewSend: () -> Unit,
|
||||||
onNavigateToDeleteAccount: () -> Unit,
|
onNavigateToDeleteAccount: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -35,6 +36,7 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
||||||
) {
|
) {
|
||||||
VaultUnlockedNavBarScreen(
|
VaultUnlockedNavBarScreen(
|
||||||
onNavigateToVaultAddItem = onNavigateToVaultAddItem,
|
onNavigateToVaultAddItem = onNavigateToVaultAddItem,
|
||||||
|
onNavigateToVaultItem = onNavigateToVaultItem,
|
||||||
onNavigateToNewSend = onNavigateToNewSend,
|
onNavigateToNewSend = onNavigateToNewSend,
|
||||||
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||||
)
|
)
|
||||||
|
|
|
@ -66,6 +66,7 @@ fun VaultUnlockedNavBarScreen(
|
||||||
viewModel: VaultUnlockedNavBarViewModel = hiltViewModel(),
|
viewModel: VaultUnlockedNavBarViewModel = hiltViewModel(),
|
||||||
navController: NavHostController = rememberNavController(),
|
navController: NavHostController = rememberNavController(),
|
||||||
onNavigateToVaultAddItem: () -> Unit,
|
onNavigateToVaultAddItem: () -> Unit,
|
||||||
|
onNavigateToVaultItem: (vaultItemId: String) -> Unit,
|
||||||
onNavigateToNewSend: () -> Unit,
|
onNavigateToNewSend: () -> Unit,
|
||||||
onNavigateToDeleteAccount: () -> Unit,
|
onNavigateToDeleteAccount: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -93,6 +94,7 @@ fun VaultUnlockedNavBarScreen(
|
||||||
}
|
}
|
||||||
VaultUnlockedNavBarScaffold(
|
VaultUnlockedNavBarScaffold(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
|
onNavigateToVaultItem = onNavigateToVaultItem,
|
||||||
navigateToVaultAddItem = onNavigateToVaultAddItem,
|
navigateToVaultAddItem = onNavigateToVaultAddItem,
|
||||||
navigateToNewSend = onNavigateToNewSend,
|
navigateToNewSend = onNavigateToNewSend,
|
||||||
navigateToDeleteAccount = onNavigateToDeleteAccount,
|
navigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||||
|
@ -123,6 +125,7 @@ private fun VaultUnlockedNavBarScaffold(
|
||||||
generatorTabClickedAction: () -> Unit,
|
generatorTabClickedAction: () -> Unit,
|
||||||
settingsTabClickedAction: () -> Unit,
|
settingsTabClickedAction: () -> Unit,
|
||||||
navigateToVaultAddItem: () -> Unit,
|
navigateToVaultAddItem: () -> Unit,
|
||||||
|
onNavigateToVaultItem: (vaultItemId: String) -> Unit,
|
||||||
navigateToNewSend: () -> Unit,
|
navigateToNewSend: () -> Unit,
|
||||||
navigateToDeleteAccount: () -> Unit,
|
navigateToDeleteAccount: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -178,6 +181,7 @@ private fun VaultUnlockedNavBarScaffold(
|
||||||
onNavigateToVaultAddItemScreen = {
|
onNavigateToVaultAddItemScreen = {
|
||||||
navigateToVaultAddItem()
|
navigateToVaultAddItem()
|
||||||
},
|
},
|
||||||
|
onNavigateToVaultItemScreen = onNavigateToVaultItem,
|
||||||
onDimBottomNavBarRequest = { shouldDim ->
|
onDimBottomNavBarRequest = { shouldDim ->
|
||||||
shouldDimNavBar = shouldDim
|
shouldDimNavBar = shouldDim
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,11 +12,13 @@ const val VAULT_ROUTE: String = "vault"
|
||||||
*/
|
*/
|
||||||
fun NavGraphBuilder.vaultDestination(
|
fun NavGraphBuilder.vaultDestination(
|
||||||
onNavigateToVaultAddItemScreen: () -> Unit,
|
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||||
|
onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit,
|
||||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
composable(VAULT_ROUTE) {
|
composable(VAULT_ROUTE) {
|
||||||
VaultScreen(
|
VaultScreen(
|
||||||
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
|
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
|
||||||
|
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
|
||||||
onDimBottomNavBarRequest = onDimBottomNavBarRequest,
|
onDimBottomNavBarRequest = onDimBottomNavBarRequest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
fun VaultScreen(
|
fun VaultScreen(
|
||||||
viewModel: VaultViewModel = hiltViewModel(),
|
viewModel: VaultViewModel = hiltViewModel(),
|
||||||
onNavigateToVaultAddItemScreen: () -> Unit,
|
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||||
|
onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit,
|
||||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
@ -52,6 +53,8 @@ fun VaultScreen(
|
||||||
when (event) {
|
when (event) {
|
||||||
VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen()
|
VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen()
|
||||||
|
|
||||||
|
is VaultEvent.NavigateToItemScreen -> onNavigateToVaultItemScreen(event.vaultItemId)
|
||||||
|
|
||||||
VaultEvent.NavigateToVaultSearchScreen -> {
|
VaultEvent.NavigateToVaultSearchScreen -> {
|
||||||
// TODO Create vault search screen and navigation implementation BIT-213
|
// TODO Create vault search screen and navigation implementation BIT-213
|
||||||
Toast
|
Toast
|
||||||
|
|
|
@ -153,6 +153,7 @@ class VaultViewModel @Inject constructor(
|
||||||
is DataState.Pending -> vaultPendingReceive(vaultData = vaultData)
|
is DataState.Pending -> vaultPendingReceive(vaultData = vaultData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
private fun vaultErrorReceive(vaultData: DataState.Error<VaultData>) {
|
||||||
// TODO update state to error state BIT-1157
|
// TODO update state to error state BIT-1157
|
||||||
mutableStateFlow.update { it.copy(viewState = VaultState.ViewState.NoItems) }
|
mutableStateFlow.update { it.copy(viewState = VaultState.ViewState.NoItems) }
|
||||||
|
@ -407,6 +408,13 @@ sealed class VaultEvent {
|
||||||
*/
|
*/
|
||||||
data object NavigateToAddItemScreen : VaultEvent()
|
data object NavigateToAddItemScreen : VaultEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the Vault Item screen.
|
||||||
|
*/
|
||||||
|
data class NavigateToItemScreen(
|
||||||
|
val vaultItemId: String,
|
||||||
|
) : VaultEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to the item details screen.
|
* Navigate to the item details screen.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.vault.item
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavOptions
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.x8bit.bitwarden.ui.platform.theme.TransitionProviders
|
||||||
|
|
||||||
|
private const val VAULT_ITEM_PREFIX = "vault_item"
|
||||||
|
private const val VAULT_ITEM_ID = "vault_item_id"
|
||||||
|
private const val VAULT_ITEM_ROUTE = "$VAULT_ITEM_PREFIX/{$VAULT_ITEM_ID}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to retrieve vault item arguments from the [SavedStateHandle].
|
||||||
|
*/
|
||||||
|
class VaultItemArgs(val vaultItemId: String) {
|
||||||
|
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||||
|
checkNotNull(savedStateHandle[VAULT_ITEM_ID]) as String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the vault item screen to the nav graph.
|
||||||
|
*/
|
||||||
|
fun NavGraphBuilder.vaultItemDestination(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
composable(
|
||||||
|
route = VAULT_ITEM_ROUTE,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument(VAULT_ITEM_ID) { type = NavType.StringType },
|
||||||
|
),
|
||||||
|
enterTransition = TransitionProviders.Enter.slideUp,
|
||||||
|
exitTransition = TransitionProviders.Exit.slideDown,
|
||||||
|
popEnterTransition = TransitionProviders.Enter.slideUp,
|
||||||
|
popExitTransition = TransitionProviders.Exit.slideDown,
|
||||||
|
) {
|
||||||
|
VaultItemScreen(onNavigateBack = onNavigateBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the vault item screen.
|
||||||
|
*/
|
||||||
|
fun NavController.navigateToVaultItem(
|
||||||
|
vaultItemId: String,
|
||||||
|
navOptions: NavOptions? = null,
|
||||||
|
) {
|
||||||
|
navigate("$VAULT_ITEM_PREFIX/$vaultItemId", navOptions)
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.vault.item
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
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.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the vault item screen.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun VaultItemScreen(
|
||||||
|
viewModel: VaultItemViewModel = hiltViewModel(),
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
EventsEffect(viewModel = viewModel) { event ->
|
||||||
|
when (event) {
|
||||||
|
VaultItemEvent.NavigateBack -> onNavigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
BitwardenScaffold(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
BitwardenTopAppBar(
|
||||||
|
title = stringResource(id = R.string.view_item),
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||||
|
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||||
|
onNavigationIconClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(VaultItemAction.CloseClick) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { innerPadding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.imePadding()
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(innerPadding)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.vault.item
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val KEY_STATE = "state"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel responsible for handling user interactions in the vault item screen
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
class VaultItemViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
) : BaseViewModel<VaultItemState, VaultItemEvent, VaultItemAction>(
|
||||||
|
initialState = savedStateHandle[KEY_STATE] ?: VaultItemState(
|
||||||
|
vaultItemId = VaultItemArgs(savedStateHandle).vaultItemId,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
stateFlow.onEach { savedStateHandle[KEY_STATE] = it }.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleAction(action: VaultItemAction) {
|
||||||
|
when (action) {
|
||||||
|
VaultItemAction.CloseClick -> handleCloseClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCloseClick() {
|
||||||
|
sendEvent(VaultItemEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the state for viewing an item in the vault.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class VaultItemState(
|
||||||
|
val vaultItemId: String,
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a set of events related view a vault item.
|
||||||
|
*/
|
||||||
|
sealed class VaultItemEvent {
|
||||||
|
/**
|
||||||
|
* Navigates back.
|
||||||
|
*/
|
||||||
|
data object NavigateBack : VaultItemEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a set of actions related view a vault item.
|
||||||
|
*/
|
||||||
|
sealed class VaultItemAction {
|
||||||
|
/**
|
||||||
|
* The user has clicked the close button.
|
||||||
|
*/
|
||||||
|
data object CloseClick : VaultItemAction()
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
@ -56,6 +57,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
@ -80,6 +82,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
@ -103,6 +106,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
@ -127,6 +131,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
@ -150,6 +155,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
@ -174,6 +180,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
@ -197,6 +204,7 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = fakeNavHostController,
|
navController = fakeNavHostController,
|
||||||
onNavigateToVaultAddItem = {},
|
onNavigateToVaultAddItem = {},
|
||||||
|
onNavigateToVaultItem = {},
|
||||||
onNavigateToNewSend = {},
|
onNavigateToNewSend = {},
|
||||||
onNavigateToDeleteAccount = {},
|
onNavigateToDeleteAccount = {},
|
||||||
)
|
)
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.Test
|
||||||
class VaultScreenTest : BaseComposeTest() {
|
class VaultScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
private var onNavigateToVaultAddItemScreenCalled = false
|
private var onNavigateToVaultAddItemScreenCalled = false
|
||||||
|
private var onNavigateToVaultItemScreenCalled = false
|
||||||
private var onDimBottomNavBarRequestCalled = false
|
private var onDimBottomNavBarRequestCalled = false
|
||||||
|
|
||||||
private val mutableEventFlow = MutableSharedFlow<VaultEvent>(
|
private val mutableEventFlow = MutableSharedFlow<VaultEvent>(
|
||||||
|
@ -45,6 +46,7 @@ class VaultScreenTest : BaseComposeTest() {
|
||||||
VaultScreen(
|
VaultScreen(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreenCalled = true },
|
onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreenCalled = true },
|
||||||
|
onNavigateToVaultItemScreen = { onNavigateToVaultItemScreenCalled = true },
|
||||||
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
|
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -115,6 +117,12 @@ class VaultScreenTest : BaseComposeTest() {
|
||||||
assertTrue(onNavigateToVaultAddItemScreenCalled)
|
assertTrue(onNavigateToVaultAddItemScreenCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `NavigateToItemScreen event should call onNavigateToVaultItemScreenCalled`() {
|
||||||
|
mutableEventFlow.tryEmit(VaultEvent.NavigateToItemScreen(vaultItemId = "id"))
|
||||||
|
assertTrue(onNavigateToVaultItemScreenCalled)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking a favorite item should send VaultItemClick with the correct item`() {
|
fun `clicking a favorite item should send VaultItemClick with the correct item`() {
|
||||||
val itemText = "Test Item"
|
val itemText = "Test Item"
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.vault.item
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class VaultItemScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
|
private var onNavigateBackCalled = false
|
||||||
|
|
||||||
|
private val mutableEventFlow = MutableSharedFlow<VaultItemEvent>(
|
||||||
|
extraBufferCapacity = Int.MAX_VALUE,
|
||||||
|
)
|
||||||
|
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||||
|
private val viewModel = mockk<VaultItemViewModel>(relaxed = true) {
|
||||||
|
every { eventFlow } returns mutableEventFlow
|
||||||
|
every { stateFlow } returns mutableStateFlow
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
VaultItemScreen(
|
||||||
|
viewModel = viewModel,
|
||||||
|
onNavigateBack = { onNavigateBackCalled = true },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking close button should send CloseClick action`() {
|
||||||
|
composeTestRule.onNodeWithContentDescription(label = "Close").performClick()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(VaultItemAction.CloseClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val VAULT_ITEM_ID = "vault_item_id"
|
||||||
|
|
||||||
|
private val DEFAULT_STATE: VaultItemState = VaultItemState(
|
||||||
|
vaultItemId = VAULT_ITEM_ID,
|
||||||
|
)
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.x8bit.bitwarden.ui.vault.feature.vault.item
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
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 VaultItemViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state should be correct when not set`() {
|
||||||
|
val viewModel = createViewModel(state = null)
|
||||||
|
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `initial state should be correct when set`() {
|
||||||
|
val state = DEFAULT_STATE.copy(vaultItemId = "something_different")
|
||||||
|
val viewModel = createViewModel(state = state)
|
||||||
|
assertEquals(state, viewModel.stateFlow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(VaultItemAction.CloseClick)
|
||||||
|
assertEquals(VaultItemEvent.NavigateBack, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createViewModel(
|
||||||
|
state: VaultItemState? = DEFAULT_STATE,
|
||||||
|
vaultItemId: String = VAULT_ITEM_ID,
|
||||||
|
): VaultItemViewModel = VaultItemViewModel(
|
||||||
|
savedStateHandle = SavedStateHandle().apply {
|
||||||
|
set("state", state)
|
||||||
|
set("vault_item_id", vaultItemId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val VAULT_ITEM_ID = "vault_item_id"
|
||||||
|
|
||||||
|
private val DEFAULT_STATE: VaultItemState = VaultItemState(
|
||||||
|
vaultItemId = VAULT_ITEM_ID,
|
||||||
|
)
|
Loading…
Reference in a new issue