mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1410: Move to organization from edit item screen (#760)
This commit is contained in:
parent
7a416de9c9
commit
c977f7617a
6 changed files with 199 additions and 1 deletions
|
@ -93,6 +93,9 @@ fun NavGraphBuilder.vaultUnlockedGraph(
|
|||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToGeneratorModal = { navController.navigateToGeneratorModal(mode = it) },
|
||||
onNavigateToAttachments = { navController.navigateToAttachment(it) },
|
||||
onNavigateToMoveToOrganization = {
|
||||
navController.navigateToVaultMoveToOrganization(it)
|
||||
},
|
||||
)
|
||||
vaultMoveToOrganizationDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
|
|
|
@ -42,12 +42,14 @@ data class VaultAddEditArgs(
|
|||
/**
|
||||
* Add the vault add & edit screen to the nav graph.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
fun NavGraphBuilder.vaultAddEditDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToManualCodeEntryScreen: () -> Unit,
|
||||
onNavigateToQrCodeScanScreen: () -> Unit,
|
||||
onNavigateToGeneratorModal: (GeneratorMode.Modal) -> Unit,
|
||||
onNavigateToAttachments: (cipherId: String) -> Unit,
|
||||
onNavigateToMoveToOrganization: (cipherId: String) -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = ADD_EDIT_ITEM_ROUTE,
|
||||
|
@ -61,6 +63,7 @@ fun NavGraphBuilder.vaultAddEditDestination(
|
|||
onNavigateToQrCodeScanScreen = onNavigateToQrCodeScanScreen,
|
||||
onNavigateToGeneratorModal = onNavigateToGeneratorModal,
|
||||
onNavigateToAttachments = onNavigateToAttachments,
|
||||
onNavigateToMoveToOrganization = onNavigateToMoveToOrganization,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ fun VaultAddEditScreen(
|
|||
onNavigateToManualCodeEntryScreen: () -> Unit,
|
||||
onNavigateToGeneratorModal: (GeneratorMode.Modal) -> Unit,
|
||||
onNavigateToAttachments: (cipherId: String) -> Unit,
|
||||
onNavigateToMoveToOrganization: (cipherId: String) -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
|
@ -78,7 +79,13 @@ fun VaultAddEditScreen(
|
|||
}
|
||||
|
||||
is VaultAddEditEvent.NavigateToAttachments -> onNavigateToAttachments(event.cipherId)
|
||||
|
||||
is VaultAddEditEvent.NavigateToMoveToOrganization -> {
|
||||
onNavigateToMoveToOrganization(event.cipherId)
|
||||
}
|
||||
is VaultAddEditEvent.NavigateToCollections -> {
|
||||
// TODO implement Collections in BIT-1575
|
||||
Toast.makeText(context, "Not yet implemented.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
VaultAddEditEvent.NavigateBack -> onNavigateBack.invoke()
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +147,28 @@ fun VaultAddEditScreen(
|
|||
},
|
||||
)
|
||||
.takeUnless { state.isAddItemMode },
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.move_to_organization),
|
||||
onClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.MoveToOrganizationClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.takeUnless { state.isAddItemMode || state.isCipherInCollection },
|
||||
OverflowMenuItemData(
|
||||
text = stringResource(id = R.string.collections),
|
||||
onClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.Common.CollectionsClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.takeUnless { state.isAddItemMode || !state.isCipherInCollection },
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
|
@ -142,6 +142,8 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is VaultAddEditAction.Common.AttachmentsClick -> handleAttachmentsClick()
|
||||
is VaultAddEditAction.Common.MoveToOrganizationClick -> handleMoveToOrganizationClick()
|
||||
is VaultAddEditAction.Common.CollectionsClick -> handleCollectionsClick()
|
||||
is VaultAddEditAction.Common.CloseClick -> handleCloseClick()
|
||||
is VaultAddEditAction.Common.DismissDialog -> handleDismissDialog()
|
||||
is VaultAddEditAction.Common.SaveClick -> handleSaveClick()
|
||||
|
@ -262,6 +264,14 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
onEdit { sendEvent(VaultAddEditEvent.NavigateToAttachments(it.vaultItemId)) }
|
||||
}
|
||||
|
||||
private fun handleMoveToOrganizationClick() {
|
||||
onEdit { sendEvent(VaultAddEditEvent.NavigateToMoveToOrganization(it.vaultItemId)) }
|
||||
}
|
||||
|
||||
private fun handleCollectionsClick() {
|
||||
onEdit { sendEvent(VaultAddEditEvent.NavigateToCollections(it.vaultItemId)) }
|
||||
}
|
||||
|
||||
private fun handleCloseClick() {
|
||||
sendEvent(
|
||||
event = VaultAddEditEvent.NavigateBack,
|
||||
|
@ -1136,6 +1146,17 @@ data class VaultAddEditState(
|
|||
is VaultAddEditType.CloneItem -> R.string.add_item.asText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the cipher is in a collection.
|
||||
*/
|
||||
val isCipherInCollection: Boolean
|
||||
get() = (viewState as? ViewState.Content)
|
||||
?.common
|
||||
?.originalCipher
|
||||
?.collectionIds
|
||||
?.isNotEmpty()
|
||||
?: false
|
||||
|
||||
/**
|
||||
* Helper to determine if the UI should display the content in add item mode.
|
||||
*/
|
||||
|
@ -1442,6 +1463,20 @@ sealed class VaultAddEditEvent {
|
|||
val cipherId: String,
|
||||
) : VaultAddEditEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the move to organization screen.
|
||||
*/
|
||||
data class NavigateToMoveToOrganization(
|
||||
val cipherId: String,
|
||||
) : VaultAddEditEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the collections screen.
|
||||
*/
|
||||
data class NavigateToCollections(
|
||||
val cipherId: String,
|
||||
) : VaultAddEditEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the QR code scan screen.
|
||||
*/
|
||||
|
@ -1491,6 +1526,16 @@ sealed class VaultAddEditAction {
|
|||
*/
|
||||
data object AttachmentsClick : Common()
|
||||
|
||||
/**
|
||||
* The user has clicked the move to organization overflow option.
|
||||
*/
|
||||
data object MoveToOrganizationClick : Common()
|
||||
|
||||
/**
|
||||
* The user has clicked the collections overflow option.
|
||||
*/
|
||||
data object CollectionsClick : Common()
|
||||
|
||||
/**
|
||||
* Represents the action when a type option is selected.
|
||||
*
|
||||
|
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.test.hasAnyAncestor
|
|||
import androidx.compose.ui.test.hasContentDescription
|
||||
import androidx.compose.ui.test.hasSetTextAction
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.isPopup
|
||||
import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onFirst
|
||||
|
@ -28,6 +29,7 @@ import androidx.compose.ui.test.performTextClearance
|
|||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
|
||||
|
@ -61,6 +63,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
private var onNavigateToManualCodeEntryScreenCalled = false
|
||||
private var onNavigateToGeneratorModalType: GeneratorMode.Modal? = null
|
||||
private var onNavigateToAttachmentsId: String? = null
|
||||
private var onNavigateToMoveToOrganizationId: String? = null
|
||||
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<VaultAddEditEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE_LOGIN)
|
||||
|
@ -83,6 +86,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
},
|
||||
onNavigateToGeneratorModal = { onNavigateToGeneratorModalType = it },
|
||||
onNavigateToAttachments = { onNavigateToAttachmentsId = it },
|
||||
onNavigateToMoveToOrganization = { onNavigateToMoveToOrganizationId = it },
|
||||
viewModel = viewModel,
|
||||
permissionsManager = fakePermissionManager,
|
||||
)
|
||||
|
@ -127,6 +131,14 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
assertEquals(cipherId, onNavigateToAttachmentsId)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on NavigateToMoveToOrganization event should invoke onNavigateToMoveToOrganization with the correct ID`() {
|
||||
val cipherId = "cipherId-1234"
|
||||
mutableEventFlow.tryEmit(VaultAddEditEvent.NavigateToMoveToOrganization(cipherId))
|
||||
assertEquals(cipherId, onNavigateToMoveToOrganizationId)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on NavigateToGeneratorModal event in username mode should invoke NavigateToGeneratorModal with Username Generator Mode `() {
|
||||
|
@ -2104,6 +2116,74 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Menu should display correct items when cipher is in a collection`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "mockId-1"),
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = createMockCipherView(1),
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Attachments")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Move to Organization")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Menu should display correct items when cipher is not in a collection`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "mockId-1"),
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(
|
||||
originalCipher = createMockCipherView(1).copy(
|
||||
collectionIds = emptyList(),
|
||||
),
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes,
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("More")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Attachments")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Move to Organization")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Collections")
|
||||
.filterToOne(hasAnyAncestor(isPopup()))
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
//region Helper functions
|
||||
|
||||
private fun updateLoginType(
|
||||
|
|
|
@ -180,6 +180,44 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MoveToOrganizationClick should emit NavigateToMoveToOrganization`() = runTest {
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType)
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = initState,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddEditAction.Common.MoveToOrganizationClick)
|
||||
assertEquals(
|
||||
VaultAddEditEvent.NavigateToMoveToOrganization(DEFAULT_EDIT_ITEM_ID),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CollectionsClick should emit NavigateToCollections`() = runTest {
|
||||
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
|
||||
val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType)
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = initState,
|
||||
vaultAddEditType = vaultAddEditType,
|
||||
),
|
||||
)
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddEditAction.Common.CollectionsClick)
|
||||
assertEquals(
|
||||
VaultAddEditEvent.NavigateToCollections(DEFAULT_EDIT_ITEM_ID),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in add mode, SaveClick should show dialog, and remove it once an item is saved`() =
|
||||
runTest {
|
||||
|
|
Loading…
Reference in a new issue