diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMultiSelectButton.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMultiSelectButton.kt index 57d696116d..1c9cf581e9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMultiSelectButton.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenMultiSelectButton.kt @@ -4,25 +4,17 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.width -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics @@ -31,7 +23,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import com.x8bit.bitwarden.R -import com.x8bit.bitwarden.ui.platform.base.util.toDp +import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme /** @@ -41,7 +33,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme * When the field is clicked, a dropdown menu appears with a list of options to select from. * * @param label The descriptive text label for the [OutlinedTextField]. - * @param options A list of strings representing the available options in the dropdown menu. + * @param options A list of strings representing the available options in the dialog. * @param selectedOption The currently selected option that is displayed in the [OutlinedTextField]. * @param onOptionSelected A lambda that is invoked when an option * is selected from the dropdown menu. @@ -56,14 +48,7 @@ fun BitwardenMultiSelectButton( onOptionSelected: (String) -> Unit, modifier: Modifier = Modifier, ) { - var expanded by remember { mutableStateOf(false) } - val focusRequester = remember { FocusRequester() } - var textFieldWidth by remember { mutableIntStateOf(0) } - - // Watch for changes to 'expanded' and request focus if needed - LaunchedEffect(expanded) { - if (expanded) focusRequester.requestFocus() else focusRequester.freeFocus() - } + var shouldShowDialog by remember { mutableStateOf(false) } Box( modifier = modifier @@ -82,12 +67,8 @@ fun BitwardenMultiSelectButton( indication = null, interactionSource = remember { MutableInteractionSource() }, ) { - expanded = !expanded - } - .onGloballyPositioned { coordinates -> - textFieldWidth = coordinates.size.width - } - .focusRequester(focusRequester), + shouldShowDialog = !shouldShowDialog + }, textStyle = MaterialTheme.typography.bodyLarge, readOnly = true, label = { @@ -97,7 +78,7 @@ fun BitwardenMultiSelectButton( }, value = selectedOption, onValueChange = onOptionSelected, - enabled = expanded, + enabled = shouldShowDialog, trailingIcon = { Icon( painter = painterResource(id = R.drawable.ic_region_select_dropdown), @@ -114,23 +95,21 @@ fun BitwardenMultiSelectButton( disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant, ), ) - DropdownMenu( - modifier = Modifier.width(textFieldWidth.toDp()), - expanded = expanded, - onDismissRequest = { - expanded = false - focusRequester.freeFocus() - }, - ) { - options.forEach { optionString -> - DropdownMenuItem( - text = { Text(text = optionString) }, - onClick = { - expanded = false - onOptionSelected(optionString) - focusRequester.freeFocus() - }, - ) + if (shouldShowDialog) { + BitwardenSelectionDialog( + title = label.asText(), + onDismissRequest = { shouldShowDialog = false }, + ) { + options.forEach { optionString -> + BitwardenSelectionRow( + text = optionString.asText(), + isSelected = optionString == selectedOption, + onClick = { + shouldShowDialog = false + onOptionSelected(optionString) + }, + ) + } } } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt index 29916d5f8c..a2aeaaf645 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt @@ -3,12 +3,15 @@ package com.x8bit.bitwarden.ui.tools.feature.generator import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.test.SemanticsMatcher.Companion.expectValue +import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsOff import androidx.compose.ui.test.assertIsOn import androidx.compose.ui.test.filterToOne +import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasProgressBarRangeInfo +import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onChildren import androidx.compose.ui.test.onLast @@ -96,6 +99,7 @@ class GeneratorScreenTest : BaseComposeTest() { .onAllNodesWithText(text = "Password") .onLast() .performScrollTo() + .assert(hasAnyAncestor(isDialog())) .performClick() verify { @@ -103,6 +107,11 @@ class GeneratorScreenTest : BaseComposeTest() { GeneratorAction.MainTypeOptionSelect(GeneratorState.MainTypeOption.PASSWORD), ) } + + // Make sure dialog is hidden: + composeTestRule + .onNode(isDialog()) + .assertDoesNotExist() } @Test @@ -120,6 +129,7 @@ class GeneratorScreenTest : BaseComposeTest() { composeTestRule .onAllNodesWithText(text = "Passphrase") .onLast() + .assert(hasAnyAncestor(isDialog())) .performClick() verify { @@ -129,6 +139,11 @@ class GeneratorScreenTest : BaseComposeTest() { ), ) } + + // Make sure dialog is hidden: + composeTestRule + .onNode(isDialog()) + .assertDoesNotExist() } //region Passcode Password Tests