diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditAdditionalOptions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditAdditionalOptions.kt index 43659d21a1b..c77d806f2a2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditAdditionalOptions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditAdditionalOptions.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -111,10 +111,10 @@ fun LazyListScope.vaultAddEditAdditionalOptions( } } - items( + itemsIndexed( items = commonState.customFieldData, - key = { "customField_${it.itemId}" }, - ) { customItem -> + key = { _, customItem -> "customField_${customItem.itemId}" }, + ) { index, customItem -> Column( modifier = Modifier .animateItem() @@ -126,6 +126,8 @@ fun LazyListScope.vaultAddEditAdditionalOptions( customField = customItem, onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect, + showMoveUpAction = index > 0, + showMoveDownAction = index < commonState.customFieldData.lastIndex, onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange, supportedLinkedTypes = itemType.vaultLinkedFieldTypes, cardStyle = CardStyle.Full, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt index b63faba11a1..d0e52e2518b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt @@ -34,6 +34,8 @@ import kotlinx.collections.immutable.toImmutableList * @param customField The field that is to be displayed. * @param onCustomFieldValueChange Invoked when the user changes the value. * @param onCustomFieldAction Invoked when the user chooses an action. + * @param showMoveUpAction Whether the move up action is displayed in the action dialog. + * @param showMoveDownAction Whether the move down action is displayed in the action dialog. * @param onHiddenVisibilityChanged Emits when the visibility of a hidden custom field changes. * @param cardStyle Indicates the type of card style to be applied. * @param modifier Modifier for the UI elements. @@ -45,6 +47,8 @@ fun VaultAddEditCustomField( customField: VaultAddEditState.Custom, onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit, onCustomFieldAction: (CustomFieldAction, VaultAddEditState.Custom) -> Unit, + showMoveUpAction: Boolean, + showMoveDownAction: Boolean, onHiddenVisibilityChanged: (Boolean) -> Unit, cardStyle: CardStyle, modifier: Modifier = Modifier, @@ -55,6 +59,16 @@ fun VaultAddEditCustomField( if (shouldShowChooserDialog) { CustomFieldActionDialog( + customFieldActions = CustomFieldAction + .entries + .filter { action -> + when (action) { + CustomFieldAction.MOVE_UP -> showMoveUpAction + CustomFieldAction.MOVE_DOWN -> showMoveDownAction + else -> true + } + } + .toImmutableList(), onCustomFieldAction = { action -> shouldShowChooserDialog = false onCustomFieldAction(action, customField) @@ -281,6 +295,7 @@ private fun CustomFieldLinkedField( */ @Composable private fun CustomFieldActionDialog( + customFieldActions: ImmutableList, onCustomFieldAction: (CustomFieldAction) -> Unit, onEditAction: () -> Unit, onDismissRequest: () -> Unit, @@ -289,20 +304,18 @@ private fun CustomFieldActionDialog( title = stringResource(id = BitwardenString.options), onDismissRequest = onDismissRequest, ) { - CustomFieldAction - .entries - .forEach { action -> - BitwardenBasicDialogRow( - text = action.actionText.invoke(), - onClick = { - if (action == CustomFieldAction.EDIT) { - onEditAction() - } else { - onCustomFieldAction(action) - } - }, - ) - } + customFieldActions.forEach { action -> + BitwardenBasicDialogRow( + text = action.actionText.invoke(), + onClick = { + if (action == CustomFieldAction.EDIT) { + onEditAction() + } else { + onCustomFieldAction(action) + } + }, + ) + } } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index 3b610fb859f..b68046c7ce3 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -3516,7 +3516,7 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { composeTestRule .onAllNodesWithContentDescriptionAfterScroll("Edit") - .onFirst() + .onLast() .performClick() composeTestRule @@ -3527,16 +3527,117 @@ class VaultAddEditScreenTest : BitwardenComposeTest() { viewModel.trySendAction( VaultAddEditAction.Common.CustomFieldActionSelect( customFieldAction = CustomFieldAction.MOVE_UP, - customField = VaultAddEditState.Custom.BooleanField( - itemId = "Test ID 1", - name = "TestBoolean", - value = false, + customField = VaultAddEditState.Custom.HiddenField( + itemId = "Test ID 3", + name = "TestHidden", + value = "TestHiddenVal", ), ), ) } } + @Test + fun `clicking first custom field edit icon only shows move down action in dialog`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + // Expand the additional options UI before interacting with it + composeTestRule + .onNodeWithTextAfterScroll(text = "Additional options") + .performClick() + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit") + .onFirst() + .performClick() + + composeTestRule + .onNodeWithText("Move Up") + .assertIsNotDisplayed() + + composeTestRule + .onNodeWithText("Move down") + .assertIsDisplayed() + } + + @Test + fun `clicking middle custom field edit icon shows both move actions in dialog`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + // Expand the additional options UI before interacting with it + composeTestRule + .onNodeWithTextAfterScroll(text = "Additional options") + .performClick() + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit")[1] + .performClick() + + composeTestRule + .onNodeWithText("Move Up") + .assertIsDisplayed() + + composeTestRule + .onNodeWithText("Move down") + .assertIsDisplayed() + } + + @Test + fun `clicking last custom field edit icon only shows move up action in dialog`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + // Expand the additional options UI before interacting with it + composeTestRule + .onNodeWithTextAfterScroll(text = "Additional options") + .performClick() + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit") + .onLast() + .performClick() + + composeTestRule + .onNodeWithText("Move Up") + .assertIsDisplayed() + + composeTestRule + .onNodeWithText("Move down") + .assertIsNotDisplayed() + } + + @Test + fun `clicking single custom field edit icon shows no move actions in dialog`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS.copy( + viewState = VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddEditState.Custom.BooleanField("Test ID 1", "TestBoolean", false), + ), + ), + type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + isIndividualVaultDisabled = false, + ), + ) + + // Expand the additional options UI before interacting with it + composeTestRule + .onNodeWithTextAfterScroll(text = "Additional options") + .performClick() + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit") + .onFirst() + .performClick() + + composeTestRule + .onNodeWithText("Move Up") + .assertIsNotDisplayed() + + composeTestRule + .onNodeWithText("Move down") + .assertIsNotDisplayed() + } + @Test fun `Menu should display correct items when cipher is in a collection`() { mutableStateFlow.update {