@@ -25,6 +25,7 @@ import android.view.View
2525import android.widget.AdapterView
2626import android.widget.ArrayAdapter
2727import android.widget.CheckBox
28+ import android.widget.Spinner
2829import androidx.appcompat.app.AlertDialog
2930import androidx.core.os.bundleOf
3031import androidx.core.view.isVisible
@@ -35,7 +36,6 @@ import androidx.lifecycle.Lifecycle
3536import androidx.lifecycle.lifecycleScope
3637import androidx.lifecycle.repeatOnLifecycle
3738import com.google.android.material.materialswitch.MaterialSwitch
38- import com.google.android.material.textfield.MaterialAutoCompleteTextView
3939import com.google.android.material.textfield.TextInputEditText
4040import com.google.android.material.textfield.TextInputLayout
4141import com.ichi2.anki.CardBrowser
@@ -52,6 +52,8 @@ import com.ichi2.utils.show
5252import com.ichi2.utils.title
5353import dev.androidbroadcast.vbpd.viewBinding
5454import kotlinx.coroutines.launch
55+ import java.util.Locale.getDefault
56+ import kotlin.text.replaceFirstChar
5557
5658/* *
5759 * Represents the screen where a filtered deck can be built or rebuilt after updating its properties.
@@ -171,7 +173,7 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
171173 SearchInputError .NotANumber -> TR .errorsInvalidInputEmpty()
172174 null -> null
173175 }
174- binding.filterCardsInput.setAdapterIfChanged (state.cardOptions, filter1State.index)
176+ binding.filterCards.setAdapterIfNeeded (state.cardOptions, filter1State.index)
175177 // rescheduling (done here because in filter 2 setup we might exit early)
176178 binding.checkBoxReschedule.setCheckedIfChanged(state.shouldReschedule)
177179 binding.rescheduleDelayAgainInput.setTextIfChanged(state.delayAgain)
@@ -188,7 +190,8 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
188190 }
189191 binding.secondFilterSearchContainer.isVisible = state.isSecondFilterEnabled
190192 binding.secondFilterLimitInputLayout.isVisible = state.isSecondFilterEnabled
191- binding.secondFilterCardsInputLayout.isVisible = state.isSecondFilterEnabled
193+ binding.secondFilterCardsLabel.isVisible = state.isSecondFilterEnabled
194+ binding.secondFilterCards.isVisible = state.isSecondFilterEnabled
192195 val filter2State = state.filter2State ? : return
193196 binding.secondFilterSearchInput.setTextIfChanged(filter2State.search)
194197 binding.secondFilterLimitInput.setTextIfChanged(filter2State.limit)
@@ -198,7 +201,7 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
198201 SearchInputError .NotANumber -> TR .errorsInvalidInputEmpty()
199202 null -> null
200203 }
201- binding.secondFilterCardsInput.setAdapterIfChanged (state.cardOptions, filter2State.index)
204+ binding.secondFilterCards.setAdapterIfNeeded (state.cardOptions, filter2State.index)
202205 }
203206
204207 private fun TextInputLayout.setupRescheduleDelay (
@@ -228,10 +231,8 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
228231 binding.filterLimitInput.onTextChanged { text ->
229232 viewModel.onLimitChange(FilterIndex .First , text)
230233 }
231- binding.filterCardsInput.onItemClickListener =
232- AdapterView .OnItemClickListener { _, _, position, _ ->
233- viewModel.onCardsOptionsChange(FilterIndex .First , position)
234- }
234+ binding.filterCards.bindListener(FilterIndex .First )
235+
235236 // filter#2
236237 binding.switchSecondFilter.onCheckedChanged(viewModel::onSecondFilterStatusChange)
237238 binding.secondFilterSearchInput.onTextChanged { text ->
@@ -243,10 +244,8 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
243244 binding.secondFilterLimitInput.onTextChanged { text ->
244245 viewModel.onLimitChange(FilterIndex .Second , text)
245246 }
246- binding.secondFilterCardsInput.onItemClickListener =
247- AdapterView .OnItemClickListener { _, _, position, _ ->
248- viewModel.onCardsOptionsChange(FilterIndex .Second , position)
249- }
247+ binding.secondFilterCards.bindListener(FilterIndex .Second )
248+
250249 // reschedule
251250 binding.checkBoxReschedule.onCheckedChanged(viewModel::onRescheduleChange)
252251 binding.rescheduleDelayAgainInput.onTextChanged { text ->
@@ -289,12 +288,13 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
289288 // first filter
290289 binding.filterSearchInputLayout.hint = TR .actionsSearch()
291290 binding.filterLimitInputLayout.hint = TR .decksLimitTo()
292- binding.filterCardsInputLayout.hint = TR .decksCardsSelectedBy()
291+ binding.filterCardsLabel.text = TR .decksCardsSelectedBy().capitalize ()
293292 // second filter
294293 binding.switchSecondFilter.text = TR .decksEnableSecondFilter()
295294 binding.secondFilterSearchInputLayout.hint = TR .actionsSearch()
296295 binding.secondFilterLimitInputLayout.hint = TR .decksLimitTo()
297- binding.secondFilterCardsInputLayout.hint = TR .decksCardsSelectedBy()
296+ binding.secondFilterCardsLabel.text = TR .decksCardsSelectedBy().capitalize()
297+
298298 // buttons
299299 binding.btnShowExcludedCards.text = TR .decksUnmovableCards()
300300 binding.checkBoxReschedule.text = TR .decksRescheduleCardsBasedOnMyAnswers()
@@ -316,10 +316,28 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
316316 if (this .isChecked != newChecked) this .isChecked = newChecked
317317 }
318318
319- /* * Sets the adapter and selection for [MaterialAutoCompleteTextView] only if its items are different */
320- private fun MaterialAutoCompleteTextView.setAdapterIfChanged (
319+ private fun Spinner.bindListener (index : FilterIndex ) {
320+ onItemSelectedListener =
321+ object : AdapterView .OnItemSelectedListener {
322+ override fun onItemSelected (
323+ adapterView : AdapterView <* >? ,
324+ view : View ? ,
325+ position : Int ,
326+ id : Long ,
327+ ) {
328+ viewModel.onCardsOptionsChange(index, position)
329+ }
330+
331+ override fun onNothingSelected (adapterView : AdapterView <* >? ) {
332+ // do nothing
333+ }
334+ }
335+ }
336+
337+ /* * Sets the adapter and selection for cards [Spinner] only if options are available */
338+ private fun Spinner.setAdapterIfNeeded (
321339 cardOptions : List <String >,
322- selectedIndex : Int ,
340+ selectedPosition : Int ,
323341 ) {
324342 if (cardOptions.isNotEmpty()) {
325343 setAdapter(
@@ -329,10 +347,17 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
329347 cardOptions,
330348 ),
331349 )
332- setText(cardOptions[selectedIndex], false )
350+ setSelection(selectedPosition )
333351 }
334352 }
335353
354+ /* *
355+ * Needed because backend returns the string TR.decksCardsSelectedBy starting with a lowercase.
356+ * This was the recommended replacement implementation for the now deprecated Kotlin
357+ * capitalize() method.
358+ */
359+ private fun String.capitalize () = replaceFirstChar { if (it.isLowerCase()) it.titlecase(getDefault()) else it.toString() }
360+
336361 companion object {
337362 const val ARG_DECK_ID = " arg_deck_id"
338363 const val ARG_SEARCH = " arg_search"
0 commit comments