Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving the Building Levels quest form to Jetpack Compose. #6022

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.westnordost.streetcomplete.quests.building_levels

import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.ui.theme.AppTheme

@Composable
fun AddBuildingLevelsButton(lastLevels: Int, lastRoofLevels: Int?, modifier: Modifier = Modifier, onClick: () -> Unit) {
Button(onClick = onClick, modifier = modifier) {
Row(modifier = Modifier
.height(52.dp), verticalAlignment = Alignment.CenterVertically) {
Text(
text = lastLevels.toString(),
fontSize = 14.sp,
modifier = Modifier.align(Alignment.Bottom)
)
Image(
painterResource(R.drawable.ic_building_levels_illustration),
"Building Illustration"
)
Text(
text = lastRoofLevels?.toString() ?: " ",
fontSize = 14.sp,
modifier = Modifier.align(Alignment.Top)
)
}
}
}

@Composable
@Preview(showBackground = true,
name = "Add Building Levels Button",
uiMode = Configuration.UI_MODE_NIGHT_YES
)
fun PreviewAddBuildingLevelsButton() {
AppTheme {
AddBuildingLevelsButton(3, 1) {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package de.westnordost.streetcomplete.quests.building_levels

import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.text.isDigitsOnly
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.ui.ktx.conditional
import de.westnordost.streetcomplete.ui.theme.AppTheme
import de.westnordost.streetcomplete.ui.theme.titleLarge

@Composable
fun AddBuildingLevelsFormControl(
regularLevels: String?,
onRegularLevels: (String) -> Unit,
roofLevels: String?,
onRoofLevels: (String) -> Unit,
onButton: (Int, Int?) -> Unit,
modifier: Modifier = Modifier,
buildingLevels: List<BuildingLevelsAnswer> = listOf(),
) {
val focusRequester = remember { FocusRequester() }

Box(modifier = modifier) {
Column {
Row(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier
.padding(3.dp)
.weight(1f)) {
Text(
stringResource(R.string.quest_buildingLevels_levelsLabel2),
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(0.dp, 12.dp),
)
TextField(
regularLevels ?: "",
onValueChange = onRegularLevels,
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
modifier = Modifier
.padding(vertical = 9.dp)
.conditional(regularLevels == null) { focusRequester(focusRequester) }
.conditional(regularLevels == null || !regularLevels.isDigitsOnly()) { border(2.dp, color = MaterialTheme.colors.error) },
textStyle = MaterialTheme.typography.titleLarge.copy(
textAlign = TextAlign.Start,
),

)
}
Image(
painter = painterResource(R.drawable.building_levels_illustration),
contentDescription = "Illustration for building Levels",
contentScale = ContentScale.FillBounds,
modifier = Modifier
.defaultMinSize()
.width(239.dp)
.weight(2f)
.padding(3.dp)
)
Column(modifier = Modifier
.padding(3.dp)
.weight(1f)) {
TextField(
roofLevels ?: "",
onValueChange = onRoofLevels,
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
modifier = Modifier
.padding(0.dp, 12.dp)
.align(Alignment.CenterHorizontally)
.conditional(regularLevels != null) { focusRequester(focusRequester) }
.conditional(roofLevels == null || !roofLevels.isDigitsOnly()) { border(2.dp, color = MaterialTheme.colors.error) },
textStyle = MaterialTheme.typography.titleLarge.copy(
textAlign = TextAlign.Start
)
)
Text(
text = stringResource(R.string.quest_buildingLevels_roofLevelsLabel2),
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(0.dp, 12.dp)
)
}
}
AddBuildingLevelsSavedButtons(buildingLevels, onButton)
}
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}

@Composable
@Preview(showBackground = true, name = "Add Building Levels Form Component")
fun PreviewAddBuildingLevelsFormControl() {
var regularLevels = remember { mutableStateOf("55") }
var roofLevels = remember { mutableStateOf("55") }
AppTheme {
AddBuildingLevelsFormControl(
regularLevels.value,
{ },
roofLevels.value,
{ },
onButton = { reg, roof -> },
buildingLevels = listOf(
BuildingLevelsAnswer(5, 2),
BuildingLevelsAnswer(4, 1),
BuildingLevelsAnswer(3, 0)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package de.westnordost.streetcomplete.quests.building_levels

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.RecyclerView
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.core.text.isDigitsOnly
import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.preferences.Preferences
import de.westnordost.streetcomplete.databinding.QuestBuildingLevelsBinding
import de.westnordost.streetcomplete.databinding.QuestBuildingLevelsLastPickedButtonBinding
import de.westnordost.streetcomplete.quests.AbstractOsmQuestForm
import de.westnordost.streetcomplete.quests.AnswerItem
import de.westnordost.streetcomplete.util.ktx.intOrNull
import de.westnordost.streetcomplete.ui.theme.AppTheme
import de.westnordost.streetcomplete.util.logs.Log
import de.westnordost.streetcomplete.util.takeFavourites
import org.koin.android.ext.android.inject

Expand All @@ -23,96 +23,86 @@ class AddBuildingLevelsForm : AbstractOsmQuestForm<BuildingLevelsAnswer>() {
private val binding by contentViewBinding(QuestBuildingLevelsBinding::bind)

private val prefs: Preferences by inject()

private lateinit var regularLevels: MutableState<String?>
private lateinit var roofLevels: MutableState<String?>
override val otherAnswers = listOf(
AnswerItem(R.string.quest_buildingLevels_answer_multipleLevels) { showMultipleLevelsHint() }
)

private val levels get() = binding.levelsInput.intOrNull?.takeIf { it >= 0 }
private val roofLevels get() = binding.roofLevelsInput.intOrNull?.takeIf { it >= 0 }

private val lastPickedAnswers by lazy {
prefs.getLastPicked(this::class.simpleName!!)
.map { value ->
value.split("#").let { BuildingLevelsAnswer(it[0].toInt(), it.getOrNull(1)?.toInt()) }
value.split("#")
.let { BuildingLevelsAnswer(it[0].toInt(), it.getOrNull(1)?.toInt()) }
}
.takeFavourites(n = 5, history = 15, first = 1)
.sortedWith(compareBy<BuildingLevelsAnswer> { it.levels }.thenBy { it.roofLevels })
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

if (savedInstanceState == null) {
binding.levelsInput.setText(element.tags["building:levels"])
binding.roofLevelsInput.setText(element.tags["roof:levels"])
binding.questBuildingLevelsBase.setContent {
regularLevels = rememberSaveable { mutableStateOf(element.tags["building:levels"] ?: "") }
roofLevels = rememberSaveable { mutableStateOf(element.tags["roof:levels"] ?: "") }
AppTheme {
AddBuildingLevelsFormControl(
regularLevels.value,
{
regularLevels.value = it
checkIsFormComplete()
},
roofLevels.value,
{
roofLevels.value = it
checkIsFormComplete()
},
onButton = {
regular, roof ->
regularLevels.value = regular.toString()
roofLevels.value = if (roof != null) roof.toString() else ""
checkIsFormComplete()
},
buildingLevels = lastPickedAnswers
)
}
}
val focusedInput = if (levels == null) binding.levelsInput else binding.roofLevelsInput
focusedInput.requestFocus()
focusedInput.selectAll()

binding.levelsInput.doAfterTextChanged { checkIsFormComplete() }
binding.roofLevelsInput.doAfterTextChanged { checkIsFormComplete() }

binding.lastPickedButtons.adapter = LastPickedAdapter(lastPickedAnswers, ::onLastPickedButtonClicked)
}

private fun onLastPickedButtonClicked(position: Int) {
val buildingLevelsAnswer = lastPickedAnswers[position]
binding.levelsInput.setText(buildingLevelsAnswer.levels.toString())
binding.roofLevelsInput.setText(buildingLevelsAnswer.roofLevels?.toString() ?: "")
}

override fun onClickOk() {
val answer = BuildingLevelsAnswer(levels!!, roofLevels)
prefs.addLastPicked(this::class.simpleName!!, listOfNotNull(levels, roofLevels).joinToString("#"))
val answer = BuildingLevelsAnswer(
regularLevels.value?.toInt() ?: 0,
roofLevels.value?.toInt() ?: null
)
prefs.addLastPicked(
this::class.simpleName!!,
listOfNotNull(answer.levels, answer.roofLevels).joinToString("#")
)
applyAnswer(answer)
}

private fun showMultipleLevelsHint() {
activity?.let { AlertDialog.Builder(it)
.setMessage(R.string.quest_buildingLevels_answer_description)
.setPositiveButton(android.R.string.ok, null)
.show()
activity?.let {
AlertDialog.Builder(it)
.setMessage(R.string.quest_buildingLevels_answer_description)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}

override fun isFormComplete(): Boolean {
val hasNonFlatRoofShape = element.tags.containsKey("roof:shape") && element.tags["roof:shape"] != "flat"
val hasNonFlatRoofShape =
element.tags.containsKey("roof:shape") && element.tags["roof:shape"] != "flat"
val roofLevelsAreOptional = countryInfo.roofsAreUsuallyFlat && !hasNonFlatRoofShape
return levels != null && (roofLevelsAreOptional || roofLevels != null)
Log.i("Form", "Roof is Optional? $roofLevelsAreOptional")
return regularLevels.value != ""
&& regularLevels.value != null
&& regularLevels.value!!.isDigitsOnly()
&& regularLevels.value!!.toInt() >= 0
&& (roofLevelsAreOptional
|| (roofLevels.value != ""
&& roofLevels.value != null
&& roofLevels.value!!.isDigitsOnly()
&& roofLevels.value!!.toInt() >= 0))
return false
}
}

private class LastPickedAdapter(
private val lastPickedAnswers: List<BuildingLevelsAnswer>,
private val onItemClicked: (position: Int) -> Unit
) : RecyclerView.Adapter<LastPickedAdapter.ViewHolder>() {

class ViewHolder(
private val binding: QuestBuildingLevelsLastPickedButtonBinding,
private val onItemClicked: (position: Int) -> Unit
) : RecyclerView.ViewHolder(binding.root) {

init {
itemView.setOnClickListener { onItemClicked(bindingAdapterPosition) }
}

fun onBind(item: BuildingLevelsAnswer) {
binding.lastLevelsLabel.text = item.levels.toString()
binding.lastRoofLevelsLabel.text = item.roofLevels?.toString() ?: " "
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = QuestBuildingLevelsLastPickedButtonBinding.inflate(inflater, parent, false)
return ViewHolder(binding, onItemClicked)
}

override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.onBind(lastPickedAnswers[position])
}

override fun getItemCount() = lastPickedAnswers.size
}
Loading