Skip to content

Commit

Permalink
UI: Improve size selection dialog
Browse files Browse the repository at this point in the history
* Support for localized size units
* Support for both `,` and `.` delimiters
* Invalid size errors
* Disable "save" if input is invalid
  • Loading branch information
d4rken committed Dec 15, 2024
1 parent bcceb37 commit 0da82da
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 19 deletions.
1 change: 1 addition & 0 deletions app-common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,5 @@
<item quantity="other">%s files</item>
</plurals>
<string name="general_advice_title">Advice</string>
<string name="general_error_invalid_input_label">Invalid input</string>
</resources>
48 changes: 30 additions & 18 deletions app/src/main/java/eu/darken/sdmse/common/ui/SizeInputDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package eu.darken.sdmse.common.ui

import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.text.format.Formatter
import android.widget.Button
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.addTextChangedListener
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
Expand All @@ -22,13 +25,10 @@ class SizeInputDialog(

private val context: Context
get() = activity

private fun parseSize(input: String): Long? {
val match = SIZE_UNITS_REGEX.matchEntire(input.trim()) ?: return null
val (value, _, unit) = match.destructured
val factor = SIZE_UNITS[unit.uppercase()] ?: return null
return (value.toDoubleOrNull()?.times(factor))?.toLong()
}
private lateinit var dialog: AlertDialog
private val sizeParser = SizeParser(context)
private val positiveButton: Button
get() = dialog.getButton(DialogInterface.BUTTON_POSITIVE)

private val dialogLayout = ViewPreferenceInputSizeBinding.inflate(activity.layoutInflater, null, false).apply {
slider.valueFrom = minimumSize.toFloat()
Expand All @@ -48,8 +48,27 @@ class SizeInputDialog(

slider.setLabelFormatter { Formatter.formatShortFileSize(context, getSliderSize()) }

sizeText.addTextChangedListener {
parseSize(it.toString())?.let { setSliderSize(it) }
sizeText.addTextChangedListener { rawSize ->
val parsedSize = sizeParser.parse(rawSize.toString())
when {
parsedSize != null && parsedSize in minimumSize..maximumSize -> {
sizeText.error = null
setSliderSize(parsedSize)
positiveButton.isEnabled = true
}

parsedSize != null -> {
val minLimit = Formatter.formatShortFileSize(context, minimumSize)
val maxLimit = Formatter.formatShortFileSize(context, maximumSize)
sizeText.error = "$minLimit <= X <= $maxLimit"
positiveButton.isEnabled = false
}

else -> {
sizeText.error = context.getText(eu.darken.sdmse.common.R.string.general_error_invalid_input_label)
positiveButton.isEnabled = false
}
}
}

slider.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
Expand All @@ -63,7 +82,7 @@ class SizeInputDialog(
}
})
}
private val dialog = MaterialAlertDialogBuilder(context).apply {
private val dialogBuilder = MaterialAlertDialogBuilder(context).apply {
setTitle(titleRes)
setView(dialogLayout.root)
setPositiveButton(eu.darken.sdmse.common.R.string.general_save_action) { _, _ ->
Expand All @@ -78,17 +97,10 @@ class SizeInputDialog(
}

fun show() {
dialog.show()
dialog = dialogBuilder.show()
}

companion object {
private const val KB_MULTIPLIER = 1024L
private val SIZE_UNITS_REGEX = Regex("(\\d+(\\.\\d+)?)\\s*(B|KB|MB|GB)", RegexOption.IGNORE_CASE)
private val SIZE_UNITS = mapOf(
"B" to 1L,
"KB" to 1_000L,
"MB" to 1_000_000L,
"GB" to 1_000_000_000L,
)
}
}
32 changes: 32 additions & 0 deletions app/src/main/java/eu/darken/sdmse/common/ui/SizeParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package eu.darken.sdmse.common.ui

import android.content.Context
import android.text.format.Formatter
import eu.darken.sdmse.common.debug.logging.log

class SizeParser(private val context: Context) {

private val sizeUnitsRegex = Regex("(\\d+(?:[,.]\\d+)?)\\s*(\\w+)", RegexOption.IGNORE_CASE)
private val sizeUnitsLocalized by lazy {
val unitDelimiterRegex = Regex("\\s")
val sizeSplitter: (Long, String) -> Pair<String, Long> = { size, fallback ->
val unit = Formatter.formatShortFileSize(context, size).split(unitDelimiterRegex).lastOrNull() ?: fallback
unit.uppercase() to size
}
mapOf(
sizeSplitter(1L, "B"),
sizeSplitter(1_000L, "kB"),
sizeSplitter(1_000_000L, "MB"),
sizeSplitter(1_000_000_000L, "GB"),
).also { log { "Size lookup map: $it" } }
}

fun parse(input: String): Long? {
val match = sizeUnitsRegex.matchEntire(input.trim()) ?: return null
val (value, unit) = match.destructured
val factor = sizeUnitsLocalized[unit.uppercase()] ?: return null
return (value.replace(',', '.').toDoubleOrNull()?.times(factor))?.toLong()?.also {
log { "Parsed size '$input' to: $it" }
}
}
}
3 changes: 2 additions & 1 deletion app/src/main/res/layout/view_preference_input_size.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:ems="6"
android:ems="8"
android:gravity="center"
android:inputType="text"
android:maxLines="1"
Expand All @@ -26,6 +26,7 @@
android:id="@+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:contentDescription="Slider for minimum app junk size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
Expand Down

0 comments on commit 0da82da

Please sign in to comment.