Skip to content

Commit

Permalink
Add brewery quest (#475)
Browse files Browse the repository at this point in the history
* add AMultiValueQuestForm and use it for cuisine, brewery and healthcare quest

---------

Co-authored-by: Helium314 <[email protected]>
  • Loading branch information
mcliquid and Helium314 authored Nov 1, 2023
1 parent afd1b0c commit 4389173
Show file tree
Hide file tree
Showing 11 changed files with 412 additions and 182 deletions.
125 changes: 125 additions & 0 deletions app/src/main/assets/brewery/brewerySuggestions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
Aktienbrauerei Kaufbeuren
Alhambra
Allgäuer Brauhaus
Alpirsbacher Klosterbräu
Amstel
Asahi
Astra
Augustiner Bräu München
Ayinger
Badger
Beck's
Beerlao
Bellheimer
Berg
Berliner Kindl
Bernard
Birra Moretti
Bitburger
Bosch
Brauhaus Faust
BrewDog
Budweiser
Calanda Bräu
Carlsberg
Castel
Castle Rock
Chang
Charles Wells
Corona
Dinkelacker-Schwaben Bräu
Eichbaum
Engelbräu
Erdinger
Erzquell
Farny
Feldschlösschen
Finsterwalder
Forst
Fuller's
Fürstenberg
Gambrinus
Ganter
Gold Ochsen
Greene King
Guinness
Gösser
Haacht
Hacker-Pschorr
Harvey's
Hatz
Heineken
Herrnbräu
Hirsch
Hoegaarden
Hoepfner
Hofbräu München
Häffner-Bräu
Härle
Jever
Jupiler
Kaiser
Karlsberg
Kirin
Klosterbrauerei Reutberg
Krombacher
Krušovice
Kulmbacher
König Ludwig
König Pilsner
Köstrizer
Kühbacher
Leffe
Leibinger
Licher
Löwenbräu
Marton's
Meckatzer
Miller
Molson Coors
Ninkasi
Oettinger
Paulaner
Pelforth
Pilsner Urquell
Puntigamer
Radeberger
Radegast
Riegele
Robinsons
Rothaus
Ruppaner
Samuel Smith
San Miguel
Sapporo
Schloss Eggenberg
Schlossbrauerei Haimhausen
Schlösser
Schützengarten
Shepherd Neame
Spalter Bier
Spandauer
Spaten
St Austell Brewery
Starobrno
Staropramen
Stella Artois
Stiegl
Stuttgarter Hofbräu
Suntory
Suwa
Svijany
Tegernseer
Thurn und Taxis
Thwaites
Tucher
Unterbaarer
Ur-Krostitzer
Veltins
Waldhaus
Warsteiner
Weldebräu
Wildbräu
Zwiefalter
Zötler
Сыктывкарпиво
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package de.westnordost.streetcomplete.quests

import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.core.view.doOnLayout
import androidx.core.widget.doAfterTextChanged
import androidx.preference.PreferenceManager
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.databinding.QuestMultiValueBinding
import de.westnordost.streetcomplete.quests.healthcare_speciality.AddHealthcareSpecialityForm
import de.westnordost.streetcomplete.util.LastPickedValuesStore
import de.westnordost.streetcomplete.util.ktx.dpToPx
import de.westnordost.streetcomplete.util.ktx.viewLifecycleScope
import de.westnordost.streetcomplete.util.mostCommonWithin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/** form for adding multiple values to a single key */
abstract class AMultiValueQuestForm<T> : AbstractOsmQuestForm<T>() {

override val contentLayoutResId = R.layout.quest_multi_value
private val binding by contentViewBinding(QuestMultiValueBinding::bind)

/** convert the multi-value string answer to type T */
abstract fun stringToAnswer(answerString: String): T

/**
* provide suggestions, loaded once and stored in companion object
* shown below all other suggestions
*/
abstract fun getConstantSuggestions(): Collection<String>

/**
* provide suggestions, loaded every time the form is opened
* shown above all other suggestions
*/
open fun getVariableSuggestions(): Collection<String> = emptyList()

/** text for the addValueButton */
abstract val addAnotherValueResId: Int

open val onlyAllowSuggestions = false

private val values = mutableSetOf<String>()

private val value get() = binding.valueInput.text?.toString().orEmpty().trim()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.addValueButton.setText(addAnotherValueResId)
if (suggestions.isEmpty()) { // load suggestions if necessary
getConstantSuggestions().forEach {
if (it.isNotBlank())
suggestions.add(it.trim().intern())
}
}

binding.valueInput.setAdapter(
ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
(getVariableSuggestions() + lastPickedAnswers + suggestions).distinct()
)
)
binding.valueInput.onItemClickListener = AdapterView.OnItemClickListener { _, t, _, _ ->
val value = (t as? TextView)?.text?.toString() ?: return@OnItemClickListener
if (!values.add(value)) return@OnItemClickListener // we don't want duplicates
onAddedValue(value)
}

binding.valueInput.doAfterTextChanged { checkIsFormComplete() }
binding.valueInput.doOnLayout { binding.valueInput.dropDownWidth = binding.valueInput.width - requireContext().dpToPx(60).toInt() }

binding.addValueButton.setOnClickListener {
if (!isFormComplete() || binding.valueInput.text.isBlank()) return@setOnClickListener
values.add(value)
onAddedValue(value)
}
showSuggestions()
}

override fun onClickOk() {
values.removeAll { it.isBlank() }
if (values.isNotEmpty()) favs.add(values)
if (value.isNotBlank()) favs.add(value)
if (value.isBlank())
applyAnswer(stringToAnswer(values.joinToString(";")))
else
applyAnswer(stringToAnswer((values + listOf(value)).joinToString(";")))
}

override fun isFormComplete() = (value.isNotBlank() || values.isNotEmpty()) && !value.contains(";")
&& !values.contains(value)
&& (!onlyAllowSuggestions || values.all { suggestions.contains(it) })

override fun onAttach(ctx: Context) {
super.onAttach(ctx)
favs = LastPickedValuesStore(
PreferenceManager.getDefaultSharedPreferences(ctx.applicationContext),
key = javaClass.simpleName,
serialize = { it },
deserialize = { it },
)
}

private fun onAddedValue(value: String) {
binding.currentValues.text = values.joinToString(";")
binding.valueInput.text.clear()
(binding.valueInput.adapter as ArrayAdapter<String>).remove(value)
showSuggestions()
}

private lateinit var favs: LastPickedValuesStore<String>

private val lastPickedAnswers by lazy {
favs.get()
.mostCommonWithin(target = 20, historyCount = 50, first = 1)
.toList()
}

private fun showSuggestions() {
viewLifecycleScope.launch {
delay(30) // delay, because otherwise it sometimes doesn't work properly
binding.valueInput.showDropDown()
}
}

companion object {
private val suggestions = mutableListOf<String>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import de.westnordost.streetcomplete.quests.bike_shop.AddSecondHandBicycleAvaila
import de.westnordost.streetcomplete.quests.board_type.AddBoardType
import de.westnordost.streetcomplete.quests.bollard_type.AddBollardType
import de.westnordost.streetcomplete.quests.bridge_structure.AddBridgeStructure
import de.westnordost.streetcomplete.quests.brewery.AddBrewery
import de.westnordost.streetcomplete.quests.building_colour.AddBuildingColour
import de.westnordost.streetcomplete.quests.building_entrance.AddEntrance
import de.westnordost.streetcomplete.quests.building_entrance_reference.AddEntranceReference
Expand Down Expand Up @@ -581,6 +582,7 @@ fun getQuestTypeList(
EE_QUEST_OFFSET + 1 to AddContactPhone(),
EE_QUEST_OFFSET + 2 to AddContactWebsite(),
EE_QUEST_OFFSET + 4 to AddCuisine(),
EE_QUEST_OFFSET + 32 to AddBrewery(),
EE_QUEST_OFFSET + 5 to AddHealthcareSpeciality(),
EE_QUEST_OFFSET + 6 to AddServiceBuildingType(),
EE_QUEST_OFFSET + 7 to AddServiceBuildingOperator(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package de.westnordost.streetcomplete.quests.brewery

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.filter
import de.westnordost.streetcomplete.data.osm.osmquests.OsmFilterQuestType
import de.westnordost.streetcomplete.osm.IS_SHOP_OR_DISUSED_SHOP_EXPRESSION
import de.westnordost.streetcomplete.osm.Tags

class AddBrewery : OsmFilterQuestType<BreweryAnswer>() {

override val elementFilter = """
nodes, ways with
amenity ~ bar|biergarten|pub|restaurant|nightclub
and drink:beer != no
and (
brewery ~ yes|no
or !brewery
or brewery older today -6 years
)
"""
override val changesetComment = "Add brewery"
override val wikiLink = "Key:brewery"
override val icon = R.drawable.ic_quest_brewery
override val isReplaceShopEnabled = true
override val defaultDisabledMessage = R.string.default_disabled_msg_go_inside

override fun getTitle(tags: Map<String, String>) = R.string.quest_brewery_title

override fun getHighlightedElements(element: Element, getMapData: () -> MapDataWithGeometry) =
getMapData().filter(IS_SHOP_OR_DISUSED_SHOP_EXPRESSION)

override fun createForm() = AddBreweryForm()

override fun applyAnswerTo(answer: BreweryAnswer, tags: Tags, geometry: ElementGeometry, timestampEdited: Long) {
when (answer) {
is NoBeerAnswer -> {
tags["drink:beer"] = "no"
if (tags["brewery"] != "no") // don't remove brewery=no
tags.remove("brewery")
}
is ManyBeersAnswer -> tags["brewery"] = "various"
is BreweryStringAnswer -> tags["brewery"] = answer.brewery
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package de.westnordost.streetcomplete.quests.brewery

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.osm.edits.MapDataWithEditsSource
import de.westnordost.streetcomplete.data.osm.mapdata.filter
import de.westnordost.streetcomplete.quests.AMultiValueQuestForm
import de.westnordost.streetcomplete.quests.AnswerItem
import de.westnordost.streetcomplete.util.math.enlargedBy
import org.koin.android.ext.android.inject

class AddBreweryForm : AMultiValueQuestForm<BreweryAnswer>() {

private val mapDataSource: MapDataWithEditsSource by inject()

override fun stringToAnswer(answerString: String) = BreweryStringAnswer(answerString)

override fun getConstantSuggestions() =
requireContext().assets.open("brewery/brewerySuggestions.txt").bufferedReader().readLines()

override val addAnotherValueResId = R.string.quest_brewery_add_more

override fun getVariableSuggestions(): Collection<String> {
val data = mapDataSource.getMapDataWithGeometry(geometry.getBounds().enlargedBy(100.0))
val suggestions = hashSetOf<String>()
data.filter("nodes, ways with brewery").forEach {
it.tags["brewery"]?.let { suggestions.addAll(it.split(";")) }
}
suggestions.remove("yes")
suggestions.remove("various")
suggestions.remove("no")
return suggestions
}

override val otherAnswers = listOf(
AnswerItem(R.string.quest_brewery_is_not_available) { applyAnswer(NoBeerAnswer) },
AnswerItem(R.string.quest_brewery_is_various) { applyAnswer(ManyBeersAnswer) }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.westnordost.streetcomplete.quests.brewery

sealed interface BreweryAnswer

data class BreweryStringAnswer(val brewery: String) : BreweryAnswer
object ManyBeersAnswer : BreweryAnswer
object NoBeerAnswer : BreweryAnswer
Loading

0 comments on commit 4389173

Please sign in to comment.