From 3163ab8c5dee312573311e8378065a5d267975d3 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 2 Mar 2022 15:50:22 +0100 Subject: [PATCH 01/11] add OnAdapterItemSelectedListener --- .../quests/max_height/AddMaxHeightForm.kt | 10 +++------- .../westnordost/streetcomplete/view/LengthInput.kt | 11 +++-------- .../view/OnAdapterItemSelectedListener.kt | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/view/OnAdapterItemSelectedListener.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeightForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeightForm.kt index 1fe282c8a7..2cba2d55e0 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeightForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/max_height/AddMaxHeightForm.kt @@ -3,7 +3,6 @@ package de.westnordost.streetcomplete.quests.max_height import android.os.Bundle import android.text.InputFilter import android.view.View -import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.EditText import android.widget.Spinner @@ -22,6 +21,7 @@ import de.westnordost.streetcomplete.osm.LengthInMeters import de.westnordost.streetcomplete.quests.AbstractQuestFormAnswerFragment import de.westnordost.streetcomplete.quests.AnswerItem import de.westnordost.streetcomplete.util.TextChangedWatcher +import de.westnordost.streetcomplete.view.OnAdapterItemSelectedListener class AddMaxHeightForm : AbstractQuestFormAnswerFragment() { @@ -68,12 +68,8 @@ class AddMaxHeightForm : AbstractQuestFormAnswerFragment() { heightUnitSelect?.isGone = lengthUnits.size == 1 heightUnitSelect?.adapter = ArrayAdapter(requireContext(), R.layout.spinner_item_centered, lengthUnits) heightUnitSelect?.setSelection(0) - heightUnitSelect?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View?, position: Int, id: Long) { - switchLayout(heightUnitSelect?.selectedItem as LengthUnit) - } - - override fun onNothingSelected(parentView: AdapterView<*>) {} + heightUnitSelect?.onItemSelectedListener = OnAdapterItemSelectedListener { + switchLayout(heightUnitSelect?.selectedItem as LengthUnit) } inchInput?.filters = arrayOf(InputFilter { source, start, end, dest, dstart, dend -> diff --git a/app/src/main/java/de/westnordost/streetcomplete/view/LengthInput.kt b/app/src/main/java/de/westnordost/streetcomplete/view/LengthInput.kt index 33d9649031..a7827ac4ed 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/view/LengthInput.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/view/LengthInput.kt @@ -3,8 +3,6 @@ package de.westnordost.streetcomplete.view import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater -import android.view.View -import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.FrameLayout import androidx.core.view.isGone @@ -34,12 +32,9 @@ class LengthInput @JvmOverloads constructor( var onInputChanged: (() -> Unit)? = null init { - binding.unitSelect.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - updateInputFieldsVisibility() - onInputChanged?.invoke() - } - override fun onNothingSelected(parent: AdapterView<*>?) {} + binding.unitSelect.onItemSelectedListener = OnAdapterItemSelectedListener { + updateInputFieldsVisibility() + onInputChanged?.invoke() } binding.feetInput.filters = arrayOf(acceptIntDigits(4)) diff --git a/app/src/main/java/de/westnordost/streetcomplete/view/OnAdapterItemSelectedListener.kt b/app/src/main/java/de/westnordost/streetcomplete/view/OnAdapterItemSelectedListener.kt new file mode 100644 index 0000000000..2fe47465a6 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/view/OnAdapterItemSelectedListener.kt @@ -0,0 +1,14 @@ +package de.westnordost.streetcomplete.view + +import android.view.View +import android.widget.AdapterView + +class OnAdapterItemSelectedListener(val onItemSelected: (position: Int) -> Unit) + : AdapterView.OnItemSelectedListener { + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + onItemSelected(position) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} +} From fcea3044ed38f1fbc7f630c31df4b8d472a48cfe Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 2 Mar 2022 15:53:11 +0100 Subject: [PATCH 02/11] rename OpeningHoursAdapter --- .../quests/opening_hours/AddOpeningHoursForm.kt | 8 +++++--- .../opening_hours/adapter/OpeningHoursAdapter.kt | 14 +++++++------- .../quests/parking_fee/AddParkingFeeForm.kt | 4 +++- .../streetcomplete/quests/parking_fee/Fee.kt | 8 ++++---- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursForm.kt index 4599c4f623..c9840f05e8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/AddOpeningHoursForm.kt @@ -17,7 +17,7 @@ import de.westnordost.streetcomplete.quests.AbstractQuestFormAnswerFragment import de.westnordost.streetcomplete.quests.AnswerItem import de.westnordost.streetcomplete.quests.opening_hours.adapter.OpeningMonthsRow import de.westnordost.streetcomplete.quests.opening_hours.adapter.OpeningWeekdaysRow -import de.westnordost.streetcomplete.quests.opening_hours.adapter.RegularOpeningHoursAdapter +import de.westnordost.streetcomplete.quests.opening_hours.adapter.OpeningHoursAdapter import de.westnordost.streetcomplete.util.AdapterDataChangedWatcher import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString @@ -47,14 +47,16 @@ class AddOpeningHoursForm : AbstractQuestFormAnswerFragment( } ) - private lateinit var openingHoursAdapter: RegularOpeningHoursAdapter + private lateinit var openingHoursAdapter: OpeningHoursAdapter private var isDisplayingPreviousOpeningHours: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - openingHoursAdapter = RegularOpeningHoursAdapter(requireContext(), countryInfo) + openingHoursAdapter = OpeningHoursAdapter(requireContext()) + openingHoursAdapter.firstDayOfWorkweek = countryInfo.firstDayOfWorkweek + openingHoursAdapter.regularShoppingDays = countryInfo.regularShoppingDays } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt index fefd14a8f1..d3bc9cedac 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt @@ -7,7 +7,6 @@ import android.view.ViewGroup import androidx.core.view.isInvisible import androidx.recyclerview.widget.RecyclerView import de.westnordost.streetcomplete.R -import de.westnordost.streetcomplete.data.meta.CountryInfo import de.westnordost.streetcomplete.databinding.QuestTimesMonthRowBinding import de.westnordost.streetcomplete.databinding.QuestTimesOffdayRowBinding import de.westnordost.streetcomplete.databinding.QuestTimesWeekdayRowBinding @@ -30,10 +29,8 @@ data class OpeningWeekdaysRow(var weekdays: Weekdays, var timeRange: TimeRange) @Serializable data class OffDaysRow(var weekdays: Weekdays) : OpeningHoursRow() -class RegularOpeningHoursAdapter( - private val context: Context, - private val countryInfo: CountryInfo -) : RecyclerView.Adapter() { +class OpeningHoursAdapter(private val context: Context) + : RecyclerView.Adapter() { var rows: MutableList = mutableListOf() set(value) { @@ -47,6 +44,9 @@ class RegularOpeningHoursAdapter( notifyDataSetChanged() } + var firstDayOfWorkweek: String = "Mo" + var regularShoppingDays: Int = 6 + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val inflater = LayoutInflater.from(parent.context) return when (viewType) { @@ -292,9 +292,9 @@ class RegularOpeningHoursAdapter( private fun getWeekdaysSuggestion(isFirst: Boolean): Weekdays { if (isFirst) { - val firstWorkDayIdx = Weekdays.getWeekdayIndex(countryInfo.firstDayOfWorkweek) + val firstWorkDayIdx = Weekdays.getWeekdayIndex(firstDayOfWorkweek) val result = BooleanArray(Weekdays.OSM_ABBR_WEEKDAYS.size) - for (i in 0 until countryInfo.regularShoppingDays) { + for (i in 0 until regularShoppingDays) { result[(i + firstWorkDayIdx) % Weekdays.WEEKDAY_COUNT] = true } return Weekdays(result) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFeeForm.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFeeForm.kt index dfa3b6866f..a1188f0ced 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFeeForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/AddParkingFeeForm.kt @@ -51,7 +51,9 @@ class AddParkingFeeForm : AbstractQuestFormAnswerFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - openingHoursAdapter = RegularOpeningHoursAdapter(requireContext(), countryInfo) + openingHoursAdapter = OpeningHoursAdapter(requireContext()) + openingHoursAdapter.firstDayOfWorkweek = countryInfo.firstDayOfWorkweek + openingHoursAdapter.regularShoppingDays = countryInfo.regularShoppingDays openingHoursAdapter.rows = loadOpeningHoursData(savedInstanceState).toMutableList() openingHoursAdapter.registerAdapterDataObserver(AdapterDataChangedWatcher { checkIsFormComplete() }) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Fee.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Fee.kt index e776f9dcae..551ed27f67 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Fee.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Fee.kt @@ -8,8 +8,8 @@ sealed class Fee object HasFee : Fee() object HasNoFee : Fee() -data class HasFeeAtHours(val openingHours: OpeningHoursRuleList) : Fee() -data class HasFeeExceptAtHours(val openingHours: OpeningHoursRuleList) : Fee() +data class HasFeeAtHours(val hours: OpeningHoursRuleList) : Fee() +data class HasFeeExceptAtHours(val hours: OpeningHoursRuleList) : Fee() fun Fee.applyTo(tags: Tags) { when (this) { @@ -23,11 +23,11 @@ fun Fee.applyTo(tags: Tags) { } is HasFeeAtHours -> { tags.updateWithCheckDate("fee", "no") - tags["fee:conditional"] = "yes @ ($openingHours)" + tags["fee:conditional"] = "yes @ ($hours)" } is HasFeeExceptAtHours -> { tags.updateWithCheckDate("fee", "yes") - tags["fee:conditional"] = "no @ ($openingHours)" + tags["fee:conditional"] = "no @ ($hours)" } } } From 1c8a5cc7e45a5b0abd92e1e8440c71304ff3b740 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Wed, 2 Mar 2022 20:32:23 +0100 Subject: [PATCH 03/11] add Maxstay sealed class --- .../quests/parking_fee/FeeAndMaxStay.kt | 10 +++++ .../quests/parking_fee/Maxstay.kt | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/FeeAndMaxStay.kt create mode 100644 app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Maxstay.kt diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/FeeAndMaxStay.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/FeeAndMaxStay.kt new file mode 100644 index 0000000000..4611c56f60 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/FeeAndMaxStay.kt @@ -0,0 +1,10 @@ +package de.westnordost.streetcomplete.quests.parking_fee + +import de.westnordost.streetcomplete.data.osm.osmquests.Tags + +data class FeeAndMaxStay(val fee: Fee, val maxstay: Maxstay? = null) + +fun FeeAndMaxStay.applyTo(tags: Tags) { + fee.applyTo(tags) + maxstay?.applyTo(tags) +} diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Maxstay.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Maxstay.kt new file mode 100644 index 0000000000..31b6894d9e --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/parking_fee/Maxstay.kt @@ -0,0 +1,43 @@ +package de.westnordost.streetcomplete.quests.parking_fee + +import de.westnordost.streetcomplete.data.meta.updateWithCheckDate +import de.westnordost.streetcomplete.data.osm.osmquests.Tags +import de.westnordost.streetcomplete.ktx.toShortString +import de.westnordost.streetcomplete.osm.opening_hours.parser.OpeningHoursRuleList +import de.westnordost.streetcomplete.quests.parking_fee.Maxstay.Unit.* + +sealed interface Maxstay { + enum class Unit { MINUTES, HOURS } +} + +object NoMaxstay : Maxstay +data class MaxstayDuration(val value: Double, val unit: Maxstay.Unit) : Maxstay +data class MaxstayAtHours(val duration: MaxstayDuration, val hours: OpeningHoursRuleList) : Maxstay +data class MaxstayExceptAtHours(val duration: MaxstayDuration, val hours: OpeningHoursRuleList) : Maxstay + +fun MaxstayDuration.toOsmValue(): String = + value.toShortString() + " " + when (unit) { + MINUTES -> if (value != 1.0) "minutes" else "minute" + HOURS -> if (value != 1.0) "hours" else "hour" + } + +fun Maxstay.applyTo(tags: Tags) { + when (this) { + is MaxstayExceptAtHours -> { + tags.updateWithCheckDate("maxstay", duration.toOsmValue()) + tags["maxstay:conditional"] = "no @ ($hours)" + } + is MaxstayAtHours -> { + tags.updateWithCheckDate("maxstay", "no") + tags["maxstay:conditional"] = "${duration.toOsmValue()} @ ($hours)" + } + is MaxstayDuration -> { + tags.updateWithCheckDate("maxstay", toOsmValue()) + tags.remove("maxstay:conditional") + } + NoMaxstay -> { + tags.updateWithCheckDate("maxstay", "no") + tags.remove("maxstay:conditional") + } + } +} From 3d2b59288eef9229f1fbc5839c0bca925b8fdab4 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 4 Mar 2022 01:56:43 +0100 Subject: [PATCH 04/11] fix 2nd row would not update when first changes weekdays --- .../quests/opening_hours/adapter/OpeningHoursAdapter.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt index d3bc9cedac..75df15e720 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt @@ -66,7 +66,8 @@ class OpeningHoursAdapter(private val context: Context) } is WeekdayViewHolder -> { val prevRow = if (position > 0) rows[position - 1] as? OpeningWeekdaysRow else null - holder.update(row as OpeningWeekdaysRow, prevRow, isEnabled) + val nextRow = if (rows.lastIndex > position) rows[position + 1] as? OpeningWeekdaysRow else null + holder.update(row as OpeningWeekdaysRow, prevRow, nextRow, isEnabled) } is OffDaysViewHolder -> { holder.update(row as OffDaysRow, isEnabled) @@ -233,7 +234,7 @@ class OpeningHoursAdapter(private val context: Context) } } - fun update(row: OpeningWeekdaysRow, rowBefore: OpeningWeekdaysRow?, isEnabled: Boolean) { + fun update(row: OpeningWeekdaysRow, rowBefore: OpeningWeekdaysRow?, nextRow: OpeningWeekdaysRow?, isEnabled: Boolean) { binding.weekdaysLabel.text = if (rowBefore != null && row.weekdays == rowBefore.weekdays) "" else if (row.weekdays.isSelectionEmpty()) "(" + context.resources.getString(R.string.quest_openingHours_unspecified_range) + ")" @@ -241,7 +242,7 @@ class OpeningHoursAdapter(private val context: Context) binding.weekdaysLabel.setOnClickListener { openSetWeekdaysDialog(row.weekdays) { weekdays -> row.weekdays = weekdays - notifyItemChanged(adapterPosition) + notifyItemRangeChanged(adapterPosition, if (nextRow != null) 2 else 1) } } From 042c39453b67d9df26609c57a5221f1ba4b7fa03 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 4 Mar 2022 01:57:08 +0100 Subject: [PATCH 05/11] don't "complain" that weekdays are unspecified if it is the first row --- .../quests/opening_hours/adapter/OpeningHoursAdapter.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt index 75df15e720..4d635b6c40 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/quests/opening_hours/adapter/OpeningHoursAdapter.kt @@ -44,7 +44,9 @@ class OpeningHoursAdapter(private val context: Context) notifyDataSetChanged() } + /** Set to change which weekdays are pre-checked in the weekday-select dialog */ var firstDayOfWorkweek: String = "Mo" + /** Set to change which weekdays are pre-checked in the weekday-select dialog */ var regularShoppingDays: Int = 6 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -237,8 +239,9 @@ class OpeningHoursAdapter(private val context: Context) fun update(row: OpeningWeekdaysRow, rowBefore: OpeningWeekdaysRow?, nextRow: OpeningWeekdaysRow?, isEnabled: Boolean) { binding.weekdaysLabel.text = if (rowBefore != null && row.weekdays == rowBefore.weekdays) "" - else if (row.weekdays.isSelectionEmpty()) "(" + context.resources.getString(R.string.quest_openingHours_unspecified_range) + ")" + else if (rowBefore != null && row.weekdays.isSelectionEmpty()) "(" + context.resources.getString(R.string.quest_openingHours_unspecified_range) + ")" else row.weekdays.toLocalizedString(context.resources) + binding.weekdaysLabel.setOnClickListener { openSetWeekdaysDialog(row.weekdays) { weekdays -> row.weekdays = weekdays From ebbe88e49a83a17652a8504c2ccd6f300f20d964 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 4 Mar 2022 02:01:31 +0100 Subject: [PATCH 06/11] add duration input --- .../streetcomplete/view/DurationInput.kt | 59 +++++++++++++++++++ app/src/main/res/layout/view_duration.xml | 32 ++++++++++ app/src/main/res/values/strings.xml | 3 + 3 files changed, 94 insertions(+) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/view/DurationInput.kt create mode 100644 app/src/main/res/layout/view_duration.xml diff --git a/app/src/main/java/de/westnordost/streetcomplete/view/DurationInput.kt b/app/src/main/java/de/westnordost/streetcomplete/view/DurationInput.kt new file mode 100644 index 0000000000..e4664c2eb9 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/view/DurationInput.kt @@ -0,0 +1,59 @@ +package de.westnordost.streetcomplete.view + +import android.content.Context +import android.content.res.Resources +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.ArrayAdapter +import android.widget.FrameLayout +import androidx.core.widget.addTextChangedListener +import de.westnordost.streetcomplete.R +import de.westnordost.streetcomplete.databinding.ViewDurationBinding +import de.westnordost.streetcomplete.ktx.numberOrNull +import de.westnordost.streetcomplete.view.inputfilter.InputValidator +import de.westnordost.streetcomplete.view.inputfilter.acceptDecimalDigits +import de.westnordost.streetcomplete.view.inputfilter.acceptIntDigits + +/** Allows to input a duration, in hours or minutes */ +class DurationInput @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : FrameLayout(context, attrs, defStyleAttr) { + + private val binding = ViewDurationBinding.inflate(LayoutInflater.from(context), this) + + var onInputChanged: (() -> Unit)? = null + + var durationUnit: DurationUnit + set(value) { binding.unitSelect.setSelection(value.ordinal) } + get() = DurationUnit.values()[binding.unitSelect.selectedItemPosition] + + var durationValue: Double + set(value) { binding.input.setText(value.toString()) } + get() = binding.input.numberOrNull ?: 0.0 + + init { + binding.unitSelect.adapter = ArrayAdapter( + context, + R.layout.spinner_item_centered, + DurationUnit.values().map { it.toLocalizedString(context.resources) } + ) + if (binding.unitSelect.selectedItemPosition < 0) binding.unitSelect.setSelection(1) + binding.unitSelect.onItemSelectedListener = OnAdapterItemSelectedListener { + + onInputChanged?.invoke() + } + binding.input.filters = arrayOf(acceptDecimalDigits(3, 1)) + binding.input.addTextChangedListener { onInputChanged?.invoke() } + } + +} + +enum class DurationUnit { MINUTES, HOURS, DAYS } + +private fun DurationUnit.toLocalizedString(resources: Resources) = when (this) { + DurationUnit.MINUTES -> resources.getString(R.string.unit_minutes) + DurationUnit.HOURS -> resources.getString(R.string.unit_hours) + DurationUnit.DAYS -> resources.getString(R.string.unit_days) +} diff --git a/app/src/main/res/layout/view_duration.xml b/app/src/main/res/layout/view_duration.xml new file mode 100644 index 0000000000..56ff602a2b --- /dev/null +++ b/app/src/main/res/layout/view_duration.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bff216158b..0b528e6857 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -579,6 +579,9 @@ Otherwise, you can download another keyboard in the app store. Popular keyboards Yes, but only… Yes, but not… at the following times: + days + hours + minutes It has multiple house numbers "You can simply enter comma–separated house numbers or ranges.\nFor instance 1,3 or 2–6." Stand (supports bike frame) From 33d4f3eae496fd7405f83fb83be31096ea4a8e32 Mon Sep 17 00:00:00 2001 From: Tobias Zwick Date: Fri, 4 Mar 2022 02:01:51 +0100 Subject: [PATCH 07/11] add time restriction select view --- .../view/TimeRestrictionSelectView.kt | 123 ++++++++++++++++++ .../layout/view_time_restriction_select.xml | 44 +++++++ app/src/main/res/values/strings.xml | 3 + 3 files changed, 170 insertions(+) create mode 100644 app/src/main/java/de/westnordost/streetcomplete/view/TimeRestrictionSelectView.kt create mode 100644 app/src/main/res/layout/view_time_restriction_select.xml diff --git a/app/src/main/java/de/westnordost/streetcomplete/view/TimeRestrictionSelectView.kt b/app/src/main/java/de/westnordost/streetcomplete/view/TimeRestrictionSelectView.kt new file mode 100644 index 0000000000..11c5e30524 --- /dev/null +++ b/app/src/main/java/de/westnordost/streetcomplete/view/TimeRestrictionSelectView.kt @@ -0,0 +1,123 @@ +package de.westnordost.streetcomplete.view + +import android.content.Context +import android.content.res.Resources +import android.os.Parcel +import android.os.Parcelable +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.ArrayAdapter +import android.widget.FrameLayout +import androidx.annotation.Keep +import androidx.core.view.isGone +import de.westnordost.streetcomplete.R +import de.westnordost.streetcomplete.databinding.ViewTimeRestrictionSelectBinding +import de.westnordost.streetcomplete.quests.opening_hours.adapter.OpeningHoursAdapter +import de.westnordost.streetcomplete.quests.opening_hours.adapter.OpeningHoursRow +import de.westnordost.streetcomplete.util.AdapterDataChangedWatcher +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +/** Allows to input a time restriction, either inclusive or exclusive, based on opening hours. */ +class TimeRestrictionSelectView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : FrameLayout(context, attrs, defStyleAttr) { + + private val binding = ViewTimeRestrictionSelectBinding.inflate(LayoutInflater.from(context), this) + + private val hoursAdapter = OpeningHoursAdapter(context) + + private val timeRestrictionAdapter = ArrayAdapter( + context, + R.layout.spinner_item_centered, + TimeRestriction.values().map { it.toLocalizedString(context.resources) }.toMutableList() + ) + + var onInputChanged: (() -> Unit)? = null + + var firstDayOfWorkweek: String + set(value) { hoursAdapter.firstDayOfWorkweek = value } + get() = hoursAdapter.firstDayOfWorkweek + + var regularShoppingDays: Int + set(value) { hoursAdapter.regularShoppingDays = value } + get() = hoursAdapter.regularShoppingDays + + var timeRestriction: TimeRestriction + set(value) { binding.selectAtHours.setSelection(selectableTimeRestrictions.indexOf(value)) } + get() = selectableTimeRestrictions[binding.selectAtHours.selectedItemPosition] + + var selectableTimeRestrictions: List = TimeRestriction.values().toList() + set(value) { + field = value + timeRestrictionAdapter.clear() + timeRestrictionAdapter.addAll(value.map { it.toLocalizedString(context.resources) }) + } + + var hours: List + set(value) { hoursAdapter.rows = value.toMutableList() } + get() = hoursAdapter.rows + + val isComplete: Boolean get() = + timeRestriction == TimeRestriction.AT_ANY_TIME || hours.isNotEmpty() + + init { + hoursAdapter.registerAdapterDataObserver(AdapterDataChangedWatcher { onInputChanged?.invoke() }) + binding.openingHoursList.adapter = hoursAdapter + binding.addTimesButton.setOnClickListener { hoursAdapter.addNewWeekdays() } + + binding.selectAtHours.adapter = timeRestrictionAdapter + if (binding.selectAtHours.selectedItemPosition < 0) binding.selectAtHours.setSelection(0) + binding.openingHoursContainer.isGone = timeRestriction == TimeRestriction.AT_ANY_TIME + binding.selectAtHours.onItemSelectedListener = OnAdapterItemSelectedListener { + binding.openingHoursContainer.isGone = timeRestriction == TimeRestriction.AT_ANY_TIME + onInputChanged?.invoke() + } + } + + public override fun onSaveInstanceState(): Parcelable { + val superState = super.onSaveInstanceState() + val ss = SavedState(superState) + ss.oh = hoursAdapter.rows + return ss + } + + public override fun onRestoreInstanceState(s: Parcelable) { + val ss = s as SavedState + super.onRestoreInstanceState(ss.superState) + ss.oh?.let { hoursAdapter.rows = it } + } + + internal class SavedState : BaseSavedState { + var oh: MutableList? = null + + constructor(superState: Parcelable?) : super(superState) + constructor(parcel: Parcel) : super(parcel) { + oh = parcel.readString()?.let { Json.decodeFromString(it) } + } + + override fun writeToParcel(out: Parcel, flags: Int) { + super.writeToParcel(out, flags) + out.writeString(Json.encodeToString(oh)) + } + + companion object { + @JvmField @Keep + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = SavedState(parcel) + override fun newArray(size: Int) = arrayOfNulls(size) + } + } + } +} + +enum class TimeRestriction { AT_ANY_TIME, ONLY_AT_HOURS, EXCEPT_AT_HOURS } + +private fun TimeRestriction.toLocalizedString(resources: Resources) = when (this) { + TimeRestriction.AT_ANY_TIME -> resources.getString(R.string.at_any_time) + TimeRestriction.ONLY_AT_HOURS -> resources.getString(R.string.only_at_hours) + TimeRestriction.EXCEPT_AT_HOURS -> resources.getString(R.string.except_at_hours) +} diff --git a/app/src/main/res/layout/view_time_restriction_select.xml b/app/src/main/res/layout/view_time_restriction_select.xml new file mode 100644 index 0000000000..6fea1819d1 --- /dev/null +++ b/app/src/main/res/layout/view_time_restriction_select.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + +