Reorganize and group modules

This commit is contained in:
MM20
2022-12-13 17:37:26 +01:00
parent bac24baad2
commit 3f8880a90a
995 changed files with 501 additions and 298 deletions
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
/
</manifest>
@@ -0,0 +1,7 @@
package de.mm20.launcher2.search.data
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.UnitValue
class CurrencyUnitConverter(dimension: Dimension, inputValue: UnitValue, values: List<UnitValue>, val updateTimestamp: Long)
: UnitConverter(dimension, inputValue, values)
@@ -0,0 +1,11 @@
package de.mm20.launcher2.search.data
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.UnitValue
open class UnitConverter(
val dimension: Dimension,
val inputValue: UnitValue,
val values: List<UnitValue>
): Searchable
@@ -0,0 +1,27 @@
package de.mm20.launcher2.unitconverter
import android.content.Context
import java.text.DecimalFormat
import kotlin.math.abs
import kotlin.math.roundToInt
internal object ConverterUtils {
fun formatName(context: Context, unit: MeasureUnit, value: Double): String {
val resId = unit.nameResource
val text = context.resources.getQuantityString(resId, value.roundToInt())
return text
}
fun formatValue(context: Context, unit: MeasureUnit, value: Double): String {
if (abs(value) > 1e5 || abs(value) < 1e-3) {
return DecimalFormat("#.###E0").apply {
}.format(value)
}
return DecimalFormat("#.###").apply {
}.format(value)
}
}
@@ -0,0 +1,17 @@
package de.mm20.launcher2.unitconverter
enum class Dimension {
Length,
Mass,
Velocity,
Volume,
Area,
Currency,
Data,
Bitrate,
Pressure,
Energy,
Frequency,
Temperature,
Time
}
@@ -0,0 +1,6 @@
package de.mm20.launcher2.unitconverter
interface MeasureUnit {
val symbol: String
val nameResource: Int
}
@@ -0,0 +1,10 @@
package de.mm20.launcher2.unitconverter
import de.mm20.launcher2.currencies.CurrencyRepository
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val unitConverterModule = module {
single { CurrencyRepository(androidContext()) }
single<UnitConverterRepository> { UnitConverterRepositoryImpl(androidContext(), get()) }
}
@@ -0,0 +1,81 @@
package de.mm20.launcher2.unitconverter
import android.content.Context
import de.mm20.launcher2.currencies.CurrencyRepository
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.unitconverter.converters.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
interface UnitConverterRepository {
fun search(query: String, includeCurrencies: Boolean): Flow<UnitConverter?>
}
internal class UnitConverterRepositoryImpl(
private val context: Context,
private val currencyRepository: CurrencyRepository,
) : UnitConverterRepository, KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val scope = CoroutineScope(Job() + Dispatchers.Default)
init {
scope.launch {
dataStore.data.map { it.unitConverterSearch }.distinctUntilChanged().collectLatest {
if (it.enabled && it.currencies) currencyRepository.enableCurrencyUpdateWorker()
else currencyRepository.disableCurrencyUpdateWorker()
}
}
}
override fun search(query: String, includeCurrencies: Boolean): Flow<UnitConverter?> = channelFlow {
if (query.isBlank()) {
send(null)
return@channelFlow
}
send(queryUnitConverter(query, includeCurrencies))
}
private suspend fun queryUnitConverter(
query: String,
includeCurrencies: Boolean
): UnitConverter? {
if (!query.matches(Regex("[0-9,.:]+ [^\\s]+")) && !query.matches(Regex("[0-9,.:]+ [^\\s]+ >> [^\\s]+"))) return null
val valueStr: String
val unitStr: String
val targetUnitStr: String?
query.split(" ").also {
valueStr = it.get(0)
unitStr = it.get(1)
targetUnitStr = it.getOrNull(3)
}
val value = valueStr.toDoubleOrNull() ?: valueStr.replace(',', '.').toDoubleOrNull()
?: return null
val converters = mutableListOf(
MassConverter(context),
LengthConverter(context),
DataConverter(context),
TimeConverter(context),
VelocityConverter(context),
AreaConverter(context),
TemperatureConverter(context)
)
if (includeCurrencies) converters.add(CurrencyConverter(currencyRepository))
for (converter in converters) {
if (!converter.isValidUnit(unitStr)) continue
if (targetUnitStr != null && !converter.isValidUnit(targetUnitStr)) continue
return converter.convert(context, unitStr, value, targetUnitStr)
}
return null
}
}
@@ -0,0 +1,8 @@
package de.mm20.launcher2.unitconverter
data class UnitValue(
val value: Double,
val symbol: String,
val formattedName: String,
val formattedValue: String
)
@@ -0,0 +1,57 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.R
class AreaConverter(context: Context) : SimpleFactorConverter() {
override val dimension = Dimension.Area
override val standardUnits = listOf(
MeasureUnitWithFactor(
0.000001,
context.getString(R.string.unit_sqkilometer_symbol),
R.plurals.unit_sqkilometer
),
MeasureUnitWithFactor(
0.0001,
context.getString(R.string.unit_hectare_symbol),
R.plurals.unit_hectare
),
MeasureUnitWithFactor(
1.0,
context.getString(R.string.unit_sqmeter_symbol),
R.plurals.unit_sqmeter
),
MeasureUnitWithFactor(
10000.0,
context.getString(R.string.unit_sqcentimeter_symbol),
R.plurals.unit_sqcentimeter
),
MeasureUnitWithFactor(
1000000.0,
context.getString(R.string.unit_sqmillimeter_symbol),
R.plurals.unit_sqmillimeter
),
MeasureUnitWithFactor(
100 * 100 / (2.54 * 2.54),
context.getString(R.string.unit_sqinch_symbol),
R.plurals.unit_sqinch
),
MeasureUnitWithFactor(
100 * 100 / (2.54 * 2.54 * 144),
context.getString(R.string.unit_sqfoot_symbol),
R.plurals.unit_sqfoot
),
MeasureUnitWithFactor(
100 * 100 / (2.54 * 2.54 * 1296),
context.getString(R.string.unit_sqyard_symbol),
R.plurals.unit_sqyard
),
MeasureUnitWithFactor(
100 * 100 / (2.54 * 2.54 * 144 * 43560),
context.getString(R.string.unit_acre_symbol),
R.plurals.unit_acre
)
)
}
@@ -0,0 +1,19 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.unitconverter.Dimension
interface Converter {
val dimension: Dimension
suspend fun isValidUnit(symbol: String): Boolean
suspend fun convert(
context: Context,
fromUnit: String,
value: Double,
toUnit: String?
): UnitConverter
}
@@ -0,0 +1,89 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import android.icu.text.PluralRules
import android.icu.util.Currency
import de.mm20.launcher2.currencies.CurrencyRepository
import de.mm20.launcher2.search.data.CurrencyUnitConverter
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.UnitValue
import java.text.DecimalFormat
import java.util.Locale
import java.util.Currency as JCurrency
import kotlin.math.abs
class CurrencyConverter(
private val repository: CurrencyRepository,
) : Converter {
override val dimension: Dimension = Dimension.Currency
private val topCurrencies = arrayOf("USD", "EUR", "JPY", "GBP", "AUD")
override suspend fun isValidUnit(symbol: String): Boolean {
return repository.isValidCurrency(symbol)
}
private fun formatName(symbol: String, value: Double): String {
val text = StringBuilder()
val currency = Currency.getInstance(symbol) ?: return formatNameFallback(symbol)
val pluralCount = PluralRules.forLocale(Locale.getDefault()).select(value)
text.append(currency.getName(Locale.getDefault(), Currency.PLURAL_LONG_NAME, pluralCount, booleanArrayOf(false)))
return text.toString()
}
private fun formatNameFallback(symbol: String): String {
return symbol
}
private fun formatValue(symbol: String, value: Double): String {
if (abs(value) > 1e10) {
return DecimalFormat("#.###E0").apply {
}.format(value)
}
val currency = JCurrency.getInstance(symbol)
return if (currency != null) {
val format = StringBuilder("#,##0")
val digits = currency.defaultFractionDigits
if (digits > 0) format.append(".")
for (i in 0 until digits) format.append("0")
DecimalFormat(format.toString()).format(value)
} else {
DecimalFormat("#,##0.00").format(value)
}
}
override suspend fun convert(context: Context, fromUnit: String, value: Double, toUnit: String?): UnitConverter {
val values = repository.convertCurrency(fromUnit, value, toUnit).map {
UnitValue(it.second, it.first, formatName(it.first, it.second), formatValue(it.first, it.second))
}.toMutableList()
val ownCurrencySymbol = JCurrency.getInstance(Locale.getDefault()).currencyCode ?: "USD"
val index = values.indexOfFirst { it.symbol == ownCurrencySymbol }
val ownCurrency = if (index != -1) {
values.removeAt(index)
} else {
null
}
values.sortBy {
val i = topCurrencies.indexOf(it.symbol)
if (i != -1) i.toString()
else it.formattedName
}
if (ownCurrency != null) {
values.add(0, ownCurrency)
}
val inputValue = UnitValue(value, fromUnit, formatName(fromUnit, value), formatValue(fromUnit, value))
val lastUpdate = repository.getLastUpdate(fromUnit)
return CurrencyUnitConverter(dimension, inputValue, values, lastUpdate)
}
}
@@ -0,0 +1,83 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.R
class DataConverter(context: Context) : SimpleFactorConverter() {
override val dimension = Dimension.Data
override val standardUnits = listOf(
MeasureUnitWithFactor(
1.0,
context.getString(R.string.unit_byte_symbol),
R.plurals.unit_byte
),
MeasureUnitWithFactor(
0.001,
context.getString(R.string.unit_kilobyte_symbol),
R.plurals.unit_kilobyte
),
MeasureUnitWithFactor(
0.000001,
context.getString(R.string.unit_megabyte_symbol),
R.plurals.unit_megabyte
),
MeasureUnitWithFactor(
0.000000001,
context.getString(R.string.unit_gigabyte_symbol),
R.plurals.unit_gigabyte
),
MeasureUnitWithFactor(
0.000000000001,
context.getString(R.string.unit_terabyte_symbol),
R.plurals.unit_terabyte
),
MeasureUnitWithFactor(
1.0 / 1024,
context.getString(R.string.unit_kibibyte_symbol),
R.plurals.unit_kibibyte
),
MeasureUnitWithFactor(
1.0 / (1024 * 1024),
context.getString(R.string.unit_mebibyte_symbol),
R.plurals.unit_mebibyte
),
MeasureUnitWithFactor(
1.0 / (1024 * 1024 * 1024),
context.getString(R.string.unit_gibibyte_symbol),
R.plurals.unit_gibibyte
),
MeasureUnitWithFactor(
1.0 / (1024.0 * 1024 * 1024 * 1024),
context.getString(R.string.unit_tebibyte_symbol),
R.plurals.unit_tebibyte
),
MeasureUnitWithFactor(
8.0,
context.getString(R.string.unit_bit_symbol),
R.plurals.unit_bit
),
MeasureUnitWithFactor(
8.0 / 1000,
context.getString(R.string.unit_kilobit_symbol),
R.plurals.unit_kilobit
),
MeasureUnitWithFactor(
8.0 / 1000000,
context.getString(R.string.unit_megabit_symbol),
R.plurals.unit_megabit
),
MeasureUnitWithFactor(
8.0 / 1000000000,
context.getString(R.string.unit_gigabit_symbol),
R.plurals.unit_gigabit
),
MeasureUnitWithFactor(
8.0 / 1000000000000,
context.getString(R.string.unit_terabit_symbol),
R.plurals.unit_terabit
)
)
}
@@ -0,0 +1,58 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.R
class LengthConverter(context: Context) : SimpleFactorConverter() {
override val dimension = Dimension.Length
override val standardUnits = listOf(
MeasureUnitWithFactor(
1.0,
context.getString(R.string.unit_meter_symbol),
R.plurals.unit_meter
),
MeasureUnitWithFactor(
0.001,
context.getString(R.string.unit_kilometer_symbol),
R.plurals.unit_kilometer
),
MeasureUnitWithFactor(
100.0,
context.getString(R.string.unit_centimeter_symbol),
R.plurals.unit_centimeter
),
MeasureUnitWithFactor(
1000.0,
context.getString(R.string.unit_millimeter_symbol),
R.plurals.unit_millimeter
),
MeasureUnitWithFactor(
100 / 2.54,
context.getString(R.string.unit_inch_symbol),
R.plurals.unit_inch
),
MeasureUnitWithFactor(
100 / (2.54 * 12),
context.getString(R.string.unit_foot_symbol),
R.plurals.unit_foot
),
MeasureUnitWithFactor(
100 / (2.54 * 12 * 3),
context.getString(R.string.unit_yard_symbol),
R.plurals.unit_yard
),
MeasureUnitWithFactor(
100 / (2.54 * 12 * 3 * 1760),
context.getString(R.string.unit_mile_symbol),
R.plurals.unit_mile
),
MeasureUnitWithFactor(
1 / 1852.0,
context.getString(R.string.unit_nautic_mile_symbol),
R.plurals.unit_nautic_mile
)
)
}
@@ -0,0 +1,53 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.R
class MassConverter(context: Context): SimpleFactorConverter() {
override val dimension = Dimension.Mass
override val standardUnits = listOf(
MeasureUnitWithFactor(
1.0,
context.getString(R.string.unit_kilogram_symbol),
R.plurals.unit_kilogram
),
MeasureUnitWithFactor(
1000.0,
context.getString(R.string.unit_gram_symbol),
R.plurals.unit_gram
),
MeasureUnitWithFactor(
0.001,
context.getString(R.string.unit_metric_ton_symbol),
R.plurals.unit_metric_ton
),
MeasureUnitWithFactor(
1000.0 / (453.59237 * 2240.0),
context.getString(R.string.unit_long_ton_symbol),
R.plurals.unit_long_ton
),
MeasureUnitWithFactor(
1000.0 / (453.59237 * 14.0),
context.getString(R.string.unit_stone_symbol),
R.plurals.unit_stone
),
MeasureUnitWithFactor(
1000.0 / 453.59237,
context.getString(R.string.unit_pound_symbol),
R.plurals.unit_pound
),
MeasureUnitWithFactor(
16.0 * 1000.0 / 453.59237,
context.getString(R.string.unit_ounce_symbol),
R.plurals.unit_ounce
),
MeasureUnitWithFactor(
1000.0 / (453.59237 * 2000.0),
context.getString(R.string.unit_short_ton_symbol),
R.plurals.unit_short_ton
)
)
}
@@ -0,0 +1,46 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.unitconverter.ConverterUtils
import de.mm20.launcher2.unitconverter.MeasureUnit
import de.mm20.launcher2.unitconverter.UnitValue
/**
* A converter for units that can converted into each other by simply multiplicating with a constant factor
*/
abstract class SimpleFactorConverter: Converter {
open val standardUnits: List<MeasureUnitWithFactor> = emptyList()
/**
* Returns true if a symbol is a valid unit of this converter
*/
override suspend fun isValidUnit(symbol: String): Boolean {
return standardUnits.any { it.symbol == symbol }
}
override suspend fun convert(context: Context, fromUnit: String, value: Double, toUnit: String?): UnitConverter {
val results = mutableListOf<UnitValue>()
val unit = standardUnits.first { it.symbol == fromUnit }
if (toUnit == null) {
for (targetUnit in standardUnits) {
if (targetUnit.symbol == unit.symbol) continue
val v = value * targetUnit.factor / unit.factor
results += UnitValue(v, targetUnit.symbol, ConverterUtils.formatName(context, targetUnit, v), ConverterUtils.formatValue(context, unit, v))
}
} else {
val targetUnit = standardUnits.first { it.symbol == toUnit }
val v = value * targetUnit.factor / unit.factor
results += UnitValue(v, targetUnit.symbol, ConverterUtils.formatName(context, targetUnit, v), ConverterUtils.formatValue(context, unit, v))
}
val inputValue = UnitValue(value, fromUnit, ConverterUtils.formatName(context, unit, value), ConverterUtils.formatValue(context, unit, value))
return UnitConverter(dimension, inputValue, results)
}
}
data class MeasureUnitWithFactor(
val factor: Double,
override val symbol: String,
override val nameResource: Int
): MeasureUnit
@@ -0,0 +1,116 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.unitconverter.*
class TemperatureConverter(context: Context) : Converter {
override val dimension = Dimension.Temperature
private val units = listOf(
TemperatureMeasureUnit(
context.getString(R.string.unit_degree_celsius_symbol),
R.plurals.unit_degree_celsius,
TemperatureUnit.DegreeCelsius
),
TemperatureMeasureUnit(
context.getString(R.string.unit_degree_fahrenheit_symbol),
R.plurals.unit_degree_fahrenheit,
TemperatureUnit.DegreeFahrenheit
),
TemperatureMeasureUnit(
context.getString(R.string.unit_kelvin_symbol),
R.plurals.unit_kelvin,
TemperatureUnit.Kelvin
),
)
override suspend fun isValidUnit(symbol: String): Boolean {
return units.any { it.symbol == symbol }
}
override suspend fun convert(
context: Context,
fromUnit: String,
value: Double,
toUnit: String?
): UnitConverter {
val from = units.first { it.symbol == fromUnit }
val to = toUnit?.let { unit -> units.first { it.symbol == unit } }
val values = mutableListOf<UnitValue>()
if (to != null) {
val toValue = convertTemperature(value, from.unit, to.unit)
values += UnitValue(
value = value,
symbol = toUnit,
formattedName = ConverterUtils.formatName(context, to, toValue),
formattedValue = ConverterUtils.formatValue(context, to, toValue),
)
} else {
for (to in units) {
if (to.symbol == from.symbol) continue
val v = convertTemperature(value, from.unit, to.unit)
values += UnitValue(
v,
to.symbol,
ConverterUtils.formatName(context, to, v),
ConverterUtils.formatValue(context, to, v)
)
}
}
return UnitConverter(
dimension = Dimension.Temperature,
inputValue = UnitValue(
value = value,
symbol = fromUnit,
formattedName = ConverterUtils.formatName(context, from, value),
formattedValue = ConverterUtils.formatValue(context, from, value),
),
values = values
)
}
private fun convertTemperature(
value: Double,
from: TemperatureUnit,
to: TemperatureUnit
): Double {
if (from == to) return value
if (from == TemperatureUnit.Kelvin && to == TemperatureUnit.DegreeCelsius) {
return value - 273.15
}
if (from == TemperatureUnit.DegreeCelsius && to == TemperatureUnit.Kelvin) {
return value + 273.15
}
if (from === TemperatureUnit.DegreeCelsius && to == TemperatureUnit.DegreeFahrenheit) {
return value * (9.0 / 5.0) + 32.0
}
if (from === TemperatureUnit.DegreeFahrenheit && to == TemperatureUnit.DegreeCelsius) {
return (value - 32.0) * (5.0 / 9.0)
}
if (from === TemperatureUnit.Kelvin && to == TemperatureUnit.DegreeFahrenheit) {
return (value - 273.15) * (9.0 / 5.0) + 32.0
}
if (from === TemperatureUnit.DegreeFahrenheit && to == TemperatureUnit.Kelvin) {
return (value - 32.0) * (5.0 / 9.0) + 273.15
}
throw IllegalArgumentException()
}
}
private data class TemperatureMeasureUnit(
override val symbol: String,
override val nameResource: Int,
val unit: TemperatureUnit
) :
MeasureUnit
private enum class TemperatureUnit {
DegreeCelsius,
DegreeFahrenheit,
Kelvin,
}
@@ -0,0 +1,43 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.R
class TimeConverter(context: Context) : SimpleFactorConverter() {
override val dimension = Dimension.Time
override val standardUnits = listOf(
MeasureUnitWithFactor(
1.0,
context.getString(R.string.unit_second_symbol),
R.plurals.unit_second
),
MeasureUnitWithFactor(
1000.0,
context.getString(R.string.unit_millisecond_symbol),
R.plurals.unit_millisecond
),
MeasureUnitWithFactor(
1.0 / 60,
context.getString(R.string.unit_minute_symbol),
R.plurals.unit_minute
),
MeasureUnitWithFactor(
1.0 / (60 * 60),
context.getString(R.string.unit_hour_symbol),
R.plurals.unit_hour
),
MeasureUnitWithFactor(
1.0 / (60 * 60 * 24),
context.getString(R.string.unit_day_symbol),
R.plurals.unit_day
),
MeasureUnitWithFactor(
1.0 / (60 * 60 * 24 * 365),
context.getString(R.string.unit_year_symbol),
R.plurals.unit_year
)
)
}
@@ -0,0 +1,33 @@
package de.mm20.launcher2.unitconverter.converters
import android.content.Context
import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.R
class VelocityConverter(context: Context) : SimpleFactorConverter() {
override val dimension = Dimension.Velocity
override val standardUnits = listOf(
MeasureUnitWithFactor(
1.0,
context.getString(R.string.unit_meter_per_second_symbol),
R.plurals.unit_meter_per_second
),
MeasureUnitWithFactor(
3.6,
context.getString(R.string.unit_kilometer_per_hour_symbol),
R.plurals.unit_kilometer_per_hour
),
MeasureUnitWithFactor(
3600.0 / 1609.344,
context.getString(R.string.unit_mile_per_hour_symbol),
R.plurals.unit_mile_per_hour
),
MeasureUnitWithFactor(
3600.0 / 1852.0,
context.getString(R.string.unit_knot_symbol),
R.plurals.unit_knot
)
)
}