Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> : AbstractOsmQuestForm<T>() {

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<TextFieldValue> = mutableStateOf(TextFieldValue())
protected val name: String get() = textFieldValue.value.text

abstract val suggestions: List<String>?

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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ class AddAtmOperatorForm : ANameWithSuggestionsForm<String>() {
override val suggestions: List<String>? get() = countryInfo.atmOperators

override fun onClickOk() {
applyAnswer(name!!)
applyAnswer(name)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ class AddChargingStationOperatorForm : ANameWithSuggestionsForm<String>() {
override val suggestions: List<String>? get() = countryInfo.chargingStationOperators

override fun onClickOk() {
applyAnswer(name!!)
applyAnswer(name)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class AddClothingBinOperator : OsmElementQuestType<ClothingBinOperatorAnswer>, A
is ClothingBinOperator -> {
tags["operator"] = answer.name
}
is NoClothingBinOperatorSigned -> {
is ClothingBinOperatorAnswer.NoneSigned -> {
tags["operator:signed"] = "no"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ class AddClothingBinOperatorForm : ANameWithSuggestionsForm<ClothingBinOperatorA
)

override fun onClickOk() {
applyAnswer(ClothingBinOperator(name!!))
applyAnswer(ClothingBinOperator(name))
}

private fun confirmNoSign() {
activity?.let { AlertDialog.Builder(it)
.setMessage(R.string.quest_generic_confirmation_title)
.setPositiveButton(R.string.quest_generic_confirmation_yes) { _, _ -> applyAnswer(NoClothingBinOperatorSigned) }
.setPositiveButton(R.string.quest_generic_confirmation_yes) { _, _ -> applyAnswer(ClothingBinOperatorAnswer.NoneSigned) }
.setNegativeButton(R.string.quest_generic_confirmation_no, null)
.show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ class AddParcelLockerBrandForm : ANameWithSuggestionsForm<String>() {
override val suggestions: List<String>? get() = countryInfo.parcelLockerBrand

override fun onClickOk() {
applyAnswer(name!!)
applyAnswer(name)
}
}

This file was deleted.

10 changes: 0 additions & 10 deletions app/src/androidMain/res/layout/quest_name_suggestion.xml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<String> = 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)
}
}
}
}
}