Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4803b1d5 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Do not expand when SettingsDropdownBox disabled

Fix: 326347738
Test: manual - with gallery
Test: unit test
Change-Id: Ie0c092709db7b9c72481e31bd1834b407e6d0ba3
parent 2962a624
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsDropdownBoxPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
@@ -99,7 +99,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
                OperateListPageProvider,
                EditorMainPageProvider,
                SettingsOutlinedTextFieldPageProvider,
                SettingsExposedDropdownMenuBoxPageProvider,
                SettingsDropdownBoxPageProvider,
                SettingsDropdownCheckBoxProvider,
                SettingsTextFieldPasswordPageProvider,
                SearchScaffoldPageProvider,
+1 −1
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ object EditorMainPageProvider : SettingsPageProvider {
        return listOf(
            SettingsOutlinedTextFieldPageProvider.buildInjectEntry().setLink(fromPage = owner)
                .build(),
            SettingsExposedDropdownMenuBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
            SettingsDropdownBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
                .build(),
            SettingsDropdownCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
                .build(),
+40 −15
Original line number Diff line number Diff line
@@ -28,16 +28,15 @@ import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
import com.android.settingslib.spa.widget.editor.SettingsDropdownBox
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold

private const val TITLE = "Sample SettingsExposedDropdownMenuBox"
private const val TITLE = "Sample SettingsDropdownBox"

object SettingsExposedDropdownMenuBoxPageProvider : SettingsPageProvider {
    override val name = "SettingsExposedDropdownMenuBox"
    private const val exposedDropdownMenuBoxLabel = "ExposedDropdownMenuBoxLabel"
object SettingsDropdownBoxPageProvider : SettingsPageProvider {
    override val name = "SettingsDropdownBox"

    override fun getTitle(arguments: Bundle?): String {
        return TITLE
@@ -45,16 +44,42 @@ object SettingsExposedDropdownMenuBoxPageProvider : SettingsPageProvider {

    @Composable
    override fun Page(arguments: Bundle?) {
        var selectedItem by remember { mutableIntStateOf(-1) }
        val options = listOf("item1", "item2", "item3")
        RegularScaffold(title = TITLE) {
            SettingsExposedDropdownMenuBox(
                label = exposedDropdownMenuBoxLabel,
                options = options,
            Regular()
            NotEnabled()
            Empty()
        }
    }

    @Composable
    private fun Regular() {
        var selectedItem by remember { mutableIntStateOf(-1) }
        SettingsDropdownBox(
            label = "SettingsDropdownBox",
            options = listOf("item1", "item2", "item3"),
            selectedOptionIndex = selectedItem,
        ) { selectedItem = it }
    }

    @Composable
    private fun NotEnabled() {
        var selectedItem by remember { mutableIntStateOf(0) }
        SettingsDropdownBox(
            label = "Not enabled",
            options = listOf("item1", "item2", "item3"),
            enabled = false,
            selectedOptionIndex = selectedItem,
                enabled = true,
                onselectedOptionTextChange = { selectedItem = it })
        ) { selectedItem = it }
    }

    @Composable
    private fun Empty() {
        var selectedItem by remember { mutableIntStateOf(-1) }
        SettingsDropdownBox(
            label = "Empty",
            options = emptyList(),
            selectedOptionIndex = selectedItem,
        ) { selectedItem = it }
    }

    fun buildInjectEntry(): SettingsEntryBuilder {
@@ -70,8 +95,8 @@ object SettingsExposedDropdownMenuBoxPageProvider : SettingsPageProvider {

@Preview(showBackground = true)
@Composable
private fun SettingsExposedDropdownMenuBoxPagePreview() {
private fun SettingsDropdownBoxPagePreview() {
    SettingsTheme {
        SettingsExposedDropdownMenuBoxPageProvider.Page(null)
        SettingsDropdownBoxPageProvider.Page(null)
    }
}
+32 −55
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ package com.android.settingslib.spa.widget.editor
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
@@ -31,80 +30,58 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme

@Composable
internal interface DropdownTextBoxScope {
    fun dismiss()
}

@OptIn(ExperimentalMaterial3Api::class)
fun SettingsExposedDropdownMenuBox(
@Composable
internal fun DropdownTextBox(
    label: String,
    options: List<String>,
    selectedOptionIndex: Int,
    enabled: Boolean,
    onselectedOptionTextChange: (Int) -> Unit,
    text: String,
    enabled: Boolean = true,
    errorMessage: String? = null,
    content: @Composable DropdownTextBoxScope.() -> Unit,
) {
    var expanded by remember { mutableStateOf(false) }
    val scope = remember {
        object : DropdownTextBoxScope {
            override fun dismiss() {
                expanded = false
            }
        }
    }
    ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = { expanded = it },
        onExpandedChange = { expanded = enabled && it },
        modifier = Modifier
            .width(350.dp)
            .padding(SettingsDimension.menuFieldPadding),
            .padding(SettingsDimension.menuFieldPadding)
            .width(Width),
    ) {
        OutlinedTextField(
            // The `menuAnchor` modifier must be passed to the text field for correctness.
            modifier = Modifier
                .menuAnchor()
                .fillMaxWidth(),
            value = options.getOrElse(selectedOptionIndex) { "" },
            value = text,
            onValueChange = { },
            label = { Text(text = label) },
            trailingIcon = {
                ExposedDropdownMenuDefaults.TrailingIcon(
                    expanded = expanded
                )
            },
            trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
            singleLine = true,
            readOnly = true,
            enabled = enabled
            enabled = enabled,
            isError = errorMessage != null,
            supportingText = errorMessage?.let { { Text(text = it) } },
        )
        if (options.isNotEmpty()) {
        ExposedDropdownMenu(
            expanded = expanded,
                modifier = Modifier
                    .fillMaxWidth(),
            modifier = Modifier.width(Width),
            onDismissRequest = { expanded = false },
            ) {
                options.forEach { option ->
                    DropdownMenuItem(
                        text = { Text(option) },
                        onClick = {
                            onselectedOptionTextChange(options.indexOf(option))
                            expanded = false
                        },
                        contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
                    )
                }
            }
        }
        ) { scope.content() }
    }
}

@Preview
@Composable
private fun SettingsExposedDropdownMenuBoxsPreview() {
    val item1 = "item1"
    val item2 = "item2"
    val item3 = "item3"
    val options = listOf(item1, item2, item3)
    SettingsTheme {
        SettingsExposedDropdownMenuBox(
            label = "ExposedDropdownMenuBoxLabel",
            options = options,
            selectedOptionIndex = 0,
            enabled = true,
            onselectedOptionTextChange = {})
    }
}
 No newline at end of file
private val Width = 310.dp
+69 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.spa.widget.editor

import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme

@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun SettingsDropdownBox(
    label: String,
    options: List<String>,
    selectedOptionIndex: Int,
    enabled: Boolean = true,
    onSelectedOptionChange: (Int) -> Unit,
) {
    DropdownTextBox(
        label = label,
        text = options.getOrElse(selectedOptionIndex) { "" },
        enabled = enabled && options.isNotEmpty(),
    ) {
        options.forEachIndexed { index, option ->
            DropdownMenuItem(
                text = { Text(option) },
                onClick = {
                    dismiss()
                    onSelectedOptionChange(index)
                },
                contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
            )
        }
    }
}

@Preview
@Composable
private fun SettingsDropdownBoxPreview() {
    val item1 = "item1"
    val item2 = "item2"
    val item3 = "item3"
    val options = listOf(item1, item2, item3)
    SettingsTheme {
        SettingsDropdownBox(
            label = "ExposedDropdownMenuBoxLabel",
            options = options,
            selectedOptionIndex = 0,
            enabled = true,
        ) {}
    }
}
Loading