From 92050257611b665d935cbc11ab535e8c4e6cfdc3 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:36:20 +0200 Subject: [PATCH] Make unit converter parsing more lenient and add units of volume Close #1323 --- .../unitconverter/UnitConverterResults.kt | 3 +- .../searchactions/EditSearchActionSheet.kt | 2 +- .../java/de/mm20/launcher2/icons/Icons.kt | 44 +++++++ .../i18n/src/main/res/values-en-rGB/units.xml | 18 +++ .../i18n/src/main/res/values-en-rUS/units.xml | 18 +++ core/i18n/src/main/res/values/units.xml | 88 ++++++++++++++ .../unitconverter/UnitConverterRepository.kt | 64 +++++++---- .../converters/VolumeConverter.kt | 107 ++++++++++++++++++ 8 files changed, 322 insertions(+), 22 deletions(-) create mode 100644 core/i18n/src/main/res/values-en-rGB/units.xml create mode 100644 core/i18n/src/main/res/values-en-rUS/units.xml create mode 100644 data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/VolumeConverter.kt diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/unitconverter/UnitConverterResults.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/unitconverter/UnitConverterResults.kt index be5ac712b..1c638fce5 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/unitconverter/UnitConverterResults.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/unitconverter/UnitConverterResults.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import de.mm20.launcher2.icons.Cube import de.mm20.launcher2.search.data.CurrencyUnitConverter import de.mm20.launcher2.search.data.UnitConverter import de.mm20.launcher2.ui.R @@ -220,7 +221,7 @@ fun getDimensionIcon(dimension: Dimension): ImageVector { Dimension.Mass -> Icons.Rounded.FitnessCenter Dimension.Length -> Icons.Rounded.Straighten Dimension.Velocity -> Icons.Rounded.Speed - Dimension.Volume -> TODO() + Dimension.Volume -> Icons.Rounded.Cube Dimension.Area -> Icons.Rounded.SquareFoot Dimension.Currency -> Icons.Rounded.Toll Dimension.Data -> Icons.Rounded.Storage diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt index 6e8346489..7bfd12748 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/searchactions/EditSearchActionSheet.kt @@ -714,7 +714,7 @@ fun CustomizeCustomIntent(viewModel: EditSearchActionSheetVM) { supportingText = { Text( if (action.queryKey == null) { - "The URI template that is used to construct the intent\\'s data URI. Use ‘\${1}’ as a placeholder for the actual search term, e.g. geo:0,0?q=,\${1}" + "The URI template that is used to construct the intent\\'s data URI. Use ‘\${1}’ as a placeholder for the actual search term" } else { "The template that is used to construct the string that is passed to the intent as a string extra. Use ‘\${1}’ as a placeholder for the actual search term" } diff --git a/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt b/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt index 00e495a48..bc1553905 100644 --- a/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt +++ b/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt @@ -2305,3 +2305,47 @@ private val _Climbing = materialIcon("Icons.Rounded.Climbing") { val Icons.Rounded.Climbing get() = _Climbing + +private val _Cube = materialIcon("Icons.Rounded.Cube") { + materialPath { + moveTo(11f, 19.425f) + verticalLineToRelative(-6.85f) + lineTo(5f, 9.1f) + verticalLineToRelative(6.85f) + close() + moveToRelative(2f, 0f) + lineToRelative(6f, -3.475f) + verticalLineTo(9.1f) + lineToRelative(-6f, 3.475f) + close() + moveTo(12f, 10.85f) + lineTo(17.925f, 7.425f) + lineTo(12f, 4f) + lineTo(6.075f, 7.425f) + close() + moveTo(4f, 17.7f) + quadTo(3.525f, 17.425f, 3.2625f, 16.975f) + quadTo(3f, 16.525f, 3f, 15.975f) + verticalLineToRelative(-7.95f) + quadToRelative(0f, -0.55f, 0.2625f, -1f) + quadTo(3.525f, 6.575f, 4f, 6.3f) + lineTo(11f, 2.275f) + quadTo(11.475f, 2f, 12f, 2f) + quadTo(12.525f, 2f, 13f, 2.275f) + lineTo(20f, 6.3f) + quadTo(20.475f, 6.575f, 20.7375f, 7.025f) + quadTo(21f, 7.475f, 21f, 8.025f) + verticalLineToRelative(7.95f) + quadToRelative(0f, 0.55f, -0.2625f, 1f) + quadTo(20.475f, 17.425f, 20f, 17.7f) + lineToRelative(-7f, 4.025f) + quadTo(12.525f, 22f, 12f, 22f) + quadTo(11.475f, 22f, 11f, 21.725f) + close() + moveTo(12f, 12f) + close() + } +} + +val Icons.Rounded.Cube + get() = _Cube \ No newline at end of file diff --git a/core/i18n/src/main/res/values-en-rGB/units.xml b/core/i18n/src/main/res/values-en-rGB/units.xml new file mode 100644 index 000000000..8e85892ee --- /dev/null +++ b/core/i18n/src/main/res/values-en-rGB/units.xml @@ -0,0 +1,18 @@ + + + gal + + gallon + gallons + + pt + + pint + pints + + fl oz + + fluid ounce + fluid ounces + + \ No newline at end of file diff --git a/core/i18n/src/main/res/values-en-rUS/units.xml b/core/i18n/src/main/res/values-en-rUS/units.xml new file mode 100644 index 000000000..7f6aeedcf --- /dev/null +++ b/core/i18n/src/main/res/values-en-rUS/units.xml @@ -0,0 +1,18 @@ + + + gal + + gallon + gallons + + pt + + pint + pints + + fl oz + + fluid ounce + fluid ounces + + \ No newline at end of file diff --git a/core/i18n/src/main/res/values/units.xml b/core/i18n/src/main/res/values/units.xml index d80ac70c8..189d729f2 100644 --- a/core/i18n/src/main/res/values/units.xml +++ b/core/i18n/src/main/res/values/units.xml @@ -294,4 +294,92 @@ kelvin kelvins + + l + + liter + liters + + ml + + milliliter + milliliters + + + + cubic meter + cubic meters + + cm³ + + cubic centimeter + cubic centimeters + + mm³ + + cubic millimeter + cubic millimeters + + cuin + + cubic inch + cubic inches + + cuft + + cubic foot + cubic feet + + cuyd + + cubic yard + cubic yards + + gal US + + gallon (US) + gallons (US) + + gal imp + + gallon (imperial) + gallons (imperial) + + pt US + + pint (US) + pints (US) + + pt imp + + pint (imperial) + pints (imperial) + + fl.oz.US + + US fluid ounce + US fluid ounces + + fl.oz.imp + + fluid ounce (imperial) + fluid ounces (imperial) + + cup + + cup (US) + cups (US) + + tbsp + + tablespoon (US) + tablespoons (US) + + tsp + + teaspoon (US) + teaspoons (US) + + + \ No newline at end of file diff --git a/data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt b/data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt index ef8d99f8e..e8aa795ac 100644 --- a/data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt +++ b/data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt @@ -13,13 +13,13 @@ import de.mm20.launcher2.unitconverter.converters.MassConverter import de.mm20.launcher2.unitconverter.converters.TemperatureConverter import de.mm20.launcher2.unitconverter.converters.TimeConverter import de.mm20.launcher2.unitconverter.converters.VelocityConverter +import de.mm20.launcher2.unitconverter.converters.VolumeConverter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -27,7 +27,7 @@ import org.koin.core.component.KoinComponent interface UnitConverterRepository { fun search(query: String): Flow - suspend fun getAvailableConverters(includeCurrencies: Boolean) : List + suspend fun getAvailableConverters(includeCurrencies: Boolean): List } internal class UnitConverterRepositoryImpl( @@ -56,7 +56,7 @@ internal class UnitConverterRepositoryImpl( } } - override suspend fun getAvailableConverters(includeCurrencies: Boolean) : List { + override suspend fun getAvailableConverters(includeCurrencies: Boolean): List { val converters = mutableListOf( MassConverter(context), LengthConverter(context), @@ -64,7 +64,8 @@ internal class UnitConverterRepositoryImpl( TimeConverter(context), VelocityConverter(context), AreaConverter(context), - TemperatureConverter(context) + TemperatureConverter(context), + VolumeConverter(context), ) if (includeCurrencies) converters.add(CurrencyConverter(currencyRepository)) @@ -75,28 +76,51 @@ internal class UnitConverterRepositoryImpl( query: String, includeCurrencies: Boolean ): UnitConverter? { - if (!query.matches(Regex("[0-9,.:]+ [^\\s]+")) && - !query.matches(Regex("[0-9,.:]+ [^\\s]+ >> [^\\s]+")) && - !query.matches(Regex("[0-9,.:]+ [^\\s]+ > [^\\s]+")) && - !query.matches(Regex("[0-9,.:]+ [^\\s]+ - [^\\s]+"))) return null - val valueStr: String - val unitStr: String - val targetUnitStr: String? + val regex = Regex("""([+\-]?[\d+\-e,.]+|[^\d>\-]+)""") - query.split(" ").also { - valueStr = it.get(0) - unitStr = it.get(1) - targetUnitStr = it.getOrNull(3) + val matches = regex.findAll(query) + + var inputStr: String? = null + var inputValue: Double? = null + var inputUnit: String? = null + var outputUnit: String? = null + + for ((i, match) in matches.withIndex()) { + when (i) { + 0 -> { + inputStr = match.value.trim() + inputValue = inputStr.toDoubleOrNull() + ?: inputStr.replace(',', '.').toDoubleOrNull() + ?: return null + } + 1 -> inputUnit = match.value.trim() + 2 -> { + if (!match.value.contains("-") && !match.value.contains(">")) { + outputUnit = match.value.trim() + } + } + 3 -> { + if (outputUnit == null) { + outputUnit = match.value.trim() + break + } else { + return null + } + } + else -> return null + } + } + + if (inputValue == null || inputUnit == null) { + return null } - val value = valueStr.toDoubleOrNull() ?: valueStr.replace(',', '.').toDoubleOrNull() - ?: return null val converters = getAvailableConverters(includeCurrencies) for (converter in converters) { - if (!converter.isValidUnit(unitStr)) continue - if (targetUnitStr != null && !converter.isValidUnit(targetUnitStr)) continue - return converter.convert(context, unitStr, value, targetUnitStr) + if (!converter.isValidUnit(inputUnit)) continue + if (outputUnit != null && !converter.isValidUnit(outputUnit)) continue + return converter.convert(context, inputUnit, inputValue, outputUnit) } return null } diff --git a/data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/VolumeConverter.kt b/data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/VolumeConverter.kt new file mode 100644 index 000000000..db8bdfee9 --- /dev/null +++ b/data/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/VolumeConverter.kt @@ -0,0 +1,107 @@ +package de.mm20.launcher2.unitconverter.converters + +import android.content.Context +import de.mm20.launcher2.unitconverter.Dimension +import de.mm20.launcher2.unitconverter.R + +internal class VolumeConverter(context: Context) : SimpleFactorConverter() { + override val dimension: Dimension = Dimension.Volume + + override val standardUnits = listOf( + // SI units + MeasureUnitWithFactor( + factor = 1.0, + symbol = context.getString(R.string.unit_cubic_meter_symbol), + nameResource = R.plurals.unit_cubic_meter + ), + MeasureUnitWithFactor( + factor = 1_000_000.0, + symbol = context.getString(R.string.unit_cubic_centimeter_symbol), + nameResource = R.plurals.unit_cubic_centimeter + ), + MeasureUnitWithFactor( + factor = 1_000_000_000.0, + symbol = context.getString(R.string.unit_cubic_millimeter_symbol), + nameResource = R.plurals.unit_cubic_millimeter + ), + // Other metric units + MeasureUnitWithFactor( + factor = 1000.0, + symbol = context.getString(R.string.unit_liter_symbol), + nameResource = R.plurals.unit_liter + ), + MeasureUnitWithFactor( + factor = 1_000_000.0, + symbol = context.getString(R.string.unit_milliliter_symbol), + nameResource = R.plurals.unit_milliliter + ), + + // Imperial geometric units + MeasureUnitWithFactor( + factor = 61023.74409473228, + symbol = context.getString(R.string.unit_cubic_inch_symbol), + nameResource = R.plurals.unit_cubic_inch + ), + MeasureUnitWithFactor( + factor = 61023.74409473228 / 1728.0, + symbol = context.getString(R.string.unit_cubic_foot_symbol), + nameResource = R.plurals.unit_cubic_foot + ), + MeasureUnitWithFactor( + factor = 61023.74409473228 / (1728.0 * 27.0), + symbol = context.getString(R.string.unit_cubic_yard_symbol), + nameResource = R.plurals.unit_cubic_yard + ), + + // US fluid units + MeasureUnitWithFactor( + factor = 264.1720523581484, + symbol = context.getString(R.string.unit_gallon_us_symbol), + nameResource = R.plurals.unit_gallon_us + ), + MeasureUnitWithFactor( + factor = 264.1720523581484 * 8, + symbol = context.getString(R.string.unit_pint_us_symbol), + nameResource = R.plurals.unit_pint_us + ), + MeasureUnitWithFactor( + factor = 264.1720523581484 * 16, + symbol = context.getString(R.string.unit_cup_symbol), + nameResource = R.plurals.unit_cup + ), + MeasureUnitWithFactor( + factor = 264.1720523581484 * 128, + symbol = context.getString(R.string.unit_fluid_ounce_us_symbol), + nameResource = R.plurals.unit_fluid_ounce_us + ), + MeasureUnitWithFactor( + factor = 264.1720523581484 * 256, + symbol = context.getString(R.string.unit_tablespoon_symbol), + nameResource = R.plurals.unit_tablespoon + ), + MeasureUnitWithFactor( + factor = 264.1720523581484 * 768, + symbol = context.getString(R.string.unit_teaspoon_symbol), + nameResource = R.plurals.unit_teaspoon + ), + + // Imperial fluid units + MeasureUnitWithFactor( + factor = 219.9692482990878, + symbol = context.getString(R.string.unit_gallon_imp_symbol), + nameResource = R.plurals.unit_gallon_imp + ), + MeasureUnitWithFactor( + factor = 219.969248299087 * 8, + symbol = context.getString(R.string.unit_pint_imp_symbol), + nameResource = R.plurals.unit_pint_imp + ), + MeasureUnitWithFactor( + factor = 219.969248299087 * 128, + symbol = context.getString(R.string.unit_fluid_ounce_imp_symbol), + nameResource = R.plurals.unit_fluid_ounce_imp + ), + + + ) +} \ No newline at end of file