Skip to content

Commit daec205

Browse files
authored
Replace setting sliders with steppers where useful (#963)
1 parent 5365b07 commit daec205

9 files changed

Lines changed: 266 additions & 32 deletions

File tree

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/PreferenceBindingExtensions.kt

Lines changed: 144 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package com.philkes.notallyx.presentation.activity.main.fragment.settings
22

3+
import android.annotation.SuppressLint
34
import android.content.Context
45
import android.hardware.biometrics.BiometricManager
56
import android.net.Uri
67
import android.os.Build
8+
import android.os.Handler
9+
import android.os.Looper
710
import android.text.method.PasswordTransformationMethod
811
import android.view.LayoutInflater
12+
import android.view.MotionEvent
913
import android.view.View
14+
import android.view.inputmethod.EditorInfo
1015
import androidx.core.view.isVisible
16+
import androidx.core.widget.doAfterTextChanged
1117
import androidx.documentfile.provider.DocumentFile
1218
import com.google.android.material.color.DynamicColors
1319
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -23,11 +29,14 @@ import com.philkes.notallyx.databinding.DialogSelectionBoxBinding
2329
import com.philkes.notallyx.databinding.DialogTextInputBinding
2430
import com.philkes.notallyx.databinding.PreferenceBinding
2531
import com.philkes.notallyx.databinding.PreferenceSeekbarBinding
32+
import com.philkes.notallyx.databinding.PreferenceStepperBinding
2633
import com.philkes.notallyx.presentation.checkedTag
34+
import com.philkes.notallyx.presentation.hideKeyboard
2735
import com.philkes.notallyx.presentation.select
2836
import com.philkes.notallyx.presentation.setCancelButton
2937
import com.philkes.notallyx.presentation.setTextSizeSp
3038
import com.philkes.notallyx.presentation.showAndFocus
39+
import com.philkes.notallyx.presentation.showKeyboard
3140
import com.philkes.notallyx.presentation.showToast
3241
import com.philkes.notallyx.presentation.view.misc.MenuDialog
3342
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
@@ -501,43 +510,157 @@ fun PreferenceSeekbarBinding.setupTextSizePreference(
501510
}
502511
}
503512

504-
fun PreferenceSeekbarBinding.setupAutoSaveIdleTime(
505-
preference: IntPreference,
513+
@SuppressLint("ClickableViewAccessibility")
514+
fun PreferenceStepperBinding.setup(
515+
value: Int,
516+
titleResId: Int,
517+
min: Int,
518+
max: Int,
506519
context: Context,
507-
value: Int = preference.value,
520+
enabled: Boolean = true,
521+
labelFormatter: ((Int) -> String)? = null,
508522
onChange: (newValue: Int) -> Unit,
509523
) {
510-
Slider.apply {
511-
setLabelFormatter { sliderValue ->
512-
if (sliderValue == -1f) {
513-
context.getString(R.string.disabled)
514-
} else "${sliderValue.toInt()}s"
524+
val initialValue = value.coerceIn(min, max)
525+
Title.setText(titleResId)
526+
ValueInput.setText(labelFormatter?.invoke(initialValue) ?: initialValue.toString())
527+
ValueInput.isEnabled = enabled
528+
529+
fun updateButtons(value: Int) {
530+
MinusButton.isEnabled = enabled && value > min
531+
PlusButton.isEnabled = enabled && value < max
532+
}
533+
534+
updateButtons(initialValue)
535+
536+
val handler = Handler(Looper.getMainLooper())
537+
fun updateValue(increment: Int, commit: Boolean): Boolean {
538+
val newValue = (ValueInput.tag as? Int ?: initialValue) + increment
539+
val valid = newValue in min..max
540+
if (valid) {
541+
ValueInput.tag = newValue
542+
ValueInput.setText(labelFormatter?.invoke(newValue) ?: newValue.toString())
543+
updateButtons(newValue)
544+
if (commit) {
545+
onChange(newValue)
546+
}
547+
}
548+
ValueInput.clearFocus()
549+
return valid
550+
}
551+
552+
fun stepperRunnable(isIncrement: Boolean) =
553+
object : Runnable {
554+
override fun run() {
555+
if (updateValue(if (isIncrement) 1 else -1, false)) {
556+
handler.postDelayed(this, 100)
557+
}
558+
}
515559
}
516-
addOnChangeListener { _, value, _ ->
517-
if (value == -1f) {
518-
setAlpha(0.6f) // Reduce opacity to make it look disabled
560+
var runnable: Runnable? = null
561+
val startAutoIncrement = { isIncrement: Boolean ->
562+
runnable?.let { handler.removeCallbacks(it) }
563+
runnable =
564+
if (isIncrement) stepperRunnable(isIncrement = true)
565+
else stepperRunnable(isIncrement = false)
566+
handler.postDelayed(runnable!!, 100)
567+
}
568+
569+
MinusButton.apply {
570+
setOnClickListener { updateValue(-1, true) }
571+
setOnLongClickListener {
572+
startAutoIncrement(false)
573+
true
574+
}
575+
setOnTouchListener { _, event ->
576+
if (
577+
runnable != null &&
578+
(event.action == MotionEvent.ACTION_UP ||
579+
event.action == MotionEvent.ACTION_CANCEL)
580+
) {
581+
handler.removeCallbacks(runnable!!)
582+
onChange(ValueInput.tag as? Int ?: value)
583+
}
584+
false
585+
}
586+
}
587+
588+
PlusButton.apply {
589+
setOnClickListener { updateValue(1, true) }
590+
setOnLongClickListener {
591+
startAutoIncrement(true)
592+
true
593+
}
594+
setOnTouchListener { _, event ->
595+
if (
596+
runnable != null &&
597+
(event.action == MotionEvent.ACTION_UP ||
598+
event.action == MotionEvent.ACTION_CANCEL)
599+
) {
600+
handler.removeCallbacks(runnable!!)
601+
onChange(ValueInput.tag as? Int ?: value)
602+
}
603+
false
604+
}
605+
}
606+
607+
ValueInput.apply {
608+
tag = value
609+
doAfterTextChanged { text ->
610+
if (ValueInput.hasFocus()) {
611+
val newValue = text.toString().toIntOrNull()
612+
if (newValue != null) {
613+
val clampedValue = newValue.coerceIn(min, max)
614+
if (clampedValue != ValueInput.tag) {
615+
ValueInput.tag = clampedValue
616+
updateButtons(clampedValue)
617+
}
618+
}
619+
}
620+
}
621+
setOnFocusChangeListener { _, hasFocus ->
622+
if (hasFocus) {
623+
val currentValue = ValueInput.tag as? Int ?: initialValue
624+
val text = currentValue.toString()
625+
ValueInput.setText(text)
626+
ValueInput.setSelection(text.length)
627+
context.showKeyboard(ValueInput)
519628
} else {
520-
setAlpha(1f) // Restore normal appearance
629+
val currentValue = ValueInput.tag as? Int ?: initialValue
630+
ValueInput.setText(labelFormatter?.invoke(currentValue) ?: currentValue.toString())
631+
updateButtons(currentValue)
632+
onChange(currentValue)
633+
}
634+
}
635+
setOnEditorActionListener { v, actionId, _ ->
636+
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT) {
637+
context.hideKeyboard(ValueInput)
638+
v.clearFocus() // This triggers the OnFocusChangeListener logic
639+
true
640+
} else {
641+
false
521642
}
522643
}
523644
}
524-
setup(preference, context, value, onChange = onChange)
525645
}
526646

527-
fun PreferenceSeekbarBinding.setupAutoEmptyBin(
647+
fun PreferenceStepperBinding.setup(
528648
preference: IntPreference,
529649
context: Context,
530650
value: Int = preference.value,
651+
labelFormatter: ((Int) -> String)? = null,
531652
onChange: (newValue: Int) -> Unit,
532653
) {
533-
Slider.apply {
534-
setLabelFormatter { sliderValue ->
535-
if (sliderValue == 0f) {
536-
context.getString(R.string.disabled)
537-
} else "${sliderValue.toInt()} ${context.getString(R.string.days)}"
538-
}
654+
setup(
655+
value,
656+
preference.titleResId!!,
657+
preference.min,
658+
preference.max,
659+
context,
660+
labelFormatter = labelFormatter,
661+
) { newValue ->
662+
onChange(newValue)
539663
}
540-
setup(preference, context, value, R.string.auto_remove_deleted_notes_hint, onChange)
541664
}
542665

543666
fun PreferenceBinding.setupStartView(

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.os.Bundle
1313
import android.provider.DocumentsContract
1414
import android.provider.Settings
1515
import android.text.method.PasswordTransformationMethod
16+
import android.util.Log
1617
import android.view.LayoutInflater
1718
import android.view.View
1819
import android.view.ViewGroup
@@ -376,10 +377,15 @@ class SettingsFragment : Fragment() {
376377
}
377378

378379
autoRemoveDeletedNotesAfterDays.observe(viewLifecycleOwner) { value ->
379-
binding.AutoEmptyBin.setupAutoEmptyBin(
380+
binding.AutoEmptyBin.setup(
380381
autoRemoveDeletedNotesAfterDays,
381382
requireContext(),
383+
labelFormatter = { v ->
384+
if (v == 0) requireContext().getString(R.string.off)
385+
else "$v ${requireContext().getString(R.string.days)}"
386+
},
382387
) { newValue ->
388+
Log.d("Stepper", "save auto remove")
383389
model.savePreference(autoRemoveDeletedNotesAfterDays, newValue)
384390
val workManager = WorkManager.getInstance(requireContext())
385391
if (newValue > 0) {
@@ -768,8 +774,13 @@ class SettingsFragment : Fragment() {
768774
}
769775
}
770776
}
771-
AutoSaveAfterIdle.setupAutoSaveIdleTime(autoSaveAfterIdleTime, requireContext()) {
772-
newValue ->
777+
AutoSaveAfterIdle.setup(
778+
autoSaveAfterIdleTime,
779+
requireContext(),
780+
labelFormatter = { v ->
781+
if (v == -1) requireContext().getString(R.string.off) else "${v}s"
782+
},
783+
) { newValue ->
773784
model.savePreference(autoSaveAfterIdleTime, newValue)
774785
}
775786

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/preference/NotallyXPreferences.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ class NotallyXPreferences private constructor(private val context: Context) {
146146
preferences,
147147
5,
148148
0,
149-
20,
149+
200,
150150
R.string.max_labels_to_display,
151151
)
152152

@@ -163,7 +163,7 @@ class NotallyXPreferences private constructor(private val context: Context) {
163163
preferences,
164164
0,
165165
0,
166-
365,
166+
3650,
167167
R.string.auto_remove_deleted_notes,
168168
)
169169

@@ -182,7 +182,7 @@ class NotallyXPreferences private constructor(private val context: Context) {
182182
preferences,
183183
5,
184184
-1,
185-
20,
185+
60 * 60 * 5,
186186
R.string.auto_save_after_idle_time,
187187
)
188188

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="960"
5+
android:viewportHeight="960"
6+
android:tint="?attr/colorControlNormal">
7+
<path
8+
android:fillColor="@android:color/white"
9+
android:pathData="M200,520L200,440L760,440L760,520L200,520Z"/>
10+
</vector>

app/src/main/res/layout/fragment_settings.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@
7171

7272
<include
7373
android:id="@+id/AutoSaveAfterIdle"
74-
layout="@layout/preference_seekbar" />
74+
layout="@layout/preference_stepper" />
7575

7676
<include
7777
android:id="@+id/AutoEmptyBin"
78-
layout="@layout/preference_seekbar" />
78+
layout="@layout/preference_stepper" />
7979

8080
<View
8181
android:layout_width="match_parent"
@@ -102,7 +102,7 @@
102102

103103
<include
104104
android:id="@+id/MaxLabels"
105-
layout="@layout/preference_seekbar" />
105+
layout="@layout/preference_stepper" />
106106

107107
<include
108108
android:id="@+id/LabelsHiddenInOverview"
@@ -172,11 +172,11 @@
172172

173173
<include
174174
android:id="@+id/PeriodicBackupsPeriodInDays"
175-
layout="@layout/preference_seekbar" />
175+
layout="@layout/preference_stepper" />
176176

177177
<include
178178
android:id="@+id/PeriodicBackupsMax"
179-
layout="@layout/preference_seekbar" />
179+
layout="@layout/preference_stepper" />
180180

181181
<View
182182
android:layout_width="match_parent"

0 commit comments

Comments
 (0)