Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package com.ichi2.preferences

import android.content.Context
import android.util.AttributeSet
import androidx.preference.PreferenceViewHolder
import com.google.android.material.materialswitch.MaterialSwitch
import com.ichi2.anki.R

/**
* A [NumberRangePreferenceCompat] with a [MaterialSwitch] widget on the preference row.
*
* The switch reflects whether the value is non-zero (enabled) or zero (disabled).
* Tapping the switch toggles between enabled/disabled without opening the dialog.
* Tapping the title area opens the incrementer dialog only when enabled.
*
* No separate preference key is needed — the existing value `0` represents "disabled".
*/
class ToggleableIncrementerNumberRangePreferenceCompat :
NumberRangePreferenceCompat,
DialogFragmentProvider {
@Suppress("unused")
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int,
) : super(context, attrs, defStyleAttr, defStyleRes)

@Suppress("unused")
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

@Suppress("unused")
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

@Suppress("unused")
constructor(context: Context) : super(context)

init {
widgetLayoutResource = R.layout.preference_widget_switch_with_separator
}

override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)

val switch = holder.findViewById(R.id.switch_widget) as MaterialSwitch
switch.setOnCheckedChangeListener(null)
switch.isChecked = getValue() != 0
switch.setOnCheckedChangeListener { _, isChecked ->
val newValue = if (isChecked) maxOf(1, min) else 0
if (callChangeListener(newValue)) {
setValue(newValue)
notifyChanged()
} else {
switch.isChecked = !isChecked
}
}
}

override fun onClick() {
if (getValue() == 0) return
super.onClick()
}

override fun makeDialogFragment() = IncrementerNumberRangePreferenceCompat.IncrementerNumberRangeDialogFragmentCompat()
}
4 changes: 2 additions & 2 deletions AnkiDroid/src/main/res/xml/preferences_reviewing.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
android:title="@string/day_offset_with_description"
app:persistent="false"
app:summaryFormat="@plurals/day_offset_summ"/>
<com.ichi2.preferences.IncrementerNumberRangePreferenceCompat
<com.ichi2.preferences.ToggleableIncrementerNumberRangePreferenceCompat
android:key="@string/learn_cutoff_preference"
android:title="@string/learn_cutoff"
app:max="999"
app:min="0"
app:summaryFormat="@plurals/pref_summ_minutes"/>
<com.ichi2.preferences.IncrementerNumberRangePreferenceCompat
<com.ichi2.preferences.ToggleableIncrementerNumberRangePreferenceCompat
android:key="@string/time_limit_preference"
android:title="@string/time_limit"
app:max="999"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package com.ichi2.preferences

import com.ichi2.preferences.IncrementerNumberRangePreferenceCompat.Companion.ValidationResult
import com.ichi2.preferences.IncrementerNumberRangePreferenceCompat.Companion.validate
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class ToggleableIncrementerNumberRangePreferenceCompatTest {
@Test
fun `validation logic works with toggleable min of 0`() {
val min = 0
val max = 999

assertThat(
"0 should be VALID (represents disabled state)",
validate("0", 0, min, max),
IsEqual(ValidationResult.VALID),
)

assertThat(
"A number within range should be VALID",
validate("20", 20, min, max),
IsEqual(ValidationResult.VALID),
)

assertThat(
"Max boundary should be VALID",
validate("999", 999, min, max),
IsEqual(ValidationResult.VALID),
)

assertThat(
"A number greater than max should return OVERFLOW",
validate("1000", 1000, min, max),
IsEqual(ValidationResult.OVERFLOW),
)

assertThat(
"An empty string should return EMPTY",
validate("", null, min, max),
IsEqual(ValidationResult.EMPTY),
)

assertThat(
"A number exceeding Integer limits should return INVALID",
validate("9999999999", null, min, max),
IsEqual(ValidationResult.INVALID),
)
}

@Test
fun `toggle on default value is 1 when min is 0`() {
val min = 0
val defaultValue = maxOf(1, min)
assertThat(
"Default value when toggling ON from disabled should be 1",
defaultValue,
IsEqual(1),
)
}

@Test
fun `toggle on default value respects min when min is positive`() {
val min = 5
val defaultValue = maxOf(1, min)
assertThat(
"Default value when toggling ON should be at least min",
defaultValue,
IsEqual(5),
)
}
}
Loading