Add basic navigation for the search screen (#707)

This commit is contained in:
David Perez 2024-01-22 09:23:32 -06:00 committed by Álison Fernandes
parent e3547f4e13
commit 75fbadb67b
27 changed files with 728 additions and 21 deletions

View file

@ -0,0 +1,131 @@
package com.x8bit.bitwarden.ui.platform.feature.search
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
private const val SEARCH_TYPE: String = "search_type"
private const val SEARCH_TYPE_SEND_ALL: String = "search_type_sends_all"
private const val SEARCH_TYPE_SEND_TEXT: String = "search_type_sends_text"
private const val SEARCH_TYPE_SEND_FILE: String = "search_type_sends_file"
private const val SEARCH_TYPE_VAULT_ALL: String = "search_type_vault_all"
private const val SEARCH_TYPE_VAULT_LOGINS: String = "search_type_vault_logins"
private const val SEARCH_TYPE_VAULT_CARDS: String = "search_type_vault_cards"
private const val SEARCH_TYPE_VAULT_IDENTITIES: String = "search_type_vault_identities"
private const val SEARCH_TYPE_VAULT_SECURE_NOTES: String = "search_type_vault_secure_notes"
private const val SEARCH_TYPE_VAULT_COLLECTION: String = "search_type_vault_collection"
private const val SEARCH_TYPE_VAULT_NO_FOLDER: String = "search_type_vault_no_folder"
private const val SEARCH_TYPE_VAULT_FOLDER: String = "search_type_vault_folder"
private const val SEARCH_TYPE_VAULT_TRASH: String = "search_type_vault_trash"
private const val SEARCH_TYPE_ID: String = "search_type_id"
private const val SEARCH_ROUTE_PREFIX: String = "search"
private const val SEARCH_ROUTE: String = "$SEARCH_ROUTE_PREFIX/{$SEARCH_TYPE}/{$SEARCH_TYPE_ID}"
/**
* Class to retrieve search arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class SearchArgs(
val type: SearchType,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
type = determineSearchType(
searchTypeString = requireNotNull(savedStateHandle.get<String>(SEARCH_TYPE)),
id = savedStateHandle.get<String>(SEARCH_TYPE_ID),
),
)
}
/**
* Add search destinations to the nav graph.
*/
fun NavGraphBuilder.searchDestination(
onNavigateBack: () -> Unit,
) {
composableWithSlideTransitions(
route = SEARCH_ROUTE,
arguments = listOf(
navArgument(SEARCH_TYPE) { type = NavType.StringType },
navArgument(SEARCH_TYPE_ID) {
type = NavType.StringType
nullable = true
},
),
) {
SearchScreen(
onNavigateBack = onNavigateBack,
)
}
}
/**
* Navigate to the search screen.
*/
fun NavController.navigateToSearch(
searchType: SearchType,
navOptions: NavOptions? = null,
) {
navigate(
route = "$SEARCH_ROUTE_PREFIX/${searchType.toTypeString()}/${searchType.toIdOrNull()}",
navOptions = navOptions,
)
}
private fun determineSearchType(
searchTypeString: String,
id: String?,
): SearchType =
when (searchTypeString) {
SEARCH_TYPE_SEND_ALL -> SearchType.Sends.All
SEARCH_TYPE_SEND_TEXT -> SearchType.Sends.Texts
SEARCH_TYPE_SEND_FILE -> SearchType.Sends.Files
SEARCH_TYPE_VAULT_ALL -> SearchType.Vault.All
SEARCH_TYPE_VAULT_LOGINS -> SearchType.Vault.Logins
SEARCH_TYPE_VAULT_CARDS -> SearchType.Vault.Cards
SEARCH_TYPE_VAULT_IDENTITIES -> SearchType.Vault.Identities
SEARCH_TYPE_VAULT_SECURE_NOTES -> SearchType.Vault.SecureNotes
SEARCH_TYPE_VAULT_COLLECTION -> SearchType.Vault.Collection(requireNotNull(id))
SEARCH_TYPE_VAULT_NO_FOLDER -> SearchType.Vault.NoFolder
SEARCH_TYPE_VAULT_FOLDER -> SearchType.Vault.Folder(requireNotNull(id))
SEARCH_TYPE_VAULT_TRASH -> SearchType.Vault.Trash
else -> throw IllegalArgumentException("Invalid Search Type")
}
private fun SearchType.toTypeString(): String =
when (this) {
SearchType.Sends.All -> SEARCH_TYPE_SEND_ALL
SearchType.Sends.Files -> SEARCH_TYPE_SEND_FILE
SearchType.Sends.Texts -> SEARCH_TYPE_SEND_TEXT
SearchType.Vault.All -> SEARCH_TYPE_VAULT_ALL
SearchType.Vault.Cards -> SEARCH_TYPE_VAULT_CARDS
is SearchType.Vault.Collection -> SEARCH_TYPE_VAULT_COLLECTION
is SearchType.Vault.Folder -> SEARCH_TYPE_VAULT_FOLDER
SearchType.Vault.Identities -> SEARCH_TYPE_VAULT_IDENTITIES
SearchType.Vault.Logins -> SEARCH_TYPE_VAULT_LOGINS
SearchType.Vault.NoFolder -> SEARCH_TYPE_VAULT_NO_FOLDER
SearchType.Vault.SecureNotes -> SEARCH_TYPE_VAULT_SECURE_NOTES
SearchType.Vault.Trash -> SEARCH_TYPE_VAULT_TRASH
}
private fun SearchType.toIdOrNull(): String? =
when (this) {
SearchType.Sends.All -> null
SearchType.Sends.Files -> null
SearchType.Sends.Texts -> null
SearchType.Vault.All -> null
SearchType.Vault.Cards -> null
is SearchType.Vault.Collection -> collectionId
is SearchType.Vault.Folder -> folderId
SearchType.Vault.Identities -> null
SearchType.Vault.Logins -> null
SearchType.Vault.NoFolder -> null
SearchType.Vault.SecureNotes -> null
SearchType.Vault.Trash -> null
}

View file

@ -0,0 +1,84 @@
package com.x8bit.bitwarden.ui.platform.feature.search
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
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.Text
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.Alignment
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.compose.ui.unit.dp
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
import com.x8bit.bitwarden.ui.platform.components.NavigationIcon
/**
* The search UI for vault items or send items.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchScreen(
onNavigateBack: () -> Unit,
viewModel: SearchViewModel = hiltViewModel(),
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
EventsEffect(viewModel = viewModel) { event ->
when (event) {
SearchEvent.NavigateBack -> onNavigateBack()
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
BitwardenScaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
// TODO: Implement search appbar (BIT-640)
BitwardenTopAppBar(
title = "Search",
scrollBehavior = scrollBehavior,
navigationIcon = NavigationIcon(
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(SearchAction.BackClick) }
},
),
)
},
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
// TODO: Search Bar (BIT-640)
Text(text = state.searchType.toString())
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Not yet implemented")
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
}

View file

@ -0,0 +1,63 @@
package com.x8bit.bitwarden.ui.platform.feature.search
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
private const val KEY_STATE = "state"
/**
* View model for the search screen.
*/
@HiltViewModel
class SearchViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : BaseViewModel<SearchState, SearchEvent, SearchAction>(
// We load the state from the savedStateHandle for testing purposes.
initialState = savedStateHandle[KEY_STATE]
?: SearchState(
searchType = SearchArgs(savedStateHandle).type,
),
) {
override fun handleAction(action: SearchAction) {
when (action) {
SearchAction.BackClick -> handleBackClick()
}
}
private fun handleBackClick() {
sendEvent(SearchEvent.NavigateBack)
}
}
/**
* Represents the overall state for the [SearchScreen].
*/
@Parcelize
data class SearchState(
val searchType: SearchType,
) : Parcelable
/**
* Models actions for the [SearchScreen].
*/
sealed class SearchAction {
/**
* User clicked the back button.
*/
data object BackClick : SearchAction()
}
/**
* Models events for the [SearchScreen].
*/
sealed class SearchEvent {
/**
* Navigates back to the previous screen.
*/
data object NavigateBack : SearchEvent()
}

View file

@ -0,0 +1,85 @@
package com.x8bit.bitwarden.ui.platform.feature.search.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
/**
* Represents the difference between searching sends and searching vault items.
*/
sealed class SearchType : Parcelable {
/**
* Indicates that we should be searching sends.
*/
@Parcelize
sealed class Sends : SearchType() {
/**
* Indicates that we should be searching all sends.
*/
data object All : Sends()
/**
* Indicates that we should be searching only text sends.
*/
data object Texts : Sends()
/**
* Indicates that we should be searching only file sends.
*/
data object Files : Sends()
}
/**
* Indicates that we should be searching vault items.
*/
@Parcelize
sealed class Vault : SearchType() {
/**
* Indicates that we should be searching all vault items.
*/
data object All : Vault()
/**
* Indicates that we should be searching only login ciphers.
*/
data object Logins : Vault()
/**
* Indicates that we should be searching only card ciphers.
*/
data object Cards : Vault()
/**
* Indicates that we should be searching only identity ciphers.
*/
data object Identities : Vault()
/**
* Indicates that we should be searching only secure note ciphers.
*/
data object SecureNotes : Vault()
/**
* Indicates that we should be searching only ciphers in the given collection.
*/
data class Collection(
val collectionId: String,
) : Vault()
/**
* Indicates that we should be searching only ciphers not in a folder.
*/
data object NoFolder : Vault()
/**
* Indicates that we should be searching only ciphers in the given folder.
*/
data class Folder(
val folderId: String,
) : Vault()
/**
* Indicates that we should be searching only ciphers in the trash.
*/
data object Trash : Vault()
}
}

View file

@ -4,6 +4,8 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.navigation import androidx.navigation.navigation
import com.x8bit.bitwarden.ui.platform.feature.search.navigateToSearch
import com.x8bit.bitwarden.ui.platform.feature.search.searchDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.deleteaccount.deleteAccountDestination 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.settings.accountsecurity.deleteaccount.navigateToDeleteAccount
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.foldersDestination import com.x8bit.bitwarden.ui.platform.feature.settings.folders.foldersDestination
@ -67,6 +69,8 @@ fun NavGraphBuilder.vaultUnlockedGraph(
onNavigateToVaultEditItem = { onNavigateToVaultEditItem = {
navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it)) navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it))
}, },
onNavigateToSearchVault = { navController.navigateToSearch(searchType = it) },
onNavigateToSearchSend = { navController.navigateToSearch(searchType = it) },
onNavigateToAddSend = { navController.navigateToAddSend(AddSendType.AddItem) }, onNavigateToAddSend = { navController.navigateToAddSend(AddSendType.AddItem) },
onNavigateToEditSend = { navController.navigateToAddSend(AddSendType.EditItem(it)) }, onNavigateToEditSend = { navController.navigateToAddSend(AddSendType.EditItem(it)) },
onNavigateToDeleteAccount = { navController.navigateToDeleteAccount() }, onNavigateToDeleteAccount = { navController.navigateToDeleteAccount() },
@ -114,6 +118,7 @@ fun NavGraphBuilder.vaultUnlockedGraph(
passwordHistoryDestination(onNavigateBack = { navController.popBackStack() }) passwordHistoryDestination(onNavigateBack = { navController.popBackStack() })
foldersDestination(onNavigateBack = { navController.popBackStack() }) foldersDestination(onNavigateBack = { navController.popBackStack() })
generatorModalDestination(onNavigateBack = { navController.popBackStack() }) generatorModalDestination(onNavigateBack = { navController.popBackStack() })
searchDestination(onNavigateBack = { navController.popBackStack() })
} }
} }

View file

@ -4,6 +4,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
/** /**
* The functions below pertain to entry into the [VaultUnlockedNavBarScreen]. * The functions below pertain to entry into the [VaultUnlockedNavBarScreen].
@ -25,6 +26,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
onNavigateToVaultAddItem: () -> Unit, onNavigateToVaultAddItem: () -> Unit,
onNavigateToVaultItem: (vaultItemId: String) -> Unit, onNavigateToVaultItem: (vaultItemId: String) -> Unit,
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit, onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
onNavigateToAddSend: () -> Unit, onNavigateToAddSend: () -> Unit,
onNavigateToEditSend: (sendItemId: String) -> Unit, onNavigateToEditSend: (sendItemId: String) -> Unit,
onNavigateToDeleteAccount: () -> Unit, onNavigateToDeleteAccount: () -> Unit,
@ -38,6 +41,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
onNavigateToVaultAddItem = onNavigateToVaultAddItem, onNavigateToVaultAddItem = onNavigateToVaultAddItem,
onNavigateToVaultItem = onNavigateToVaultItem, onNavigateToVaultItem = onNavigateToVaultItem,
onNavigateToVaultEditItem = onNavigateToVaultEditItem, onNavigateToVaultEditItem = onNavigateToVaultEditItem,
onNavigateToSearchSend = onNavigateToSearchSend,
onNavigateToSearchVault = onNavigateToSearchVault,
onNavigateToAddSend = onNavigateToAddSend, onNavigateToAddSend = onNavigateToAddSend,
onNavigateToEditSend = onNavigateToEditSend, onNavigateToEditSend = onNavigateToEditSend,
onNavigateToDeleteAccount = onNavigateToDeleteAccount, onNavigateToDeleteAccount = onNavigateToDeleteAccount,

View file

@ -46,6 +46,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.max
import com.x8bit.bitwarden.ui.platform.base.util.toDp import com.x8bit.bitwarden.ui.platform.base.util.toDp
import com.x8bit.bitwarden.ui.platform.components.BitwardenAnimatedScrim import com.x8bit.bitwarden.ui.platform.components.BitwardenAnimatedScrim
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.platform.feature.settings.SETTINGS_GRAPH_ROUTE import com.x8bit.bitwarden.ui.platform.feature.settings.SETTINGS_GRAPH_ROUTE
import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraph import com.x8bit.bitwarden.ui.platform.feature.settings.navigateToSettingsGraph
import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph
@ -74,6 +75,8 @@ fun VaultUnlockedNavBarScreen(
onNavigateToVaultAddItem: () -> Unit, onNavigateToVaultAddItem: () -> Unit,
onNavigateToVaultItem: (vaultItemId: String) -> Unit, onNavigateToVaultItem: (vaultItemId: String) -> Unit,
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit, onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
onNavigateToAddSend: () -> Unit, onNavigateToAddSend: () -> Unit,
onNavigateToEditSend: (sendItemId: String) -> Unit, onNavigateToEditSend: (sendItemId: String) -> Unit,
onNavigateToDeleteAccount: () -> Unit, onNavigateToDeleteAccount: () -> Unit,
@ -116,6 +119,8 @@ fun VaultUnlockedNavBarScreen(
onNavigateToVaultItem = onNavigateToVaultItem, onNavigateToVaultItem = onNavigateToVaultItem,
onNavigateToVaultEditItem = onNavigateToVaultEditItem, onNavigateToVaultEditItem = onNavigateToVaultEditItem,
navigateToVaultAddItem = onNavigateToVaultAddItem, navigateToVaultAddItem = onNavigateToVaultAddItem,
onNavigateToSearchSend = onNavigateToSearchSend,
onNavigateToSearchVault = onNavigateToSearchVault,
navigateToAddSend = onNavigateToAddSend, navigateToAddSend = onNavigateToAddSend,
onNavigateToEditSend = onNavigateToEditSend, onNavigateToEditSend = onNavigateToEditSend,
navigateToDeleteAccount = onNavigateToDeleteAccount, navigateToDeleteAccount = onNavigateToDeleteAccount,
@ -151,6 +156,8 @@ private fun VaultUnlockedNavBarScaffold(
navigateToVaultAddItem: () -> Unit, navigateToVaultAddItem: () -> Unit,
onNavigateToVaultItem: (vaultItemId: String) -> Unit, onNavigateToVaultItem: (vaultItemId: String) -> Unit,
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit, onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
navigateToAddSend: () -> Unit, navigateToAddSend: () -> Unit,
onNavigateToEditSend: (sendItemId: String) -> Unit, onNavigateToEditSend: (sendItemId: String) -> Unit,
navigateToDeleteAccount: () -> Unit, navigateToDeleteAccount: () -> Unit,
@ -210,6 +217,7 @@ private fun VaultUnlockedNavBarScaffold(
onNavigateToVaultAddItemScreen = navigateToVaultAddItem, onNavigateToVaultAddItemScreen = navigateToVaultAddItem,
onNavigateToVaultItemScreen = onNavigateToVaultItem, onNavigateToVaultItemScreen = onNavigateToVaultItem,
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItem, onNavigateToVaultEditItemScreen = onNavigateToVaultEditItem,
onNavigateToSearchVault = onNavigateToSearchVault,
onDimBottomNavBarRequest = { shouldDim -> onDimBottomNavBarRequest = { shouldDim ->
shouldDimNavBar = shouldDim shouldDimNavBar = shouldDim
}, },
@ -218,6 +226,7 @@ private fun VaultUnlockedNavBarScaffold(
navController = navController, navController = navController,
onNavigateToAddSend = navigateToAddSend, onNavigateToAddSend = navigateToAddSend,
onNavigateToEditSend = onNavigateToEditSend, onNavigateToEditSend = onNavigateToEditSend,
onNavigateToSearchSend = onNavigateToSearchSend,
) )
generatorGraph( generatorGraph(
onNavigateToPasswordHistory = { navigateToPasswordHistory() }, onNavigateToPasswordHistory = { navigateToPasswordHistory() },

View file

@ -4,6 +4,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.navigation import androidx.navigation.navigation
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToSendItemListing import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToSendItemListing
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.sendItemListingDestination import com.x8bit.bitwarden.ui.vault.feature.itemlisting.sendItemListingDestination
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
@ -17,6 +18,7 @@ fun NavGraphBuilder.sendGraph(
navController: NavController, navController: NavController,
onNavigateToAddSend: () -> Unit, onNavigateToAddSend: () -> Unit,
onNavigateToEditSend: (sendItemId: String) -> Unit, onNavigateToEditSend: (sendItemId: String) -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
) { ) {
navigation( navigation(
startDestination = SEND_ROUTE, startDestination = SEND_ROUTE,
@ -31,11 +33,13 @@ fun NavGraphBuilder.sendGraph(
onNavigateToSendTextList = { onNavigateToSendTextList = {
navController.navigateToSendItemListing(VaultItemListingType.SendText) navController.navigateToSendItemListing(VaultItemListingType.SendText)
}, },
onNavigateToSearchSend = onNavigateToSearchSend,
) )
sendItemListingDestination( sendItemListingDestination(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },
onNavigateToAddSendItem = onNavigateToAddSend, onNavigateToAddSendItem = onNavigateToAddSend,
onNavigateToEditSendItem = onNavigateToEditSend, onNavigateToEditSendItem = onNavigateToEditSend,
onNavigateToSearchSend = onNavigateToSearchSend,
) )
} }
} }

View file

@ -4,6 +4,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
const val SEND_ROUTE: String = "send" const val SEND_ROUTE: String = "send"
@ -15,6 +16,7 @@ fun NavGraphBuilder.sendDestination(
onNavigateToEditSend: (sendItemId: String) -> Unit, onNavigateToEditSend: (sendItemId: String) -> Unit,
onNavigateToSendFilesList: () -> Unit, onNavigateToSendFilesList: () -> Unit,
onNavigateToSendTextList: () -> Unit, onNavigateToSendTextList: () -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
) { ) {
composableWithRootPushTransitions( composableWithRootPushTransitions(
route = SEND_ROUTE, route = SEND_ROUTE,
@ -24,6 +26,7 @@ fun NavGraphBuilder.sendDestination(
onNavigateToEditSend = onNavigateToEditSend, onNavigateToEditSend = onNavigateToEditSend,
onNavigateToSendFilesList = onNavigateToSendFilesList, onNavigateToSendFilesList = onNavigateToSendFilesList,
onNavigateToSendTextList = onNavigateToSendTextList, onNavigateToSendTextList = onNavigateToSendTextList,
onNavigateToSearchSend = onNavigateToSearchSend,
) )
} }
} }

View file

@ -39,6 +39,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
import com.x8bit.bitwarden.ui.tools.feature.send.handlers.SendHandlers import com.x8bit.bitwarden.ui.tools.feature.send.handlers.SendHandlers
@ -47,7 +48,7 @@ import kotlinx.collections.immutable.persistentListOf
/** /**
* UI for the send screen. * UI for the send screen.
*/ */
@Suppress("LongMethod") @Suppress("LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SendScreen( fun SendScreen(
@ -55,6 +56,7 @@ fun SendScreen(
onNavigateToEditSend: (sendItemId: String) -> Unit, onNavigateToEditSend: (sendItemId: String) -> Unit,
onNavigateToSendFilesList: () -> Unit, onNavigateToSendFilesList: () -> Unit,
onNavigateToSendTextList: () -> Unit, onNavigateToSendTextList: () -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
viewModel: SendViewModel = hiltViewModel(), viewModel: SendViewModel = hiltViewModel(),
intentManager: IntentManager = LocalIntentManager.current, intentManager: IntentManager = LocalIntentManager.current,
) { ) {
@ -72,6 +74,8 @@ fun SendScreen(
when (event) { when (event) {
is SendEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh() is SendEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh()
is SendEvent.NavigateToSearch -> onNavigateToSearchSend(SearchType.Sends.All)
is SendEvent.NavigateNewSend -> onNavigateToAddSend() is SendEvent.NavigateNewSend -> onNavigateToAddSend()
is SendEvent.NavigateToEditSend -> onNavigateToEditSend(event.sendId) is SendEvent.NavigateToEditSend -> onNavigateToEditSend(event.sendId)

View file

@ -233,8 +233,7 @@ class SendViewModel @Inject constructor(
} }
private fun handleSearchClick() { private fun handleSearchClick() {
// TODO: navigate to send search BIT-594 sendEvent(SendEvent.NavigateToSearch)
sendEvent(SendEvent.ShowToast("Search Not Implemented".asText()))
} }
private fun handleSyncClick() { private fun handleSyncClick() {
@ -566,6 +565,11 @@ sealed class SendEvent {
*/ */
data object NavigateToFileSends : SendEvent() data object NavigateToFileSends : SendEvent()
/**
* Navigate to the send search screen.
*/
data object NavigateToSearch : SendEvent()
/** /**
* Navigate to the send text screen. * Navigate to the send text screen.
*/ */

View file

@ -8,6 +8,7 @@ import androidx.navigation.NavType
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
private const val CARD: String = "card" private const val CARD: String = "card"
@ -54,6 +55,7 @@ fun NavGraphBuilder.vaultItemListingDestination(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateToVaultItemScreen: (id: String) -> Unit, onNavigateToVaultItemScreen: (id: String) -> Unit,
onNavigateToVaultAddItemScreen: () -> Unit, onNavigateToVaultAddItemScreen: () -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
) { ) {
internalVaultItemListingDestination( internalVaultItemListingDestination(
route = VAULT_ITEM_LISTING_ROUTE, route = VAULT_ITEM_LISTING_ROUTE,
@ -62,6 +64,7 @@ fun NavGraphBuilder.vaultItemListingDestination(
onNavigateToEditSendItem = { }, onNavigateToEditSendItem = { },
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen, onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen, onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) },
) )
} }
@ -72,6 +75,7 @@ fun NavGraphBuilder.sendItemListingDestination(
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
onNavigateToAddSendItem: () -> Unit, onNavigateToAddSendItem: () -> Unit,
onNavigateToEditSendItem: (sendId: String) -> Unit, onNavigateToEditSendItem: (sendId: String) -> Unit,
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
) { ) {
internalVaultItemListingDestination( internalVaultItemListingDestination(
route = SEND_ITEM_LISTING_ROUTE, route = SEND_ITEM_LISTING_ROUTE,
@ -80,6 +84,7 @@ fun NavGraphBuilder.sendItemListingDestination(
onNavigateToEditSendItem = onNavigateToEditSendItem, onNavigateToEditSendItem = onNavigateToEditSendItem,
onNavigateToVaultAddItemScreen = { }, onNavigateToVaultAddItemScreen = { },
onNavigateToVaultItemScreen = { }, onNavigateToVaultItemScreen = { },
onNavigateToSearch = { onNavigateToSearchSend(it as SearchType.Sends) },
) )
} }
@ -94,6 +99,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination(
onNavigateToVaultAddItemScreen: () -> Unit, onNavigateToVaultAddItemScreen: () -> Unit,
onNavigateToAddSendItem: () -> Unit, onNavigateToAddSendItem: () -> Unit,
onNavigateToEditSendItem: (sendId: String) -> Unit, onNavigateToEditSendItem: (sendId: String) -> Unit,
onNavigateToSearch: (searchType: SearchType) -> Unit,
) { ) {
composableWithPushTransitions( composableWithPushTransitions(
route = route, route = route,
@ -117,6 +123,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination(
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen, onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
onNavigateToAddSendItem = onNavigateToAddSendItem, onNavigateToAddSendItem = onNavigateToAddSendItem,
onNavigateToEditSendItem = onNavigateToEditSendItem, onNavigateToEditSendItem = onNavigateToEditSendItem,
onNavigateToSearch = onNavigateToSearch,
) )
} }
} }

View file

@ -35,6 +35,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers.VaultItemListingHandlers import com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers.VaultItemListingHandlers
@ -51,6 +52,7 @@ fun VaultItemListingScreen(
onNavigateToVaultAddItemScreen: () -> Unit, onNavigateToVaultAddItemScreen: () -> Unit,
onNavigateToAddSendItem: () -> Unit, onNavigateToAddSendItem: () -> Unit,
onNavigateToEditSendItem: (sendId: String) -> Unit, onNavigateToEditSendItem: (sendId: String) -> Unit,
onNavigateToSearch: (searchType: SearchType) -> Unit,
intentManager: IntentManager = LocalIntentManager.current, intentManager: IntentManager = LocalIntentManager.current,
viewModel: VaultItemListingViewModel = hiltViewModel(), viewModel: VaultItemListingViewModel = hiltViewModel(),
) { ) {
@ -94,11 +96,8 @@ fun VaultItemListingScreen(
onNavigateToEditSendItem(event.id) onNavigateToEditSendItem(event.id)
} }
is VaultItemListingEvent.NavigateToVaultSearchScreen -> { is VaultItemListingEvent.NavigateToSearchScreen -> {
// TODO Create vault search screen and navigation implementation BIT-213 onNavigateToSearch(event.searchType)
Toast
.makeText(context, "Navigate to the vault search screen.", Toast.LENGTH_SHORT)
.show()
} }
} }
} }

View file

@ -20,9 +20,11 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.concat import com.x8bit.bitwarden.ui.platform.base.util.concat
import com.x8bit.bitwarden.ui.platform.components.model.IconData import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.platform.components.model.IconRes import com.x8bit.bitwarden.ui.platform.components.model.IconRes
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.determineListingPredicate import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.determineListingPredicate
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toSearchType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toViewState import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toViewState
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.updateWithAdditionalDataIfNecessary import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.updateWithAdditionalDataIfNecessary
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
@ -207,7 +209,9 @@ class VaultItemListingViewModel @Inject constructor(
private fun handleSearchIconClick() { private fun handleSearchIconClick() {
sendEvent( sendEvent(
event = VaultItemListingEvent.NavigateToVaultSearchScreen, event = VaultItemListingEvent.NavigateToSearchScreen(
searchType = state.itemListingType.toSearchType(),
),
) )
} }
@ -693,9 +697,11 @@ sealed class VaultItemListingEvent {
data class NavigateToVaultItem(val id: String) : VaultItemListingEvent() data class NavigateToVaultItem(val id: String) : VaultItemListingEvent()
/** /**
* Navigates to the VaultSearchScreen. * Navigates to the SearchScreen with the given type filter.
*/ */
data object NavigateToVaultSearchScreen : VaultItemListingEvent() data class NavigateToSearchScreen(
val searchType: SearchType,
) : VaultItemListingEvent()
/** /**
* Show a share sheet with the given content. * Show a share sheet with the given content.

View file

@ -0,0 +1,28 @@
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
/**
* Transforms a [VaultItemListingState.ItemListingType] into a [SearchType].
*/
fun VaultItemListingState.ItemListingType.toSearchType(): SearchType =
when (this) {
is VaultItemListingState.ItemListingType.Vault.Card -> SearchType.Vault.Cards
is VaultItemListingState.ItemListingType.Vault.Folder -> {
folderId
?.let { SearchType.Vault.Folder(folderId = it) }
?: SearchType.Vault.NoFolder
}
is VaultItemListingState.ItemListingType.Vault.Identity -> SearchType.Vault.Identities
is VaultItemListingState.ItemListingType.Vault.Login -> SearchType.Vault.Logins
is VaultItemListingState.ItemListingType.Vault.SecureNote -> SearchType.Vault.SecureNotes
is VaultItemListingState.ItemListingType.Vault.Trash -> SearchType.Vault.Trash
is VaultItemListingState.ItemListingType.Vault.Collection -> {
SearchType.Vault.Collection(collectionId = collectionId)
}
is VaultItemListingState.ItemListingType.Send.SendFile -> SearchType.Sends.Files
is VaultItemListingState.ItemListingType.Send.SendText -> SearchType.Sends.Texts
}

View file

@ -4,6 +4,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.navigation import androidx.navigation.navigation
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListing import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListing
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.vaultItemListingDestination import com.x8bit.bitwarden.ui.vault.feature.itemlisting.vaultItemListingDestination
import com.x8bit.bitwarden.ui.vault.feature.verificationcode.navigateToVerificationCodeScreen import com.x8bit.bitwarden.ui.vault.feature.verificationcode.navigateToVerificationCodeScreen
@ -14,11 +15,13 @@ const val VAULT_GRAPH_ROUTE: String = "vault_graph"
/** /**
* Add vault destinations to the nav graph. * Add vault destinations to the nav graph.
*/ */
@Suppress("LongParameterList")
fun NavGraphBuilder.vaultGraph( fun NavGraphBuilder.vaultGraph(
navController: NavController, navController: NavController,
onNavigateToVaultAddItemScreen: () -> Unit, onNavigateToVaultAddItemScreen: () -> Unit,
onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit, onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit,
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit, onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit, onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
) { ) {
navigation( navigation(
@ -33,13 +36,14 @@ fun NavGraphBuilder.vaultGraph(
onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = {
navController.navigateToVerificationCodeScreen() navController.navigateToVerificationCodeScreen()
}, },
onNavigateToSearchVault = onNavigateToSearchVault,
onDimBottomNavBarRequest = onDimBottomNavBarRequest, onDimBottomNavBarRequest = onDimBottomNavBarRequest,
) )
vaultItemListingDestination( vaultItemListingDestination(
onNavigateBack = { navController.popBackStack() }, onNavigateBack = { navController.popBackStack() },
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen, onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen, onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
onNavigateToSearchVault = onNavigateToSearchVault,
) )
vaultVerificationCodeDestination( vaultVerificationCodeDestination(

View file

@ -4,6 +4,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions import com.x8bit.bitwarden.ui.platform.base.util.composableWithRootPushTransitions
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
const val VAULT_ROUTE: String = "vault" const val VAULT_ROUTE: String = "vault"
@ -18,6 +19,7 @@ fun NavGraphBuilder.vaultDestination(
onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit, onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit,
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit, onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit, onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit, onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
) { ) {
composableWithRootPushTransitions( composableWithRootPushTransitions(
@ -29,6 +31,7 @@ fun NavGraphBuilder.vaultDestination(
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen, onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
onNavigateToVaultItemListingScreen = onNavigateToVaultItemListingScreen, onNavigateToVaultItemListingScreen = onNavigateToVaultItemListingScreen,
onNavigateToVerificationCodeScreen = onNavigateToVerificationCodeScreen, onNavigateToVerificationCodeScreen = onNavigateToVerificationCodeScreen,
onNavigateToSearchVault = onNavigateToSearchVault,
onDimBottomNavBarRequest = onDimBottomNavBarRequest, onDimBottomNavBarRequest = onDimBottomNavBarRequest,
) )
} }

View file

@ -51,6 +51,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
import com.x8bit.bitwarden.ui.platform.theme.LocalExitManager import com.x8bit.bitwarden.ui.platform.theme.LocalExitManager
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
@ -71,6 +72,7 @@ fun VaultScreen(
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit, onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
onNavigateToVerificationCodeScreen: () -> Unit, onNavigateToVerificationCodeScreen: () -> Unit,
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit, onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit, onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
exitManager: ExitManager = LocalExitManager.current, exitManager: ExitManager = LocalExitManager.current,
) { ) {
@ -88,12 +90,7 @@ fun VaultScreen(
VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen() VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen()
VaultEvent.NavigateToVaultSearchScreen -> { VaultEvent.NavigateToVaultSearchScreen -> onNavigateToSearchVault(SearchType.Vault.All)
// TODO Create vault search screen and navigation implementation BIT-213
Toast
.makeText(context, "Navigate to the vault search screen.", Toast.LENGTH_SHORT)
.show()
}
is VaultEvent.NavigateToVerificationCodeScreen -> { is VaultEvent.NavigateToVerificationCodeScreen -> {
onNavigateToVerificationCodeScreen() onNavigateToVerificationCodeScreen()

View file

@ -0,0 +1,42 @@
package com.x8bit.bitwarden.ui.platform.feature.search
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class SearchScreenTest : BaseComposeTest() {
private val mutableEventFlow = bufferedMutableSharedFlow<SearchEvent>()
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
private val viewModel = mockk<SearchViewModel> {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
private var onNavigateBackCalled = false
@Before
fun setup() {
composeTestRule.setContent {
SearchScreen(
viewModel = viewModel,
onNavigateBack = { onNavigateBackCalled = true },
)
}
}
@Test
fun `NavigateBack should call onNavigateBack`() {
mutableEventFlow.tryEmit(SearchEvent.NavigateBack)
assertTrue(onNavigateBackCalled)
}
}
private val DEFAULT_STATE: SearchState = SearchState(
searchType = SearchType.Vault.All,
)

View file

@ -0,0 +1,83 @@
package com.x8bit.bitwarden.ui.platform.feature.search
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class SearchViewModelTest : BaseViewModelTest() {
@Test
fun `initial state should be correct when not set`() {
val viewModel = createViewModel(initialState = null)
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
}
@Test
fun `initial state should be correct when set`() {
val state = DEFAULT_STATE.copy(searchType = SearchType.Sends.All)
val viewModel = createViewModel(initialState = state)
assertEquals(state, viewModel.stateFlow.value)
}
@Test
fun `BackClick should emit NavigateBack`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SearchAction.BackClick)
assertEquals(SearchEvent.NavigateBack, awaitItem())
}
}
@Suppress("CyclomaticComplexMethod")
private fun createViewModel(
initialState: SearchState? = null,
): SearchViewModel = SearchViewModel(
SavedStateHandle().apply {
set("state", initialState)
set(
"search_type",
when (initialState?.searchType) {
SearchType.Sends.All -> "search_type_sends_all"
SearchType.Sends.Files -> "search_type_sends_file"
SearchType.Sends.Texts -> "search_type_sends_text"
SearchType.Vault.All -> "search_type_vault_all"
SearchType.Vault.Cards -> "search_type_vault_cards"
is SearchType.Vault.Collection -> "search_type_vault_collection"
is SearchType.Vault.Folder -> "search_type_vault_folder"
SearchType.Vault.Identities -> "search_type_vault_identities"
SearchType.Vault.Logins -> "search_type_vault_logins"
SearchType.Vault.NoFolder -> "search_type_vault_no_folder"
SearchType.Vault.SecureNotes -> "search_type_vault_secure_notes"
SearchType.Vault.Trash -> "search_type_vault_trash"
null -> "search_type_vault_all"
},
)
set(
"search_type_id",
when (val searchType = initialState?.searchType) {
SearchType.Sends.All -> null
SearchType.Sends.Files -> null
SearchType.Sends.Texts -> null
SearchType.Vault.All -> null
SearchType.Vault.Cards -> null
is SearchType.Vault.Collection -> searchType.collectionId
is SearchType.Vault.Folder -> searchType.folderId
SearchType.Vault.Identities -> null
SearchType.Vault.Logins -> null
SearchType.Vault.NoFolder -> null
SearchType.Vault.SecureNotes -> null
SearchType.Vault.Trash -> null
null -> null
},
)
},
)
}
private val DEFAULT_STATE: SearchState = SearchState(
searchType = SearchType.Vault.All,
)

View file

@ -47,6 +47,8 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
onNavigateToDeleteAccount = {}, onNavigateToDeleteAccount = {},
onNavigateToFolders = {}, onNavigateToFolders = {},
onNavigateToPasswordHistory = {}, onNavigateToPasswordHistory = {},
onNavigateToSearchVault = {},
onNavigateToSearchSend = {},
) )
} }
} }

View file

@ -41,6 +41,7 @@ class SendScreenTest : BaseComposeTest() {
private var onNavigateToNewSendCalled = false private var onNavigateToNewSendCalled = false
private var onNavigateToSendFilesListCalled = false private var onNavigateToSendFilesListCalled = false
private var onNavigateToSendTextListCalled = false private var onNavigateToSendTextListCalled = false
private var onNavigateToSendSearchCalled = false
private var onNavigateToEditSendId: String? = null private var onNavigateToEditSendId: String? = null
private val intentManager = mockk<IntentManager> { private val intentManager = mockk<IntentManager> {
@ -63,6 +64,7 @@ class SendScreenTest : BaseComposeTest() {
onNavigateToEditSend = { onNavigateToEditSendId = it }, onNavigateToEditSend = { onNavigateToEditSendId = it },
onNavigateToSendFilesList = { onNavigateToSendFilesListCalled = true }, onNavigateToSendFilesList = { onNavigateToSendFilesListCalled = true },
onNavigateToSendTextList = { onNavigateToSendTextListCalled = true }, onNavigateToSendTextList = { onNavigateToSendTextListCalled = true },
onNavigateToSearchSend = { onNavigateToSendSearchCalled = true },
intentManager = intentManager, intentManager = intentManager,
) )
} }
@ -93,6 +95,12 @@ class SendScreenTest : BaseComposeTest() {
assertTrue(onNavigateToSendTextListCalled) assertTrue(onNavigateToSendTextListCalled)
} }
@Test
fun `on NavigateToSearch should call onNavigateToSendSearch`() {
mutableEventFlow.tryEmit(SendEvent.NavigateToSearch)
assertTrue(onNavigateToSendSearchCalled)
}
@Test @Test
fun `on NavigateToAboutSend should call launchUri on intentManager`() { fun `on NavigateToAboutSend should call launchUri on intentManager`() {
mutableEventFlow.tryEmit(SendEvent.NavigateToAboutSend) mutableEventFlow.tryEmit(SendEvent.NavigateToAboutSend)

View file

@ -108,11 +108,11 @@ class SendViewModelTest : BaseViewModelTest() {
} }
@Test @Test
fun `SearchClick should emit ShowToast`() = runTest { fun `SearchClick should emit NavigateToSearch`() = runTest {
val viewModel = createViewModel() val viewModel = createViewModel()
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(SendAction.SearchClick) viewModel.trySendAction(SendAction.SearchClick)
assertEquals(SendEvent.ShowToast("Search Not Implemented".asText()), awaitItem()) assertEquals(SendEvent.NavigateToSearch, awaitItem())
} }
} }

View file

@ -26,6 +26,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.IconData import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.platform.components.model.IconRes import com.x8bit.bitwarden.ui.platform.components.model.IconRes
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import com.x8bit.bitwarden.ui.util.isProgressBar import com.x8bit.bitwarden.ui.util.isProgressBar
@ -50,6 +51,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
private var onNavigateToAddSendScreenCalled = false private var onNavigateToAddSendScreenCalled = false
private var onNavigateToEditSendItemId: String? = null private var onNavigateToEditSendItemId: String? = null
private var onNavigateToVaultItemId: String? = null private var onNavigateToVaultItemId: String? = null
private var onNavigateToSearchType: SearchType? = null
private val intentManager: IntentManager = mockk { private val intentManager: IntentManager = mockk {
every { shareText(any()) } just runs every { shareText(any()) } just runs
@ -72,6 +74,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreenCalled = true }, onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreenCalled = true },
onNavigateToAddSendItem = { onNavigateToAddSendScreenCalled = true }, onNavigateToAddSendItem = { onNavigateToAddSendScreenCalled = true },
onNavigateToEditSendItem = { onNavigateToEditSendItemId = it }, onNavigateToEditSendItem = { onNavigateToEditSendItemId = it },
onNavigateToSearch = { onNavigateToSearchType = it },
) )
} }
} }
@ -147,6 +150,13 @@ class VaultItemListingScreenTest : BaseComposeTest() {
assertTrue(onNavigateToAddSendScreenCalled) assertTrue(onNavigateToAddSendScreenCalled)
} }
@Test
fun `NavigateToVaultSearchScreen should call onNavigateToSearch`() {
val searchType = SearchType.Vault.SecureNotes
mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToSearchScreen(searchType))
assertEquals(searchType, onNavigateToSearchType)
}
@Test @Test
fun `NavigateToSendItem event should call onNavigateToEditSendItemId`() { fun `NavigateToSendItem event should call onNavigateToEditSendItemId`() {
val sendId = "sendId" val sendId = "sendId"

View file

@ -22,6 +22,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.concat import com.x8bit.bitwarden.ui.platform.base.util.concat
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockDisplayItemForCipher import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.createMockDisplayItemForCipher
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
@ -106,10 +107,11 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
@Test @Test
fun `SearchIconClick should emit NavigateToVaultSearchScreen`() = runTest { fun `SearchIconClick should emit NavigateToVaultSearchScreen`() = runTest {
val searchType = SearchType.Vault.Logins
val viewModel = createVaultItemListingViewModel() val viewModel = createVaultItemListingViewModel()
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.actionChannel.trySend(VaultItemListingsAction.SearchIconClick) viewModel.actionChannel.trySend(VaultItemListingsAction.SearchIconClick)
assertEquals(VaultItemListingEvent.NavigateToVaultSearchScreen, awaitItem()) assertEquals(VaultItemListingEvent.NavigateToSearchScreen(searchType), awaitItem())
} }
} }

View file

@ -0,0 +1,111 @@
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class VaultItemListingStateExtensionsTest {
@Test
fun `toSearchType should return Texts when item type is SendText`() {
val expected = SearchType.Sends.Texts
val itemType = VaultItemListingState.ItemListingType.Send.SendText
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return Files when item type is SendFile`() {
val expected = SearchType.Sends.Files
val itemType = VaultItemListingState.ItemListingType.Send.SendFile
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return Logins when item type is Login`() {
val expected = SearchType.Vault.Logins
val itemType = VaultItemListingState.ItemListingType.Vault.Login
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return Card when item type is Card`() {
val expected = SearchType.Vault.Cards
val itemType = VaultItemListingState.ItemListingType.Vault.Card
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return Identities when item type is Identity`() {
val expected = SearchType.Vault.Identities
val itemType = VaultItemListingState.ItemListingType.Vault.Identity
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return SecureNotes when item type is SecureNote`() {
val expected = SearchType.Vault.SecureNotes
val itemType = VaultItemListingState.ItemListingType.Vault.SecureNote
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return Trash when item type is Trash`() {
val expected = SearchType.Vault.Trash
val itemType = VaultItemListingState.ItemListingType.Vault.Trash
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return Folder when item type is Folder with an ID`() {
val folderId = "folderId"
val expected = SearchType.Vault.Folder(folderId)
val itemType = VaultItemListingState.ItemListingType.Vault.Folder(folderId)
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return NoFolder when item type is Folder without an ID`() {
val expected = SearchType.Vault.NoFolder
val itemType = VaultItemListingState.ItemListingType.Vault.Folder(null)
val result = itemType.toSearchType()
assertEquals(expected, result)
}
@Test
fun `toSearchType should return Collection when item type is Collection`() {
val collectionId = "collectionId"
val expected = SearchType.Vault.Collection(collectionId)
val itemType = VaultItemListingState.ItemListingType.Vault.Collection(collectionId)
val result = itemType.toSearchType()
assertEquals(expected, result)
}
}

View file

@ -61,6 +61,7 @@ class VaultScreenTest : BaseComposeTest() {
private var onNavigateToVaultItemListingType: VaultItemListingType? = null private var onNavigateToVaultItemListingType: VaultItemListingType? = null
private var onDimBottomNavBarRequestCalled = false private var onDimBottomNavBarRequestCalled = false
private var onNavigateToVerificationCodeScreen = false private var onNavigateToVerificationCodeScreen = false
private var onNavigateToSearchScreen = false
private val exitManager = mockk<ExitManager>(relaxed = true) private val exitManager = mockk<ExitManager>(relaxed = true)
private val mutableEventFlow = bufferedMutableSharedFlow<VaultEvent>() private val mutableEventFlow = bufferedMutableSharedFlow<VaultEvent>()
@ -81,6 +82,7 @@ class VaultScreenTest : BaseComposeTest() {
onNavigateToVaultItemListingScreen = { onNavigateToVaultItemListingType = it }, onNavigateToVaultItemListingScreen = { onNavigateToVaultItemListingType = it },
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true }, onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true }, onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true },
onNavigateToSearchVault = { onNavigateToSearchScreen = true },
exitManager = exitManager, exitManager = exitManager,
) )
} }
@ -586,6 +588,12 @@ class VaultScreenTest : BaseComposeTest() {
assertTrue(onNavigateToVaultAddItemScreenCalled) assertTrue(onNavigateToVaultAddItemScreenCalled)
} }
@Test
fun `NavigateToVaultSearchScreen event should call onNavigateToSearchScreen`() {
mutableEventFlow.tryEmit(VaultEvent.NavigateToVaultSearchScreen)
assertTrue(onNavigateToSearchScreen)
}
@Test @Test
fun `NavigateToVaultItem event should call onNavigateToVaultItemScreen`() { fun `NavigateToVaultItem event should call onNavigateToVaultItemScreen`() {
val id = "id4321" val id = "id4321"