diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/ANameWithSuggestionsForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/ANameWithSuggestionsForm.kt index cd7702d1b25..40617566744 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/ANameWithSuggestionsForm.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/ANameWithSuggestionsForm.kt @@ -2,33 +2,54 @@ package de.westnordost.streetcomplete.quests import android.os.Bundle import android.view.View -import android.widget.ArrayAdapter -import androidx.core.widget.doAfterTextChanged +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.TextFieldValue import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.databinding.QuestNameSuggestionBinding -import de.westnordost.streetcomplete.util.ktx.nonBlankTextOrNull +import de.westnordost.streetcomplete.databinding.ComposeViewBinding +import de.westnordost.streetcomplete.ui.common.AutoCompleteTextField +import de.westnordost.streetcomplete.ui.theme.largeInput +import de.westnordost.streetcomplete.ui.util.content abstract class ANameWithSuggestionsForm : AbstractOsmQuestForm() { - final override val contentLayoutResId = R.layout.quest_name_suggestion - private val binding by contentViewBinding(QuestNameSuggestionBinding::bind) + override val contentLayoutResId = R.layout.compose_view + private val binding by contentViewBinding(ComposeViewBinding::bind) - protected val name get() = binding.nameInput.nonBlankTextOrNull + private val textFieldValue: MutableState = mutableStateOf(TextFieldValue()) + protected val name: String get() = textFieldValue.value.text abstract val suggestions: List? override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - suggestions?.let { - binding.nameInput.setAdapter(ArrayAdapter( - requireContext(), - android.R.layout.simple_dropdown_item_1line, it - )) - } - - binding.nameInput.doAfterTextChanged { checkIsFormComplete() } + binding.composeViewBase.content { Surface { + + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + AutoCompleteTextField( + value = textFieldValue.value, + onValueChange = { + textFieldValue.value = it + checkIsFormComplete() + }, + suggestions = suggestions + ?.takeIf { name.length >= 3 } + ?.filter { it.startsWith(name, ignoreCase = true) } + .orEmpty(), + textStyle = MaterialTheme.typography.largeInput, + ) + } + } } } - override fun isFormComplete() = name != null + override fun isFormComplete() = textFieldValue.value.text.isNotEmpty() } diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperatorForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperatorForm.kt index 5923cc83309..5404174ce5b 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperatorForm.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/atm_operator/AddAtmOperatorForm.kt @@ -7,6 +7,6 @@ class AddAtmOperatorForm : ANameWithSuggestionsForm() { override val suggestions: List? get() = countryInfo.atmOperators override fun onClickOk() { - applyAnswer(name!!) + applyAnswer(name) } } diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperatorForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperatorForm.kt index be6b8dab029..e465631268a 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperatorForm.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/charging_station_operator/AddChargingStationOperatorForm.kt @@ -7,6 +7,6 @@ class AddChargingStationOperatorForm : ANameWithSuggestionsForm() { override val suggestions: List? get() = countryInfo.chargingStationOperators override fun onClickOk() { - applyAnswer(name!!) + applyAnswer(name) } } diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt index dfafe058f2c..a3f05c448d3 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperator.kt @@ -56,7 +56,7 @@ class AddClothingBinOperator : OsmElementQuestType, A is ClothingBinOperator -> { tags["operator"] = answer.name } - is NoClothingBinOperatorSigned -> { + is ClothingBinOperatorAnswer.NoneSigned -> { tags["operator:signed"] = "no" } } diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorForm.kt index 5dbb30e8957..5cb791b6a6b 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorForm.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/AddClothingBinOperatorForm.kt @@ -14,13 +14,13 @@ class AddClothingBinOperatorForm : ANameWithSuggestionsForm applyAnswer(NoClothingBinOperatorSigned) } + .setPositiveButton(R.string.quest_generic_confirmation_yes) { _, _ -> applyAnswer(ClothingBinOperatorAnswer.NoneSigned) } .setNegativeButton(R.string.quest_generic_confirmation_no, null) .show() } diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/parcel_locker_brand/AddParcelLockerBrandForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/parcel_locker_brand/AddParcelLockerBrandForm.kt index b7fc3460f08..a9e3dd307c9 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/parcel_locker_brand/AddParcelLockerBrandForm.kt +++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/parcel_locker_brand/AddParcelLockerBrandForm.kt @@ -7,6 +7,6 @@ class AddParcelLockerBrandForm : ANameWithSuggestionsForm() { override val suggestions: List? get() = countryInfo.parcelLockerBrand override fun onClickOk() { - applyAnswer(name!!) + applyAnswer(name) } } diff --git a/app/src/androidMain/res/layout/dialog_living_street_confirmation.xml b/app/src/androidMain/res/layout/dialog_living_street_confirmation.xml deleted file mode 100644 index 5c121ab0ee4..00000000000 --- a/app/src/androidMain/res/layout/dialog_living_street_confirmation.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/app/src/androidMain/res/layout/quest_name_suggestion.xml b/app/src/androidMain/res/layout/quest_name_suggestion.xml deleted file mode 100644 index 68f301074f6..00000000000 --- a/app/src/androidMain/res/layout/quest_name_suggestion.xml +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/ClothingBinOperatorAnswer.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/ClothingBinOperatorAnswer.kt similarity index 56% rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/ClothingBinOperatorAnswer.kt rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/ClothingBinOperatorAnswer.kt index 44b9fde7043..52c588c34ac 100644 --- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/ClothingBinOperatorAnswer.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/clothing_bin_operator/ClothingBinOperatorAnswer.kt @@ -1,6 +1,7 @@ package de.westnordost.streetcomplete.quests.clothing_bin_operator -sealed interface ClothingBinOperatorAnswer +sealed interface ClothingBinOperatorAnswer { + data object NoneSigned : ClothingBinOperatorAnswer +} -data object NoClothingBinOperatorSigned : ClothingBinOperatorAnswer data class ClothingBinOperator(val name: String) : ClothingBinOperatorAnswer diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/AutoCompleteTextField.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/AutoCompleteTextField.kt new file mode 100644 index 00000000000..d1675c67f62 --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/AutoCompleteTextField.kt @@ -0,0 +1,105 @@ +package de.westnordost.streetcomplete.ui.common + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.DropdownMenu +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldColors +import androidx.compose.material.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +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.onFocusChanged +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.window.PopupProperties +import kotlin.text.startsWith + +/** A TextField that suggests auto-completion based on the supplied [suggestions] */ +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun AutoCompleteTextField( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + modifier: Modifier = Modifier, + suggestions: List = emptyList(), + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions(), + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + interactionSource: MutableInteractionSource? = null, + shape: Shape = TextFieldDefaults.TextFieldShape, + colors: TextFieldColors = TextFieldDefaults.textFieldColors(), +) { + var isFocused by remember { mutableStateOf(false) } + var isExpanded by remember { mutableStateOf(false) } + + Box(modifier = modifier) { + TextField( + value = value, + onValueChange = { + onValueChange(it) + if (!isExpanded) isExpanded = true + }, + modifier = Modifier.onFocusChanged { isFocused = it.isFocused }, + enabled = enabled, + readOnly = readOnly, + textStyle = textStyle, + label = label, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + isError = isError, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + singleLine = singleLine, + maxLines = maxLines, + minLines = minLines, + interactionSource = interactionSource, + shape = shape, + colors = colors, + ) + + val expanded = isFocused && isExpanded && suggestions.isNotEmpty() + DropdownMenu( + expanded = expanded, + onDismissRequest = { /* closes automatically when text field is not focused */ }, + properties = PopupProperties(focusable = false), + ) { + for (suggestion in suggestions.filter { it.startsWith(value.text, ignoreCase = true) }) { + DropdownMenuItem(onClick = { + onValueChange(value.copy( + text = suggestion, + selection = TextRange(suggestion.length) + )) + isExpanded = false + }) { + Text(suggestion) + } + } + } + } +}