@@ -3,8 +3,14 @@ package de.westnordost.streetcomplete.overlays.places
33import android.os.Bundle
44import android.view.View
55import androidx.appcompat.app.AlertDialog
6+ import androidx.compose.foundation.layout.Arrangement
7+ import androidx.compose.foundation.layout.Box
68import androidx.compose.foundation.layout.Column
9+ import androidx.compose.foundation.layout.Spacer
10+ import androidx.compose.foundation.layout.defaultMinSize
11+ import androidx.compose.foundation.layout.fillMaxWidth
712import androidx.compose.foundation.layout.padding
13+ import androidx.compose.foundation.layout.size
814import androidx.compose.material.ContentAlpha
915import androidx.compose.material.LocalContentColor
1016import androidx.compose.material.LocalTextStyle
@@ -31,7 +37,7 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
3137import de.westnordost.streetcomplete.data.osm.mapdata.Element
3238import de.westnordost.streetcomplete.data.osm.mapdata.ElementType
3339import de.westnordost.streetcomplete.data.preferences.Preferences
34- import de.westnordost.streetcomplete.databinding.FragmentOverlayPlacesBinding
40+ import de.westnordost.streetcomplete.databinding.ComposeViewBinding
3541import de.westnordost.streetcomplete.osm.POPULAR_PLACE_FEATURE_IDS
3642import de.westnordost.streetcomplete.osm.applyReplacePlaceTo
3743import de.westnordost.streetcomplete.osm.applyTo
@@ -47,15 +53,17 @@ import de.westnordost.streetcomplete.overlays.AnswerItem
4753import de.westnordost.streetcomplete.resources.Res
4854import de.westnordost.streetcomplete.resources.name_label
4955import de.westnordost.streetcomplete.resources.quest_placeName_no_name_answer
56+ import de.westnordost.streetcomplete.ui.common.feature.FeatureIcon
57+ import de.westnordost.streetcomplete.ui.common.feature.FeatureSelect
58+ import de.westnordost.streetcomplete.ui.common.last_picked.LastPickedChipsRow
5059import de.westnordost.streetcomplete.ui.common.localized_name.LocalizedNamesForm
5160import de.westnordost.streetcomplete.ui.util.content
5261import de.westnordost.streetcomplete.ui.util.rememberSerializable
53- import de.westnordost.streetcomplete.util.getLanguagesForFeatureDictionary
5462import de.westnordost.streetcomplete.util.getLocationSpanned
5563import de.westnordost.streetcomplete.util.ktx.geometryType
5664import de.westnordost.streetcomplete.util.ktx.viewLifecycleScope
57- import de.westnordost.streetcomplete.view.controller.FeatureViewController
58- import de.westnordost.streetcomplete.view.dialogs.SearchFeaturesDialog
65+ import de.westnordost.streetcomplete.util.locale.getLanguagesForFeatureDictionary
66+ import de.westnordost.streetcomplete.util.takeFavorites
5967import kotlinx.coroutines.launch
6068import kotlinx.coroutines.suspendCancellableCoroutine
6169import org.jetbrains.compose.resources.stringResource
@@ -64,19 +72,31 @@ import kotlin.coroutines.resume
6472
6573class PlacesOverlayForm : AbstractOverlayForm () {
6674
67- override val contentLayoutResId = R .layout.fragment_overlay_places
68- private val binding by contentViewBinding(FragmentOverlayPlacesBinding ::bind)
75+ override val contentLayoutResId = R .layout.compose_view
76+ private val binding by contentViewBinding(ComposeViewBinding ::bind)
6977
7078 private val prefs: Preferences by inject()
7179
7280 private var originalFeature: Feature ? = null
7381 private var originalNoName: Boolean = false
7482 private var originalNames: List <LocalizedName > = emptyList()
75-
83+ private var selectedFeature : MutableState < Feature ?> = mutableStateOf( null )
7684 private var localizedNames: MutableState <List <LocalizedName >> = mutableStateOf(emptyList())
7785 private var isNoName: MutableState <Boolean > = mutableStateOf(false )
7886
79- private lateinit var featureCtrl: FeatureViewController
87+ private val lastPickedFeatures: List <Feature > by lazy {
88+ val languages = getLanguagesForFeatureDictionary()
89+ prefs.getLastPicked<String >(this ::class .simpleName!! )
90+ .takeFavorites(n = 5 , first = 1 )
91+ .mapNotNull { featureId ->
92+ featureDictionary.getById(
93+ id = featureId,
94+ languages = languages,
95+ country = countryOrSubdivisionCode,
96+ )
97+ }
98+ }
99+
80100
81101 private lateinit var vacantShopFeature: Feature
82102
@@ -88,10 +108,11 @@ class PlacesOverlayForm : AbstractOverlayForm() {
88108 override fun onCreate (savedInstanceState : Bundle ? ) {
89109 super .onCreate(savedInstanceState)
90110
91- val languages = getLanguagesForFeatureDictionary(resources.configuration )
111+ val languages = getLanguagesForFeatureDictionary()
92112 vacantShopFeature = featureDictionary.getById(" shop/vacant" , languages)!!
93113 originalNames = parseLocalizedNames(element?.tags.orEmpty()).orEmpty()
94114 originalFeature = getOriginalFeature()
115+ selectedFeature.value = originalFeature
95116 originalNoName = element?.tags?.get(" name:signed" ) == " no" || element?.tags?.get(" noname" ) == " yes"
96117 }
97118
@@ -110,7 +131,7 @@ class PlacesOverlayForm : AbstractOverlayForm() {
110131 }
111132
112133 private fun getFeatureDictionaryFeature (element : Element ): Feature ? {
113- val languages = getLanguagesForFeatureDictionary(resources.configuration )
134+ val languages = getLanguagesForFeatureDictionary()
114135 val geometryType = if (element.type == ElementType .NODE ) null else element.geometryType
115136
116137 return featureDictionary.getByTags(
@@ -128,24 +149,6 @@ class PlacesOverlayForm : AbstractOverlayForm() {
128149 setTitleHintLabel(element?.tags?.let { getLocationSpanned(it, resources) })
129150 setMarkerIcon(R .drawable.quest_shop)
130151
131- featureCtrl = FeatureViewController (featureDictionary, binding.featureTextView, binding.featureIconView)
132- featureCtrl.countryOrSubdivisionCode = countryOrSubdivisionCode
133- featureCtrl.feature = originalFeature
134-
135- binding.featureView.setOnClickListener {
136- SearchFeaturesDialog (
137- requireContext(),
138- featureDictionary,
139- element?.geometryType ? : GeometryType .POINT ,
140- countryOrSubdivisionCode,
141- featureCtrl.feature?.name,
142- { it.toElement().isPlace() || it.id == " shop/vacant" },
143- ::onSelectedFeature,
144- POPULAR_PLACE_FEATURE_IDS ,
145- ).show()
146- }
147-
148-
149152 val selectableLanguages = (
150153 countryInfo.officialLanguages + countryInfo.additionalStreetsignLanguages
151154 ).distinct().toMutableList()
@@ -156,66 +159,105 @@ class PlacesOverlayForm : AbstractOverlayForm() {
156159 }
157160 }
158161
159- binding.names. composeViewBase.content { Surface {
162+ binding.composeViewBase.content { Surface {
160163 localizedNames = rememberSerializable {
161164 mutableStateOf(originalNames.takeIf { it.isNotEmpty() } ? : defaultNames())
162165 }
163166 isNoName = rememberSaveable { mutableStateOf(originalNoName) }
164167
165- Column {
166- Text (
167- text = stringResource(Res .string.name_label),
168- style = MaterialTheme .typography.caption.copy(
169- color = LocalContentColor .current.copy(alpha = ContentAlpha .medium)
170- )
168+ Column (
169+ modifier = Modifier
170+ .defaultMinSize(minHeight = 96 .dp)
171+ .fillMaxWidth(),
172+ horizontalAlignment = Alignment .CenterHorizontally ,
173+ verticalArrangement = Arrangement .spacedBy(8 .dp, Alignment .CenterVertically ),
174+ ) {
175+ val feature = selectedFeature.value
176+
177+ FeatureSelect (
178+ feature = feature,
179+ onSelectedFeature = ::onSelectedFeature,
180+ featureDictionary = featureDictionary,
181+ geometryType = element?.geometryType ? : GeometryType .POINT ,
182+ countryCode = countryOrSubdivisionCode,
183+ filterFn = { it.toElement().isPlace() || it.id == " shop/vacant" },
184+ codesOfDefaultFeatures = POPULAR_PLACE_FEATURE_IDS ,
171185 )
172- if (isNoName.value && localizedNames.value.isEmpty()) {
173- Text (
174- text = stringResource(Res .string.quest_placeName_no_name_answer),
175- style = LocalTextStyle .current.copy(
176- fontWeight = FontWeight .Bold ,
177- color = LocalContentColor .current.copy(alpha = ContentAlpha .medium)
178- ),
179- modifier = Modifier
180- .padding(20 .dp)
181- .align(Alignment .CenterHorizontally )
186+ if (feature != null && ! feature.hasFixedName) {
187+ Column {
188+ Text (
189+ text = stringResource(Res .string.name_label),
190+ style = MaterialTheme .typography.caption.copy(
191+ color = LocalContentColor .current.copy(alpha = ContentAlpha .medium)
192+ )
193+ )
194+ if (isNoName.value && localizedNames.value.isEmpty()) {
195+ Text (
196+ text = stringResource(Res .string.quest_placeName_no_name_answer),
197+ style = LocalTextStyle .current.copy(
198+ fontWeight = FontWeight .Bold ,
199+ color = LocalContentColor .current.copy(alpha = ContentAlpha .medium)
200+ ),
201+ modifier = Modifier
202+ .padding(20 .dp)
203+ .align(Alignment .CenterHorizontally )
204+ )
205+ }
206+ LocalizedNamesForm (
207+ localizedNames = localizedNames.value,
208+ onChanged = {
209+ localizedNames.value = it
210+ if (it.isNotEmpty()) isNoName.value = false
211+ checkIsFormComplete()
212+ },
213+ languageTags = selectableLanguages,
214+ )
215+ }
216+ }
217+ // show only for adding new POIs becaues it gets too busy with also the name form
218+ // being displayed
219+ if (lastPickedFeatures.isNotEmpty() && element == null && selectedFeature.value == null ) {
220+ LastPickedChipsRow (
221+ items = lastPickedFeatures,
222+ onClick = {
223+ selectedFeature.value = it
224+ checkIsFormComplete()
225+ },
226+ modifier = Modifier .padding(start = 48 .dp, end = 56 .dp),
227+ itemContent = {
228+ FeatureIcon (
229+ feature = it,
230+ modifier = Modifier .size(22.5 .dp)
231+ )
232+ }
182233 )
234+ } else {
235+ Spacer (Modifier .size(48 .dp))
183236 }
184- LocalizedNamesForm (
185- localizedNames = localizedNames.value,
186- onChanged = {
187- localizedNames.value = it
188- if (it.isNotEmpty()) isNoName.value = false
189- checkIsFormComplete()
190- },
191- languageTags = selectableLanguages,
192- )
193237 }
194238 } }
195-
196- updateNameContainerVisibility()
239+ checkIsFormComplete()
197240 }
198241
199242 private fun onSelectedFeature (feature : Feature ) {
200- featureCtrl.feature = feature
243+ selectedFeature.value = feature
201244 isNoName.value = false
202245 // clear previous names (if necessary, and if any)
203- if (feature.hasFixedName) {
246+ if (feature.hasFixedName == true ) {
204247 localizedNames.value = listOf ()
205248 } else {
206249 localizedNames.value = defaultNames()
207250 }
208- updateNameContainerVisibility()
209251 checkIsFormComplete()
210252 }
211253
212254 private fun setVacant () {
213- val languages = getLanguagesForFeatureDictionary(resources.configuration )
255+ val languages = getLanguagesForFeatureDictionary()
214256 onSelectedFeature(featureDictionary.getById(" shop/vacant" , languages)!! )
215257 }
216258
217259 private fun createNoNameAnswer (): AnswerItem ? {
218- val feature = featureCtrl.feature
260+ val feature = selectedFeature.value
219261 return if (feature == null || isNoName.value || feature.hasFixedName) {
220262 null
221263 } else {
@@ -235,34 +277,33 @@ class PlacesOverlayForm : AbstractOverlayForm() {
235277 checkIsFormComplete()
236278 }
237279
238- private fun updateNameContainerVisibility () {
239- val feature = featureCtrl.feature
240- val isNameInputInvisible = feature == null || feature.hasFixedName
241- binding.names.root.isGone = isNameInputInvisible
242- }
243-
244280 private fun defaultNames (): List <LocalizedName > =
245281 listOf (LocalizedName (countryInfo.language.orEmpty(), " " ))
246282
247283 override fun hasChanges (): Boolean =
248- originalFeature != featureCtrl.feature
284+ originalFeature != selectedFeature.value
249285 || originalNames != localizedNames.value.filter { it.name.isNotEmpty() }
250286 || originalNoName != isNoName.value
251287
252288 override fun isFormComplete (): Boolean =
253- featureCtrl.feature != null
289+ selectedFeature.value != null
254290 // name is not necessary
255291
256292 override fun onClickOk () {
257293 val inputNames = localizedNames.value.filter { it.name.isNotEmpty() }
258294 val firstLanguage = inputNames.firstOrNull()?.languageTag
259295 if (! firstLanguage.isNullOrEmpty()) prefs.preferredLanguageForNames = firstLanguage
260296
297+ val feature = selectedFeature.value!!
298+ if (! feature.isSuggestion) {
299+ prefs.addLastPicked(this ::class .simpleName!! , feature.id)
300+ }
301+
261302 viewLifecycleScope.launch {
262303 applyEdit(createEditAction(
263304 element, geometry,
264305 inputNames, originalNames,
265- featureCtrl.feature !! , originalFeature,
306+ selectedFeature.value !! , originalFeature,
266307 isNoName.value,
267308 ::confirmReplaceShop
268309 ))
0 commit comments