diff --git a/app/src/commonMain/composeResources/values/strings.xml b/app/src/commonMain/composeResources/values/strings.xml index 8dead15fb4..ec0afa3315 100644 --- a/app/src/commonMain/composeResources/values/strings.xml +++ b/app/src/commonMain/composeResources/values/strings.xml @@ -1121,6 +1121,8 @@ Spring Disused drinking water object + What doctors are present here? + Is this still here? Does this ferry route transport pedestrians? diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/QuestTypesRegistry.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/QuestTypesRegistry.kt index a0771b3f1d..b99bbfc843 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/QuestTypesRegistry.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/QuestTypesRegistry.kt @@ -61,6 +61,7 @@ import de.westnordost.streetcomplete.quests.camping.AddCampPower import de.westnordost.streetcomplete.quests.camping.AddCampShower import de.westnordost.streetcomplete.quests.camping.AddCaravans import de.westnordost.streetcomplete.quests.camping.AddTents +import de.westnordost.streetcomplete.quests.doctor_type.AddDoctorType import de.westnordost.streetcomplete.quests.car_wash_type.AddCarWashType import de.westnordost.streetcomplete.quests.charge.AddParkingCharge import de.westnordost.streetcomplete.quests.charging_station_bicycles.AddChargingStationBicycles @@ -365,6 +366,8 @@ fun questTypeRegistry( 60 to AddGeneralFee(), + 200 to AddDoctorType(getCountryInfoByLocation), + 61 to AddDrinkingWater(), // used by AnyFinder 62 to AddDrinkingWaterType(), diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/doctor_type/AddDoctorType.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/doctor_type/AddDoctorType.kt new file mode 100644 index 0000000000..2486f30d4e --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/doctor_type/AddDoctorType.kt @@ -0,0 +1,39 @@ +package de.westnordost.streetcomplete.quests.doctor_type + +import androidx.compose.runtime.Composable +import de.westnordost.osmfeatures.Feature +import de.westnordost.streetcomplete.data.meta.CountryInfo +import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry +import de.westnordost.streetcomplete.data.osm.mapdata.Element +import de.westnordost.streetcomplete.data.osm.mapdata.LatLon +import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType +import de.westnordost.streetcomplete.data.osm.osmquests.QuestAction +import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.LIFESAVER +import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CITIZEN +import de.westnordost.streetcomplete.osm.Tags +import de.westnordost.streetcomplete.resources.* + +class AddDoctorType(getCountryInfoByLocation: (LatLon) -> CountryInfo) : OsmFilterQuestType>() { + + override val elementFilter = """ + nodes, ways with + ( + amenity=doctors + and !healthcare:speciality + ) + """ + override val changesetComment = "Survey what type of healthcare a doctor provides" + override val wikiLink = "Key:healthcare:speciality" + override val icon = Res.drawable.quest_restaurant_vegan + override val title = Res.string.quest_doctor_type_title + override val achievements = listOf(CITIZEN, LIFESAVER) + + @Composable + override fun Form(on: (QuestAction>) -> Unit, element: Element, geometry: ElementGeometry, countryInfo: CountryInfo) { + AddDoctorTypeForm(on, element) + } + + override fun applyAnswerTo(answer: List, tags: Tags, geometry: ElementGeometry, timestampEdited: Long) { + tags["healthcare:speciality"] = answer.joinToString(";") { it.tags["healthcare:speciality"] ?: "" } + } +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/doctor_type/AddDoctorTypeForm.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/doctor_type/AddDoctorTypeForm.kt new file mode 100644 index 0000000000..25adb29bf8 --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/quests/doctor_type/AddDoctorTypeForm.kt @@ -0,0 +1,35 @@ +package de.westnordost.streetcomplete.quests.doctor_type + +import androidx.compose.runtime.Composable +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.FeatureDictionary +import de.westnordost.streetcomplete.data.osm.mapdata.Element +import de.westnordost.streetcomplete.data.osm.osmquests.QuestAction +import de.westnordost.streetcomplete.ui.common.quest.FeatureSelectionForm +import de.westnordost.streetcomplete.util.ktx.geometryType +import org.koin.compose.koinInject + +val POPULAR_DOCTORS_FEATURE_IDS = listOf( + // ordered roughly by usage number according to taginfo + "amenity/doctors/ophthalmology", + "amenity/doctors/paediatrics", + "amenity/doctors/gynaecology", + "amenity/doctors/psychiatry", + "amenity/doctors/orthodontics", + "amenity/doctors/dermatology", +) + +@Composable +fun AddDoctorTypeForm( + on: (QuestAction>) -> Unit, + element: Element, + featureDictionary: FeatureDictionary = koinInject() +) { + FeatureSelectionForm( + on = on, + featureDictionary = featureDictionary, + geometryType = element.geometryType, + filterFn = { (it.tags["amenity"] == "doctors" && it.tags["healthcare:speciality"] != null) }, + codesOfDefaultFeatures = POPULAR_DOCTORS_FEATURE_IDS + ) +} diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/feature/FeatureSearch.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/feature/FeatureSearch.kt index b82c59c6c1..69e3ac3728 100644 --- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/feature/FeatureSearch.kt +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/feature/FeatureSearch.kt @@ -47,7 +47,6 @@ fun FeatureSearch( filterFn: (Feature) -> Boolean = { true }, codesOfDefaultFeatures: List = emptyList(), ) { - val state = rememberLazyListState() val focusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { @@ -63,7 +62,7 @@ fun FeatureSearch( languages = languages, country = countryCode ) - } + }.filter(filterFn) } val features = remember(search, featureDictionary, languages, countryCode, geometryType, filterFn, defaultFeatures) { if (search.isNotEmpty()) { diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/FeatureSelectionForm.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/FeatureSelectionForm.kt new file mode 100644 index 0000000000..0eeb1e0bff --- /dev/null +++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/ui/common/quest/FeatureSelectionForm.kt @@ -0,0 +1,116 @@ +package de.westnordost.streetcomplete.ui.common.quest + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import de.westnordost.osmfeatures.Feature +import de.westnordost.osmfeatures.FeatureDictionary +import de.westnordost.osmfeatures.GeometryType +import de.westnordost.streetcomplete.data.osm.osmquests.Answer +import de.westnordost.streetcomplete.data.osm.osmquests.QuestAction +import de.westnordost.streetcomplete.resources.* +import de.westnordost.streetcomplete.ui.common.Button2 +import de.westnordost.streetcomplete.ui.common.feature.FeatureItem +import de.westnordost.streetcomplete.ui.common.feature.FeatureSearchDialog +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource + +/** + * A quest form that allows selecting of features via search or from a list of presets. + */ +@Composable +fun FeatureSelectionForm( + on: (QuestAction>) -> Unit, + featureDictionary: FeatureDictionary, + modifier: Modifier = Modifier, + initialSelectedFeatures: List = emptyList(), + geometryType: GeometryType? = null, + countryCode: String? = null, + filterFn: (Feature) -> Boolean = { true }, + codesOfDefaultFeatures: List = emptyList(), +) { + var selectedFeatures by remember { mutableStateOf(initialSelectedFeatures) } + var showSearch by remember { mutableStateOf(false) } + + QuestForm( + on = on, + isComplete = selectedFeatures.isNotEmpty(), + onClickOk = { on(Answer(selectedFeatures)) }, + modifier = modifier, + ) { + Column { + selectedFeatures.forEach { feature -> + FeatureDisplay( + feature = feature, + featureDictionary = featureDictionary, + countryCode = countryCode, + onRemove = { selectedFeatures = selectedFeatures - feature }, + modifier = Modifier.padding(vertical = 4.dp) + ) + } + + Button2( + onClick = { showSearch = true } + ) { + Icon(painterResource(Res.drawable.ic_add_24), contentDescription = null) + Spacer(Modifier.size(8.dp)) + Text(stringResource(Res.string.quest_presets_preset_add)) + } + + if (showSearch) { + FeatureSearchDialog( + onDismissRequest = { showSearch = false }, + onSelectedFeature = { + selectedFeatures = selectedFeatures + it + showSearch = false + }, + featureDictionary = featureDictionary, + geometryType = geometryType, + countryCode = countryCode, + // Ensure a preset cannot be selected twice + filterFn = { feature -> feature !in selectedFeatures && filterFn(feature) }, + codesOfDefaultFeatures = codesOfDefaultFeatures + ) + } + } + } +} + +@Composable +private fun FeatureDisplay( + feature: Feature, + featureDictionary: FeatureDictionary, + countryCode: String?, + onRemove: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + FeatureItem( + feature = feature, + featureDictionary = featureDictionary, + countryCode = countryCode, + modifier = Modifier.weight(1f) + ) + IconButton(onClick = onRemove) { + Icon(painterResource(Res.drawable.ic_delete_24), contentDescription = null) + } + } +}