mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-956: UI for item listing screen (#356)
This commit is contained in:
parent
dd37721e51
commit
b7578b8f96
10 changed files with 1228 additions and 10 deletions
|
@ -0,0 +1,49 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderTextWithSupportLabel
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultEntryListItem
|
||||
|
||||
/**
|
||||
* Content view for the [VaultItemListingScreen].
|
||||
*/
|
||||
@Composable
|
||||
fun VaultItemListingContent(
|
||||
state: VaultItemListingState.ViewState.Content,
|
||||
vaultItemClick: (id: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
) {
|
||||
item {
|
||||
BitwardenListHeaderTextWithSupportLabel(
|
||||
label = stringResource(id = R.string.items),
|
||||
supportingLabel = state.displayItemList.size.toString(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
items(state.displayItemList) {
|
||||
VaultEntryListItem(
|
||||
startIcon = painterResource(id = it.iconRes),
|
||||
label = it.title,
|
||||
supportingLabel = it.subtitle,
|
||||
onClick = { vaultItemClick(it.id) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultNoItems
|
||||
|
||||
/**
|
||||
* Empty view for the [VaultItemListingScreen].
|
||||
*/
|
||||
@Composable
|
||||
fun VaultItemListingEmpty(
|
||||
paddingValues: PaddingValues,
|
||||
itemListingType: VaultItemListingState.ItemListingType,
|
||||
addItemClickAction: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (itemListingType) {
|
||||
is VaultItemListingState.ItemListingType.Folder -> {
|
||||
GenericNoItems(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.no_items_folder),
|
||||
)
|
||||
}
|
||||
|
||||
is VaultItemListingState.ItemListingType.Trash -> {
|
||||
GenericNoItems(
|
||||
modifier = modifier,
|
||||
text = stringResource(id = R.string.no_items_trash),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
VaultNoItems(
|
||||
paddingValues = paddingValues,
|
||||
addItemClickAction = addItemClickAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GenericNoItems(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
|
||||
/**
|
||||
* The top level error UI state for the [VaultItemListingScreen].
|
||||
*/
|
||||
@Composable
|
||||
fun VaultItemListingError(
|
||||
state: VaultItemListingState.ViewState.Error,
|
||||
onRefreshClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = state.message(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.try_again),
|
||||
onClick = onRefreshClick,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(88.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
/**
|
||||
* Loading view for the [VaultItemListingScreen].
|
||||
*/
|
||||
@Composable
|
||||
fun VaultItemListingLoading(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
|
@ -1,12 +1,29 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.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.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
|
||||
/**
|
||||
* Displays the vault item listing screen.
|
||||
|
@ -16,12 +33,133 @@ fun VaultItemListingScreen(
|
|||
onNavigateBack: () -> Unit,
|
||||
onNavigateToVaultItem: (id: String) -> Unit,
|
||||
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||
viewModel: VaultItemListingViewModel = hiltViewModel(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(text = "Listing Screen")
|
||||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is VaultItemListingEvent.NavigateBack -> onNavigateBack()
|
||||
|
||||
is VaultItemListingEvent.NavigateToVaultItem -> {
|
||||
onNavigateToVaultItem(event.id)
|
||||
}
|
||||
|
||||
is VaultItemListingEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
is VaultItemListingEvent.NavigateToAddVaultItem -> {
|
||||
onNavigateToVaultAddItemScreen()
|
||||
}
|
||||
|
||||
is VaultItemListingEvent.NavigateToVaultSearchScreen -> {
|
||||
// TODO Create vault search screen and navigation implementation BIT-213
|
||||
Toast
|
||||
.makeText(context, "Navigate to the vault search screen.", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
VaultItemListingScaffold(
|
||||
state = viewModel.stateFlow.collectAsState().value,
|
||||
backClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultItemListingsAction.BackClick) }
|
||||
},
|
||||
searchIconClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultItemListingsAction.SearchIconClick) }
|
||||
},
|
||||
addVaultItemClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick) }
|
||||
},
|
||||
vaultItemClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultItemListingsAction.ItemClick(it)) }
|
||||
},
|
||||
refreshClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultItemListingsAction.RefreshClick) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun VaultItemListingScaffold(
|
||||
state: VaultItemListingState,
|
||||
backClick: () -> Unit,
|
||||
searchIconClick: () -> Unit,
|
||||
addVaultItemClick: () -> Unit,
|
||||
vaultItemClick: (id: String) -> Unit,
|
||||
refreshClick: () -> Unit,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = state.itemListingType.titleText(),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = painterResource(id = R.drawable.ic_back),
|
||||
navigationIconContentDescription = stringResource(id = R.string.back),
|
||||
onNavigationIconClick = backClick,
|
||||
actions = {
|
||||
BitwardenSearchActionItem(
|
||||
contentDescription = stringResource(id = R.string.search_vault),
|
||||
onClick = searchIconClick,
|
||||
)
|
||||
BitwardenOverflowActionItem()
|
||||
},
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (state.itemListingType.hasFab) {
|
||||
FloatingActionButton(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
onClick = addVaultItemClick,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = stringResource(id = R.string.add_item),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
) { paddingValues ->
|
||||
val modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
|
||||
when (state.viewState) {
|
||||
is VaultItemListingState.ViewState.Content -> {
|
||||
VaultItemListingContent(
|
||||
state = state.viewState,
|
||||
vaultItemClick = vaultItemClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultItemListingState.ViewState.NoItems -> {
|
||||
VaultItemListingEmpty(
|
||||
paddingValues = paddingValues,
|
||||
itemListingType = state.itemListingType,
|
||||
addItemClickAction = addVaultItemClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultItemListingState.ViewState.Error -> {
|
||||
VaultItemListingError(
|
||||
state = state.viewState,
|
||||
onRefreshClick = refreshClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultItemListingState.ViewState.Loading -> {
|
||||
VaultItemListingLoading(modifier = modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
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 com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Manages [VaultItemListingState], handles [VaultItemListingsAction],
|
||||
* and launches [VaultItemListingEvent] for the [VaultItemListingScreen].
|
||||
*/
|
||||
@HiltViewModel
|
||||
@Suppress("MagicNumber")
|
||||
class VaultItemListingViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<VaultItemListingState, VaultItemListingEvent, VaultItemListingsAction>(
|
||||
initialState = VaultItemListingState(
|
||||
itemListingType = VaultItemListingArgs(savedStateHandle = savedStateHandle)
|
||||
.vaultItemListingType
|
||||
.toItemListingType(),
|
||||
viewState = VaultItemListingState.ViewState.Loading,
|
||||
),
|
||||
) {
|
||||
|
||||
init {
|
||||
// TODO fetch real listing data in BIT-1057
|
||||
viewModelScope.launch {
|
||||
delay(2000)
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultItemListingState.ViewState.NoItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleAction(action: VaultItemListingsAction) {
|
||||
when (action) {
|
||||
is VaultItemListingsAction.BackClick -> handleBackClick()
|
||||
is VaultItemListingsAction.SearchIconClick -> handleSearchIconClick()
|
||||
is VaultItemListingsAction.ItemClick -> handleItemClick(action)
|
||||
is VaultItemListingsAction.AddVaultItemClick -> handleAddVaultItemClick()
|
||||
is VaultItemListingsAction.RefreshClick -> handleRefreshClick()
|
||||
}
|
||||
}
|
||||
|
||||
//region VaultItemListing Handlers
|
||||
private fun handleRefreshClick() {
|
||||
// TODO implement refresh in BIT-1057
|
||||
sendEvent(
|
||||
event = VaultItemListingEvent.ShowToast(
|
||||
text = "Not yet implemented".asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleAddVaultItemClick() {
|
||||
sendEvent(
|
||||
event = VaultItemListingEvent.NavigateToAddVaultItem,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleItemClick(action: VaultItemListingsAction.ItemClick) {
|
||||
sendEvent(
|
||||
event = VaultItemListingEvent.NavigateToVaultItem(
|
||||
id = action.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleBackClick() {
|
||||
sendEvent(
|
||||
event = VaultItemListingEvent.NavigateBack,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleSearchIconClick() {
|
||||
sendEvent(
|
||||
event = VaultItemListingEvent.NavigateToVaultSearchScreen,
|
||||
)
|
||||
}
|
||||
//endregion VaultItemListing Handlers
|
||||
}
|
||||
|
||||
/**
|
||||
* Models state for the [VaultItemListingScreen].
|
||||
*/
|
||||
data class VaultItemListingState(
|
||||
val itemListingType: ItemListingType,
|
||||
val viewState: ViewState,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represents the specific view states for the [VaultItemListingScreen].
|
||||
*/
|
||||
sealed class ViewState {
|
||||
|
||||
/**
|
||||
* Loading state for the [VaultItemListingScreen],
|
||||
* signifying that the content is being processed.
|
||||
*/
|
||||
data object Loading : ViewState()
|
||||
|
||||
/**
|
||||
* Represents a state where the [VaultItemListingScreen] has no items to display.
|
||||
*/
|
||||
data object NoItems : ViewState()
|
||||
|
||||
/**
|
||||
* Content state for the [VaultItemListingScreen] showing the actual content or items.
|
||||
*
|
||||
* @property displayItemList List of items to display.
|
||||
*/
|
||||
data class Content(
|
||||
val displayItemList: List<DisplayItem>,
|
||||
) : ViewState()
|
||||
|
||||
/**
|
||||
* Represents an error state for the [VaultItemListingScreen].
|
||||
*
|
||||
* @property message Error message to display.
|
||||
*/
|
||||
data class Error(
|
||||
val message: Text,
|
||||
) : ViewState()
|
||||
}
|
||||
|
||||
/**
|
||||
* An item to be displayed.
|
||||
*
|
||||
* @property id the id of the item.
|
||||
* @property title title of the item.
|
||||
* @property subtitle subtitle of the item.
|
||||
* @property uri uri for the icon to be displayed (nullable).
|
||||
* @property iconRes the icon to be displayed.
|
||||
*/
|
||||
data class DisplayItem(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val uri: String?,
|
||||
@DrawableRes
|
||||
val iconRes: Int,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents different types of item listing.
|
||||
*/
|
||||
sealed class ItemListingType {
|
||||
|
||||
/**
|
||||
* The title to display at the top of the screen.
|
||||
*/
|
||||
abstract val titleText: Text
|
||||
|
||||
/**
|
||||
* Whether or not the screen has a floating action button (FAB).
|
||||
*/
|
||||
abstract val hasFab: Boolean
|
||||
|
||||
/**
|
||||
* A Login item listing.
|
||||
*/
|
||||
data object Login : ItemListingType() {
|
||||
override val titleText: Text
|
||||
get() = R.string.logins.asText()
|
||||
override val hasFab: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
* A Card item listing.
|
||||
*/
|
||||
data object Card : ItemListingType() {
|
||||
override val titleText: Text
|
||||
get() = R.string.cards.asText()
|
||||
override val hasFab: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
* An Identity item listing.
|
||||
*/
|
||||
data object Identity : ItemListingType() {
|
||||
override val titleText: Text
|
||||
get() = R.string.identities.asText()
|
||||
override val hasFab: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
* A Secure Note item listing.
|
||||
*/
|
||||
data object SecureNote : ItemListingType() {
|
||||
override val titleText: Text
|
||||
get() = R.string.secure_notes.asText()
|
||||
override val hasFab: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
/**
|
||||
* A Secure Trash item listing.
|
||||
*/
|
||||
data object Trash : ItemListingType() {
|
||||
override val titleText: Text
|
||||
get() = R.string.trash.asText()
|
||||
override val hasFab: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* A Folder item listing.
|
||||
*
|
||||
* @property folderId the id of the folder.
|
||||
* @property folderName the name of the folder.
|
||||
*/
|
||||
data class Folder(
|
||||
val folderId: String?,
|
||||
// The folderName will always initially be an empty string
|
||||
val folderName: String = "",
|
||||
) : ItemListingType() {
|
||||
override val titleText: Text
|
||||
get() = folderName.asText()
|
||||
override val hasFab: Boolean
|
||||
get() = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models events for the [VaultItemListingScreen].
|
||||
*/
|
||||
sealed class VaultItemListingEvent {
|
||||
|
||||
/**
|
||||
* Navigates to the Create Account screen.
|
||||
*/
|
||||
data object NavigateBack : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the VaultAddItemScreen.
|
||||
*/
|
||||
data object NavigateToAddVaultItem : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the VaultItemScreen.
|
||||
*
|
||||
* @property id the id of the item to navigate to.
|
||||
*/
|
||||
data class NavigateToVaultItem(val id: String) : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the VaultSearchScreen.
|
||||
*/
|
||||
data object NavigateToVaultSearchScreen : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* Show a toast with the given message.
|
||||
*
|
||||
* @property text the text to display.
|
||||
*/
|
||||
data class ShowToast(val text: Text) : VaultItemListingEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the [VaultItemListingScreen].
|
||||
*/
|
||||
sealed class VaultItemListingsAction {
|
||||
|
||||
/**
|
||||
* Click the refresh button.
|
||||
*/
|
||||
data object RefreshClick : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Click the back button.
|
||||
*/
|
||||
data object BackClick : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Click the search icon.
|
||||
*/
|
||||
data object SearchIconClick : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Click the add item button.
|
||||
*/
|
||||
data object AddVaultItemClick : VaultItemListingsAction()
|
||||
|
||||
/**
|
||||
* Click on an item.
|
||||
*
|
||||
* @property id the id of the item that has been clicked.
|
||||
*/
|
||||
data class ItemClick(val id: String) : VaultItemListingsAction()
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
|
||||
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
|
||||
/**
|
||||
* Transforms a [VaultItemListingType] into a [VaultItemListingState.ItemListingType].
|
||||
*/
|
||||
fun VaultItemListingType.toItemListingType(): VaultItemListingState.ItemListingType =
|
||||
when (this) {
|
||||
is VaultItemListingType.Card -> VaultItemListingState.ItemListingType.Card
|
||||
is VaultItemListingType.Folder -> {
|
||||
VaultItemListingState.ItemListingType.Folder(folderId = folderId)
|
||||
}
|
||||
|
||||
is VaultItemListingType.Identity -> VaultItemListingState.ItemListingType.Card
|
||||
is VaultItemListingType.Login -> VaultItemListingState.ItemListingType.Login
|
||||
is VaultItemListingType.SecureNote -> VaultItemListingState.ItemListingType.SecureNote
|
||||
is VaultItemListingType.Trash -> VaultItemListingState.ItemListingType.Trash
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.hasScrollToNodeAction
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollToNode
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import com.x8bit.bitwarden.R
|
||||
|
||||
class VaultItemListingScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
private var onNavigateToVaultAddItemScreenCalled = false
|
||||
private var onNavigateToVaultItemId: String? = null
|
||||
|
||||
private val mutableEventFlow = MutableSharedFlow<VaultItemListingEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val viewModel = mockk<VaultItemListingViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
VaultItemListingScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
onNavigateToVaultItem = { onNavigateToVaultItemId = it },
|
||||
onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreenCalled = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateBack event should invoke NavigateBack`() {
|
||||
mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking back button should send BackClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Back")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(VaultItemListingsAction.BackClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `search icon click should send SearchIconClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Search vault")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(VaultItemListingsAction.SearchIconClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `floating action button click should send AddItemClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Add item")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add an item button click should send AddItemClick action`() {
|
||||
mutableStateFlow.update { it.copy(viewState = VaultItemListingState.ViewState.NoItems) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Add an Item")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(VaultItemListingsAction.AddVaultItemClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `refresh button click should send RefreshClick action`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(viewState = VaultItemListingState.ViewState.Error(message = "".asText()))
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText("Try again")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(VaultItemListingsAction.RefreshClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToAdd VaultItem event should call NavigateToVaultAddItemScreen`() {
|
||||
mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToAddVaultItem)
|
||||
assertTrue(onNavigateToVaultAddItemScreenCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToVaultItem event should call NavigateToVaultItemScreen`() {
|
||||
val id = "id4321"
|
||||
mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToVaultItem(id = id))
|
||||
assertEquals(id, onNavigateToVaultItemId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `progressbar should be displayed according to state`() {
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
|
||||
composeTestRule
|
||||
.onNode(isProgressBar)
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(viewState = VaultItemListingState.ViewState.NoItems)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(isProgressBar)
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error text and retry should be displayed according to state`() {
|
||||
val message = "error_message"
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
composeTestRule
|
||||
.onNodeWithText(message)
|
||||
.assertIsNotDisplayed()
|
||||
|
||||
mutableStateFlow.update { it.copy(viewState = VaultItemListingState.ViewState.NoItems) }
|
||||
composeTestRule
|
||||
.onNodeWithText(message)
|
||||
.assertIsNotDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(viewState = VaultItemListingState.ViewState.Error(message.asText()))
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(message)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Add an item button should be displayed according to state`() {
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Add an Item")
|
||||
.assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(viewState = VaultItemListingState.ViewState.NoItems)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Add an Item")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Add an Item")
|
||||
.assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Folder(folderId = null))
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Add an Item")
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty text should be displayed according to state`() {
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(viewState = VaultItemListingState.ViewState.NoItems)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "There are no items in your vault.")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "There are no items in the trash.")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Folder(folderId = null))
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "There are no items in this folder.")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `floating action button should be displayed according to state`() {
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Add item")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Add item")
|
||||
.assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Folder(folderId = null))
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Add item")
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Items text should be displayed according to state`() {
|
||||
val items = "Items"
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
composeTestRule
|
||||
.onNodeWithText(text = items)
|
||||
.assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayItemList = listOf(
|
||||
createDisplayItem(number = 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNode(hasScrollToNodeAction())
|
||||
.performScrollToNode(hasText(items))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = items)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Items text count should be displayed according to state`() {
|
||||
val items = "Items"
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
composeTestRule
|
||||
.onNodeWithText(text = items)
|
||||
.assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayItemList = listOf(
|
||||
createDisplayItem(number = 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNode(hasScrollToNodeAction())
|
||||
.performScrollToNode(hasText(items))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = items)
|
||||
.assertIsDisplayed()
|
||||
.assertTextEquals(items, 1.toString())
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayItemList = listOf(
|
||||
createDisplayItem(number = 1),
|
||||
createDisplayItem(number = 2),
|
||||
createDisplayItem(number = 3),
|
||||
createDisplayItem(number = 4),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(hasScrollToNodeAction())
|
||||
.performScrollToNode(hasText(items))
|
||||
composeTestRule
|
||||
.onNodeWithText(text = items)
|
||||
.assertIsDisplayed()
|
||||
.assertTextEquals(items, 4.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `displayItems should be displayed according to state`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayItemList = listOf(
|
||||
createDisplayItem(number = 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "mockTitle-1")
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "mockSubtitle-1")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on a display item should send ItemClick action`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayItemList = listOf(
|
||||
createDisplayItem(number = 1),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "mockTitle-1")
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
verify {
|
||||
viewModel.trySendAction(VaultItemListingsAction.ItemClick("mockId-1"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `topBar title should be displayed according to state`() {
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Logins")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.SecureNote)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Secure notes")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Card)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Cards")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Identity)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Identities")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(itemListingType = VaultItemListingState.ItemListingType.Trash)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Trash")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
itemListingType = VaultItemListingState.ItemListingType.Folder(
|
||||
folderId = "mockId",
|
||||
folderName = "mockName",
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "mockName")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE = VaultItemListingState(
|
||||
itemListingType = VaultItemListingState.ItemListingType.Login,
|
||||
viewState = VaultItemListingState.ViewState.Loading,
|
||||
)
|
||||
|
||||
private fun createDisplayItem(number: Int): VaultItemListingState.DisplayItem =
|
||||
VaultItemListingState.DisplayItem(
|
||||
id = "mockId-$number",
|
||||
title = "mockTitle-$number",
|
||||
subtitle = "mockSubtitle-$number",
|
||||
uri = "mockUri-$number",
|
||||
iconRes = R.drawable.ic_card_item,
|
||||
)
|
|
@ -0,0 +1,120 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val initialState = createVaultItemListingState()
|
||||
private val initialSavedStateHandle = createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType = VaultItemListingType.Login,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
initialState, awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultItemListingsAction.BackClick)
|
||||
assertEquals(VaultItemListingEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SearchIconClick should emit NavigateToVaultSearchScreen`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultItemListingsAction.SearchIconClick)
|
||||
assertEquals(VaultItemListingEvent.NavigateToVaultSearchScreen, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ItemClick should emit NavigateToVaultItem`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultItemListingsAction.ItemClick(id = "mock"))
|
||||
assertEquals(VaultItemListingEvent.NavigateToVaultItem(id = "mock"), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddVaultItemClick should emit NavigateToAddVaultItem`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultItemListingsAction.AddVaultItemClick)
|
||||
assertEquals(VaultItemListingEvent.NavigateToAddVaultItem, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `RefreshClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultItemListingsAction.RefreshClick)
|
||||
assertEquals(
|
||||
VaultItemListingEvent.ShowToast("Not yet implemented".asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSavedStateHandleWithVaultItemListingType(
|
||||
vaultItemListingType: VaultItemListingType,
|
||||
) = SavedStateHandle().apply {
|
||||
set(
|
||||
"vault_item_listing_type",
|
||||
when (vaultItemListingType) {
|
||||
is VaultItemListingType.Card -> "card"
|
||||
is VaultItemListingType.Folder -> "folder"
|
||||
is VaultItemListingType.Identity -> "identity"
|
||||
is VaultItemListingType.Login -> "login"
|
||||
is VaultItemListingType.SecureNote -> "secure_note"
|
||||
is VaultItemListingType.Trash -> "trash"
|
||||
},
|
||||
)
|
||||
set(
|
||||
"id",
|
||||
when (vaultItemListingType) {
|
||||
is VaultItemListingType.Card -> null
|
||||
is VaultItemListingType.Folder -> vaultItemListingType.folderId
|
||||
is VaultItemListingType.Identity -> null
|
||||
is VaultItemListingType.Login -> null
|
||||
is VaultItemListingType.SecureNote -> null
|
||||
is VaultItemListingType.Trash -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVaultItemListingViewModel(
|
||||
savedStateHandle: SavedStateHandle = initialSavedStateHandle,
|
||||
): VaultItemListingViewModel =
|
||||
VaultItemListingViewModel(
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun createVaultItemListingState(
|
||||
itemListingType: VaultItemListingState.ItemListingType = VaultItemListingState.ItemListingType.Login,
|
||||
viewState: VaultItemListingState.ViewState = VaultItemListingState.ViewState.Loading,
|
||||
): VaultItemListingState =
|
||||
VaultItemListingState(
|
||||
itemListingType = itemListingType,
|
||||
viewState = viewState,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
|
||||
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class VaultItemListingTypeExtensionsTest {
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toItemListingType should transform a VaultItemListingType into a VaultItemListingState ItemListingType`() {
|
||||
val itemListingTypeList = listOf(
|
||||
VaultItemListingType.Folder(folderId = "mock"),
|
||||
VaultItemListingType.Trash,
|
||||
)
|
||||
|
||||
val result = itemListingTypeList.map { it.toItemListingType() }
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
VaultItemListingState.ItemListingType.Folder(folderId = "mock"),
|
||||
VaultItemListingState.ItemListingType.Trash,
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue