mirror of
https://github.com/bitwarden/android.git
synced 2024-11-23 18:06:08 +03:00
Add basic navigation for the search screen (#707)
This commit is contained in:
parent
e3547f4e13
commit
75fbadb67b
27 changed files with 728 additions and 21 deletions
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navigation
|
||||
import com.x8bit.bitwarden.ui.platform.feature.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.navigateToDeleteAccount
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.folders.foldersDestination
|
||||
|
@ -67,6 +69,8 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
|||
onNavigateToVaultEditItem = {
|
||||
navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it))
|
||||
},
|
||||
onNavigateToSearchVault = { navController.navigateToSearch(searchType = it) },
|
||||
onNavigateToSearchSend = { navController.navigateToSearch(searchType = it) },
|
||||
onNavigateToAddSend = { navController.navigateToAddSend(AddSendType.AddItem) },
|
||||
onNavigateToEditSend = { navController.navigateToAddSend(AddSendType.EditItem(it)) },
|
||||
onNavigateToDeleteAccount = { navController.navigateToDeleteAccount() },
|
||||
|
@ -114,6 +118,7 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
|||
passwordHistoryDestination(onNavigateBack = { navController.popBackStack() })
|
||||
foldersDestination(onNavigateBack = { navController.popBackStack() })
|
||||
generatorModalDestination(onNavigateBack = { navController.popBackStack() })
|
||||
searchDestination(onNavigateBack = { navController.popBackStack() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
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].
|
||||
|
@ -25,6 +26,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
|||
onNavigateToVaultAddItem: () -> Unit,
|
||||
onNavigateToVaultItem: (vaultItemId: String) -> Unit,
|
||||
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onNavigateToAddSend: () -> Unit,
|
||||
onNavigateToEditSend: (sendItemId: String) -> Unit,
|
||||
onNavigateToDeleteAccount: () -> Unit,
|
||||
|
@ -38,6 +41,8 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
|
|||
onNavigateToVaultAddItem = onNavigateToVaultAddItem,
|
||||
onNavigateToVaultItem = onNavigateToVaultItem,
|
||||
onNavigateToVaultEditItem = onNavigateToVaultEditItem,
|
||||
onNavigateToSearchSend = onNavigateToSearchSend,
|
||||
onNavigateToSearchVault = onNavigateToSearchVault,
|
||||
onNavigateToAddSend = onNavigateToAddSend,
|
||||
onNavigateToEditSend = onNavigateToEditSend,
|
||||
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||
|
|
|
@ -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.components.BitwardenAnimatedScrim
|
||||
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.navigateToSettingsGraph
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.settingsGraph
|
||||
|
@ -74,6 +75,8 @@ fun VaultUnlockedNavBarScreen(
|
|||
onNavigateToVaultAddItem: () -> Unit,
|
||||
onNavigateToVaultItem: (vaultItemId: String) -> Unit,
|
||||
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onNavigateToAddSend: () -> Unit,
|
||||
onNavigateToEditSend: (sendItemId: String) -> Unit,
|
||||
onNavigateToDeleteAccount: () -> Unit,
|
||||
|
@ -116,6 +119,8 @@ fun VaultUnlockedNavBarScreen(
|
|||
onNavigateToVaultItem = onNavigateToVaultItem,
|
||||
onNavigateToVaultEditItem = onNavigateToVaultEditItem,
|
||||
navigateToVaultAddItem = onNavigateToVaultAddItem,
|
||||
onNavigateToSearchSend = onNavigateToSearchSend,
|
||||
onNavigateToSearchVault = onNavigateToSearchVault,
|
||||
navigateToAddSend = onNavigateToAddSend,
|
||||
onNavigateToEditSend = onNavigateToEditSend,
|
||||
navigateToDeleteAccount = onNavigateToDeleteAccount,
|
||||
|
@ -151,6 +156,8 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
navigateToVaultAddItem: () -> Unit,
|
||||
onNavigateToVaultItem: (vaultItemId: String) -> Unit,
|
||||
onNavigateToVaultEditItem: (vaultItemId: String) -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
navigateToAddSend: () -> Unit,
|
||||
onNavigateToEditSend: (sendItemId: String) -> Unit,
|
||||
navigateToDeleteAccount: () -> Unit,
|
||||
|
@ -210,6 +217,7 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
onNavigateToVaultAddItemScreen = navigateToVaultAddItem,
|
||||
onNavigateToVaultItemScreen = onNavigateToVaultItem,
|
||||
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItem,
|
||||
onNavigateToSearchVault = onNavigateToSearchVault,
|
||||
onDimBottomNavBarRequest = { shouldDim ->
|
||||
shouldDimNavBar = shouldDim
|
||||
},
|
||||
|
@ -218,6 +226,7 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
navController = navController,
|
||||
onNavigateToAddSend = navigateToAddSend,
|
||||
onNavigateToEditSend = onNavigateToEditSend,
|
||||
onNavigateToSearchSend = onNavigateToSearchSend,
|
||||
)
|
||||
generatorGraph(
|
||||
onNavigateToPasswordHistory = { navigateToPasswordHistory() },
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
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.sendItemListingDestination
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
|
@ -17,6 +18,7 @@ fun NavGraphBuilder.sendGraph(
|
|||
navController: NavController,
|
||||
onNavigateToAddSend: () -> Unit,
|
||||
onNavigateToEditSend: (sendItemId: String) -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = SEND_ROUTE,
|
||||
|
@ -31,11 +33,13 @@ fun NavGraphBuilder.sendGraph(
|
|||
onNavigateToSendTextList = {
|
||||
navController.navigateToSendItemListing(VaultItemListingType.SendText)
|
||||
},
|
||||
onNavigateToSearchSend = onNavigateToSearchSend,
|
||||
)
|
||||
sendItemListingDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToAddSendItem = onNavigateToAddSend,
|
||||
onNavigateToEditSendItem = onNavigateToEditSend,
|
||||
onNavigateToSearchSend = onNavigateToSearchSend,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
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"
|
||||
|
||||
|
@ -15,6 +16,7 @@ fun NavGraphBuilder.sendDestination(
|
|||
onNavigateToEditSend: (sendItemId: String) -> Unit,
|
||||
onNavigateToSendFilesList: () -> Unit,
|
||||
onNavigateToSendTextList: () -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
) {
|
||||
composableWithRootPushTransitions(
|
||||
route = SEND_ROUTE,
|
||||
|
@ -24,6 +26,7 @@ fun NavGraphBuilder.sendDestination(
|
|||
onNavigateToEditSend = onNavigateToEditSend,
|
||||
onNavigateToSendFilesList = onNavigateToSendFilesList,
|
||||
onNavigateToSendTextList = onNavigateToSendTextList,
|
||||
onNavigateToSearchSend = onNavigateToSearchSend,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.LoadingDialogState
|
||||
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.theme.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.handlers.SendHandlers
|
||||
|
@ -47,7 +48,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
/**
|
||||
* UI for the send screen.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SendScreen(
|
||||
|
@ -55,6 +56,7 @@ fun SendScreen(
|
|||
onNavigateToEditSend: (sendItemId: String) -> Unit,
|
||||
onNavigateToSendFilesList: () -> Unit,
|
||||
onNavigateToSendTextList: () -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
viewModel: SendViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
|
@ -72,6 +74,8 @@ fun SendScreen(
|
|||
when (event) {
|
||||
is SendEvent.DismissPullToRefresh -> pullToRefreshState?.endRefresh()
|
||||
|
||||
is SendEvent.NavigateToSearch -> onNavigateToSearchSend(SearchType.Sends.All)
|
||||
|
||||
is SendEvent.NavigateNewSend -> onNavigateToAddSend()
|
||||
|
||||
is SendEvent.NavigateToEditSend -> onNavigateToEditSend(event.sendId)
|
||||
|
|
|
@ -233,8 +233,7 @@ class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleSearchClick() {
|
||||
// TODO: navigate to send search BIT-594
|
||||
sendEvent(SendEvent.ShowToast("Search Not Implemented".asText()))
|
||||
sendEvent(SendEvent.NavigateToSearch)
|
||||
}
|
||||
|
||||
private fun handleSyncClick() {
|
||||
|
@ -566,6 +565,11 @@ sealed class SendEvent {
|
|||
*/
|
||||
data object NavigateToFileSends : SendEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the send search screen.
|
||||
*/
|
||||
data object NavigateToSearch : SendEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the send text screen.
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.navigation.NavType
|
|||
import androidx.navigation.navArgument
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
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
|
||||
|
||||
private const val CARD: String = "card"
|
||||
|
@ -54,6 +55,7 @@ fun NavGraphBuilder.vaultItemListingDestination(
|
|||
onNavigateBack: () -> Unit,
|
||||
onNavigateToVaultItemScreen: (id: String) -> Unit,
|
||||
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
) {
|
||||
internalVaultItemListingDestination(
|
||||
route = VAULT_ITEM_LISTING_ROUTE,
|
||||
|
@ -62,6 +64,7 @@ fun NavGraphBuilder.vaultItemListingDestination(
|
|||
onNavigateToEditSendItem = { },
|
||||
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
|
||||
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
|
||||
onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -72,6 +75,7 @@ fun NavGraphBuilder.sendItemListingDestination(
|
|||
onNavigateBack: () -> Unit,
|
||||
onNavigateToAddSendItem: () -> Unit,
|
||||
onNavigateToEditSendItem: (sendId: String) -> Unit,
|
||||
onNavigateToSearchSend: (searchType: SearchType.Sends) -> Unit,
|
||||
) {
|
||||
internalVaultItemListingDestination(
|
||||
route = SEND_ITEM_LISTING_ROUTE,
|
||||
|
@ -80,6 +84,7 @@ fun NavGraphBuilder.sendItemListingDestination(
|
|||
onNavigateToEditSendItem = onNavigateToEditSendItem,
|
||||
onNavigateToVaultAddItemScreen = { },
|
||||
onNavigateToVaultItemScreen = { },
|
||||
onNavigateToSearch = { onNavigateToSearchSend(it as SearchType.Sends) },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -94,6 +99,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination(
|
|||
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||
onNavigateToAddSendItem: () -> Unit,
|
||||
onNavigateToEditSendItem: (sendId: String) -> Unit,
|
||||
onNavigateToSearch: (searchType: SearchType) -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = route,
|
||||
|
@ -117,6 +123,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination(
|
|||
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
|
||||
onNavigateToAddSendItem = onNavigateToAddSendItem,
|
||||
onNavigateToEditSendItem = onNavigateToEditSendItem,
|
||||
onNavigateToSearch = onNavigateToSearch,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.LoadingDialogState
|
||||
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.theme.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers.VaultItemListingHandlers
|
||||
|
@ -51,6 +52,7 @@ fun VaultItemListingScreen(
|
|||
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||
onNavigateToAddSendItem: () -> Unit,
|
||||
onNavigateToEditSendItem: (sendId: String) -> Unit,
|
||||
onNavigateToSearch: (searchType: SearchType) -> Unit,
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
viewModel: VaultItemListingViewModel = hiltViewModel(),
|
||||
) {
|
||||
|
@ -94,11 +96,8 @@ fun VaultItemListingScreen(
|
|||
onNavigateToEditSendItem(event.id)
|
||||
}
|
||||
|
||||
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()
|
||||
is VaultItemListingEvent.NavigateToSearchScreen -> {
|
||||
onNavigateToSearch(event.searchType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.components.model.IconData
|
||||
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.util.determineListingPredicate
|
||||
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.updateWithAdditionalDataIfNecessary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
|
@ -207,7 +209,9 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
|
||||
private fun handleSearchIconClick() {
|
||||
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()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -4,6 +4,7 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
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.vaultItemListingDestination
|
||||
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.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun NavGraphBuilder.vaultGraph(
|
||||
navController: NavController,
|
||||
onNavigateToVaultAddItemScreen: () -> Unit,
|
||||
onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit,
|
||||
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
|
@ -33,13 +36,14 @@ fun NavGraphBuilder.vaultGraph(
|
|||
onNavigateToVerificationCodeScreen = {
|
||||
navController.navigateToVerificationCodeScreen()
|
||||
},
|
||||
|
||||
onNavigateToSearchVault = onNavigateToSearchVault,
|
||||
onDimBottomNavBarRequest = onDimBottomNavBarRequest,
|
||||
)
|
||||
vaultItemListingDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
|
||||
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
|
||||
onNavigateToSearchVault = onNavigateToSearchVault,
|
||||
)
|
||||
|
||||
vaultVerificationCodeDestination(
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
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
|
||||
|
||||
const val VAULT_ROUTE: String = "vault"
|
||||
|
@ -18,6 +19,7 @@ fun NavGraphBuilder.vaultDestination(
|
|||
onNavigateToVaultItemScreen: (vaultItemId: String) -> Unit,
|
||||
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
|
||||
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||
) {
|
||||
composableWithRootPushTransitions(
|
||||
|
@ -29,6 +31,7 @@ fun NavGraphBuilder.vaultDestination(
|
|||
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
|
||||
onNavigateToVaultItemListingScreen = onNavigateToVaultItemListingScreen,
|
||||
onNavigateToVerificationCodeScreen = onNavigateToVerificationCodeScreen,
|
||||
onNavigateToSearchVault = onNavigateToSearchVault,
|
||||
onDimBottomNavBarRequest = onDimBottomNavBarRequest,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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.OverflowMenuItemData
|
||||
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.theme.LocalExitManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
|
@ -71,6 +72,7 @@ fun VaultScreen(
|
|||
onNavigateToVaultEditItemScreen: (vaultItemId: String) -> Unit,
|
||||
onNavigateToVerificationCodeScreen: () -> Unit,
|
||||
onNavigateToVaultItemListingScreen: (vaultItemType: VaultItemListingType) -> Unit,
|
||||
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
|
||||
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
|
||||
exitManager: ExitManager = LocalExitManager.current,
|
||||
) {
|
||||
|
@ -88,12 +90,7 @@ fun VaultScreen(
|
|||
|
||||
VaultEvent.NavigateToAddItemScreen -> onNavigateToVaultAddItemScreen()
|
||||
|
||||
VaultEvent.NavigateToVaultSearchScreen -> {
|
||||
// TODO Create vault search screen and navigation implementation BIT-213
|
||||
Toast
|
||||
.makeText(context, "Navigate to the vault search screen.", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
VaultEvent.NavigateToVaultSearchScreen -> onNavigateToSearchVault(SearchType.Vault.All)
|
||||
|
||||
is VaultEvent.NavigateToVerificationCodeScreen -> {
|
||||
onNavigateToVerificationCodeScreen()
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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,
|
||||
)
|
|
@ -47,6 +47,8 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
|||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToFolders = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
onNavigateToSearchVault = {},
|
||||
onNavigateToSearchSend = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ class SendScreenTest : BaseComposeTest() {
|
|||
private var onNavigateToNewSendCalled = false
|
||||
private var onNavigateToSendFilesListCalled = false
|
||||
private var onNavigateToSendTextListCalled = false
|
||||
private var onNavigateToSendSearchCalled = false
|
||||
private var onNavigateToEditSendId: String? = null
|
||||
|
||||
private val intentManager = mockk<IntentManager> {
|
||||
|
@ -63,6 +64,7 @@ class SendScreenTest : BaseComposeTest() {
|
|||
onNavigateToEditSend = { onNavigateToEditSendId = it },
|
||||
onNavigateToSendFilesList = { onNavigateToSendFilesListCalled = true },
|
||||
onNavigateToSendTextList = { onNavigateToSendTextListCalled = true },
|
||||
onNavigateToSearchSend = { onNavigateToSendSearchCalled = true },
|
||||
intentManager = intentManager,
|
||||
)
|
||||
}
|
||||
|
@ -93,6 +95,12 @@ class SendScreenTest : BaseComposeTest() {
|
|||
assertTrue(onNavigateToSendTextListCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToSearch should call onNavigateToSendSearch`() {
|
||||
mutableEventFlow.tryEmit(SendEvent.NavigateToSearch)
|
||||
assertTrue(onNavigateToSendSearchCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToAboutSend should call launchUri on intentManager`() {
|
||||
mutableEventFlow.tryEmit(SendEvent.NavigateToAboutSend)
|
||||
|
|
|
@ -108,11 +108,11 @@ class SendViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `SearchClick should emit ShowToast`() = runTest {
|
||||
fun `SearchClick should emit NavigateToSearch`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SendAction.SearchClick)
|
||||
assertEquals(SendEvent.ShowToast("Search Not Implemented".asText()), awaitItem())
|
||||
assertEquals(SendEvent.NavigateToSearch, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.components.model.IconData
|
||||
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.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||
|
@ -50,6 +51,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
private var onNavigateToAddSendScreenCalled = false
|
||||
private var onNavigateToEditSendItemId: String? = null
|
||||
private var onNavigateToVaultItemId: String? = null
|
||||
private var onNavigateToSearchType: SearchType? = null
|
||||
|
||||
private val intentManager: IntentManager = mockk {
|
||||
every { shareText(any()) } just runs
|
||||
|
@ -72,6 +74,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
onNavigateToVaultAddItemScreen = { onNavigateToVaultAddItemScreenCalled = true },
|
||||
onNavigateToAddSendItem = { onNavigateToAddSendScreenCalled = true },
|
||||
onNavigateToEditSendItem = { onNavigateToEditSendItemId = it },
|
||||
onNavigateToSearch = { onNavigateToSearchType = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -147,6 +150,13 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
assertTrue(onNavigateToAddSendScreenCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToVaultSearchScreen should call onNavigateToSearch`() {
|
||||
val searchType = SearchType.Vault.SecureNotes
|
||||
mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToSearchScreen(searchType))
|
||||
assertEquals(searchType, onNavigateToSearchType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToSendItem event should call onNavigateToEditSendItemId`() {
|
||||
val sendId = "sendId"
|
||||
|
|
|
@ -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.util.asText
|
||||
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.util.createMockDisplayItemForCipher
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
|
@ -106,10 +107,11 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `SearchIconClick should emit NavigateToVaultSearchScreen`() = runTest {
|
||||
val searchType = SearchType.Vault.Logins
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultItemListingsAction.SearchIconClick)
|
||||
assertEquals(VaultItemListingEvent.NavigateToVaultSearchScreen, awaitItem())
|
||||
assertEquals(VaultItemListingEvent.NavigateToSearchScreen(searchType), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -61,6 +61,7 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
private var onNavigateToVaultItemListingType: VaultItemListingType? = null
|
||||
private var onDimBottomNavBarRequestCalled = false
|
||||
private var onNavigateToVerificationCodeScreen = false
|
||||
private var onNavigateToSearchScreen = false
|
||||
private val exitManager = mockk<ExitManager>(relaxed = true)
|
||||
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<VaultEvent>()
|
||||
|
@ -81,6 +82,7 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
onNavigateToVaultItemListingScreen = { onNavigateToVaultItemListingType = it },
|
||||
onDimBottomNavBarRequest = { onDimBottomNavBarRequestCalled = true },
|
||||
onNavigateToVerificationCodeScreen = { onNavigateToVerificationCodeScreen = true },
|
||||
onNavigateToSearchVault = { onNavigateToSearchScreen = true },
|
||||
exitManager = exitManager,
|
||||
)
|
||||
}
|
||||
|
@ -586,6 +588,12 @@ class VaultScreenTest : BaseComposeTest() {
|
|||
assertTrue(onNavigateToVaultAddItemScreenCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToVaultSearchScreen event should call onNavigateToSearchScreen`() {
|
||||
mutableEventFlow.tryEmit(VaultEvent.NavigateToVaultSearchScreen)
|
||||
assertTrue(onNavigateToSearchScreen)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToVaultItem event should call onNavigateToVaultItemScreen`() {
|
||||
val id = "id4321"
|
||||
|
|
Loading…
Reference in a new issue