Refactor Converter

- no longer abusing mapLatest and StateFlow
- id-based system in UI and business layers
This commit is contained in:
Sad Ellie 2024-02-28 22:07:16 +03:00
parent eb00d8a76e
commit eb96868afc
69 changed files with 1934 additions and 2060 deletions

View File

@ -83,76 +83,5 @@ fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
)
}
@Suppress("UNCHECKED_CAST", "UNUSED")
fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
flow: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
flow7: Flow<T7>,
flow8: Flow<T8>,
transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R,
): Flow<R> =
kotlinx.coroutines.flow.combine(
flow,
flow2,
flow3,
flow4,
flow5,
flow6,
flow7,
flow8,
) { args: Array<*> ->
transform(
args[0] as T1,
args[1] as T2,
args[2] as T3,
args[3] as T4,
args[4] as T5,
args[5] as T6,
args[6] as T7,
args[7] as T8,
)
}
@Suppress("UNCHECKED_CAST", "UNUSED")
fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
flow: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
flow7: Flow<T7>,
flow8: Flow<T8>,
flow9: Flow<T9>,
transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R,
): Flow<R> =
kotlinx.coroutines.flow.combine(
flow,
flow2,
flow3,
flow4,
flow5,
flow6,
flow7,
flow8,
flow9,
) { args: Array<*> ->
transform(
args[0] as T1,
args[1] as T2,
args[2] as T3,
args[3] as T4,
args[4] as T5,
args[5] as T6,
args[6] as T7,
args[7] as T8,
args[8] as T9,
)
}
fun <T> Flow<T>.stateIn(scope: CoroutineScope, initialValue: T): StateFlow<T> =
stateIn(scope, SharingStarted.WhileSubscribed(5000L), initialValue)

View File

@ -35,11 +35,13 @@ android {
}
dependencies {
testImplementation(libs.org.robolectric.robolectric)
implementation(libs.com.squareup.moshi.moshi.kotlin)
implementation(libs.com.squareup.retrofit2.converter.moshi)
implementation(project(":core:base"))
implementation(project(":data:common"))
implementation(project(":data:database"))
implementation(project(":data:evaluatto"))
implementation(project(":data:model"))
}

View File

@ -1,6 +1,6 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
* Copyright (c) 2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,10 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.unit
package com.sadellie.unitto.data.converter
import java.math.BigDecimal
interface DefaultUnit : AbstractUnit {
fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal
}
interface BatchConvertResult
@JvmInline
value class DefaultBatchConvertResult(
val value: BigDecimal,
) : BatchConvertResult
@JvmInline
value class NumberBaseBatchConvertResult(
val value: String,
) : BatchConvertResult

View File

@ -0,0 +1,82 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.converter
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import java.math.BigDecimal
sealed class ConverterResult {
sealed class Error : ConverterResult() {
data object Currency : Error()
data object BadInput : Error()
data object ConversionError : Error()
data object DivideByZero : Error()
}
data class Default(
val value: BigDecimal,
val calculation: BigDecimal,
) : ConverterResult()
data class NumberBase(val value: String) : ConverterResult()
data class Time(
val negative: Boolean = false,
val day: BigDecimal = BigDecimal.ZERO,
val hour: BigDecimal = BigDecimal.ZERO,
val minute: BigDecimal = BigDecimal.ZERO,
val second: BigDecimal = BigDecimal.ZERO,
val millisecond: BigDecimal = BigDecimal.ZERO,
val microsecond: BigDecimal = BigDecimal.ZERO,
val nanosecond: BigDecimal = BigDecimal.ZERO,
val attosecond: BigDecimal = BigDecimal.ZERO,
) : ConverterResult()
data class FootInch(
val foot: BigDecimal,
val inch: BigDecimal,
) : ConverterResult() {
companion object {
/**
* Creates an object for displaying formatted foot and inch output. Units are passed as objects so
* that changes in basic units don't require modifying the method. Also this method can't access
* units repository directly.
*
* @param input Input in feet.
* @param footUnit Foot unit [BasicUnit.Default].
* @param inchUnit Inch unit [BasicUnit.Default].
* @return Result where decimal places are converter into inches.
*/
fun fromBigDecimal(
input: BigDecimal,
footUnit: BasicUnit.Default,
inchUnit: BasicUnit.Default,
): FootInch {
val (integral, fractional) = input.divideAndRemainder(BigDecimal.ONE)
return FootInch(integral, footUnit.convert(inchUnit, fractional))
}
}
}
data object Loading : ConverterResult()
}

View File

@ -1,46 +0,0 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.converter
import android.content.Context
import com.sadellie.unitto.data.database.CurrencyRatesDao
import com.sadellie.unitto.data.database.UnitsDao
import com.sadellie.unitto.data.model.repository.UnitsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
class DataStoreModule {
@Provides
fun provideUnitsRepository(
unitsDao: UnitsDao,
currencyRatesDao: CurrencyRatesDao,
@ApplicationContext appContext: Context,
): UnitsRepository {
return UnitsRepositoryImpl(
unitsDao = unitsDao,
currencyRatesDao = currencyRatesDao,
mContext = appContext,
)
}
}

View File

@ -1,6 +1,6 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
* Copyright (c) 2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,45 +16,32 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.unit
package com.sadellie.unitto.data.converter
import android.content.Context
import com.sadellie.unitto.data.common.lev
import com.sadellie.unitto.data.common.normalizeSuperscript
import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal
import com.sadellie.unitto.data.database.UnitsEntity
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
interface AbstractUnit {
val id: String
val basicUnit: BigDecimal
val group: UnitGroup
val displayName: Int
val shortName: Int
val isFavorite: Boolean
val pairId: String?
val counter: Int
data class UnitSearchResultItem(
val basicUnit: BasicUnit,
val stats: UnitsEntity,
val conversion: BatchConvertResult?,
)
fun clone(
id: String = this.id,
basicUnit: BigDecimal = this.basicUnit,
group: UnitGroup = this.group,
displayName: Int = this.displayName,
shortName: Int = this.shortName,
isFavorite: Boolean = this.isFavorite,
pairId: String? = this.pairId,
counter: Int = this.counter,
): AbstractUnit
}
fun Sequence<AbstractUnit>.filterByLev(stringA: String, context: Context): Sequence<AbstractUnit> {
fun Sequence<UnitSearchResultItem>.filterByLev(
stringA: String,
context: Context,
): Sequence<UnitSearchResultItem> {
val stringToCompare = stringA.trim().lowercase()
// We don't need units where name is too different, half of the symbols is wrong in this situation
val threshold: Int = stringToCompare.length / 2
// List of pair: Unit and it's levDist
val unitsWithDist = mutableListOf<Pair<AbstractUnit, Int>>()
val unitsWithDist = mutableListOf<Pair<UnitSearchResultItem, Int>>()
this.forEach { unit ->
val unitShortName: String = context
.getString(unit.shortName)
.getString(unit.basicUnit.shortName)
.lowercase()
.normalizeSuperscript()
/**
@ -67,7 +54,7 @@ fun Sequence<AbstractUnit>.filterByLev(stringA: String, context: Context): Seque
}
val unitFullName: String = context
.getString(unit.displayName)
.getString(unit.basicUnit.displayName)
.lowercase()
.normalizeSuperscript()

View File

@ -20,6 +20,10 @@ package com.sadellie.unitto.data.converter
import android.content.Context
import android.util.Log
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.data.common.isGreaterThan
import com.sadellie.unitto.data.common.isLessThan
import com.sadellie.unitto.data.common.trimZeros
import com.sadellie.unitto.data.converter.collections.accelerationCollection
import com.sadellie.unitto.data.converter.collections.angleCollection
import com.sadellie.unitto.data.converter.collections.areaCollection
@ -49,22 +53,16 @@ import com.sadellie.unitto.data.database.CurrencyRatesDao
import com.sadellie.unitto.data.database.CurrencyRatesEntity
import com.sadellie.unitto.data.database.UnitsDao
import com.sadellie.unitto.data.database.UnitsEntity
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.repository.UnitsRepository
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.ReverseUnit
import com.sadellie.unitto.data.model.unit.filterByLev
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.sadellie.evaluatto.Expression
import io.github.sadellie.evaluatto.ExpressionException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.LocalDate
import javax.inject.Inject
@ -72,87 +70,58 @@ class UnitsRepositoryImpl @Inject constructor(
private val unitsDao: UnitsDao,
private val currencyRatesDao: CurrencyRatesDao,
@ApplicationContext private val mContext: Context,
) : UnitsRepository {
private val inMemoryUnits = MutableStateFlow(
lengthCollection +
currencyCollection +
massCollection +
speedCollection +
temperatureCollection +
areaCollection +
timeCollection +
volumeCollection +
dataCollection +
pressureCollection +
accelerationCollection +
energyCollection +
powerCollection +
angleCollection +
dataTransferCollection +
fluxCollection +
numberBaseCollection +
electrostaticCapacitance +
prefixCollection +
forceCollection +
torqueCollection +
flowRateCollection +
luminanceCollection +
fuelConsumptionCollection,
)
) {
private val inMemory = lengthCollection +
currencyCollection +
massCollection +
speedCollection +
temperatureCollection +
areaCollection +
timeCollection +
volumeCollection +
dataCollection +
pressureCollection +
accelerationCollection +
energyCollection +
powerCollection +
angleCollection +
dataTransferCollection +
fluxCollection +
numberBaseCollection +
electrostaticCapacitance +
prefixCollection +
forceCollection +
torqueCollection +
flowRateCollection +
luminanceCollection +
fuelConsumptionCollection
override val units: Flow<List<AbstractUnit>> = combine(
unitsDao.getAllFlow(),
inMemoryUnits,
) { basedList, inMemoryList ->
return@combine inMemoryList.map { inMemoryUnit ->
val inBaseUnit = basedList.find { it.unitId == inMemoryUnit.id }
?: return@map inMemoryUnit
inMemoryUnit.clone(
isFavorite = inBaseUnit.isFavorite,
counter = inBaseUnit.frequency,
pairId = inBaseUnit.pairedUnitId,
)
}
}
.flowOn(Dispatchers.IO)
override suspend fun getById(id: String): AbstractUnit {
return units.first().first { it.id == id }
suspend fun getById(id: String): BasicUnit = withContext(Dispatchers.Default) {
return@withContext inMemory.first { it.id == id }
}
override suspend fun getCollection(group: UnitGroup): List<AbstractUnit> {
return units.first().filter { it.group == group }
suspend fun getPairId(id: String): String = withContext(Dispatchers.IO) {
val basedUnitPair = getUnitStats(id).pairedUnitId
if (basedUnitPair != null) return@withContext basedUnitPair
val inMemoryUnit = inMemory.first { it.id == id }
val collection = inMemory.filter { it.group == inMemoryUnit.group }
val pair = collection
.map { getById(it.id) to getUnitStats(it.id) }
.sortedByDescending { it.second.frequency }
.firstOrNull { it.second.isFavorite }?.first ?: collection.first()
return@withContext pair.id
}
override suspend fun favorite(unit: AbstractUnit) = withContext(Dispatchers.IO) {
val basedUnit = unitsDao.getById(unit.id)
suspend fun incrementCounter(id: String) = withContext(Dispatchers.IO) {
val basedUnit = unitsDao.getById(id)
if (basedUnit == null) {
unitsDao.insertUnit(
UnitsEntity(
unitId = unit.id,
isFavorite = true,
),
)
} else {
unitsDao.insertUnit(
UnitsEntity(
unitId = basedUnit.unitId,
isFavorite = !basedUnit.isFavorite,
pairedUnitId = basedUnit.pairedUnitId,
frequency = basedUnit.frequency,
),
)
}
}
override suspend fun incrementCounter(unit: AbstractUnit) = withContext(Dispatchers.IO) {
val basedUnit = unitsDao.getById(unit.id)
if (basedUnit == null) {
unitsDao.insertUnit(
UnitsEntity(
unitId = unit.id,
unitId = id,
frequency = 1,
),
)
@ -168,14 +137,17 @@ class UnitsRepositoryImpl @Inject constructor(
}
}
override suspend fun setPair(unit: AbstractUnit, pair: AbstractUnit) = withContext(Dispatchers.IO) {
val basedUnit = unitsDao.getById(unit.id)
suspend fun setPair(
id: String,
pairId: String,
) = withContext(Dispatchers.IO) {
val basedUnit = unitsDao.getById(id)
if (basedUnit == null) {
unitsDao.insertUnit(
UnitsEntity(
unitId = unit.id,
pairedUnitId = pair.id,
unitId = id,
pairedUnitId = pairId,
),
)
} else {
@ -183,95 +155,375 @@ class UnitsRepositoryImpl @Inject constructor(
UnitsEntity(
unitId = basedUnit.unitId,
isFavorite = basedUnit.isFavorite,
pairedUnitId = pair.id,
pairedUnitId = pairId,
frequency = basedUnit.frequency,
),
)
}
}
override suspend fun updateRates(unit: AbstractUnit): LocalDate? = withContext(Dispatchers.IO) {
var basedConversions = currencyRatesDao.getLatestRates(baseId = unit.id)
val epochDay = LocalDate.now().toEpochDay()
suspend fun favorite(id: String) = withContext(Dispatchers.IO) {
val basedUnit = unitsDao.getById(id)
if (basedConversions.firstOrNull()?.date != epochDay) {
if (basedUnit == null) {
unitsDao.insertUnit(
UnitsEntity(
unitId = id,
isFavorite = true,
),
)
} else {
unitsDao.insertUnit(
UnitsEntity(
unitId = basedUnit.unitId,
isFavorite = !basedUnit.isFavorite,
pairedUnitId = basedUnit.pairedUnitId,
frequency = basedUnit.frequency,
),
)
}
}
suspend fun filterUnits(
query: String,
unitGroups: List<UnitGroup>,
favoritesOnly: Boolean,
sorting: UnitsListSorting,
): Map<UnitGroup, List<UnitSearchResultItem>> = withContext(Dispatchers.IO) {
return@withContext filterUnitCollections(
query = query,
unitGroups = unitGroups,
favoritesOnly = favoritesOnly,
sorting = sorting,
)
.groupBy { it.basicUnit.group }
}
suspend fun filterUnitsAndBatchConvert(
query: String,
unitGroup: UnitGroup,
favoritesOnly: Boolean,
sorting: UnitsListSorting,
unitFromId: String,
input: String?,
): Map<UnitGroup, List<UnitSearchResultItem>> = withContext(Dispatchers.IO) {
val units = filterUnitCollections(
query = query,
unitGroups = listOf(unitGroup),
favoritesOnly = favoritesOnly,
sorting = sorting,
)
if (input == null) {
return@withContext units.groupBy { it.basicUnit.group }
}
val unitWithConversions = try {
when (unitGroup) {
UnitGroup.CURRENCY -> {
val inputBD = BigDecimal(input)
val validCurrencyPairs = withContext(Dispatchers.IO) {
currencyRatesDao.getLatestRates(unitFromId)
}
.filter { it.pairUnitValue?.isGreaterThan(BigDecimal.ZERO) ?: false }
val validIds = validCurrencyPairs.map { it.pairUnitId }
units
.filter { it.basicUnit.id in validIds }
.map { unitTo ->
unitTo.basicUnit as BasicUnit.Default
val factor = validCurrencyPairs
.first { it.pairUnitId == unitTo.basicUnit.id }
.pairUnitValue
val conversion = inputBD.multiply(factor)
unitTo.copy(
conversion = DefaultBatchConvertResult(conversion),
)
}
}
UnitGroup.NUMBER_BASE -> {
val unitFrom = getById(unitFromId) as BasicUnit.NumberBase
units.map { unitTo ->
unitTo.basicUnit as BasicUnit.NumberBase
val conversion = unitFrom.convert(unitTo.basicUnit, input)
unitTo.copy(
conversion = NumberBaseBatchConvertResult(conversion),
)
}
}
else -> {
val unitFrom = getById(unitFromId) as BasicUnit.Default
val inputBD = BigDecimal(input)
units.map { unitTo ->
unitTo.basicUnit as BasicUnit.Default
val conversion = unitFrom.convert(unitTo.basicUnit, inputBD)
unitTo.copy(
conversion = DefaultBatchConvertResult(conversion),
)
}
}
}
} catch (e: Exception) {
Log.e("UnitsRepositoryImpl", "Failed to batch convert: $e")
units
}
return@withContext unitWithConversions.groupBy { it.basicUnit.group }
}
suspend fun convertDefault(
unitFromId: String,
unitToId: String,
value1: String,
value2: String,
formatTime: Boolean,
): ConverterResult = withContext(Dispatchers.Default) {
val calculated: BigDecimal = try {
// Calculate expression in first text field
var calculated1 = Expression(value1.ifEmpty { Token.Digit._0 }).calculate()
// Calculate expression in second text field
if (unitFromId == UnitID.foot) {
val calculatedInches = Expression(value2.ifEmpty { Token.Digit._0 }).calculate()
// turn inches into feet so that it all comes down to converting from feet only
val inches = getById(UnitID.inch) as BasicUnit.Default
val feet = getById(UnitID.foot) as BasicUnit.Default
val inchesConvertedToFeet = inches.convert(feet, calculatedInches)
calculated1 += inchesConvertedToFeet
}
calculated1
} catch (e: ExpressionException.DivideByZero) {
return@withContext ConverterResult.Error.DivideByZero
} catch (e: Exception) {
return@withContext ConverterResult.Error.BadInput
}
return@withContext try {
val unitFrom = getById(unitFromId) as BasicUnit.Default
val unitTo = getById(unitToId) as BasicUnit.Default
when {
(unitFrom.group == UnitGroup.TIME) and (formatTime) ->
convertTime(unitFrom, calculated)
unitTo.id == UnitID.foot ->
convertToFoot(unitFrom, unitTo, calculated)
unitFrom.group == UnitGroup.CURRENCY ->
convertCurrencies(unitFromId, unitToId, calculated)
else ->
convertDefault(unitFrom, unitTo, calculated)
}
} catch (e: Exception) {
ConverterResult.Error.ConversionError
}
}
suspend fun convertNumberBase(
unitFromId: String,
unitToId: String,
value: String,
): ConverterResult = withContext(Dispatchers.Default) {
return@withContext try {
val unitFrom = getById(unitFromId) as BasicUnit.NumberBase
val unitTo = getById(unitToId) as BasicUnit.NumberBase
val conversion = unitFrom.convert(unitTo, value)
ConverterResult.NumberBase(conversion)
} catch (e: Exception) {
ConverterResult.Error.ConversionError
}
}
private suspend fun getUnitStats(id: String): UnitsEntity = withContext(Dispatchers.IO) {
unitsDao.getById(id) ?: UnitsEntity(unitId = id)
}
private fun convertDefault(
unitFrom: BasicUnit.Default,
unitTo: BasicUnit.Default,
value: BigDecimal,
): ConverterResult.Default = ConverterResult.Default(unitFrom.convert(unitTo, value), value)
internal fun convertTime(
unitFrom: BasicUnit.Default,
value: BigDecimal,
): ConverterResult.Time {
val input = value.multiply(unitFrom.factor)
val negative = input < BigDecimal.ZERO
val inputAbs = input.abs()
if (inputAbs.isLessThan(attosecondBasicUnit)) {
return ConverterResult.Time(
negative = negative,
day = BigDecimal.ZERO,
hour = BigDecimal.ZERO,
minute = BigDecimal.ZERO,
second = BigDecimal.ZERO,
millisecond = BigDecimal.ZERO,
microsecond = BigDecimal.ZERO,
nanosecond = BigDecimal.ZERO,
attosecond = inputAbs,
)
}
if (inputAbs.isLessThan(nanosecondBasicUnit)) {
return ConverterResult.Time(
negative = negative,
day = BigDecimal.ZERO,
hour = BigDecimal.ZERO,
minute = BigDecimal.ZERO,
second = BigDecimal.ZERO,
millisecond = BigDecimal.ZERO,
microsecond = BigDecimal.ZERO,
nanosecond = BigDecimal.ZERO,
attosecond = inputAbs.trimZeros(),
)
}
// DAY
var division = inputAbs.divideAndRemainder(dayBasicUnit)
val day = division.component1().setScale(0, RoundingMode.HALF_EVEN)
var remainingSeconds = division.component2().setScale(0, RoundingMode.HALF_EVEN)
division = remainingSeconds.divideAndRemainder(hourBasicUnit)
val hour = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(minuteBasicUnit)
val minute = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(secondBasicUnit)
val second = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(millisecondBasicUnit)
val millisecond = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(microsecondBasicUnit)
val microsecond = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(nanosecondBasicUnit)
val nanosecond = division.component1()
remainingSeconds = division.component2()
val attosecond = remainingSeconds
return ConverterResult.Time(
negative = negative,
day = day,
hour = hour,
minute = minute,
second = second,
millisecond = millisecond,
microsecond = microsecond,
nanosecond = nanosecond,
attosecond = attosecond,
)
}
private suspend fun convertToFoot(
unitFrom: BasicUnit.Default,
unitTo: BasicUnit.Default,
value: BigDecimal,
): ConverterResult.FootInch = ConverterResult.FootInch.fromBigDecimal(
input = unitFrom.convert(unitTo, value),
footUnit = unitTo,
inchUnit = getById(UnitID.inch) as BasicUnit.Default,
)
private suspend fun convertCurrencies(
unitFromId: String,
unitToId: String,
value: BigDecimal,
): ConverterResult = withContext(Dispatchers.IO) {
refreshCurrencyRates(unitFromId)
val latestRate = currencyRatesDao.getLatestRate(unitFromId, unitToId)
if (latestRate?.pairUnitValue == null) return@withContext ConverterResult.Error.Currency
val conversion = value.multiply(latestRate.pairUnitValue)
return@withContext ConverterResult.Default(conversion, value)
}
private suspend fun filterUnitCollections(
query: String,
unitGroups: List<UnitGroup>,
favoritesOnly: Boolean,
sorting: UnitsListSorting,
): Sequence<UnitSearchResultItem> = withContext(Dispatchers.IO) {
var units = inMemory
.filter { it.group in unitGroups }
.map { UnitSearchResultItem(it, getUnitStats(it.id), null) }
.asSequence()
if (favoritesOnly) {
units = units.filter { it.stats.isFavorite }
}
units = when (sorting) {
UnitsListSorting.USAGE -> units.sortedByDescending { it.stats.frequency }
UnitsListSorting.ALPHABETICAL -> units.sortedBy { mContext.getString(it.basicUnit.displayName) }
UnitsListSorting.SCALE_ASC -> units.sortedBy { it.basicUnit.factor }
UnitsListSorting.SCALE_DESC -> units.sortedByDescending { it.basicUnit.factor }
else -> units
}
units = if (query.isEmpty()) {
units.sortedByDescending { it.stats.isFavorite }
} else {
units.filterByLev(query, mContext)
}
return@withContext units
}
private suspend fun refreshCurrencyRates(unitFromId: String) = withContext(Dispatchers.IO) {
val latestUpdateDate = currencyRatesDao.getLatestRateTimeStamp(unitFromId)
val currentDate = LocalDate.now().toEpochDay()
if (latestUpdateDate != currentDate) {
// Need to update cache needed
try {
val conversions = CurrencyApi.service.getCurrencyPairs(unit.id)
val conversions = CurrencyApi.service.getCurrencyPairs(unitFromId)
val rates = conversions.currency
.map { (pairId, pairValue) ->
CurrencyRatesEntity(
baseUnitId = unit.id,
date = epochDay,
baseUnitId = unitFromId,
date = currentDate,
pairUnitId = pairId,
pairUnitValue = BigDecimal.valueOf(pairValue),
)
}
currencyRatesDao.insertRates(rates)
basedConversions = currencyRatesDao.getLatestRates(baseId = unit.id)
} catch (e: Exception) {
Log.d("UnitsRepository", "Skipped update: $e")
Log.d("UnitsRepositoryImpl", "Skipped update: $e")
}
}
inMemoryUnits.update { units ->
units.map { localUnit ->
if (localUnit.group != UnitGroup.CURRENCY) return@map localUnit
if (localUnit !is ReverseUnit) return@map localUnit
val rate = basedConversions
.firstOrNull { localUnit.id == it.pairUnitId }
?.pairUnitValue ?: BigDecimal.ZERO
return@map if (rate > BigDecimal.ZERO) {
localUnit.copy(basicUnit = rate)
} else {
localUnit.copy(basicUnit = BigDecimal.ZERO)
}
}
}
return@withContext basedConversions
.firstOrNull()
?.date
?.let { LocalDate.ofEpochDay(it) }
}
override suspend fun filterUnits(
query: String,
unitGroup: UnitGroup?,
favoritesOnly: Boolean,
hideBrokenUnits: Boolean,
sorting: UnitsListSorting,
shownUnitGroups: List<UnitGroup>,
): Map<UnitGroup, List<AbstractUnit>> {
// Leave only shown unit groups
var units: Sequence<AbstractUnit> = if (unitGroup == null) {
units.first().filter { it.group in shownUnitGroups }
} else {
getCollection(unitGroup)
}.asSequence()
if (favoritesOnly) {
units = units.filter { it.isFavorite }
}
if (hideBrokenUnits) {
units = units.filter { it.basicUnit > BigDecimal.ZERO }
}
units = when (sorting) {
UnitsListSorting.USAGE -> units.sortedByDescending { it.counter }
UnitsListSorting.ALPHABETICAL -> units.sortedBy { mContext.getString(it.displayName) }
UnitsListSorting.SCALE_ASC -> units.sortedBy { it.basicUnit }
UnitsListSorting.SCALE_DESC -> units.sortedByDescending { it.basicUnit }
else -> units
}
units = if (query.isEmpty()) {
units.sortedByDescending { it.isFavorite }
} else {
// For search we sort by popularity and Levenshtein distance (short and long name).
units.filterByLev(query, mContext)
}
return units.groupBy { it.group }
}
}
private val dayBasicUnit by lazy { BigDecimal("86400000000000000000000") }
private val hourBasicUnit by lazy { BigDecimal("3600000000000000000000") }
private val minuteBasicUnit by lazy { BigDecimal("60000000000000000000") }
private val secondBasicUnit by lazy { BigDecimal("1000000000000000000") }
private val millisecondBasicUnit by lazy { BigDecimal("1000000000000000") }
private val microsecondBasicUnit by lazy { BigDecimal("1000000000000") }
private val nanosecondBasicUnit by lazy { BigDecimal("1000000000") }
private val attosecondBasicUnit by lazy { BigDecimal("1") }

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val accelerationCollection: List<AbstractUnit> by lazy {
internal val accelerationCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attometer_per_square_second, BigDecimal("1"), UnitGroup.ACCELERATION, R.string.unit_attometer_per_square_second, R.string.unit_attometer_per_square_second_short),
NormalUnit(UnitID.femtometer_per_square_second, BigDecimal("1000"), UnitGroup.ACCELERATION, R.string.unit_femtometer_per_square_second, R.string.unit_femtometer_per_square_second_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val angleCollection: List<AbstractUnit> by lazy {
internal val angleCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.angle_second, BigDecimal("1"), UnitGroup.ANGLE, R.string.unit_angle_second, R.string.unit_angle_second_short),
NormalUnit(UnitID.angle_minute, BigDecimal("60"), UnitGroup.ANGLE, R.string.unit_angle_minute, R.string.unit_angle_minute_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val areaCollection: List<AbstractUnit> by lazy {
internal val areaCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.cent, BigDecimal("6083246572000000000000000000000000"), UnitGroup.AREA, R.string.unit_cent, R.string.unit_cent_short),
NormalUnit(UnitID.acre, BigDecimal("60832465720000000000000000000000"), UnitGroup.AREA, R.string.unit_acre, R.string.unit_acre_short),

View File

@ -19,221 +19,221 @@
package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.ReverseUnit
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val currencyCollection: List<AbstractUnit> by lazy {
internal val currencyCollection: List<BasicUnit> by lazy {
listOf(
ReverseUnit(UnitID.currency_1inch, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_1inch, R.string.unit_currency_1inch_short),
ReverseUnit(UnitID.currency_ada, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ada, R.string.unit_currency_ada_short),
ReverseUnit(UnitID.currency_aed, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_aed, R.string.unit_currency_aed_short),
ReverseUnit(UnitID.currency_afn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_afn, R.string.unit_currency_afn_short),
ReverseUnit(UnitID.currency_algo, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_algo, R.string.unit_currency_algo_short),
ReverseUnit(UnitID.currency_all, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_all, R.string.unit_currency_all_short),
ReverseUnit(UnitID.currency_amd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_amd, R.string.unit_currency_amd_short),
ReverseUnit(UnitID.currency_ang, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ang, R.string.unit_currency_ang_short),
ReverseUnit(UnitID.currency_aoa, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_aoa, R.string.unit_currency_aoa_short),
ReverseUnit(UnitID.currency_ars, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ars, R.string.unit_currency_ars_short),
ReverseUnit(UnitID.currency_atom, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_atom, R.string.unit_currency_atom_short),
ReverseUnit(UnitID.currency_aud, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_aud, R.string.unit_currency_aud_short),
ReverseUnit(UnitID.currency_avax, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_avax, R.string.unit_currency_avax_short),
ReverseUnit(UnitID.currency_awg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_awg, R.string.unit_currency_awg_short),
ReverseUnit(UnitID.currency_azn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_azn, R.string.unit_currency_azn_short),
ReverseUnit(UnitID.currency_bam, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bam, R.string.unit_currency_bam_short),
ReverseUnit(UnitID.currency_bbd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bbd, R.string.unit_currency_bbd_short),
ReverseUnit(UnitID.currency_bch, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bch, R.string.unit_currency_bch_short),
ReverseUnit(UnitID.currency_bdt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bdt, R.string.unit_currency_bdt_short),
ReverseUnit(UnitID.currency_bgn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bgn, R.string.unit_currency_bgn_short),
ReverseUnit(UnitID.currency_bhd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bhd, R.string.unit_currency_bhd_short),
ReverseUnit(UnitID.currency_bif, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bif, R.string.unit_currency_bif_short),
ReverseUnit(UnitID.currency_bmd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bmd, R.string.unit_currency_bmd_short),
ReverseUnit(UnitID.currency_bnb, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bnb, R.string.unit_currency_bnb_short),
ReverseUnit(UnitID.currency_bnd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bnd, R.string.unit_currency_bnd_short),
ReverseUnit(UnitID.currency_bob, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bob, R.string.unit_currency_bob_short),
ReverseUnit(UnitID.currency_brl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_brl, R.string.unit_currency_brl_short),
ReverseUnit(UnitID.currency_bsd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bsd, R.string.unit_currency_bsd_short),
ReverseUnit(UnitID.currency_btc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_btc, R.string.unit_currency_btc_short),
ReverseUnit(UnitID.currency_btn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_btn, R.string.unit_currency_btn_short),
ReverseUnit(UnitID.currency_busd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_busd, R.string.unit_currency_busd_short),
ReverseUnit(UnitID.currency_bwp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bwp, R.string.unit_currency_bwp_short),
ReverseUnit(UnitID.currency_byn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_byn, R.string.unit_currency_byn_short),
ReverseUnit(UnitID.currency_byr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_byr, R.string.unit_currency_byr_short),
ReverseUnit(UnitID.currency_bzd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bzd, R.string.unit_currency_bzd_short),
ReverseUnit(UnitID.currency_cad, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cad, R.string.unit_currency_cad_short),
ReverseUnit(UnitID.currency_cdf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cdf, R.string.unit_currency_cdf_short),
ReverseUnit(UnitID.currency_chf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_chf, R.string.unit_currency_chf_short),
ReverseUnit(UnitID.currency_chz, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_chz, R.string.unit_currency_chz_short),
ReverseUnit(UnitID.currency_clf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_clf, R.string.unit_currency_clf_short),
ReverseUnit(UnitID.currency_clp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_clp, R.string.unit_currency_clp_short),
ReverseUnit(UnitID.currency_cny, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cny, R.string.unit_currency_cny_short),
ReverseUnit(UnitID.currency_cop, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cop, R.string.unit_currency_cop_short),
ReverseUnit(UnitID.currency_crc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_crc, R.string.unit_currency_crc_short),
ReverseUnit(UnitID.currency_cro, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cro, R.string.unit_currency_cro_short),
ReverseUnit(UnitID.currency_cuc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cuc, R.string.unit_currency_cuc_short),
ReverseUnit(UnitID.currency_cup, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cup, R.string.unit_currency_cup_short),
ReverseUnit(UnitID.currency_cve, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cve, R.string.unit_currency_cve_short),
ReverseUnit(UnitID.currency_czk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_czk, R.string.unit_currency_czk_short),
ReverseUnit(UnitID.currency_dai, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dai, R.string.unit_currency_dai_short),
ReverseUnit(UnitID.currency_djf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_djf, R.string.unit_currency_djf_short),
ReverseUnit(UnitID.currency_dkk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dkk, R.string.unit_currency_dkk_short),
ReverseUnit(UnitID.currency_doge, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_doge, R.string.unit_currency_doge_short),
ReverseUnit(UnitID.currency_dop, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dop, R.string.unit_currency_dop_short),
ReverseUnit(UnitID.currency_dot, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dot, R.string.unit_currency_dot_short),
ReverseUnit(UnitID.currency_dzd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dzd, R.string.unit_currency_dzd_short),
ReverseUnit(UnitID.currency_egld, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_egld, R.string.unit_currency_egld_short),
ReverseUnit(UnitID.currency_egp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_egp, R.string.unit_currency_egp_short),
ReverseUnit(UnitID.currency_enj, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_enj, R.string.unit_currency_enj_short),
ReverseUnit(UnitID.currency_ern, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ern, R.string.unit_currency_ern_short),
ReverseUnit(UnitID.currency_etb, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_etb, R.string.unit_currency_etb_short),
ReverseUnit(UnitID.currency_etc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_etc, R.string.unit_currency_etc_short),
ReverseUnit(UnitID.currency_eth, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_eth, R.string.unit_currency_eth_short),
ReverseUnit(UnitID.currency_eur, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_eur, R.string.unit_currency_eur_short),
ReverseUnit(UnitID.currency_fil, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_fil, R.string.unit_currency_fil_short),
ReverseUnit(UnitID.currency_fjd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_fjd, R.string.unit_currency_fjd_short),
ReverseUnit(UnitID.currency_fkp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_fkp, R.string.unit_currency_fkp_short),
ReverseUnit(UnitID.currency_ftt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ftt, R.string.unit_currency_ftt_short),
ReverseUnit(UnitID.currency_gbp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gbp, R.string.unit_currency_gbp_short),
ReverseUnit(UnitID.currency_gel, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gel, R.string.unit_currency_gel_short),
ReverseUnit(UnitID.currency_ggp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ggp, R.string.unit_currency_ggp_short),
ReverseUnit(UnitID.currency_ghs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ghs, R.string.unit_currency_ghs_short),
ReverseUnit(UnitID.currency_gip, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gip, R.string.unit_currency_gip_short),
ReverseUnit(UnitID.currency_gmd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gmd, R.string.unit_currency_gmd_short),
ReverseUnit(UnitID.currency_gnf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gnf, R.string.unit_currency_gnf_short),
ReverseUnit(UnitID.currency_grt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_grt, R.string.unit_currency_grt_short),
ReverseUnit(UnitID.currency_gtq, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gtq, R.string.unit_currency_gtq_short),
ReverseUnit(UnitID.currency_gyd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gyd, R.string.unit_currency_gyd_short),
ReverseUnit(UnitID.currency_hkd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_hkd, R.string.unit_currency_hkd_short),
ReverseUnit(UnitID.currency_hnl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_hnl, R.string.unit_currency_hnl_short),
ReverseUnit(UnitID.currency_hrk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_hrk, R.string.unit_currency_hrk_short),
ReverseUnit(UnitID.currency_htg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_htg, R.string.unit_currency_htg_short),
ReverseUnit(UnitID.currency_huf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_huf, R.string.unit_currency_huf_short),
ReverseUnit(UnitID.currency_icp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_icp, R.string.unit_currency_icp_short),
ReverseUnit(UnitID.currency_idr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_idr, R.string.unit_currency_idr_short),
ReverseUnit(UnitID.currency_ils, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ils, R.string.unit_currency_ils_short),
ReverseUnit(UnitID.currency_imp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_imp, R.string.unit_currency_imp_short),
ReverseUnit(UnitID.currency_inj, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_inj, R.string.unit_currency_inj_short),
ReverseUnit(UnitID.currency_inr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_inr, R.string.unit_currency_inr_short),
ReverseUnit(UnitID.currency_iqd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_iqd, R.string.unit_currency_iqd_short),
ReverseUnit(UnitID.currency_irr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_irr, R.string.unit_currency_irr_short),
ReverseUnit(UnitID.currency_isk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_isk, R.string.unit_currency_isk_short),
ReverseUnit(UnitID.currency_jep, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jep, R.string.unit_currency_jep_short),
ReverseUnit(UnitID.currency_jmd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jmd, R.string.unit_currency_jmd_short),
ReverseUnit(UnitID.currency_jod, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jod, R.string.unit_currency_jod_short),
ReverseUnit(UnitID.currency_jpy, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jpy, R.string.unit_currency_jpy_short),
ReverseUnit(UnitID.currency_kes, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kes, R.string.unit_currency_kes_short),
ReverseUnit(UnitID.currency_kgs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kgs, R.string.unit_currency_kgs_short),
ReverseUnit(UnitID.currency_khr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_khr, R.string.unit_currency_khr_short),
ReverseUnit(UnitID.currency_kmf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kmf, R.string.unit_currency_kmf_short),
ReverseUnit(UnitID.currency_kpw, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kpw, R.string.unit_currency_kpw_short),
ReverseUnit(UnitID.currency_krw, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_krw, R.string.unit_currency_krw_short),
ReverseUnit(UnitID.currency_ksm, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ksm, R.string.unit_currency_ksm_short),
ReverseUnit(UnitID.currency_kwd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kwd, R.string.unit_currency_kwd_short),
ReverseUnit(UnitID.currency_kyd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kyd, R.string.unit_currency_kyd_short),
ReverseUnit(UnitID.currency_kzt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kzt, R.string.unit_currency_kzt_short),
ReverseUnit(UnitID.currency_lak, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lak, R.string.unit_currency_lak_short),
ReverseUnit(UnitID.currency_lbp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lbp, R.string.unit_currency_lbp_short),
ReverseUnit(UnitID.currency_link, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_link, R.string.unit_currency_link_short),
ReverseUnit(UnitID.currency_lkr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lkr, R.string.unit_currency_lkr_short),
ReverseUnit(UnitID.currency_lrd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lrd, R.string.unit_currency_lrd_short),
ReverseUnit(UnitID.currency_lsl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lsl, R.string.unit_currency_lsl_short),
ReverseUnit(UnitID.currency_ltc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ltc, R.string.unit_currency_ltc_short),
ReverseUnit(UnitID.currency_ltl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ltl, R.string.unit_currency_ltl_short),
ReverseUnit(UnitID.currency_luna, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_luna, R.string.unit_currency_luna_short),
ReverseUnit(UnitID.currency_lvl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lvl, R.string.unit_currency_lvl_short),
ReverseUnit(UnitID.currency_lyd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lyd, R.string.unit_currency_lyd_short),
ReverseUnit(UnitID.currency_mad, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mad, R.string.unit_currency_mad_short),
ReverseUnit(UnitID.currency_matic, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_matic, R.string.unit_currency_matic_short),
ReverseUnit(UnitID.currency_mdl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mdl, R.string.unit_currency_mdl_short),
ReverseUnit(UnitID.currency_mga, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mga, R.string.unit_currency_mga_short),
ReverseUnit(UnitID.currency_mkd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mkd, R.string.unit_currency_mkd_short),
ReverseUnit(UnitID.currency_mmk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mmk, R.string.unit_currency_mmk_short),
ReverseUnit(UnitID.currency_mnt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mnt, R.string.unit_currency_mnt_short),
ReverseUnit(UnitID.currency_mop, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mop, R.string.unit_currency_mop_short),
ReverseUnit(UnitID.currency_mro, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mro, R.string.unit_currency_mro_short),
ReverseUnit(UnitID.currency_mur, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mur, R.string.unit_currency_mur_short),
ReverseUnit(UnitID.currency_mvr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mvr, R.string.unit_currency_mvr_short),
ReverseUnit(UnitID.currency_mwk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mwk, R.string.unit_currency_mwk_short),
ReverseUnit(UnitID.currency_mxn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mxn, R.string.unit_currency_mxn_short),
ReverseUnit(UnitID.currency_myr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_myr, R.string.unit_currency_myr_short),
ReverseUnit(UnitID.currency_mzn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mzn, R.string.unit_currency_mzn_short),
ReverseUnit(UnitID.currency_nad, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nad, R.string.unit_currency_nad_short),
ReverseUnit(UnitID.currency_ngn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ngn, R.string.unit_currency_ngn_short),
ReverseUnit(UnitID.currency_nio, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nio, R.string.unit_currency_nio_short),
ReverseUnit(UnitID.currency_nok, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nok, R.string.unit_currency_nok_short),
ReverseUnit(UnitID.currency_npr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_npr, R.string.unit_currency_npr_short),
ReverseUnit(UnitID.currency_nzd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nzd, R.string.unit_currency_nzd_short),
ReverseUnit(UnitID.currency_omr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_omr, R.string.unit_currency_omr_short),
ReverseUnit(UnitID.currency_one, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_one, R.string.unit_currency_one_short),
ReverseUnit(UnitID.currency_pab, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pab, R.string.unit_currency_pab_short),
ReverseUnit(UnitID.currency_pen, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pen, R.string.unit_currency_pen_short),
ReverseUnit(UnitID.currency_pgk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pgk, R.string.unit_currency_pgk_short),
ReverseUnit(UnitID.currency_php, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_php, R.string.unit_currency_php_short),
ReverseUnit(UnitID.currency_pkr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pkr, R.string.unit_currency_pkr_short),
ReverseUnit(UnitID.currency_pln, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pln, R.string.unit_currency_pln_short),
ReverseUnit(UnitID.currency_pyg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pyg, R.string.unit_currency_pyg_short),
ReverseUnit(UnitID.currency_qar, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_qar, R.string.unit_currency_qar_short),
ReverseUnit(UnitID.currency_ron, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ron, R.string.unit_currency_ron_short),
ReverseUnit(UnitID.currency_rsd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_rsd, R.string.unit_currency_rsd_short),
ReverseUnit(UnitID.currency_rub, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_rub, R.string.unit_currency_rub_short),
ReverseUnit(UnitID.currency_rwf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_rwf, R.string.unit_currency_rwf_short),
ReverseUnit(UnitID.currency_sar, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sar, R.string.unit_currency_sar_short),
ReverseUnit(UnitID.currency_sbd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sbd, R.string.unit_currency_sbd_short),
ReverseUnit(UnitID.currency_scr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_scr, R.string.unit_currency_scr_short),
ReverseUnit(UnitID.currency_sdg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sdg, R.string.unit_currency_sdg_short),
ReverseUnit(UnitID.currency_sek, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sek, R.string.unit_currency_sek_short),
ReverseUnit(UnitID.currency_sgd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sgd, R.string.unit_currency_sgd_short),
ReverseUnit(UnitID.currency_shib, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_shib, R.string.unit_currency_shib_short),
ReverseUnit(UnitID.currency_shp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_shp, R.string.unit_currency_shp_short),
ReverseUnit(UnitID.currency_sll, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sll, R.string.unit_currency_sll_short),
ReverseUnit(UnitID.currency_sol, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sol, R.string.unit_currency_sol_short),
ReverseUnit(UnitID.currency_sos, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sos, R.string.unit_currency_sos_short),
ReverseUnit(UnitID.currency_srd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_srd, R.string.unit_currency_srd_short),
ReverseUnit(UnitID.currency_std, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_std, R.string.unit_currency_std_short),
ReverseUnit(UnitID.currency_svc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_svc, R.string.unit_currency_svc_short),
ReverseUnit(UnitID.currency_syp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_syp, R.string.unit_currency_syp_short),
ReverseUnit(UnitID.currency_szl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_szl, R.string.unit_currency_szl_short),
ReverseUnit(UnitID.currency_thb, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_thb, R.string.unit_currency_thb_short),
ReverseUnit(UnitID.currency_theta, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_theta, R.string.unit_currency_theta_short),
ReverseUnit(UnitID.currency_tjs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tjs, R.string.unit_currency_tjs_short),
ReverseUnit(UnitID.currency_tmt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tmt, R.string.unit_currency_tmt_short),
ReverseUnit(UnitID.currency_tnd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tnd, R.string.unit_currency_tnd_short),
ReverseUnit(UnitID.currency_top, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_top, R.string.unit_currency_top_short),
ReverseUnit(UnitID.currency_trx, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_trx, R.string.unit_currency_trx_short),
ReverseUnit(UnitID.currency_try, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_try, R.string.unit_currency_try_short),
ReverseUnit(UnitID.currency_ttd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ttd, R.string.unit_currency_ttd_short),
ReverseUnit(UnitID.currency_twd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_twd, R.string.unit_currency_twd_short),
ReverseUnit(UnitID.currency_tzs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tzs, R.string.unit_currency_tzs_short),
ReverseUnit(UnitID.currency_uah, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uah, R.string.unit_currency_uah_short),
ReverseUnit(UnitID.currency_ugx, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ugx, R.string.unit_currency_ugx_short),
ReverseUnit(UnitID.currency_uni, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uni, R.string.unit_currency_uni_short),
ReverseUnit(UnitID.currency_usd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_usd, R.string.unit_currency_usd_short),
ReverseUnit(UnitID.currency_usdc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_usdc, R.string.unit_currency_usdc_short),
ReverseUnit(UnitID.currency_usdt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_usdt, R.string.unit_currency_usdt_short),
ReverseUnit(UnitID.currency_uyu, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uyu, R.string.unit_currency_uyu_short),
ReverseUnit(UnitID.currency_uzs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uzs, R.string.unit_currency_uzs_short),
ReverseUnit(UnitID.currency_vef, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vef, R.string.unit_currency_vef_short),
ReverseUnit(UnitID.currency_vet, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vet, R.string.unit_currency_vet_short),
ReverseUnit(UnitID.currency_vnd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vnd, R.string.unit_currency_vnd_short),
ReverseUnit(UnitID.currency_vuv, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vuv, R.string.unit_currency_vuv_short),
ReverseUnit(UnitID.currency_wbtc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_wbtc, R.string.unit_currency_wbtc_short),
ReverseUnit(UnitID.currency_wst, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_wst, R.string.unit_currency_wst_short),
ReverseUnit(UnitID.currency_xaf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xaf, R.string.unit_currency_xaf_short),
ReverseUnit(UnitID.currency_xag, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xag, R.string.unit_currency_xag_short),
ReverseUnit(UnitID.currency_xau, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xau, R.string.unit_currency_xau_short),
ReverseUnit(UnitID.currency_xcd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xcd, R.string.unit_currency_xcd_short),
ReverseUnit(UnitID.currency_xdr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xdr, R.string.unit_currency_xdr_short),
ReverseUnit(UnitID.currency_xlm, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xlm, R.string.unit_currency_xlm_short),
ReverseUnit(UnitID.currency_xmr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xmr, R.string.unit_currency_xmr_short),
ReverseUnit(UnitID.currency_xof, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xof, R.string.unit_currency_xof_short),
ReverseUnit(UnitID.currency_xpf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xpf, R.string.unit_currency_xpf_short),
ReverseUnit(UnitID.currency_xrp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xrp, R.string.unit_currency_xrp_short),
ReverseUnit(UnitID.currency_yer, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_yer, R.string.unit_currency_yer_short),
ReverseUnit(UnitID.currency_zar, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zar, R.string.unit_currency_zar_short),
ReverseUnit(UnitID.currency_zmk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zmk, R.string.unit_currency_zmk_short),
ReverseUnit(UnitID.currency_zmw, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zmw, R.string.unit_currency_zmw_short),
ReverseUnit(UnitID.currency_zwl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zwl, R.string.unit_currency_zwl_short),
NormalUnit(UnitID.currency_1inch, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_1inch, R.string.unit_currency_1inch_short, backward = true),
NormalUnit(UnitID.currency_ada, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ada, R.string.unit_currency_ada_short, backward = true),
NormalUnit(UnitID.currency_aed, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_aed, R.string.unit_currency_aed_short, backward = true),
NormalUnit(UnitID.currency_afn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_afn, R.string.unit_currency_afn_short, backward = true),
NormalUnit(UnitID.currency_algo, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_algo, R.string.unit_currency_algo_short, backward = true),
NormalUnit(UnitID.currency_all, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_all, R.string.unit_currency_all_short, backward = true),
NormalUnit(UnitID.currency_amd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_amd, R.string.unit_currency_amd_short, backward = true),
NormalUnit(UnitID.currency_ang, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ang, R.string.unit_currency_ang_short, backward = true),
NormalUnit(UnitID.currency_aoa, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_aoa, R.string.unit_currency_aoa_short, backward = true),
NormalUnit(UnitID.currency_ars, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ars, R.string.unit_currency_ars_short, backward = true),
NormalUnit(UnitID.currency_atom, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_atom, R.string.unit_currency_atom_short, backward = true),
NormalUnit(UnitID.currency_aud, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_aud, R.string.unit_currency_aud_short, backward = true),
NormalUnit(UnitID.currency_avax, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_avax, R.string.unit_currency_avax_short, backward = true),
NormalUnit(UnitID.currency_awg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_awg, R.string.unit_currency_awg_short, backward = true),
NormalUnit(UnitID.currency_azn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_azn, R.string.unit_currency_azn_short, backward = true),
NormalUnit(UnitID.currency_bam, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bam, R.string.unit_currency_bam_short, backward = true),
NormalUnit(UnitID.currency_bbd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bbd, R.string.unit_currency_bbd_short, backward = true),
NormalUnit(UnitID.currency_bch, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bch, R.string.unit_currency_bch_short, backward = true),
NormalUnit(UnitID.currency_bdt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bdt, R.string.unit_currency_bdt_short, backward = true),
NormalUnit(UnitID.currency_bgn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bgn, R.string.unit_currency_bgn_short, backward = true),
NormalUnit(UnitID.currency_bhd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bhd, R.string.unit_currency_bhd_short, backward = true),
NormalUnit(UnitID.currency_bif, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bif, R.string.unit_currency_bif_short, backward = true),
NormalUnit(UnitID.currency_bmd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bmd, R.string.unit_currency_bmd_short, backward = true),
NormalUnit(UnitID.currency_bnb, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bnb, R.string.unit_currency_bnb_short, backward = true),
NormalUnit(UnitID.currency_bnd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bnd, R.string.unit_currency_bnd_short, backward = true),
NormalUnit(UnitID.currency_bob, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bob, R.string.unit_currency_bob_short, backward = true),
NormalUnit(UnitID.currency_brl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_brl, R.string.unit_currency_brl_short, backward = true),
NormalUnit(UnitID.currency_bsd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bsd, R.string.unit_currency_bsd_short, backward = true),
NormalUnit(UnitID.currency_btc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_btc, R.string.unit_currency_btc_short, backward = true),
NormalUnit(UnitID.currency_btn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_btn, R.string.unit_currency_btn_short, backward = true),
NormalUnit(UnitID.currency_busd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_busd, R.string.unit_currency_busd_short, backward = true),
NormalUnit(UnitID.currency_bwp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bwp, R.string.unit_currency_bwp_short, backward = true),
NormalUnit(UnitID.currency_byn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_byn, R.string.unit_currency_byn_short, backward = true),
NormalUnit(UnitID.currency_byr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_byr, R.string.unit_currency_byr_short, backward = true),
NormalUnit(UnitID.currency_bzd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_bzd, R.string.unit_currency_bzd_short, backward = true),
NormalUnit(UnitID.currency_cad, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cad, R.string.unit_currency_cad_short, backward = true),
NormalUnit(UnitID.currency_cdf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cdf, R.string.unit_currency_cdf_short, backward = true),
NormalUnit(UnitID.currency_chf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_chf, R.string.unit_currency_chf_short, backward = true),
NormalUnit(UnitID.currency_chz, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_chz, R.string.unit_currency_chz_short, backward = true),
NormalUnit(UnitID.currency_clf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_clf, R.string.unit_currency_clf_short, backward = true),
NormalUnit(UnitID.currency_clp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_clp, R.string.unit_currency_clp_short, backward = true),
NormalUnit(UnitID.currency_cny, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cny, R.string.unit_currency_cny_short, backward = true),
NormalUnit(UnitID.currency_cop, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cop, R.string.unit_currency_cop_short, backward = true),
NormalUnit(UnitID.currency_crc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_crc, R.string.unit_currency_crc_short, backward = true),
NormalUnit(UnitID.currency_cro, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cro, R.string.unit_currency_cro_short, backward = true),
NormalUnit(UnitID.currency_cuc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cuc, R.string.unit_currency_cuc_short, backward = true),
NormalUnit(UnitID.currency_cup, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cup, R.string.unit_currency_cup_short, backward = true),
NormalUnit(UnitID.currency_cve, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_cve, R.string.unit_currency_cve_short, backward = true),
NormalUnit(UnitID.currency_czk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_czk, R.string.unit_currency_czk_short, backward = true),
NormalUnit(UnitID.currency_dai, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dai, R.string.unit_currency_dai_short, backward = true),
NormalUnit(UnitID.currency_djf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_djf, R.string.unit_currency_djf_short, backward = true),
NormalUnit(UnitID.currency_dkk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dkk, R.string.unit_currency_dkk_short, backward = true),
NormalUnit(UnitID.currency_doge, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_doge, R.string.unit_currency_doge_short, backward = true),
NormalUnit(UnitID.currency_dop, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dop, R.string.unit_currency_dop_short, backward = true),
NormalUnit(UnitID.currency_dot, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dot, R.string.unit_currency_dot_short, backward = true),
NormalUnit(UnitID.currency_dzd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_dzd, R.string.unit_currency_dzd_short, backward = true),
NormalUnit(UnitID.currency_egld, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_egld, R.string.unit_currency_egld_short, backward = true),
NormalUnit(UnitID.currency_egp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_egp, R.string.unit_currency_egp_short, backward = true),
NormalUnit(UnitID.currency_enj, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_enj, R.string.unit_currency_enj_short, backward = true),
NormalUnit(UnitID.currency_ern, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ern, R.string.unit_currency_ern_short, backward = true),
NormalUnit(UnitID.currency_etb, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_etb, R.string.unit_currency_etb_short, backward = true),
NormalUnit(UnitID.currency_etc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_etc, R.string.unit_currency_etc_short, backward = true),
NormalUnit(UnitID.currency_eth, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_eth, R.string.unit_currency_eth_short, backward = true),
NormalUnit(UnitID.currency_eur, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_eur, R.string.unit_currency_eur_short, backward = true),
NormalUnit(UnitID.currency_fil, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_fil, R.string.unit_currency_fil_short, backward = true),
NormalUnit(UnitID.currency_fjd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_fjd, R.string.unit_currency_fjd_short, backward = true),
NormalUnit(UnitID.currency_fkp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_fkp, R.string.unit_currency_fkp_short, backward = true),
NormalUnit(UnitID.currency_ftt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ftt, R.string.unit_currency_ftt_short, backward = true),
NormalUnit(UnitID.currency_gbp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gbp, R.string.unit_currency_gbp_short, backward = true),
NormalUnit(UnitID.currency_gel, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gel, R.string.unit_currency_gel_short, backward = true),
NormalUnit(UnitID.currency_ggp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ggp, R.string.unit_currency_ggp_short, backward = true),
NormalUnit(UnitID.currency_ghs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ghs, R.string.unit_currency_ghs_short, backward = true),
NormalUnit(UnitID.currency_gip, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gip, R.string.unit_currency_gip_short, backward = true),
NormalUnit(UnitID.currency_gmd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gmd, R.string.unit_currency_gmd_short, backward = true),
NormalUnit(UnitID.currency_gnf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gnf, R.string.unit_currency_gnf_short, backward = true),
NormalUnit(UnitID.currency_grt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_grt, R.string.unit_currency_grt_short, backward = true),
NormalUnit(UnitID.currency_gtq, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gtq, R.string.unit_currency_gtq_short, backward = true),
NormalUnit(UnitID.currency_gyd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_gyd, R.string.unit_currency_gyd_short, backward = true),
NormalUnit(UnitID.currency_hkd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_hkd, R.string.unit_currency_hkd_short, backward = true),
NormalUnit(UnitID.currency_hnl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_hnl, R.string.unit_currency_hnl_short, backward = true),
NormalUnit(UnitID.currency_hrk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_hrk, R.string.unit_currency_hrk_short, backward = true),
NormalUnit(UnitID.currency_htg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_htg, R.string.unit_currency_htg_short, backward = true),
NormalUnit(UnitID.currency_huf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_huf, R.string.unit_currency_huf_short, backward = true),
NormalUnit(UnitID.currency_icp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_icp, R.string.unit_currency_icp_short, backward = true),
NormalUnit(UnitID.currency_idr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_idr, R.string.unit_currency_idr_short, backward = true),
NormalUnit(UnitID.currency_ils, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ils, R.string.unit_currency_ils_short, backward = true),
NormalUnit(UnitID.currency_imp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_imp, R.string.unit_currency_imp_short, backward = true),
NormalUnit(UnitID.currency_inj, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_inj, R.string.unit_currency_inj_short, backward = true),
NormalUnit(UnitID.currency_inr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_inr, R.string.unit_currency_inr_short, backward = true),
NormalUnit(UnitID.currency_iqd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_iqd, R.string.unit_currency_iqd_short, backward = true),
NormalUnit(UnitID.currency_irr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_irr, R.string.unit_currency_irr_short, backward = true),
NormalUnit(UnitID.currency_isk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_isk, R.string.unit_currency_isk_short, backward = true),
NormalUnit(UnitID.currency_jep, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jep, R.string.unit_currency_jep_short, backward = true),
NormalUnit(UnitID.currency_jmd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jmd, R.string.unit_currency_jmd_short, backward = true),
NormalUnit(UnitID.currency_jod, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jod, R.string.unit_currency_jod_short, backward = true),
NormalUnit(UnitID.currency_jpy, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_jpy, R.string.unit_currency_jpy_short, backward = true),
NormalUnit(UnitID.currency_kes, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kes, R.string.unit_currency_kes_short, backward = true),
NormalUnit(UnitID.currency_kgs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kgs, R.string.unit_currency_kgs_short, backward = true),
NormalUnit(UnitID.currency_khr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_khr, R.string.unit_currency_khr_short, backward = true),
NormalUnit(UnitID.currency_kmf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kmf, R.string.unit_currency_kmf_short, backward = true),
NormalUnit(UnitID.currency_kpw, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kpw, R.string.unit_currency_kpw_short, backward = true),
NormalUnit(UnitID.currency_krw, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_krw, R.string.unit_currency_krw_short, backward = true),
NormalUnit(UnitID.currency_ksm, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ksm, R.string.unit_currency_ksm_short, backward = true),
NormalUnit(UnitID.currency_kwd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kwd, R.string.unit_currency_kwd_short, backward = true),
NormalUnit(UnitID.currency_kyd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kyd, R.string.unit_currency_kyd_short, backward = true),
NormalUnit(UnitID.currency_kzt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_kzt, R.string.unit_currency_kzt_short, backward = true),
NormalUnit(UnitID.currency_lak, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lak, R.string.unit_currency_lak_short, backward = true),
NormalUnit(UnitID.currency_lbp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lbp, R.string.unit_currency_lbp_short, backward = true),
NormalUnit(UnitID.currency_link, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_link, R.string.unit_currency_link_short, backward = true),
NormalUnit(UnitID.currency_lkr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lkr, R.string.unit_currency_lkr_short, backward = true),
NormalUnit(UnitID.currency_lrd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lrd, R.string.unit_currency_lrd_short, backward = true),
NormalUnit(UnitID.currency_lsl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lsl, R.string.unit_currency_lsl_short, backward = true),
NormalUnit(UnitID.currency_ltc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ltc, R.string.unit_currency_ltc_short, backward = true),
NormalUnit(UnitID.currency_ltl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ltl, R.string.unit_currency_ltl_short, backward = true),
NormalUnit(UnitID.currency_luna, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_luna, R.string.unit_currency_luna_short, backward = true),
NormalUnit(UnitID.currency_lvl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lvl, R.string.unit_currency_lvl_short, backward = true),
NormalUnit(UnitID.currency_lyd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_lyd, R.string.unit_currency_lyd_short, backward = true),
NormalUnit(UnitID.currency_mad, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mad, R.string.unit_currency_mad_short, backward = true),
NormalUnit(UnitID.currency_matic, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_matic, R.string.unit_currency_matic_short, backward = true),
NormalUnit(UnitID.currency_mdl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mdl, R.string.unit_currency_mdl_short, backward = true),
NormalUnit(UnitID.currency_mga, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mga, R.string.unit_currency_mga_short, backward = true),
NormalUnit(UnitID.currency_mkd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mkd, R.string.unit_currency_mkd_short, backward = true),
NormalUnit(UnitID.currency_mmk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mmk, R.string.unit_currency_mmk_short, backward = true),
NormalUnit(UnitID.currency_mnt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mnt, R.string.unit_currency_mnt_short, backward = true),
NormalUnit(UnitID.currency_mop, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mop, R.string.unit_currency_mop_short, backward = true),
NormalUnit(UnitID.currency_mro, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mro, R.string.unit_currency_mro_short, backward = true),
NormalUnit(UnitID.currency_mur, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mur, R.string.unit_currency_mur_short, backward = true),
NormalUnit(UnitID.currency_mvr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mvr, R.string.unit_currency_mvr_short, backward = true),
NormalUnit(UnitID.currency_mwk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mwk, R.string.unit_currency_mwk_short, backward = true),
NormalUnit(UnitID.currency_mxn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mxn, R.string.unit_currency_mxn_short, backward = true),
NormalUnit(UnitID.currency_myr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_myr, R.string.unit_currency_myr_short, backward = true),
NormalUnit(UnitID.currency_mzn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_mzn, R.string.unit_currency_mzn_short, backward = true),
NormalUnit(UnitID.currency_nad, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nad, R.string.unit_currency_nad_short, backward = true),
NormalUnit(UnitID.currency_ngn, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ngn, R.string.unit_currency_ngn_short, backward = true),
NormalUnit(UnitID.currency_nio, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nio, R.string.unit_currency_nio_short, backward = true),
NormalUnit(UnitID.currency_nok, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nok, R.string.unit_currency_nok_short, backward = true),
NormalUnit(UnitID.currency_npr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_npr, R.string.unit_currency_npr_short, backward = true),
NormalUnit(UnitID.currency_nzd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_nzd, R.string.unit_currency_nzd_short, backward = true),
NormalUnit(UnitID.currency_omr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_omr, R.string.unit_currency_omr_short, backward = true),
NormalUnit(UnitID.currency_one, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_one, R.string.unit_currency_one_short, backward = true),
NormalUnit(UnitID.currency_pab, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pab, R.string.unit_currency_pab_short, backward = true),
NormalUnit(UnitID.currency_pen, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pen, R.string.unit_currency_pen_short, backward = true),
NormalUnit(UnitID.currency_pgk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pgk, R.string.unit_currency_pgk_short, backward = true),
NormalUnit(UnitID.currency_php, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_php, R.string.unit_currency_php_short, backward = true),
NormalUnit(UnitID.currency_pkr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pkr, R.string.unit_currency_pkr_short, backward = true),
NormalUnit(UnitID.currency_pln, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pln, R.string.unit_currency_pln_short, backward = true),
NormalUnit(UnitID.currency_pyg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_pyg, R.string.unit_currency_pyg_short, backward = true),
NormalUnit(UnitID.currency_qar, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_qar, R.string.unit_currency_qar_short, backward = true),
NormalUnit(UnitID.currency_ron, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ron, R.string.unit_currency_ron_short, backward = true),
NormalUnit(UnitID.currency_rsd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_rsd, R.string.unit_currency_rsd_short, backward = true),
NormalUnit(UnitID.currency_rub, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_rub, R.string.unit_currency_rub_short, backward = true),
NormalUnit(UnitID.currency_rwf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_rwf, R.string.unit_currency_rwf_short, backward = true),
NormalUnit(UnitID.currency_sar, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sar, R.string.unit_currency_sar_short, backward = true),
NormalUnit(UnitID.currency_sbd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sbd, R.string.unit_currency_sbd_short, backward = true),
NormalUnit(UnitID.currency_scr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_scr, R.string.unit_currency_scr_short, backward = true),
NormalUnit(UnitID.currency_sdg, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sdg, R.string.unit_currency_sdg_short, backward = true),
NormalUnit(UnitID.currency_sek, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sek, R.string.unit_currency_sek_short, backward = true),
NormalUnit(UnitID.currency_sgd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sgd, R.string.unit_currency_sgd_short, backward = true),
NormalUnit(UnitID.currency_shib, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_shib, R.string.unit_currency_shib_short, backward = true),
NormalUnit(UnitID.currency_shp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_shp, R.string.unit_currency_shp_short, backward = true),
NormalUnit(UnitID.currency_sll, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sll, R.string.unit_currency_sll_short, backward = true),
NormalUnit(UnitID.currency_sol, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sol, R.string.unit_currency_sol_short, backward = true),
NormalUnit(UnitID.currency_sos, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_sos, R.string.unit_currency_sos_short, backward = true),
NormalUnit(UnitID.currency_srd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_srd, R.string.unit_currency_srd_short, backward = true),
NormalUnit(UnitID.currency_std, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_std, R.string.unit_currency_std_short, backward = true),
NormalUnit(UnitID.currency_svc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_svc, R.string.unit_currency_svc_short, backward = true),
NormalUnit(UnitID.currency_syp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_syp, R.string.unit_currency_syp_short, backward = true),
NormalUnit(UnitID.currency_szl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_szl, R.string.unit_currency_szl_short, backward = true),
NormalUnit(UnitID.currency_thb, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_thb, R.string.unit_currency_thb_short, backward = true),
NormalUnit(UnitID.currency_theta, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_theta, R.string.unit_currency_theta_short, backward = true),
NormalUnit(UnitID.currency_tjs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tjs, R.string.unit_currency_tjs_short, backward = true),
NormalUnit(UnitID.currency_tmt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tmt, R.string.unit_currency_tmt_short, backward = true),
NormalUnit(UnitID.currency_tnd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tnd, R.string.unit_currency_tnd_short, backward = true),
NormalUnit(UnitID.currency_top, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_top, R.string.unit_currency_top_short, backward = true),
NormalUnit(UnitID.currency_trx, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_trx, R.string.unit_currency_trx_short, backward = true),
NormalUnit(UnitID.currency_try, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_try, R.string.unit_currency_try_short, backward = true),
NormalUnit(UnitID.currency_ttd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ttd, R.string.unit_currency_ttd_short, backward = true),
NormalUnit(UnitID.currency_twd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_twd, R.string.unit_currency_twd_short, backward = true),
NormalUnit(UnitID.currency_tzs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_tzs, R.string.unit_currency_tzs_short, backward = true),
NormalUnit(UnitID.currency_uah, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uah, R.string.unit_currency_uah_short, backward = true),
NormalUnit(UnitID.currency_ugx, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_ugx, R.string.unit_currency_ugx_short, backward = true),
NormalUnit(UnitID.currency_uni, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uni, R.string.unit_currency_uni_short, backward = true),
NormalUnit(UnitID.currency_usd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_usd, R.string.unit_currency_usd_short, backward = true),
NormalUnit(UnitID.currency_usdc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_usdc, R.string.unit_currency_usdc_short, backward = true),
NormalUnit(UnitID.currency_usdt, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_usdt, R.string.unit_currency_usdt_short, backward = true),
NormalUnit(UnitID.currency_uyu, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uyu, R.string.unit_currency_uyu_short, backward = true),
NormalUnit(UnitID.currency_uzs, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_uzs, R.string.unit_currency_uzs_short, backward = true),
NormalUnit(UnitID.currency_vef, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vef, R.string.unit_currency_vef_short, backward = true),
NormalUnit(UnitID.currency_vet, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vet, R.string.unit_currency_vet_short, backward = true),
NormalUnit(UnitID.currency_vnd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vnd, R.string.unit_currency_vnd_short, backward = true),
NormalUnit(UnitID.currency_vuv, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_vuv, R.string.unit_currency_vuv_short, backward = true),
NormalUnit(UnitID.currency_wbtc, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_wbtc, R.string.unit_currency_wbtc_short, backward = true),
NormalUnit(UnitID.currency_wst, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_wst, R.string.unit_currency_wst_short, backward = true),
NormalUnit(UnitID.currency_xaf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xaf, R.string.unit_currency_xaf_short, backward = true),
NormalUnit(UnitID.currency_xag, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xag, R.string.unit_currency_xag_short, backward = true),
NormalUnit(UnitID.currency_xau, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xau, R.string.unit_currency_xau_short, backward = true),
NormalUnit(UnitID.currency_xcd, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xcd, R.string.unit_currency_xcd_short, backward = true),
NormalUnit(UnitID.currency_xdr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xdr, R.string.unit_currency_xdr_short, backward = true),
NormalUnit(UnitID.currency_xlm, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xlm, R.string.unit_currency_xlm_short, backward = true),
NormalUnit(UnitID.currency_xmr, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xmr, R.string.unit_currency_xmr_short, backward = true),
NormalUnit(UnitID.currency_xof, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xof, R.string.unit_currency_xof_short, backward = true),
NormalUnit(UnitID.currency_xpf, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xpf, R.string.unit_currency_xpf_short, backward = true),
NormalUnit(UnitID.currency_xrp, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_xrp, R.string.unit_currency_xrp_short, backward = true),
NormalUnit(UnitID.currency_yer, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_yer, R.string.unit_currency_yer_short, backward = true),
NormalUnit(UnitID.currency_zar, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zar, R.string.unit_currency_zar_short, backward = true),
NormalUnit(UnitID.currency_zmk, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zmk, R.string.unit_currency_zmk_short, backward = true),
NormalUnit(UnitID.currency_zmw, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zmw, R.string.unit_currency_zmw_short, backward = true),
NormalUnit(UnitID.currency_zwl, BigDecimal.ZERO, UnitGroup.CURRENCY, R.string.unit_currency_zwl, R.string.unit_currency_zwl_short, backward = true),
)
}

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val dataCollection: List<AbstractUnit> by lazy {
internal val dataCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.bit, BigDecimal("1"), UnitGroup.DATA, R.string.unit_bit, R.string.unit_bit_short),
NormalUnit(UnitID.kibibit, BigDecimal("1024"), UnitGroup.DATA, R.string.unit_kibibit, R.string.unit_kibibit_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val dataTransferCollection: List<AbstractUnit> by lazy {
internal val dataTransferCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.bit_per_second, BigDecimal("1"), UnitGroup.DATA_TRANSFER, R.string.unit_bit_per_second, R.string.unit_bit_per_second_short),
NormalUnit(UnitID.kibibit_per_second, BigDecimal("1024"), UnitGroup.DATA_TRANSFER, R.string.unit_kibibit_per_second, R.string.unit_kibibit_per_second_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val electrostaticCapacitance: List<AbstractUnit> by lazy {
internal val electrostaticCapacitance: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attofarad, BigDecimal("1"), UnitGroup.ELECTROSTATIC_CAPACITANCE, R.string.unit_attofarad, R.string.unit_attofarad_short),
NormalUnit(UnitID.picofarad, BigDecimal("1000000"), UnitGroup.ELECTROSTATIC_CAPACITANCE, R.string.unit_picofarad, R.string.unit_picofarad_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val energyCollection: List<AbstractUnit> by lazy {
internal val energyCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.electron_volt, BigDecimal("0.160217733"), UnitGroup.ENERGY, R.string.unit_electron_volt, R.string.unit_electron_volt_short),
NormalUnit(UnitID.attojoule, BigDecimal("1.00"), UnitGroup.ENERGY, R.string.unit_attojoule, R.string.unit_attojoule_short),

View File

@ -20,33 +20,33 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.ReverseUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
val flowRateCollection: List<AbstractUnit> by lazy {
val flowRateCollection: List<BasicUnit> by lazy {
listOf(
ReverseUnit(UnitID.liter_per_hour, BigDecimal("3600000"), UnitGroup.FLOW_RATE, R.string.unit_liter_per_hour, R.string.unit_liter_per_hour_short),
ReverseUnit(UnitID.liter_per_minute, BigDecimal("60000"), UnitGroup.FLOW_RATE, R.string.unit_liter_per_minute, R.string.unit_liter_per_minute_short),
ReverseUnit(UnitID.liter_per_second, BigDecimal("1000"), UnitGroup.FLOW_RATE, R.string.unit_liter_per_second, R.string.unit_liter_per_second_short),
ReverseUnit(UnitID.milliliter_per_hour, BigDecimal("3600000000"), UnitGroup.FLOW_RATE, R.string.unit_milliliter_per_hour, R.string.unit_milliliter_per_hour_short),
ReverseUnit(UnitID.milliliter_per_minute, BigDecimal("60000000"), UnitGroup.FLOW_RATE, R.string.unit_milliliter_per_minute, R.string.unit_milliliter_per_minute_short),
ReverseUnit(UnitID.milliliter_per_second, BigDecimal("1000000"), UnitGroup.FLOW_RATE, R.string.unit_milliliter_per_second, R.string.unit_milliliter_per_second_short),
ReverseUnit(UnitID.cubic_meter_per_hour, BigDecimal("3600"), UnitGroup.FLOW_RATE, R.string.unit_cubic_meter_per_hour, R.string.unit_cubic_meter_per_hour_short),
ReverseUnit(UnitID.cubic_meter_per_minute, BigDecimal("60"), UnitGroup.FLOW_RATE, R.string.unit_cubic_meter_per_minute, R.string.unit_cubic_meter_per_minute_short),
ReverseUnit(UnitID.cubic_meter_per_second, BigDecimal("1"), UnitGroup.FLOW_RATE, R.string.unit_cubic_meter_per_second, R.string.unit_cubic_meter_per_second_short),
ReverseUnit(UnitID.cubic_millimeter_per_hour, BigDecimal("3600000000000"), UnitGroup.FLOW_RATE, R.string.unit_cubic_millimeter_per_hour, R.string.unit_cubic_millimeter_per_hour_short),
ReverseUnit(UnitID.cubic_millimeter_per_minute, BigDecimal("60000000000"), UnitGroup.FLOW_RATE, R.string.unit_cubic_millimeter_per_minute, R.string.unit_cubic_millimeter_per_minute_short),
ReverseUnit(UnitID.cubic_millimeter_per_second, BigDecimal("1000000000"), UnitGroup.FLOW_RATE, R.string.unit_cubic_millimeter_per_second, R.string.unit_cubic_millimeter_per_second_short),
ReverseUnit(UnitID.cubic_foot_per_hour, BigDecimal("127132.80019736"), UnitGroup.FLOW_RATE, R.string.unit_cubic_foot_per_hour, R.string.unit_cubic_foot_per_hour_short),
ReverseUnit(UnitID.cubic_foot_per_minute, BigDecimal("2118.8800032893"), UnitGroup.FLOW_RATE, R.string.unit_cubic_foot_per_minute, R.string.unit_cubic_foot_per_minute_short),
ReverseUnit(UnitID.cubic_foot_per_second, BigDecimal("35.314666721489"), UnitGroup.FLOW_RATE, R.string.unit_cubic_foot_per_second, R.string.unit_cubic_foot_per_second_short),
ReverseUnit(UnitID.gallons_per_hour_us, BigDecimal("951019.38848933"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_hour_us, R.string.unit_gallon_per_hour_us_short),
ReverseUnit(UnitID.gallons_per_minute_us, BigDecimal("15850.323141489"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_minute_us, R.string.unit_gallon_per_minute_us_short),
ReverseUnit(UnitID.gallons_per_second_us, BigDecimal("264.17205235815"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_second_us, R.string.unit_gallon_per_second_us_short),
ReverseUnit(UnitID.gallons_per_hour_imperial, BigDecimal("791889.29387672"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_hour_imperial, R.string.unit_gallon_per_hour_imperial_short),
ReverseUnit(UnitID.gallons_per_minute_imperial, BigDecimal("13198.154897945"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_minute_imperial, R.string.unit_gallon_per_minute_imperial_short),
ReverseUnit(UnitID.gallons_per_second_imperial, BigDecimal("219.96924829909"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_second_imperial, R.string.unit_gallon_per_second_imperial_short),
NormalUnit(UnitID.liter_per_hour, BigDecimal("3600000"), UnitGroup.FLOW_RATE, R.string.unit_liter_per_hour, R.string.unit_liter_per_hour_short, true),
NormalUnit(UnitID.liter_per_minute, BigDecimal("60000"), UnitGroup.FLOW_RATE, R.string.unit_liter_per_minute, R.string.unit_liter_per_minute_short, true),
NormalUnit(UnitID.liter_per_second, BigDecimal("1000"), UnitGroup.FLOW_RATE, R.string.unit_liter_per_second, R.string.unit_liter_per_second_short, true),
NormalUnit(UnitID.milliliter_per_hour, BigDecimal("3600000000"), UnitGroup.FLOW_RATE, R.string.unit_milliliter_per_hour, R.string.unit_milliliter_per_hour_short, true),
NormalUnit(UnitID.milliliter_per_minute, BigDecimal("60000000"), UnitGroup.FLOW_RATE, R.string.unit_milliliter_per_minute, R.string.unit_milliliter_per_minute_short, true),
NormalUnit(UnitID.milliliter_per_second, BigDecimal("1000000"), UnitGroup.FLOW_RATE, R.string.unit_milliliter_per_second, R.string.unit_milliliter_per_second_short, true),
NormalUnit(UnitID.cubic_meter_per_hour, BigDecimal("3600"), UnitGroup.FLOW_RATE, R.string.unit_cubic_meter_per_hour, R.string.unit_cubic_meter_per_hour_short, true),
NormalUnit(UnitID.cubic_meter_per_minute, BigDecimal("60"), UnitGroup.FLOW_RATE, R.string.unit_cubic_meter_per_minute, R.string.unit_cubic_meter_per_minute_short, true),
NormalUnit(UnitID.cubic_meter_per_second, BigDecimal("1"), UnitGroup.FLOW_RATE, R.string.unit_cubic_meter_per_second, R.string.unit_cubic_meter_per_second_short, true),
NormalUnit(UnitID.cubic_millimeter_per_hour, BigDecimal("3600000000000"), UnitGroup.FLOW_RATE, R.string.unit_cubic_millimeter_per_hour, R.string.unit_cubic_millimeter_per_hour_short, true),
NormalUnit(UnitID.cubic_millimeter_per_minute, BigDecimal("60000000000"), UnitGroup.FLOW_RATE, R.string.unit_cubic_millimeter_per_minute, R.string.unit_cubic_millimeter_per_minute_short, true),
NormalUnit(UnitID.cubic_millimeter_per_second, BigDecimal("1000000000"), UnitGroup.FLOW_RATE, R.string.unit_cubic_millimeter_per_second, R.string.unit_cubic_millimeter_per_second_short, true),
NormalUnit(UnitID.cubic_foot_per_hour, BigDecimal("127132.80019736"), UnitGroup.FLOW_RATE, R.string.unit_cubic_foot_per_hour, R.string.unit_cubic_foot_per_hour_short, true),
NormalUnit(UnitID.cubic_foot_per_minute, BigDecimal("2118.8800032893"), UnitGroup.FLOW_RATE, R.string.unit_cubic_foot_per_minute, R.string.unit_cubic_foot_per_minute_short, true),
NormalUnit(UnitID.cubic_foot_per_second, BigDecimal("35.314666721489"), UnitGroup.FLOW_RATE, R.string.unit_cubic_foot_per_second, R.string.unit_cubic_foot_per_second_short, true),
NormalUnit(UnitID.gallons_per_hour_us, BigDecimal("951019.38848933"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_hour_us, R.string.unit_gallon_per_hour_us_short, true),
NormalUnit(UnitID.gallons_per_minute_us, BigDecimal("15850.323141489"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_minute_us, R.string.unit_gallon_per_minute_us_short, true),
NormalUnit(UnitID.gallons_per_second_us, BigDecimal("264.17205235815"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_second_us, R.string.unit_gallon_per_second_us_short, true),
NormalUnit(UnitID.gallons_per_hour_imperial, BigDecimal("791889.29387672"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_hour_imperial, R.string.unit_gallon_per_hour_imperial_short, true),
NormalUnit(UnitID.gallons_per_minute_imperial, BigDecimal("13198.154897945"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_minute_imperial, R.string.unit_gallon_per_minute_imperial_short, true),
NormalUnit(UnitID.gallons_per_second_imperial, BigDecimal("219.96924829909"), UnitGroup.FLOW_RATE, R.string.unit_gallon_per_second_imperial, R.string.unit_gallon_per_second_imperial_short, true),
)
}

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val fluxCollection: List<AbstractUnit> by lazy {
internal val fluxCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.maxwell, BigDecimal("1"), UnitGroup.FLUX, R.string.unit_maxwell, R.string.unit_maxwell_short),
NormalUnit(UnitID.microweber, BigDecimal("100"), UnitGroup.FLUX, R.string.unit_microweber, R.string.unit_microweber_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
val forceCollection: List<AbstractUnit> by lazy {
val forceCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attonewton, BigDecimal("1"), UnitGroup.FORCE, R.string.unit_attonewton, R.string.unit_attonewton_short),
NormalUnit(UnitID.dyne, BigDecimal("10000000000000"), UnitGroup.FORCE, R.string.unit_dyne, R.string.unit_dyne_short),

View File

@ -20,23 +20,22 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.BackwardUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
val fuelConsumptionCollection: List<AbstractUnit> by lazy {
val fuelConsumptionCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit( UnitID.kilometer_per_liter, BigDecimal("1"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_km_per_l, R.string.unit_km_per_l_short),
BackwardUnit(UnitID.liter_per_kilometer, BigDecimal("1"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_l_per_km, R.string.unit_l_per_km_short),
BackwardUnit(UnitID.liter_per_100_kilometer, BigDecimal("100"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_l_per_100_km, R.string.unit_l_per_100_km_short),
NormalUnit(UnitID.liter_per_kilometer, BigDecimal("1"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_l_per_km, R.string.unit_l_per_km_short, true),
NormalUnit(UnitID.liter_per_100_kilometer, BigDecimal("100"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_l_per_100_km, R.string.unit_l_per_100_km_short, true),
NormalUnit( UnitID.mile_per_gallon_uk, BigDecimal("0.35400619"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_mi_per_gallon_uk, R.string.unit_mi_per_gallon_uk_short),
NormalUnit( UnitID.mile_per_gallon_us, BigDecimal("0.4251437075"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_mi_per_gallon_us, R.string.unit_mi_per_gallon_us_short),
NormalUnit( UnitID.mile_us_per_liter, BigDecimal("1.609344"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_mi_us_per_l, R.string.unit_mi_us_per_l_short),
BackwardUnit(UnitID.gallon_us_per_mile, BigDecimal("0.4251437075"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_us_per_mile, R.string.unit_gallon_us_per_mile_short),
BackwardUnit(UnitID.gallon_uk_per_mile, BigDecimal("0.35400619"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_uk_per_mile, R.string.unit_gallon_uk_per_mile_short),
BackwardUnit(UnitID.gallon_us_per_100_mile, BigDecimal("42.51437075"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_us_per_100_mile, R.string.unit_gallon_us_per_100_mile_short),
BackwardUnit(UnitID.gallon_uk_per_100_mile, BigDecimal("35.400618996"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_uk_per_100_mile, R.string.unit_gallon_uk_per_100_mile_short),
NormalUnit(UnitID.gallon_us_per_mile, BigDecimal("0.4251437075"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_us_per_mile, R.string.unit_gallon_us_per_mile_short, true),
NormalUnit(UnitID.gallon_uk_per_mile, BigDecimal("0.35400619"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_uk_per_mile, R.string.unit_gallon_uk_per_mile_short, true),
NormalUnit(UnitID.gallon_us_per_100_mile, BigDecimal("42.51437075"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_us_per_100_mile, R.string.unit_gallon_us_per_100_mile_short, true),
NormalUnit(UnitID.gallon_uk_per_100_mile, BigDecimal("35.400618996"), UnitGroup.FUEL_CONSUMPTION, R.string.unit_gallon_uk_per_100_mile, R.string.unit_gallon_uk_per_100_mile_short, true),
)
}

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val lengthCollection: List<AbstractUnit> by lazy {
internal val lengthCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attometer, BigDecimal("1"), UnitGroup.LENGTH, R.string.unit_attometer, R.string.unit_attometer_short),
NormalUnit(UnitID.nanometer, BigDecimal("1000000000"), UnitGroup.LENGTH, R.string.unit_nanometer, R.string.unit_nanometer_short),
@ -55,3 +55,4 @@ internal val lengthCollection: List<AbstractUnit> by lazy {
NormalUnit(UnitID.sun_equatorial_radius, BigDecimal("695508000000000000000000000"), UnitGroup.LENGTH, R.string.unit_sun_equatorial_radius, R.string.unit_sun_equatorial_radius_short),
)
}

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
val luminanceCollection: List<AbstractUnit> by lazy {
val luminanceCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.candela_per_square_meter, BigDecimal("31415926.5359"), UnitGroup.LUMINANCE, R.string.unit_candela_per_square_meter, R.string.unit_candela_per_square_meter_short),
NormalUnit(UnitID.candela_per_square_centimeter, BigDecimal("314159265359"), UnitGroup.LUMINANCE, R.string.unit_candela_per_square_centimeter, R.string.unit_candela_per_square_centimeter_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val massCollection: List<AbstractUnit> by lazy {
internal val massCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.electron_mass_rest, BigDecimal("0.00000000000000000000000000091093897"), UnitGroup.MASS, R.string.unit_electron_mass_rest, R.string.unit_electron_mass_rest_short),
NormalUnit(UnitID.atomic_mass_unit, BigDecimal("0.0000000000000000000000016605402"), UnitGroup.MASS, R.string.unit_atomic_mass_unit, R.string.unit_atomic_mass_unit_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NumberBaseUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NumberBaseUnit
import java.math.BigDecimal
internal val numberBaseCollection: List<AbstractUnit> by lazy {
internal val numberBaseCollection: List<BasicUnit> by lazy {
listOf(
NumberBaseUnit(UnitID.binary, BigDecimal("2.0"), UnitGroup.NUMBER_BASE, R.string.unit_binary, R.string.unit_binary_short),
NumberBaseUnit(UnitID.ternary, BigDecimal("3.0"), UnitGroup.NUMBER_BASE, R.string.unit_ternary, R.string.unit_ternary_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val powerCollection: List<AbstractUnit> by lazy {
internal val powerCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attowatt, BigDecimal("1"), UnitGroup.POWER, R.string.unit_attowatt, R.string.unit_attowatt_short),
NormalUnit(UnitID.watt, BigDecimal("1000000000000000000"), UnitGroup.POWER, R.string.unit_watt, R.string.unit_watt_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
val prefixCollection: List<AbstractUnit> by lazy {
val prefixCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.prefix_quetta, BigDecimal("1000000000000000000000000000000"), UnitGroup.PREFIX, R.string.unit_prefix_quetta, R.string.unit_prefix_quetta_short),
NormalUnit(UnitID.prefix_ronna, BigDecimal("1000000000000000000000000000"), UnitGroup.PREFIX, R.string.unit_prefix_ronna, R.string.unit_prefix_ronna_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val pressureCollection: List<AbstractUnit> by lazy {
internal val pressureCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attopascal, BigDecimal("1"), UnitGroup.PRESSURE, R.string.unit_attopascal, R.string.unit_attopascal_short),
NormalUnit(UnitID.femtopascal, BigDecimal("1000"), UnitGroup.PRESSURE, R.string.unit_femtopascal, R.string.unit_femtopascal_short),

View File

@ -20,13 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.BackwardUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val speedCollection: List<AbstractUnit> by lazy {
internal val speedCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit( UnitID.millimeter_per_hour, BigDecimal("1"), UnitGroup.SPEED, R.string.unit_millimeter_per_hour, R.string.unit_millimeter_per_hour_short),
NormalUnit( UnitID.millimeter_per_minute, BigDecimal("60"), UnitGroup.SPEED, R.string.unit_millimeter_per_minute, R.string.unit_millimeter_per_minute_short),
@ -49,10 +48,10 @@ internal val speedCollection: List<AbstractUnit> by lazy {
NormalUnit( UnitID.mile_per_hour, BigDecimal("1609344"), UnitGroup.SPEED, R.string.unit_mile_per_hour, R.string.unit_mile_per_hour_short),
NormalUnit( UnitID.mile_per_minute, BigDecimal("96560640"), UnitGroup.SPEED, R.string.unit_mile_per_minute, R.string.unit_mile_per_minute_short),
NormalUnit( UnitID.mile_per_second, BigDecimal("5793638400"), UnitGroup.SPEED, R.string.unit_mile_per_second, R.string.unit_mile_per_second_short),
BackwardUnit(UnitID.minute_per_kilometer, BigDecimal("60000000"), UnitGroup.SPEED, R.string.unit_minute_per_kilometer, R.string.unit_minute_per_kilometer_short),
BackwardUnit(UnitID.minute_per_mile, BigDecimal("96560640"), UnitGroup.SPEED, R.string.unit_minute_per_mile, R.string.unit_minute_per_mile_short),
BackwardUnit(UnitID.hour_per_kilometer, BigDecimal("1000000"), UnitGroup.SPEED, R.string.unit_hour_per_kilometer, R.string.unit_hour_per_kilometer_short),
BackwardUnit(UnitID.hour_per_mile, BigDecimal("1609344"), UnitGroup.SPEED, R.string.unit_hour_per_mile, R.string.unit_hour_per_mile_short),
NormalUnit(UnitID.minute_per_kilometer, BigDecimal("60000000"), UnitGroup.SPEED, R.string.unit_minute_per_kilometer, R.string.unit_minute_per_kilometer_short, true),
NormalUnit(UnitID.minute_per_mile, BigDecimal("96560640"), UnitGroup.SPEED, R.string.unit_minute_per_mile, R.string.unit_minute_per_mile_short, true),
NormalUnit(UnitID.hour_per_kilometer, BigDecimal("1000000"), UnitGroup.SPEED, R.string.unit_hour_per_kilometer, R.string.unit_hour_per_kilometer_short, true),
NormalUnit(UnitID.hour_per_mile, BigDecimal("1609344"), UnitGroup.SPEED, R.string.unit_hour_per_mile, R.string.unit_hour_per_mile_short, true),
NormalUnit( UnitID.knot, BigDecimal("1852000"), UnitGroup.SPEED, R.string.unit_knot, R.string.unit_knot_short),
NormalUnit( UnitID.velocity_of_light_in_vacuum, BigDecimal("1079252848799998"), UnitGroup.SPEED, R.string.unit_velocity_of_light_in_vacuum, R.string.unit_velocity_of_light_in_vacuum_short),
NormalUnit( UnitID.cosmic_velocity_first, BigDecimal("28440000000"), UnitGroup.SPEED, R.string.unit_cosmic_velocity_first, R.string.unit_cosmic_velocity_first_short),

View File

@ -21,89 +21,90 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.TemperatureUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import java.math.BigDecimal
import java.math.RoundingMode
internal val temperatureCollection: List<AbstractUnit> by lazy {
listOf(
TemperatureUnit(
id = UnitID.celsius,
basicUnit = BigDecimal.ONE,
group = UnitGroup.TEMPERATURE,
displayName = R.string.unit_celsius,
shortName = R.string.unit_celsius_short,
customConvert = { unitTo, value ->
when (unitTo.id) {
UnitID.fahrenheit -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.times(BigDecimal("1.8"))
.plus(BigDecimal("32"))
}
internal val temperatureCollection: List<BasicUnit> by lazy {
val celsius = object : BasicUnit.Default {
override val id: String = UnitID.celsius
override val group: UnitGroup = UnitGroup.TEMPERATURE
override val displayName: Int = R.string.unit_celsius
override val shortName: Int = R.string.unit_celsius_short
override val factor: BigDecimal = BigDecimal.ONE
override val backward: Boolean = false
UnitID.kelvin -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.plus(BigDecimal("273.15"))
}
override fun convert(unitTo: BasicUnit.Default, value: BigDecimal): BigDecimal = when (unitTo.id) {
UnitID.fahrenheit -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.times(BigDecimal("1.8"))
.plus(BigDecimal("32"))
}
else -> value
}
UnitID.kelvin -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.plus(BigDecimal("273.15"))
}
),
TemperatureUnit(
id = UnitID.fahrenheit,
basicUnit = BigDecimal.ONE,
group = UnitGroup.TEMPERATURE,
displayName = R.string.unit_fahrenheit,
shortName = R.string.unit_fahrenheit_short,
customConvert = { unitTo, value ->
when (unitTo.id) {
UnitID.celsius -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("32"))
.times(BigDecimal("5"))
.div(BigDecimal("9"))
}
UnitID.kelvin -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("32"))
.times(BigDecimal("5"))
.div(BigDecimal("9"))
.add(BigDecimal("273.15"))
}
else -> value
}
else -> value
}
}
val fahrenheit = object : BasicUnit.Default {
override val id: String = UnitID.fahrenheit
override val group: UnitGroup = UnitGroup.TEMPERATURE
override val displayName: Int = R.string.unit_fahrenheit
override val shortName: Int = R.string.unit_fahrenheit_short
override val factor: BigDecimal = BigDecimal.ONE
override val backward: Boolean = false
override fun convert(unitTo: BasicUnit.Default, value: BigDecimal): BigDecimal = when (unitTo.id) {
UnitID.celsius -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("32"))
.times(BigDecimal("5"))
.div(BigDecimal("9"))
}
),
TemperatureUnit(
id = UnitID.kelvin,
basicUnit = BigDecimal.ONE,
group = UnitGroup.TEMPERATURE,
displayName = R.string.unit_kelvin,
shortName = R.string.unit_kelvin_short,
customConvert = { unitTo, value ->
when (unitTo.id) {
UnitID.celsius -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("273.15"))
}
UnitID.fahrenheit -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("273.15"))
.times(BigDecimal("1.8"))
.plus(BigDecimal("32"))
}
else -> value
}
UnitID.kelvin -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("32"))
.times(BigDecimal("5"))
.div(BigDecimal("9"))
.add(BigDecimal("273.15"))
}
),
)
else -> value
}
}
val kelvin = object : BasicUnit.Default {
override val id: String = UnitID.kelvin
override val group: UnitGroup = UnitGroup.TEMPERATURE
override val displayName: Int = R.string.unit_kelvin
override val shortName: Int = R.string.unit_kelvin_short
override val factor: BigDecimal = BigDecimal.ONE
override val backward: Boolean = false
override fun convert(unitTo: BasicUnit.Default, value: BigDecimal): BigDecimal = when (unitTo.id) {
UnitID.celsius -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("273.15"))
}
UnitID.fahrenheit -> {
value
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.minus(BigDecimal("273.15"))
.times(BigDecimal("1.8"))
.plus(BigDecimal("32"))
}
else -> value
}
}
listOf(celsius, fahrenheit, kelvin)
}

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val timeCollection: List<AbstractUnit> by lazy {
internal val timeCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attosecond, BigDecimal("1"), UnitGroup.TIME, R.string.unit_attosecond, R.string.unit_attosecond_short),
NormalUnit(UnitID.nanosecond, BigDecimal("1000000000"), UnitGroup.TIME, R.string.unit_nanosecond, R.string.unit_nanosecond_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
val torqueCollection: List<AbstractUnit> by lazy {
val torqueCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.dyne_millimeter, BigDecimal("1"), UnitGroup.TORQUE, R.string.unit_dyne_millimeter, R.string.unit_dyne_millimeter_short),
NormalUnit(UnitID.dyne_centimeter, BigDecimal("10"), UnitGroup.TORQUE, R.string.unit_dyne_centimeter, R.string.unit_dyne_centimeter_short),

View File

@ -20,12 +20,12 @@ package com.sadellie.unitto.data.converter.collections
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
internal val volumeCollection: List<AbstractUnit> by lazy {
internal val volumeCollection: List<BasicUnit> by lazy {
listOf(
NormalUnit(UnitID.attoliter, BigDecimal("1"), UnitGroup.VOLUME, R.string.unit_attoliter, R.string.unit_attoliter_short),
NormalUnit(UnitID.milliliter, BigDecimal("1000000000000000"), UnitGroup.VOLUME, R.string.unit_milliliter, R.string.unit_milliliter_short),

View File

@ -44,10 +44,9 @@ import com.sadellie.unitto.data.converter.collections.temperatureCollection
import com.sadellie.unitto.data.converter.collections.timeCollection
import com.sadellie.unitto.data.converter.collections.torqueCollection
import com.sadellie.unitto.data.converter.collections.volumeCollection
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.DefaultUnit
import com.sadellie.unitto.data.model.unit.NumberBaseUnit
import com.sadellie.unitto.data.model.unit.ReverseUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.converter.unit.NumberBaseUnit
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
@ -595,13 +594,11 @@ class AllUnitsTest {
val actual = when (unitFrom.group) {
UnitGroup.NUMBER_BASE -> (unitFrom as NumberBaseUnit).convert((unitTo as NumberBaseUnit), value)
UnitGroup.FLOW_RATE -> (unitFrom as ReverseUnit)
.convert((unitTo as DefaultUnit), BigDecimal(value))
.format(5, OutputFormat.PLAIN)
else -> (unitFrom as DefaultUnit)
.convert((unitTo as DefaultUnit), BigDecimal(value))
else -> (unitFrom as BasicUnit.Default)
.convert((unitTo as BasicUnit.Default), BigDecimal(value))
.format(5, OutputFormat.PLAIN)
}
assertEquals("Failed at $this to $checkingId", expected, actual)
println("PASSED: $this -> $expected == $actual")
val content: Set<String> = history.getOrDefault(unitFrom.group, setOf())

View File

@ -0,0 +1,247 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.converter
import android.content.Context
import com.sadellie.unitto.data.database.CurrencyRatesDao
import com.sadellie.unitto.data.database.CurrencyRatesEntity
import com.sadellie.unitto.data.database.UnitsDao
import com.sadellie.unitto.data.database.UnitsEntity
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import java.math.BigDecimal
@RunWith(RobolectricTestRunner::class)
class ConverterUIStateKtTest {
@Test
fun convertTime() {
var basicValue = BigDecimal("1")
val mContext: Context = RuntimeEnvironment.getApplication().applicationContext
val repo = UnitsRepositoryImpl(
unitsDao = object : UnitsDao {
override fun getAllFlow(): Flow<List<UnitsEntity>> = emptyFlow()
override suspend fun insertUnit(unit: UnitsEntity) {}
override suspend fun getById(unitId: String): UnitsEntity? = null
override suspend fun clear() {}
},
currencyRatesDao = object : CurrencyRatesDao {
override suspend fun insertRates(currencyRates: List<CurrencyRatesEntity>) {}
override suspend fun getLatestRateTimeStamp(baseId: String): Long? = null
override suspend fun getLatestRates(baseId: String): List<CurrencyRatesEntity> = emptyList()
override suspend fun getLatestRate(baseId: String, pairId: String): CurrencyRatesEntity? = null
override fun size(): Flow<Int> = emptyFlow()
override suspend fun clear() {}
},
mContext = mContext,
)
fun String.formatTime(): ConverterResult.Time {
val unitFrom = NormalUnit(
id = "",
factor = basicValue,
group = UnitGroup.TIME,
displayName = 0,
shortName = 0,
backward = false,
)
return repo.convertTime(unitFrom, BigDecimal(this))
}
// Edgy things (minus, decimals and zeros)
assertEquals(
ConverterResult.Time(
negative = true,
attosecond = BigDecimal("28"),
),
"-28".formatTime(),
)
assertEquals(
ConverterResult.Time(
negative = true,
attosecond = BigDecimal("0.05"),
),
"-0.05".formatTime(),
)
assertEquals(
ConverterResult.Time(),
"0".formatTime(),
)
basicValue = BigDecimal("86400000000000000000000")
assertEquals(
ConverterResult.Time(
negative = true,
day = BigDecimal("28"),
),
"-28".formatTime(),
)
assertEquals(
ConverterResult.Time(
negative = true,
hour = BigDecimal("1"),
minute = BigDecimal("12"),
),
"-0.05".formatTime(),
)
assertEquals(
ConverterResult.Time(),
"0".formatTime(),
)
assertEquals(
ConverterResult.Time(),
"-0".formatTime(),
)
// DAYS
basicValue = BigDecimal("86400000000000000000000")
assertEquals(
ConverterResult.Time(
hour = BigDecimal("12"),
),
"0.5".formatTime(),
)
assertEquals(
ConverterResult.Time(
day = BigDecimal("90"),
minute = BigDecimal("7"),
second = BigDecimal("12"),
),
"90.005".formatTime(),
)
// HOURS
basicValue = BigDecimal("3600000000000000000000")
assertEquals(
ConverterResult.Time(
minute = BigDecimal("30"),
),
"0.5".formatTime(),
)
assertEquals(
ConverterResult.Time(
day = BigDecimal("3"),
hour = BigDecimal("18"),
second = BigDecimal("18"),
),
"90.005".formatTime(),
)
// MINUTES
basicValue = BigDecimal("60000000000000000000")
assertEquals(
ConverterResult.Time(
second = BigDecimal("30"),
),
"0.5".formatTime(),
)
assertEquals(
ConverterResult.Time(
hour = BigDecimal("1"),
minute = BigDecimal("30"),
millisecond = BigDecimal("300"),
),
"90.005".formatTime(),
)
// SECONDS
basicValue = BigDecimal("1000000000000000000")
assertEquals(
ConverterResult.Time(
millisecond = BigDecimal("500"),
),
"0.5".formatTime(),
)
assertEquals(
ConverterResult.Time(
minute = BigDecimal("1"),
second = BigDecimal("30"),
millisecond = BigDecimal("5"),
),
"90.005".formatTime(),
)
// MILLISECONDS
basicValue = BigDecimal("1000000000000000")
assertEquals(
ConverterResult.Time(
microsecond = BigDecimal("500"),
),
"0.5".formatTime(),
)
assertEquals(
ConverterResult.Time(
millisecond = BigDecimal("90"),
microsecond = BigDecimal("5"),
),
"90.005".formatTime(),
)
// MICROSECONDS
basicValue = BigDecimal("1000000000000")
assertEquals(
ConverterResult.Time(
nanosecond = BigDecimal("500"),
),
"0.5".formatTime(),
)
assertEquals(
ConverterResult.Time(
microsecond = BigDecimal("90"),
nanosecond = BigDecimal("5"),
),
"90.005".formatTime(),
)
// NANOSECONDS
basicValue = BigDecimal("1000000000")
assertEquals(
ConverterResult.Time(
nanosecond = BigDecimal("90"),
attosecond = BigDecimal("5000000"),
),
"90.005".formatTime(),
)
// ATTOSECONDS
basicValue = BigDecimal("1")
assertEquals(
ConverterResult.Time(
attosecond = BigDecimal("0.5"),
),
"0.5".formatTime(),
)
assertEquals(
ConverterResult.Time(
attosecond = BigDecimal("90.005"),
),
"90.005".formatTime(),
)
}
}

View File

@ -30,9 +30,15 @@ interface CurrencyRatesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRates(currencyRates: List<CurrencyRatesEntity>)
@Query("SELECT DISTINCT timestamp FROM currency_rates WHERE base_unit_id = :baseId")
suspend fun getLatestRateTimeStamp(baseId: String): Long?
@Query("SELECT DISTINCT * FROM currency_rates WHERE timestamp = (SELECT MAX(timestamp) FROM currency_rates) AND base_unit_id = :baseId")
suspend fun getLatestRates(baseId: String): List<CurrencyRatesEntity>
@Query("SELECT DISTINCT * FROM currency_rates WHERE timestamp = (SELECT MAX(timestamp) FROM currency_rates) AND base_unit_id = :baseId AND pair_unit_id = :pairId")
suspend fun getLatestRate(baseId: String, pairId: String): CurrencyRatesEntity?
@Query("SELECT COUNT(*) from currency_rates")
fun size(): Flow<Int>

View File

@ -30,7 +30,7 @@ import androidx.room.PrimaryKey
* @param frequency Show the amount of time this unit was used
*/
@Entity(tableName = "units")
class UnitsEntity(
data class UnitsEntity(
@PrimaryKey val unitId: String,
@ColumnInfo(name = "is_favorite") val isFavorite: Boolean = false,
@ColumnInfo(name = "paired_unit_id") val pairedUnitId: String? = null,

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model
package com.sadellie.unitto.data.model.converter
import androidx.annotation.StringRes
import com.sadellie.unitto.core.base.R

View File

@ -16,6 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model
package com.sadellie.unitto.data.model.converter
enum class UnitsListSorting { USAGE, ALPHABETICAL, SCALE_DESC, SCALE_ASC }

View File

@ -0,0 +1,47 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.converter.unit
import androidx.annotation.StringRes
import com.sadellie.unitto.data.model.converter.UnitGroup
import java.math.BigDecimal
sealed interface BasicUnit {
val id: String
val group: UnitGroup
@get:StringRes
val displayName: Int
@get:StringRes val shortName: Int
val factor: BigDecimal
interface NumberBase : BasicUnit {
fun convert(unitTo: NumberBase, value: String): String
}
interface Default : BasicUnit {
val backward: Boolean
fun convert(unitTo: Default, value: BigDecimal): BigDecimal
}
}

View File

@ -16,64 +16,56 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.unit
package com.sadellie.unitto.data.model.converter.unit
import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
import java.math.BigDecimal
import java.math.RoundingMode
data class BackwardUnit(
data class NormalUnit(
override val id: String,
override val basicUnit: BigDecimal,
override val factor: BigDecimal,
override val group: UnitGroup,
override val displayName: Int,
override val shortName: Int,
override val isFavorite: Boolean = false,
override val pairId: String? = null,
override val counter: Int = 0,
) : DefaultUnit {
override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal {
// Avoid division by zero
if (unitTo.basicUnit.isEqualTo(BigDecimal.ZERO)) return BigDecimal.ZERO
return when (unitTo) {
is NormalUnit ->
this
.basicUnit
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.div(unitTo.basicUnit)
.div(value)
is BackwardUnit ->
override val backward: Boolean = false,
) : BasicUnit.Default {
override fun convert(unitTo: BasicUnit.Default, value: BigDecimal): BigDecimal {
if (value.isEqualTo(BigDecimal.ZERO)) return BigDecimal.ZERO
return when {
// BACKWARD -> BACKWARD
backward and unitTo.backward ->
unitTo
.basicUnit
.factor
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.multiply(value)
.div(this.basicUnit)
.div(this.factor)
else -> BigDecimal.ZERO
// BACKWARD -> FORWARD
backward and !unitTo.backward ->
this
.factor
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.div(unitTo.factor)
.div(value)
// FORWARD -> BACKWARD
!backward and unitTo.backward ->
unitTo
.factor
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.div(this.factor)
.div(value)
// FORWARD -> FORWARD
else ->
this
.factor
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.multiply(value)
.div(unitTo.factor)
}
}
override fun clone(
id: String,
basicUnit: BigDecimal,
group: UnitGroup,
displayName: Int,
shortName: Int,
isFavorite: Boolean,
pairId: String?,
counter: Int,
): BackwardUnit = copy(
id = id,
basicUnit = basicUnit,
group = group,
displayName = displayName,
shortName = shortName,
isFavorite = isFavorite,
pairId = pairId,
counter = counter,
)
}

View File

@ -0,0 +1,34 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.converter.unit
import com.sadellie.unitto.data.model.converter.UnitGroup
import java.math.BigDecimal
data class NumberBaseUnit(
override val id: String,
override val factor: BigDecimal,
override val group: UnitGroup,
override val displayName: Int,
override val shortName: Int,
) : BasicUnit.NumberBase {
override fun convert(unitTo: BasicUnit.NumberBase, value: String): String = value
.toBigInteger(factor.intValueExact())
.toString(unitTo.factor.intValueExact())
}

View File

@ -1,50 +0,0 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.repository
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.unit.AbstractUnit
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate
interface UnitsRepository {
val units: Flow<List<AbstractUnit>>
suspend fun getById(id: String): AbstractUnit
suspend fun getCollection(group: UnitGroup): List<AbstractUnit>
suspend fun favorite(unit: AbstractUnit)
suspend fun incrementCounter(unit: AbstractUnit)
suspend fun setPair(unit: AbstractUnit, pair: AbstractUnit)
suspend fun updateRates(unit: AbstractUnit): LocalDate?
suspend fun filterUnits(
query: String,
unitGroup: UnitGroup?,
favoritesOnly: Boolean,
hideBrokenUnits: Boolean,
sorting: UnitsListSorting,
shownUnitGroups: List<UnitGroup> = emptyList(),
): Map<UnitGroup, List<AbstractUnit>>
}

View File

@ -18,9 +18,8 @@
package com.sadellie.unitto.data.model.repository
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
import com.sadellie.unitto.data.model.userprefs.AppPreferences
@ -55,7 +54,7 @@ interface UserPreferencesRepository {
suspend fun updateOutputFormat(outputFormat: Int)
suspend fun updateLatestPairOfUnits(unitFrom: AbstractUnit, unitTo: AbstractUnit)
suspend fun updateLatestPairOfUnits(unitFrom: String, unitTo: String)
suspend fun updateThemingMode(themingMode: ThemingMode)

View File

@ -1,79 +0,0 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.unit
import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal
import java.math.RoundingMode
data class NormalUnit(
override val id: String,
override val basicUnit: BigDecimal,
override val group: UnitGroup,
override val displayName: Int,
override val shortName: Int,
override val isFavorite: Boolean = false,
override val pairId: String? = null,
override val counter: Int = 0,
) : DefaultUnit {
override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal {
// Avoid division by zero
if (unitTo.basicUnit.isEqualTo(BigDecimal.ZERO)) return BigDecimal.ZERO
return when (unitTo) {
is NormalUnit ->
this
.basicUnit
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.multiply(value)
.div(unitTo.basicUnit)
is BackwardUnit ->
unitTo
.basicUnit
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.div(this.basicUnit)
.div(value)
else -> BigDecimal.ZERO
}
}
override fun clone(
id: String,
basicUnit: BigDecimal,
group: UnitGroup,
displayName: Int,
shortName: Int,
isFavorite: Boolean,
pairId: String?,
counter: Int,
): NormalUnit = copy(
id = id,
basicUnit = basicUnit,
group = group,
displayName = displayName,
shortName = shortName,
isFavorite = isFavorite,
pairId = pairId,
counter = counter,
)
}

View File

@ -1,57 +0,0 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.unit
import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal
data class NumberBaseUnit(
override val id: String,
override val basicUnit: BigDecimal,
override val group: UnitGroup,
override val displayName: Int,
override val shortName: Int,
override val isFavorite: Boolean = false,
override val pairId: String? = null,
override val counter: Int = 0,
) : AbstractUnit {
fun convert(toBase: NumberBaseUnit, input: String): String {
return input.toBigInteger(basicUnit.intValueExact()).toString(toBase.basicUnit.intValueExact())
}
override fun clone(
id: String,
basicUnit: BigDecimal,
group: UnitGroup,
displayName: Int,
shortName: Int,
isFavorite: Boolean,
pairId: String?,
counter: Int,
): NumberBaseUnit = copy(
id = id,
basicUnit = basicUnit,
group = group,
displayName = displayName,
shortName = shortName,
isFavorite = isFavorite,
pairId = pairId,
counter = counter,
)
}

View File

@ -1,63 +0,0 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.unit
import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal
import java.math.RoundingMode
data class ReverseUnit(
override val id: String,
override val basicUnit: BigDecimal,
override val group: UnitGroup,
override val displayName: Int,
override val shortName: Int,
override val isFavorite: Boolean = false,
override val pairId: String? = null,
override val counter: Int = 0,
) : DefaultUnit {
override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal {
return unitTo
.basicUnit
.setScale(MAX_PRECISION, RoundingMode.HALF_EVEN)
.div(this.basicUnit)
.multiply(value)
}
override fun clone(
id: String,
basicUnit: BigDecimal,
group: UnitGroup,
displayName: Int,
shortName: Int,
isFavorite: Boolean,
pairId: String?,
counter: Int,
): ReverseUnit = copy(
id = id,
basicUnit = basicUnit,
group = group,
displayName = displayName,
shortName = shortName,
isFavorite = isFavorite,
pairId = pairId,
counter = counter,
)
}

View File

@ -1,56 +0,0 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.data.model.unit
import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal
data class TemperatureUnit(
override val id: String,
override val basicUnit: BigDecimal,
override val group: UnitGroup,
override val displayName: Int,
override val shortName: Int,
override val isFavorite: Boolean = false,
override val pairId: String? = null,
override val counter: Int = 0,
val customConvert: (unitTo: DefaultUnit, value: BigDecimal) -> BigDecimal,
) : DefaultUnit {
override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal = customConvert(unitTo, value)
override fun clone(
id: String,
basicUnit: BigDecimal,
group: UnitGroup,
displayName: Int,
shortName: Int,
isFavorite: Boolean,
pairId: String?,
counter: Int,
): TemperatureUnit = copy(
id = id,
basicUnit = basicUnit,
group = group,
displayName = displayName,
shortName = shortName,
isFavorite = isFavorite,
pairId = pairId,
counter = counter,
)
}

View File

@ -19,8 +19,8 @@
package com.sadellie.unitto.data.model.userprefs
import com.sadellie.unitto.core.base.FormatterSymbols
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
interface ConverterPreferences {
val formatterSymbols: FormatterSymbols

View File

@ -18,7 +18,7 @@
package com.sadellie.unitto.data.model.userprefs
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
interface UnitGroupsPreferences {
val shownUnitGroups: List<UnitGroup>

View File

@ -24,8 +24,8 @@ import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import io.github.sadellie.themmo.core.MonetMode
import io.github.sadellie.themmo.core.ThemingMode

View File

@ -19,8 +19,8 @@
package com.sadellie.unitto.data.userprefs
import com.sadellie.unitto.core.base.FormatterSymbols
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
import com.sadellie.unitto.data.model.userprefs.AppPreferences

View File

@ -22,10 +22,9 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.userprefs.AboutPreferences
import com.sadellie.unitto.data.model.userprefs.AddSubtractPreferences
import com.sadellie.unitto.data.model.userprefs.AppPreferences
@ -182,10 +181,10 @@ class UserPreferencesRepositoryImpl @Inject constructor(
}
}
override suspend fun updateLatestPairOfUnits(unitFrom: AbstractUnit, unitTo: AbstractUnit) {
override suspend fun updateLatestPairOfUnits(unitFrom: String, unitTo: String) {
dataStore.edit { preferences ->
preferences[PrefsKeys.LATEST_LEFT_SIDE] = unitFrom.id
preferences[PrefsKeys.LATEST_RIGHT_SIDE] = unitTo.id
preferences[PrefsKeys.LATEST_LEFT_SIDE] = unitFrom
preferences[PrefsKeys.LATEST_RIGHT_SIDE] = unitTo
}
}

View File

@ -55,6 +55,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -83,24 +84,30 @@ import com.sadellie.unitto.core.ui.common.ScaffoldWithTopBar
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTextField
import com.sadellie.unitto.core.ui.common.textfield.NumberBaseTextField
import com.sadellie.unitto.core.ui.common.textfield.SimpleTextField
import com.sadellie.unitto.core.ui.common.textfield.addBracket
import com.sadellie.unitto.core.ui.common.textfield.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.core.ui.datetime.formatDateWeekDayMonthYear
import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.common.isExpression
import com.sadellie.unitto.data.converter.ConverterResult
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.feature.converter.components.DefaultKeyboard
import com.sadellie.unitto.feature.converter.components.NumberBaseKeyboard
import com.sadellie.unitto.feature.converter.components.UnitSelectionButton
import java.math.BigDecimal
import java.util.Locale
@Composable
internal fun ConverterRoute(
viewModel: ConverterViewModel = hiltViewModel(),
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
navigateToLeftScreen: (unitFromId: String, group: UnitGroup) -> Unit,
navigateToRightScreen: (unitFromId: String, unitToId: String, group: UnitGroup, input: String) -> Unit,
openDrawer: () -> Unit,
) {
val uiState = viewModel.converterUiState.collectAsStateWithLifecycle()
val uiState = viewModel.converterUIState.collectAsStateWithLifecycle()
ConverterScreen(
uiState = uiState.value,
@ -108,30 +115,24 @@ internal fun ConverterRoute(
navigateToRightScreen = navigateToRightScreen,
openDrawer = openDrawer,
swapUnits = viewModel::swapUnits,
processInput = viewModel::addTokens,
deleteDigit = viewModel::deleteTokens,
clearInput = viewModel::clearInput,
onValueChange = viewModel::updateInput,
onFocusOnInput2 = viewModel::updateFocused,
onErrorClick = viewModel::updateCurrencyRates,
addBracket = viewModel::addBracket,
updateInput1 = viewModel::updateInput1,
updateInput2 = viewModel::updateInput2,
convertDefault = viewModel::convertDefault,
convertNumberBase = viewModel::convertNumberBase,
)
}
@Composable
private fun ConverterScreen(
uiState: UnitConverterUIState,
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
navigateToLeftScreen: (unitFromId: String, group: UnitGroup) -> Unit,
navigateToRightScreen: (unitFromId: String, unitToId: String, group: UnitGroup, input: String) -> Unit,
openDrawer: () -> Unit,
swapUnits: () -> Unit,
processInput: (String) -> Unit,
deleteDigit: () -> Unit,
clearInput: () -> Unit,
onValueChange: (TextFieldValue) -> Unit,
onFocusOnInput2: (Boolean) -> Unit,
onErrorClick: (AbstractUnit) -> Unit,
addBracket: () -> Unit,
swapUnits: (String, String) -> Unit,
updateInput1: (TextFieldValue) -> Unit,
updateInput2: (TextFieldValue) -> Unit,
convertDefault: () -> Unit,
convertNumberBase: () -> Unit,
) {
when (uiState) {
UnitConverterUIState.Loading -> EmptyScreen()
@ -143,13 +144,11 @@ private fun ConverterScreen(
NumberBase(
modifier = Modifier.padding(it),
uiState = uiState,
onValueChange = onValueChange,
processInput = processInput,
deleteDigit = deleteDigit,
updateInput1 = updateInput1,
navigateToLeftScreen = navigateToLeftScreen,
swapUnits = swapUnits,
navigateToRightScreen = navigateToRightScreen,
clearInput = clearInput,
convert = convertNumberBase,
)
}
}
@ -161,16 +160,12 @@ private fun ConverterScreen(
Default(
modifier = Modifier.padding(it),
uiState = uiState,
onValueChange = onValueChange,
onFocusOnInput2 = onFocusOnInput2,
processInput = processInput,
deleteDigit = deleteDigit,
updateInput1 = updateInput1,
updateInput2 = updateInput2,
navigateToLeftScreen = navigateToLeftScreen,
swapUnits = swapUnits,
navigateToRightScreen = navigateToRightScreen,
clearInput = clearInput,
refreshCurrencyRates = onErrorClick,
addBracket = addBracket,
convert = convertDefault,
)
}
}
@ -194,14 +189,20 @@ private fun UnitConverterTopBar(
private fun NumberBase(
modifier: Modifier,
uiState: UnitConverterUIState.NumberBase,
onValueChange: (TextFieldValue) -> Unit,
processInput: (String) -> Unit,
deleteDigit: () -> Unit,
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
swapUnits: () -> Unit,
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
clearInput: () -> Unit,
updateInput1: (TextFieldValue) -> Unit,
navigateToLeftScreen: (unitFromId: String, group: UnitGroup) -> Unit,
swapUnits: (String, String) -> Unit,
navigateToRightScreen: (unitFromId: String, unitToId: String, group: UnitGroup, input: String) -> Unit,
convert: () -> Unit,
) {
LaunchedEffect(
uiState.input.text,
uiState.unitFrom.id,
uiState.unitTo.id,
) {
convert()
}
PortraitLandscape(
modifier = modifier.fillMaxSize(),
content1 = { contentModifier ->
@ -213,7 +214,7 @@ private fun NumberBase(
minRatio = 0.7f,
placeholder = Token.Digit._0,
value = uiState.input,
onValueChange = onValueChange,
onValueChange = updateInput1,
)
AnimatedUnitShortName(stringResource(uiState.unitFrom.shortName))
@ -228,18 +229,27 @@ private fun NumberBase(
UnitSelectionButtons(
unitFromLabel = stringResource(uiState.unitFrom.displayName),
unitToLabel = stringResource(uiState.unitTo.displayName),
swapUnits = swapUnits,
navigateToLeftScreen = { navigateToLeftScreen(uiState) },
navigateToRightScreen = { navigateToRightScreen(uiState) },
swapUnits = { swapUnits(uiState.unitTo.id, uiState.unitFrom.id) },
navigateToLeftScreen = {
navigateToLeftScreen(uiState.unitFrom.id, uiState.unitFrom.group)
},
navigateToRightScreen = {
navigateToRightScreen(
uiState.unitFrom.id,
uiState.unitTo.id,
uiState.unitFrom.group,
uiState.input.text,
)
},
)
}
},
content2 = {
content2 = { modifier2 ->
NumberBaseKeyboard(
modifier = it,
addDigit = processInput,
deleteDigit = deleteDigit,
clearInput = clearInput,
modifier = modifier2,
addDigit = { updateInput1(uiState.input.addTokens(it)) },
deleteDigit = { updateInput1(uiState.input.deleteTokens()) },
clearInput = { updateInput1(TextFieldValue()) },
)
},
)
@ -249,22 +259,21 @@ private fun NumberBase(
private fun Default(
modifier: Modifier,
uiState: UnitConverterUIState.Default,
onValueChange: (TextFieldValue) -> Unit,
onFocusOnInput2: (Boolean) -> Unit,
processInput: (String) -> Unit,
deleteDigit: () -> Unit,
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
swapUnits: () -> Unit,
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
clearInput: () -> Unit,
refreshCurrencyRates: (AbstractUnit) -> Unit,
addBracket: () -> Unit,
updateInput1: (TextFieldValue) -> Unit,
updateInput2: (TextFieldValue) -> Unit,
navigateToLeftScreen: (unitFromId: String, group: UnitGroup) -> Unit,
swapUnits: (String, String) -> Unit,
navigateToRightScreen: (unitFromId: String, unitToId: String, group: UnitGroup, input: String) -> Unit,
convert: () -> Unit,
) {
val locale: Locale = LocalLocale.current
var calculation by remember(uiState.calculation) {
mutableStateOf(
TextFieldValue(uiState.calculation?.format(uiState.scale, uiState.outputFormat) ?: ""),
)
val showCalculation = remember(uiState.input1.text, uiState.result) {
if (uiState.input1.text.isExpression()) {
if (uiState.result is ConverterResult.Default) {
return@remember !uiState.result.calculation.isEqualTo(BigDecimal.ZERO)
}
}
false
}
val connection by connectivityState()
val lastUpdate by remember(uiState.currencyRateUpdateState) {
@ -273,14 +282,25 @@ private fun Default(
uiState.currencyRateUpdateState.date.formatDateWeekDayMonthYear(locale)
}
}
var focusedOnInput1 by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(connection) {
if ((connection == ConnectionState.Available) and (uiState.result == ConverterResult.Error)) {
if ((connection == ConnectionState.Available) and (uiState.result is ConverterResult.Error)) {
val unitFrom = uiState.unitFrom
if (unitFrom.group == UnitGroup.CURRENCY) refreshCurrencyRates(unitFrom)
if (unitFrom.group == UnitGroup.CURRENCY) convert()
}
}
LaunchedEffect(
uiState.input1.text,
uiState.input2.text,
uiState.unitFrom.id,
uiState.unitTo.id,
uiState.formatTime,
) {
convert()
}
PortraitLandscape(
modifier = modifier.fillMaxSize(),
content1 = { contentModifier ->
@ -322,7 +342,7 @@ private fun Default(
.weight(1f),
value = uiState.input1,
minRatio = 0.7f,
onValueChange = onValueChange,
onValueChange = updateInput1,
formatterSymbols = uiState.formatterSymbols,
placeholder = Token.Digit._0,
)
@ -340,10 +360,10 @@ private fun Default(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.onFocusEvent { state -> onFocusOnInput2(state.hasFocus) },
.onFocusEvent { state -> focusedOnInput1 = !state.hasFocus },
value = uiState.input2,
minRatio = 0.7f,
onValueChange = onValueChange,
onValueChange = updateInput2,
formatterSymbols = uiState.formatterSymbols,
placeholder = Token.Digit._0,
)
@ -355,21 +375,30 @@ private fun Default(
modifier = textFieldModifier,
value = uiState.input1,
minRatio = 0.7f,
onValueChange = onValueChange,
onValueChange = updateInput1,
formatterSymbols = uiState.formatterSymbols,
placeholder = Token.Digit._0,
)
AnimatedVisibility(
visible = calculation.text.isNotEmpty(),
visible = showCalculation,
modifier = Modifier.weight(1f),
enter = expandVertically(clip = false),
exit = shrinkVertically(clip = false),
) {
var calculationTextField by remember(uiState.result) {
val text = if (uiState.result is ConverterResult.Default) {
uiState.result.calculation
.format(uiState.scale, uiState.outputFormat)
} else {
""
}
mutableStateOf(TextFieldValue(text))
}
ExpressionTextField(
modifier = Modifier,
value = calculation,
value = calculationTextField,
minRatio = 0.7f,
onValueChange = { calculation = it },
onValueChange = { calculationTextField = it },
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
formatterSymbols = uiState.formatterSymbols,
readOnly = true,
@ -384,14 +413,14 @@ private fun Default(
scale = uiState.scale,
outputFormat = uiState.outputFormat,
formatterSymbols = uiState.formatterSymbols,
onErrorClick = { refreshCurrencyRates(uiState.unitFrom) },
onErrorClick = convert,
)
AnimatedUnitShortName(
stringResource(
if (uiState.result is ConverterResult.Error) {
R.string.click_to_try_again_label
} else {
uiState.unitTo.shortName
when (uiState.result) {
// Currency conversion can be retried
is ConverterResult.Error.Currency -> R.string.click_to_try_again_label
else -> uiState.unitTo.shortName
},
),
)
@ -401,22 +430,58 @@ private fun Default(
UnitSelectionButtons(
unitFromLabel = stringResource(uiState.unitFrom.displayName),
unitToLabel = stringResource(uiState.unitTo.displayName),
swapUnits = swapUnits,
navigateToLeftScreen = { navigateToLeftScreen(uiState) },
navigateToRightScreen = { navigateToRightScreen(uiState) },
swapUnits = { swapUnits(uiState.unitTo.id, uiState.unitFrom.id) },
navigateToLeftScreen = {
navigateToLeftScreen(uiState.unitFrom.id, uiState.unitFrom.group)
},
navigateToRightScreen = {
val input = if (uiState.result is ConverterResult.Default) {
uiState.result.calculation.toPlainString()
} else {
uiState.input1.text
}
navigateToRightScreen(
uiState.unitFrom.id,
uiState.unitTo.id,
uiState.unitFrom.group,
input,
)
},
)
}
},
content2 = {
content2 = { modifier2 ->
DefaultKeyboard(
modifier = it,
addDigit = processInput,
deleteDigit = deleteDigit,
clearInput = clearInput,
modifier = modifier2,
addDigit = {
if (focusedOnInput1) {
updateInput1(uiState.input1.addTokens(it))
} else {
updateInput2(uiState.input2.addTokens(it))
}
},
deleteDigit = {
if (focusedOnInput1) {
updateInput1(uiState.input1.deleteTokens())
} else {
updateInput2(uiState.input2.deleteTokens())
}
},
clearInput = {
updateInput1(TextFieldValue())
updateInput2(TextFieldValue())
},
fractional = uiState.formatterSymbols.fractional,
middleZero = uiState.middleZero,
acButton = uiState.acButton,
addBracket = addBracket,
addBracket = {
if (focusedOnInput1) {
updateInput1(uiState.input1.addBracket())
} else {
updateInput2(uiState.input2.addBracket())
}
},
)
},
)
@ -454,6 +519,17 @@ private fun ConverterResultTextField(
)
}
is ConverterResult.Error.DivideByZero -> {
SimpleTextField(
modifier = modifier,
value = TextFieldValue(stringResource(R.string.calculator_divide_by_zero_error)),
onValueChange = { onErrorClick() },
minRatio = 0.7f,
readOnly = true,
textColor = MaterialTheme.colorScheme.error,
)
}
is ConverterResult.Error -> {
SimpleTextField(
modifier = modifier,
@ -578,16 +654,13 @@ private fun UnitSelectionButtons(
private fun PreviewConverterScreen() {
ConverterScreen(
uiState = UnitConverterUIState.Loading,
navigateToLeftScreen = {},
navigateToRightScreen = {},
navigateToLeftScreen = { _, _ -> },
navigateToRightScreen = { _, _, _, _ -> },
openDrawer = {},
swapUnits = {},
processInput = {},
deleteDigit = {},
clearInput = {},
onValueChange = {},
onFocusOnInput2 = {},
onErrorClick = {},
addBracket = {},
swapUnits = { _, _ -> },
updateInput1 = {},
updateInput2 = {},
convertDefault = {},
convertNumberBase = {},
)
}

View File

@ -25,14 +25,10 @@ import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.common.isGreaterThan
import com.sadellie.unitto.data.common.isLessThan
import com.sadellie.unitto.data.common.trimZeros
import com.sadellie.unitto.data.model.unit.DefaultUnit
import com.sadellie.unitto.data.model.unit.NumberBaseUnit
import com.sadellie.unitto.data.converter.ConverterResult
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import java.math.BigDecimal
import java.math.RoundingMode
internal sealed class UnitConverterUIState {
data object Loading : UnitConverterUIState()
@ -40,10 +36,9 @@ internal sealed class UnitConverterUIState {
data class Default(
val input1: TextFieldValue,
val input2: TextFieldValue,
val calculation: BigDecimal?,
val result: ConverterResult,
val unitFrom: DefaultUnit,
val unitTo: DefaultUnit,
val unitFrom: BasicUnit.Default,
val unitTo: BasicUnit.Default,
val middleZero: Boolean,
val formatterSymbols: FormatterSymbols,
val scale: Int,
@ -56,45 +51,11 @@ internal sealed class UnitConverterUIState {
data class NumberBase(
val input: TextFieldValue,
val result: ConverterResult,
val unitFrom: NumberBaseUnit,
val unitTo: NumberBaseUnit,
val unitFrom: BasicUnit.NumberBase,
val unitTo: BasicUnit.NumberBase,
) : UnitConverterUIState()
}
internal sealed class ConverterResult {
data class Default(val value: BigDecimal) : ConverterResult() {
override fun equals(other: Any?): Boolean {
if (other !is Default) return false
return this.value.isEqualTo(other.value)
}
override fun hashCode(): Int = value.hashCode()
}
data class NumberBase(val value: String) : ConverterResult()
data class Time(
val negative: Boolean,
val day: BigDecimal,
val hour: BigDecimal,
val minute: BigDecimal,
val second: BigDecimal,
val millisecond: BigDecimal,
val microsecond: BigDecimal,
val nanosecond: BigDecimal,
val attosecond: BigDecimal,
) : ConverterResult()
data class FootInch(
val foot: BigDecimal,
val inch: BigDecimal,
) : ConverterResult()
data object Loading : ConverterResult()
data object Error : ConverterResult()
}
internal fun ConverterResult.Time.format(
mContext: Context,
formatterSymbols: FormatterSymbols,
@ -154,110 +115,3 @@ internal fun ConverterResult.FootInch.format(
return result
}
internal fun formatTime(
input: BigDecimal,
): ConverterResult.Time {
val negative = input < BigDecimal.ZERO
val inputAbs = input.abs()
if (inputAbs.isLessThan(attosecondBasicUnit)) {
return ConverterResult.Time(
negative = negative,
day = BigDecimal.ZERO,
hour = BigDecimal.ZERO,
minute = BigDecimal.ZERO,
second = BigDecimal.ZERO,
millisecond = BigDecimal.ZERO,
microsecond = BigDecimal.ZERO,
nanosecond = BigDecimal.ZERO,
attosecond = inputAbs,
)
}
if (inputAbs.isLessThan(nanosecondBasicUnit)) {
return ConverterResult.Time(
negative = negative,
day = BigDecimal.ZERO,
hour = BigDecimal.ZERO,
minute = BigDecimal.ZERO,
second = BigDecimal.ZERO,
millisecond = BigDecimal.ZERO,
microsecond = BigDecimal.ZERO,
nanosecond = BigDecimal.ZERO,
attosecond = inputAbs.trimZeros(),
)
}
// DAY
var division = inputAbs.divideAndRemainder(dayBasicUnit)
val day = division.component1().setScale(0, RoundingMode.HALF_EVEN)
var remainingSeconds = division.component2().setScale(0, RoundingMode.HALF_EVEN)
division = remainingSeconds.divideAndRemainder(hourBasicUnit)
val hour = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(minuteBasicUnit)
val minute = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(secondBasicUnit)
val second = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(millisecondBasicUnit)
val millisecond = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(microsecondBasicUnit)
val microsecond = division.component1()
remainingSeconds = division.component2()
division = remainingSeconds.divideAndRemainder(nanosecondBasicUnit)
val nanosecond = division.component1()
remainingSeconds = division.component2()
val attosecond = remainingSeconds
return ConverterResult.Time(
negative = negative,
day = day,
hour = hour,
minute = minute,
second = second,
millisecond = millisecond,
microsecond = microsecond,
nanosecond = nanosecond,
attosecond = attosecond,
)
}
/**
* Creates an object for displaying formatted foot and inch output. Units are passed as objects so
* that changes in basic units don't require modifying the method. Also this method can't access
* units repository directly.
*
* @param input Input in feet.
* @param footUnit Foot unit [DefaultUnit].
* @param inchUnit Inch unit [DefaultUnit].
* @return Result where decimal places are converter into inches.
*/
internal fun formatFootInch(
input: BigDecimal,
footUnit: DefaultUnit,
inchUnit: DefaultUnit,
): ConverterResult.FootInch {
val (integral, fractional) = input.divideAndRemainder(BigDecimal.ONE)
return ConverterResult.FootInch(integral, footUnit.convert(inchUnit, fractional))
}
private val dayBasicUnit by lazy { BigDecimal("86400000000000000000000") }
private val hourBasicUnit by lazy { BigDecimal("3600000000000000000000") }
private val minuteBasicUnit by lazy { BigDecimal("60000000000000000000") }
private val secondBasicUnit by lazy { BigDecimal("1000000000000000000") }
private val millisecondBasicUnit by lazy { BigDecimal("1000000000000000") }
private val microsecondBasicUnit by lazy { BigDecimal("1000000000000") }
private val nanosecondBasicUnit by lazy { BigDecimal("1000000000") }
private val attosecondBasicUnit by lazy { BigDecimal("1") }

View File

@ -22,365 +22,210 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.addBracket
import com.sadellie.unitto.core.ui.common.textfield.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.ui.common.textfield.getTextField
import com.sadellie.unitto.data.common.combine
import com.sadellie.unitto.data.common.isExpression
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.repository.UnitsRepository
import com.sadellie.unitto.data.converter.ConverterResult
import com.sadellie.unitto.data.converter.UnitsRepositoryImpl
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.DefaultUnit
import com.sadellie.unitto.data.model.unit.NumberBaseUnit
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.evaluatto.Expression
import io.github.sadellie.evaluatto.ExpressionException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
import javax.inject.Inject
@HiltViewModel
internal class ConverterViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
private val unitsRepo: UnitsRepository,
private val unitsRepo: UnitsRepositoryImpl,
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
private var conversionJob: Job? = null
private val converterInputKey1 = "CONVERTER_INPUT_1"
private val converterInputKey2 = "CONVERTER_INPUT_2"
private val input1 = MutableStateFlow(savedStateHandle.getTextField(converterInputKey1))
private val input2 = MutableStateFlow(savedStateHandle.getTextField(converterInputKey2))
private val focusedOnInput2 = MutableStateFlow(false)
private val calculation = MutableStateFlow<BigDecimal?>(null)
private val result = MutableStateFlow<ConverterResult>(ConverterResult.Loading)
private val unitFrom = MutableStateFlow<AbstractUnit?>(null)
private val unitTo = MutableStateFlow<AbstractUnit?>(null)
private val output = MutableStateFlow<ConverterResult>(ConverterResult.Loading)
private val currenciesState = MutableStateFlow<CurrencyRateUpdateState>(CurrencyRateUpdateState.Nothing)
private var loadCurrenciesJob: Job? = null
private val unitFromId = MutableStateFlow<String?>(null)
private val unitToId = MutableStateFlow<String?>(null)
val converterUiState: StateFlow<UnitConverterUIState> = combine(
private val unitFrom = unitFromId
.mapLatest { it?.let { unitsRepo.getById(it) } }
.flowOn(Dispatchers.Default)
.stateIn(viewModelScope, null)
private val unitTo = unitToId
.mapLatest { it?.let { unitsRepo.getById(it) } }
.flowOn(Dispatchers.Default)
.stateIn(viewModelScope, null)
val converterUIState: StateFlow<UnitConverterUIState> = combine(
input1,
input2,
calculation,
result,
output,
unitFrom,
unitTo,
userPrefsRepository.converterPrefs,
currenciesState,
) { input1, input2, calculation, result, unitFrom, unitTo, prefs, currenciesState ->
return@combine when {
(unitFrom is DefaultUnit) and (unitTo is DefaultUnit) -> {
UnitConverterUIState.Default(
input1 = input1,
input2 = input2,
calculation = calculation,
result = result,
unitFrom = unitFrom as DefaultUnit,
unitTo = unitTo as DefaultUnit,
middleZero = prefs.middleZero,
formatterSymbols = prefs.formatterSymbols,
scale = prefs.precision,
outputFormat = prefs.outputFormat,
formatTime = prefs.unitConverterFormatTime,
currencyRateUpdateState = currenciesState,
acButton = prefs.acButton,
)
}
(unitFrom is NumberBaseUnit) and (unitTo is NumberBaseUnit) -> {
UnitConverterUIState.NumberBase(
input = input1,
result = result,
unitFrom = unitFrom as NumberBaseUnit,
unitTo = unitTo as NumberBaseUnit,
)
}
else -> UnitConverterUIState.Loading
) { input1Value, input2Value, outputValue, unitFromValue, unitToValue, prefs ->
if (unitFromValue == null) return@combine UnitConverterUIState.Loading
if (unitToValue == null) return@combine UnitConverterUIState.Loading
whenBothAre<BasicUnit.Default>(
value1 = unitFromValue,
value2 = unitToValue,
) { unitFrom, unitTo ->
return@combine UnitConverterUIState.Default(
input1 = input1Value,
input2 = input2Value,
result = outputValue,
unitFrom = unitFrom,
unitTo = unitTo,
middleZero = prefs.middleZero,
formatterSymbols = prefs.formatterSymbols,
scale = prefs.precision,
outputFormat = OutputFormat.PLAIN,
formatTime = prefs.unitConverterFormatTime,
currencyRateUpdateState = CurrencyRateUpdateState.Nothing,
acButton = prefs.acButton,
)
}
whenBothAre<BasicUnit.NumberBase>(
value1 = unitFromValue,
value2 = unitToValue,
) { unitFrom, unitTo ->
return@combine UnitConverterUIState.NumberBase(
input = input1Value,
result = outputValue,
unitFrom = unitFrom,
unitTo = unitTo,
)
}
return@combine UnitConverterUIState.Loading
}
.mapLatest { ui ->
when (currenciesState.value) {
is CurrencyRateUpdateState.Loading -> {
result.update { ConverterResult.Loading }
return@mapLatest ui
}
is CurrencyRateUpdateState.Error -> {
result.update { ConverterResult.Error }
return@mapLatest ui
}
is CurrencyRateUpdateState.Ready, is CurrencyRateUpdateState.Nothing -> {}
}
when (ui) {
is UnitConverterUIState.Default -> {
convertDefault(
unitFrom = ui.unitFrom,
unitTo = ui.unitTo,
input1 = ui.input1,
input2 = ui.input2,
formatTime = ui.formatTime,
)
}
is UnitConverterUIState.NumberBase -> {
convertNumberBase(
unitFrom = ui.unitFrom,
unitTo = ui.unitTo,
input = ui.input,
)
}
is UnitConverterUIState.Loading -> {}
}
ui
}
.stateIn(viewModelScope, UnitConverterUIState.Loading)
fun swapUnits() {
unitFrom
.getAndUpdate { unitTo.value }
.also { oldUnitFrom -> unitTo.update { oldUnitFrom } }
fun updateInput1(value: TextFieldValue) {
input1.update { value }
savedStateHandle[converterInputKey1] = value.text
}
loadCurrenciesJob?.cancel()
currenciesState.update { CurrencyRateUpdateState.Nothing }
unitFrom.value?.let {
if (it.group == UnitGroup.CURRENCY) updateCurrencyRates(it)
fun updateInput2(value: TextFieldValue) {
input2.update { value }
savedStateHandle[converterInputKey2] = value.text
}
fun updateUnitFromId(id: String) = viewModelScope.launch {
val pairId = unitsRepo.getPairId(id)
unitFromId.update { id }
unitToId.update { pairId }
unitsRepo.incrementCounter(id)
updateLatestPairOfUnits()
}
fun updateUnitToId(id: String) = viewModelScope.launch {
unitToId.update { id }
unitsRepo.incrementCounter(id)
setPair()
updateLatestPairOfUnits()
}
fun swapUnits(
newUnitFromId: String,
newInputToId: String,
) = viewModelScope.launch {
unitFromId.update { newUnitFromId }
unitToId.update { newInputToId }
setPair()
updateLatestPairOfUnits()
}
fun convertDefault() {
conversionJob?.cancel()
conversionJob = viewModelScope.launch {
val result = unitsRepo.convertDefault(
unitFromId = unitFromId.value ?: return@launch,
unitToId = unitToId.value ?: return@launch,
value1 = input1.value.text,
value2 = input2.value.text,
formatTime = userPrefsRepository.converterPrefs.first().unitConverterFormatTime,
)
when (result) {
is ConverterResult.Error.BadInput -> Unit
else -> output.update { result }
}
}
}
viewModelScope.launch {
val unitTo = unitTo.value ?: return@launch
val unitFrom = unitFrom.value ?: return@launch
userPrefsRepository.updateLatestPairOfUnits(unitFrom = unitFrom, unitTo = unitTo)
fun convertNumberBase() {
conversionJob?.cancel()
conversionJob = viewModelScope.launch {
unitsRepo.convertNumberBase(
unitFromId = unitFromId.value ?: return@launch,
unitToId = unitToId.value ?: return@launch,
value = input1.value.text,
)
}
}
private fun loadInitialUnits() = viewModelScope.launch {
val prefs = userPrefsRepository.converterPrefs.first()
unitFromId.update { prefs.latestLeftSideUnit }
unitToId.update { prefs.latestRightSideUnit }
}
private fun setPair() = viewModelScope.launch {
unitsRepo.setPair(
id = unitFromId.value ?: return@launch,
pairId = unitToId.value ?: return@launch,
)
}
private fun updateLatestPairOfUnits() = viewModelScope.launch {
userPrefsRepository
.updateLatestPairOfUnits(
unitFrom = unitFromId.value ?: return@launch,
unitTo = unitToId.value ?: return@launch,
)
}
/**
* Change currently focused text field. For feet and inches input
* Will call [block] if both [value1] and [value2] are [T].
*
* @param focusOnInput2 `true` if focus is on inches input. `false`if focus on feet input.
* @param T What to check against.
* @param value1 Value to check.
* @param value2 Value to check.
* @param block Block that will be called if the has passed.
*/
fun updateFocused(focusOnInput2: Boolean) = focusedOnInput2.update { focusOnInput2 }
fun addTokens(tokens: String) {
if (focusedOnInput2.value) {
input2.update {
val newValue = it.addTokens(tokens)
savedStateHandle[converterInputKey2] = newValue.text
newValue
}
} else {
input1.update {
val newValue = it.addTokens(tokens)
savedStateHandle[converterInputKey1] = newValue.text
newValue
}
}
}
fun addBracket() {
if (focusedOnInput2.value) {
input2.update {
val newValue = it.addBracket()
savedStateHandle[converterInputKey2] = newValue.text
newValue
}
} else {
input1.update {
val newValue = it.addBracket()
savedStateHandle[converterInputKey1] = newValue.text
newValue
}
}
}
fun deleteTokens() {
if (focusedOnInput2.value) {
input2.update {
val newValue = it.deleteTokens()
savedStateHandle[converterInputKey2] = newValue.text
newValue
}
} else {
input1.update {
val newValue = it.deleteTokens()
savedStateHandle[converterInputKey1] = newValue.text
newValue
}
}
}
fun clearInput() {
input1.update {
savedStateHandle[converterInputKey1] = ""
TextFieldValue()
}
input2.update {
savedStateHandle[converterInputKey2] = ""
TextFieldValue()
}
}
fun updateInput(value: TextFieldValue) = input1.update { value }
fun updateCurrencyRates(unit: AbstractUnit) {
loadCurrenciesJob = viewModelScope.launch(Dispatchers.IO) {
try {
currenciesState.update { CurrencyRateUpdateState.Loading }
val updateDate = unitsRepo.updateRates(unit) ?: throw Exception("Empty cache")
// Set to fresh objects with updated basic unit values
unitFrom.update { unitFrom -> unitFrom?.id?.let { unitsRepo.getById(it) } }
unitTo.update { unitTo -> unitTo?.id?.let { unitsRepo.getById(it) } }
currenciesState.update { CurrencyRateUpdateState.Ready(updateDate) }
} catch (e: Exception) {
currenciesState.update { CurrencyRateUpdateState.Error }
}
}
}
fun updateUnitFrom(unit: AbstractUnit) = viewModelScope.launch {
val pairId = unit.pairId
val pair: AbstractUnit = if (pairId == null) {
val collection = unitsRepo.getCollection(unit.group).sortedByDescending { it.counter }
collection.firstOrNull { it.isFavorite } ?: collection.first()
} else {
unitsRepo.getById(pairId)
}
withContext(Dispatchers.Default) {
unitsRepo.incrementCounter(unit)
userPrefsRepository.updateLatestPairOfUnits(unitFrom = unit, unitTo = pair)
}
loadCurrenciesJob?.cancel()
currenciesState.update { CurrencyRateUpdateState.Nothing }
if (unit.group == UnitGroup.CURRENCY) updateCurrencyRates(unit)
unitFrom.update {
// We change from something to base converter or the other way around
if ((it?.group == UnitGroup.NUMBER_BASE) xor (unit.group == UnitGroup.NUMBER_BASE)) {
clearInput()
}
unit
}
unitTo.update { pair }
}
fun updateUnitTo(unit: AbstractUnit) {
unitTo.update { unit }
viewModelScope.launch {
val unitTo = unitTo.value ?: return@launch
val unitFrom = unitFrom.value ?: return@launch
unitsRepo.incrementCounter(unitTo)
unitsRepo.setPair(unitFrom, unitTo)
userPrefsRepository.updateLatestPairOfUnits(unitFrom = unitFrom, unitTo = unitTo)
}
}
private fun convertDefault(
unitFrom: DefaultUnit,
unitTo: DefaultUnit,
input1: TextFieldValue,
input2: TextFieldValue,
formatTime: Boolean,
) = viewModelScope.launch(Dispatchers.Default) {
val footInchInput = unitFrom.id == UnitID.foot
if (footInchInput) {
calculation.update { null }
}
// Calculate
val calculated1 = try {
Expression(input1.text.ifEmpty { Token.Digit._0 }).calculate()
} catch (e: ExpressionException.DivideByZero) {
calculation.update { null }
return@launch
} catch (e: Exception) {
return@launch
}
// Update calculation
calculation.update { if (input1.text.isExpression()) calculated1 else null }
// Convert
val result: ConverterResult = try {
var conversion = unitFrom.convert(unitTo, calculated1)
if (footInchInput) {
// Converted from second text field too
val inches = unitsRepo.getById(UnitID.inch) as DefaultUnit
val calculated2 = try {
Expression(input2.text.ifEmpty { Token.Digit._0 }).calculate()
} catch (e: ExpressionException.DivideByZero) {
calculation.update { null }
return@launch
} catch (e: Exception) {
return@launch
}
conversion += inches.convert(unitTo, calculated2)
}
when {
(unitFrom.group == UnitGroup.TIME) and (formatTime) -> formatTime(calculated1.multiply(unitFrom.basicUnit))
unitTo.id == UnitID.foot -> formatFootInch(conversion, unitTo, unitsRepo.getById(UnitID.inch) as DefaultUnit)
else -> ConverterResult.Default(conversion)
}
} catch (e: Exception) {
ConverterResult.Default(BigDecimal.ZERO)
}
// Update result
this@ConverterViewModel.result.update { result }
}
private fun convertNumberBase(
unitFrom: NumberBaseUnit,
unitTo: NumberBaseUnit,
input: TextFieldValue,
) = viewModelScope.launch(Dispatchers.Default) {
val conversion = try {
unitFrom.convert(unitTo, input.text.ifEmpty { Token.Digit._0 })
} catch (e: Exception) {
""
}
result.update { ConverterResult.NumberBase(conversion) }
private inline fun <reified T> whenBothAre(
value1: Any?,
value2: Any?,
block: (v1: T, v2: T) -> Unit,
) {
if ((value1 is T) and (value2 is T)) block(value1 as T, value2 as T)
}
init {
viewModelScope.launch(Dispatchers.Default) {
try {
val userPrefs = userPrefsRepository.converterPrefs.first()
val unitFrom = unitsRepo.getById(userPrefs.latestLeftSideUnit)
val unitTo = unitsRepo.getById(userPrefs.latestRightSideUnit)
loadInitialUnits()
}
this@ConverterViewModel.unitFrom.update { unitFrom }
this@ConverterViewModel.unitTo.update { unitTo }
if (unitFrom.group == UnitGroup.CURRENCY) updateCurrencyRates(unitFrom)
} catch (e: NoSuchElementException) {
val unitFrom = unitsRepo.getById(UnitID.kilometer)
val unitTo = unitsRepo.getById(UnitID.mile)
this@ConverterViewModel.unitFrom.update { unitFrom }
this@ConverterViewModel.unitTo.update { unitTo }
}
}
override fun onCleared() {
super.onCleared()
viewModelScope.cancel()
}
}

View File

@ -22,12 +22,10 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@ -39,10 +37,11 @@ import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.EmptyScreen
import com.sadellie.unitto.core.ui.common.SearchBar
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.converter.UnitSearchResultItem
import com.sadellie.unitto.data.database.UnitsEntity
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import com.sadellie.unitto.feature.converter.components.ChipsRow
import com.sadellie.unitto.feature.converter.components.FavoritesButton
import com.sadellie.unitto.feature.converter.components.UnitsList
@ -50,7 +49,7 @@ import java.math.BigDecimal
@Composable
internal fun UnitFromSelectorRoute(
unitSelectorViewModel: UnitSelectorViewModel,
unitSelectorViewModel: UnitFromSelectorViewModel,
converterViewModel: ConverterViewModel,
navigateUp: () -> Unit,
navigateToUnitGroups: () -> Unit,
@ -62,7 +61,7 @@ internal fun UnitFromSelectorRoute(
uiState = uiState,
onQueryChange = unitSelectorViewModel::updateSelectorQuery,
toggleFavoritesOnly = unitSelectorViewModel::updateShowFavoritesOnly,
updateUnitFrom = converterViewModel::updateUnitFrom,
updateUnitFrom = converterViewModel::updateUnitFromId,
updateUnitGroup = unitSelectorViewModel::updateSelectedUnitGroup,
favoriteUnit = unitSelectorViewModel::favoriteUnit,
navigateUp = navigateUp,
@ -77,25 +76,14 @@ private fun UnitFromSelectorScreen(
uiState: UnitSelectorUIState.UnitFrom,
onQueryChange: (TextFieldValue) -> Unit,
toggleFavoritesOnly: (Boolean) -> Unit,
updateUnitFrom: (AbstractUnit) -> Unit,
updateUnitFrom: (String) -> Unit,
updateUnitGroup: (UnitGroup?) -> Unit,
favoriteUnit: (AbstractUnit) -> Unit,
favoriteUnit: (UnitSearchResultItem) -> Unit,
navigateUp: () -> Unit,
navigateToUnitGroups: () -> Unit,
) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
val chipsRowLazyListState = rememberLazyListState()
LaunchedEffect(uiState.unitFrom, uiState.shownUnitGroups) {
kotlin.runCatching {
val groupToSelect = uiState.shownUnitGroups.indexOf(uiState.unitFrom.group)
if (groupToSelect > -1) {
chipsRowLazyListState.scrollToItem(groupToSelect)
}
}
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
@ -127,15 +115,16 @@ private fun UnitFromSelectorScreen(
},
) { paddingValues ->
val resources = LocalContext.current.resources
UnitsList(
modifier = Modifier.padding(paddingValues),
searchResult = uiState.units,
navigateToUnitGroups = navigateToUnitGroups,
currentUnitId = uiState.unitFrom.id,
supportLabel = { resources.getString(it.shortName) },
currentUnitId = uiState.unitFromId,
supportLabel = { resources.getString(it.basicUnit.shortName) },
onClick = {
onQueryChange(TextFieldValue())
updateUnitFrom(it)
updateUnitFrom(it.basicUnit.id)
navigateUp()
},
favoriteUnit = { favoriteUnit(it) },
@ -146,7 +135,7 @@ private fun UnitFromSelectorScreen(
@Preview
@Composable
private fun UnitFromSelectorScreenPreview() {
val units: Map<UnitGroup, List<AbstractUnit>> = mapOf(
val units: Map<UnitGroup, List<UnitSearchResultItem>> = mapOf(
UnitGroup.LENGTH to listOf(
NormalUnit(UnitID.meter, BigDecimal("1000000000000000000"), UnitGroup.LENGTH, R.string.unit_meter, R.string.unit_meter_short),
NormalUnit(UnitID.kilometer, BigDecimal("1000000000000000000000"), UnitGroup.LENGTH, R.string.unit_kilometer, R.string.unit_kilometer_short),
@ -155,14 +144,15 @@ private fun UnitFromSelectorScreenPreview() {
NormalUnit(UnitID.foot, BigDecimal("304800000000000000"), UnitGroup.LENGTH, R.string.unit_foot, R.string.unit_foot_short),
NormalUnit(UnitID.yard, BigDecimal("914400000000000000"), UnitGroup.LENGTH, R.string.unit_yard, R.string.unit_yard_short),
NormalUnit(UnitID.mile, BigDecimal("1609344000000000000000"), UnitGroup.LENGTH, R.string.unit_mile, R.string.unit_mile_short),
),
)
.map { UnitSearchResultItem(it, UnitsEntity(unitId = it.id), null) },
)
UnitFromSelectorScreen(
uiState = UnitSelectorUIState.UnitFrom(
unitFrom = units.values.first().first(),
unitFromId = UnitID.kilometer,
query = TextFieldValue("test"),
units = UnitSearchResult.Success(units),
units = units,
selectedUnitGroup = UnitGroup.SPEED,
shownUnitGroups = UnitGroup.entries,
showFavoritesOnly = false,

View File

@ -0,0 +1,124 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.converter
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.converter.UnitSearchResultItem
import com.sadellie.unitto.data.converter.UnitsRepositoryImpl
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import com.sadellie.unitto.feature.converter.navigation.UNIT_FROM_ID_ARG
import com.sadellie.unitto.feature.converter.navigation.UNIT_GROUP_ARG
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
internal class UnitFromSelectorViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
private val unitsRepo: UnitsRepositoryImpl,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private var searchJob: Job? = null
private val query = MutableStateFlow(TextFieldValue())
private val searchResults = MutableStateFlow<Map<UnitGroup, List<UnitSearchResultItem>>?>(null)
private val selectedUnitGroup = MutableStateFlow(savedStateHandle.get<UnitGroup>(UNIT_GROUP_ARG))
private val unitFromId = savedStateHandle.get<String>(UNIT_FROM_ID_ARG)
val unitFromUIState: StateFlow<UnitSelectorUIState> = combine(
query,
searchResults,
selectedUnitGroup,
userPrefsRepository.converterPrefs,
) { query, searchResults, selectedUnitGroup, prefs ->
if (searchResults == null) return@combine UnitSelectorUIState.Loading
return@combine UnitSelectorUIState.UnitFrom(
query = query,
unitFromId = unitFromId ?: "",
shownUnitGroups = prefs.shownUnitGroups,
showFavoritesOnly = prefs.unitConverterFavoritesOnly,
units = searchResults,
selectedUnitGroup = selectedUnitGroup,
sorting = prefs.unitConverterSorting,
)
}
.stateIn(viewModelScope, UnitSelectorUIState.Loading)
fun updateSelectorQuery(value: TextFieldValue) {
query.update { value }
onSearch()
}
fun updateShowFavoritesOnly(value: Boolean) = viewModelScope.launch {
userPrefsRepository.updateUnitConverterFavoritesOnly(value)
onSearch()
}
fun updateSelectedUnitGroup(value: UnitGroup?) {
selectedUnitGroup.update { value }
onSearch()
}
fun favoriteUnit(unit: UnitSearchResultItem) = viewModelScope.launch {
unitsRepo.favorite(unit.basicUnit.id)
onSearch()
}
private fun onSearch() {
searchJob?.cancel()
searchJob = viewModelScope.launch {
val prefs = userPrefsRepository.converterPrefs.first()
val selectedGroupValue = selectedUnitGroup.value
val result = unitsRepo.filterUnits(
query = query.value.text,
favoritesOnly = prefs.unitConverterFavoritesOnly,
sorting = prefs.unitConverterSorting,
unitGroups = if (selectedGroupValue == null) {
prefs.shownUnitGroups
} else {
listOf(selectedGroupValue)
},
)
searchResults.update { result }
}
}
init {
onSearch()
}
override fun onCleared() {
super.onCleared()
viewModelScope.cancel()
}
}

View File

@ -20,29 +20,30 @@ package com.sadellie.unitto.feature.converter
import androidx.compose.ui.text.input.TextFieldValue
import com.sadellie.unitto.core.base.FormatterSymbols
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.converter.UnitSearchResultItem
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.converter.unit.BasicUnit
internal sealed class UnitSelectorUIState {
data object Loading : UnitSelectorUIState()
data class UnitFrom(
val query: TextFieldValue,
val unitFrom: AbstractUnit,
val unitFromId: String,
val shownUnitGroups: List<UnitGroup>,
val showFavoritesOnly: Boolean,
val units: UnitSearchResult,
val units: Map<UnitGroup, List<UnitSearchResultItem>>,
val selectedUnitGroup: UnitGroup?,
val sorting: UnitsListSorting,
) : UnitSelectorUIState()
data class UnitTo(
val query: TextFieldValue,
val unitFrom: AbstractUnit,
val unitTo: AbstractUnit,
val unitFrom: BasicUnit,
val unitTo: BasicUnit,
val showFavoritesOnly: Boolean,
val units: UnitSearchResult,
val units: Map<UnitGroup, List<UnitSearchResultItem>>,
val input: String?,
val sorting: UnitsListSorting,
val scale: Int,
@ -50,13 +51,3 @@ internal sealed class UnitSelectorUIState {
val formatterSymbols: FormatterSymbols,
) : UnitSelectorUIState()
}
internal sealed class UnitSearchResult {
data object Empty : UnitSearchResult()
data object Loading : UnitSearchResult()
data class Success(
val units: Map<UnitGroup, List<AbstractUnit>>,
) : UnitSearchResult()
}

View File

@ -36,20 +36,21 @@ import com.sadellie.unitto.core.ui.common.EmptyScreen
import com.sadellie.unitto.core.ui.common.SearchBar
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.converter.DefaultBatchConvertResult
import com.sadellie.unitto.data.converter.NumberBaseBatchConvertResult
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.DefaultUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.data.model.unit.NumberBaseUnit
import com.sadellie.unitto.data.converter.UnitSearchResultItem
import com.sadellie.unitto.data.database.UnitsEntity
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import com.sadellie.unitto.feature.converter.components.FavoritesButton
import com.sadellie.unitto.feature.converter.components.UnitsList
import java.math.BigDecimal
@Composable
internal fun UnitToSelectorRoute(
unitSelectorViewModel: UnitSelectorViewModel,
unitSelectorViewModel: UnitToSelectorViewModel,
converterViewModel: ConverterViewModel,
navigateUp: () -> Unit,
navigateToUnitGroups: () -> Unit,
@ -61,7 +62,7 @@ internal fun UnitToSelectorRoute(
uiState = uiState,
onQueryChange = unitSelectorViewModel::updateSelectorQuery,
toggleFavoritesOnly = unitSelectorViewModel::updateShowFavoritesOnly,
updateUnitTo = converterViewModel::updateUnitTo,
updateUnitTo = converterViewModel::updateUnitToId,
favoriteUnit = unitSelectorViewModel::favoriteUnit,
navigateUp = navigateUp,
navigateToUnitGroups = navigateToUnitGroups,
@ -75,8 +76,8 @@ private fun UnitToSelectorScreen(
uiState: UnitSelectorUIState.UnitTo,
onQueryChange: (TextFieldValue) -> Unit,
toggleFavoritesOnly: (Boolean) -> Unit,
updateUnitTo: (AbstractUnit) -> Unit,
favoriteUnit: (AbstractUnit) -> Unit,
updateUnitTo: (String) -> Unit,
favoriteUnit: (UnitSearchResultItem) -> Unit,
navigateUp: () -> Unit,
navigateToUnitGroups: () -> Unit,
) {
@ -105,19 +106,25 @@ private fun UnitToSelectorScreen(
navigateToUnitGroups = navigateToUnitGroups,
currentUnitId = uiState.unitTo.id,
supportLabel = {
formatUnitToSupportLabel(
unitFrom = uiState.unitFrom,
unitTo = it,
input = uiState.input,
shortName = resources.getString(it.shortName),
scale = uiState.scale,
outputFormat = uiState.outputFormat,
formatterSymbols = uiState.formatterSymbols,
)
val label = resources.getString(it.basicUnit.shortName)
when (val conversion = it.conversion) {
is DefaultBatchConvertResult -> {
val formattedConversion = conversion.value
.format(uiState.scale, uiState.outputFormat)
.formatExpression(uiState.formatterSymbols)
"$formattedConversion $label"
}
is NumberBaseBatchConvertResult -> {
"${conversion.value} $label"
}
else -> label
}
},
onClick = {
onQueryChange(TextFieldValue())
updateUnitTo(it)
updateUnitTo(it.basicUnit.id)
navigateUp()
},
favoriteUnit = { favoriteUnit(it) },
@ -125,49 +132,10 @@ private fun UnitToSelectorScreen(
}
}
private fun formatUnitToSupportLabel(
unitFrom: AbstractUnit?,
unitTo: AbstractUnit?,
input: String?,
shortName: String,
scale: Int,
outputFormat: Int,
formatterSymbols: FormatterSymbols,
): String {
if (input.isNullOrEmpty()) return shortName
try {
if ((unitFrom is DefaultUnit) and (unitTo is DefaultUnit)) {
unitFrom as DefaultUnit
unitTo as DefaultUnit
val conversion = unitFrom
.convert(unitTo, BigDecimal(input))
.format(scale, outputFormat)
.formatExpression(formatterSymbols)
return "$conversion $shortName"
}
if ((unitFrom is NumberBaseUnit) and (unitTo is NumberBaseUnit)) {
unitFrom as NumberBaseUnit
unitTo as NumberBaseUnit
val conversion = unitFrom.convert(unitTo, input).uppercase()
return "$conversion $shortName"
}
} catch (e: Exception) {
return shortName
}
return shortName
}
@Preview
@Composable
private fun UnitToSelectorPreview() {
val units: Map<UnitGroup, List<AbstractUnit>> = mapOf(
val units: Map<UnitGroup, List<UnitSearchResultItem>> = mapOf(
UnitGroup.LENGTH to listOf(
NormalUnit(UnitID.meter, BigDecimal("1000000000000000000"), UnitGroup.LENGTH, R.string.unit_meter, R.string.unit_meter_short),
NormalUnit(UnitID.kilometer, BigDecimal("1000000000000000000000"), UnitGroup.LENGTH, R.string.unit_kilometer, R.string.unit_kilometer_short),
@ -176,15 +144,16 @@ private fun UnitToSelectorPreview() {
NormalUnit(UnitID.foot, BigDecimal("304800000000000000"), UnitGroup.LENGTH, R.string.unit_foot, R.string.unit_foot_short),
NormalUnit(UnitID.yard, BigDecimal("914400000000000000"), UnitGroup.LENGTH, R.string.unit_yard, R.string.unit_yard_short),
NormalUnit(UnitID.mile, BigDecimal("1609344000000000000000"), UnitGroup.LENGTH, R.string.unit_mile, R.string.unit_mile_short),
),
)
.map { UnitSearchResultItem(it, UnitsEntity(unitId = it.id), null) },
)
UnitToSelectorScreen(
uiState = UnitSelectorUIState.UnitTo(
unitFrom = units.values.first().first(),
unitTo = units.values.first().first(),
unitFrom = units.values.first().first().basicUnit,
unitTo = units.values.first().first().basicUnit,
query = TextFieldValue("test"),
units = UnitSearchResult.Success(units),
units = units,
showFavoritesOnly = false,
sorting = UnitsListSorting.USAGE,
input = "100",

View File

@ -23,83 +23,48 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.repository.UnitsRepository
import com.sadellie.unitto.data.converter.UnitSearchResultItem
import com.sadellie.unitto.data.converter.UnitsRepositoryImpl
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.feature.converter.navigation.INPUT_ARG
import com.sadellie.unitto.feature.converter.navigation.UNIT_FROM_ID_ARG
import com.sadellie.unitto.feature.converter.navigation.UNIT_GROUP_ARG
import com.sadellie.unitto.feature.converter.navigation.UNIT_TO_ID_ARG
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
internal class UnitSelectorViewModel @Inject constructor(
internal class UnitToSelectorViewModel @Inject constructor(
private val userPrefsRepository: UserPreferencesRepository,
private val unitsRepo: UnitsRepository,
private val unitsRepo: UnitsRepositoryImpl,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private var searchJob: Job? = null
private val query = MutableStateFlow(TextFieldValue())
private val searchResults = MutableStateFlow<UnitSearchResult>(UnitSearchResult.Loading)
private val searchResults = MutableStateFlow<Map<UnitGroup, List<UnitSearchResultItem>>?>(null)
private val selectedUnitGroup = MutableStateFlow(savedStateHandle.get<UnitGroup>(UNIT_GROUP_ARG))
private val unitFromId = savedStateHandle.get<String>(UNIT_FROM_ID_ARG)
private val unitToId = savedStateHandle.get<String>(UNIT_TO_ID_ARG)
private val input = savedStateHandle.get<String>(INPUT_ARG)
val unitFromUIState: StateFlow<UnitSelectorUIState> = combine(
query,
searchResults,
selectedUnitGroup,
userPrefsRepository.converterPrefs,
) { query, searchResults, selectedUnitGroup, prefs ->
if (unitFromId.isNullOrEmpty()) return@combine UnitSelectorUIState.Loading
return@combine UnitSelectorUIState.UnitFrom(
query = query,
unitFrom = unitsRepo.getById(unitFromId),
shownUnitGroups = prefs.shownUnitGroups,
showFavoritesOnly = prefs.unitConverterFavoritesOnly,
units = searchResults,
selectedUnitGroup = selectedUnitGroup,
sorting = prefs.unitConverterSorting,
)
}
.mapLatest { ui ->
if (ui is UnitSelectorUIState.UnitFrom) {
searchResults.update {
val result = unitsRepo.filterUnits(
query = ui.query.text,
unitGroup = ui.selectedUnitGroup,
favoritesOnly = ui.showFavoritesOnly,
hideBrokenUnits = false,
sorting = ui.sorting,
shownUnitGroups = ui.shownUnitGroups,
)
if (result.isEmpty()) UnitSearchResult.Empty else UnitSearchResult.Success(result)
}
}
ui
}
.stateIn(viewModelScope, UnitSelectorUIState.Loading)
val unitToUIState: StateFlow<UnitSelectorUIState> = combine(
query,
searchResults,
userPrefsRepository.converterPrefs,
unitsRepo.units,
) { query, searchResults, prefs, _ ->
) { query, searchResults, prefs ->
if (unitFromId.isNullOrEmpty()) return@combine UnitSelectorUIState.Loading
if (unitToId.isNullOrEmpty()) return@combine UnitSelectorUIState.Loading
if (searchResults == null) return@combine UnitSelectorUIState.Loading
UnitSelectorUIState.UnitTo(
query = query,
@ -114,35 +79,46 @@ internal class UnitSelectorViewModel @Inject constructor(
formatterSymbols = prefs.formatterSymbols,
)
}
.mapLatest { ui ->
if (ui is UnitSelectorUIState.UnitTo) {
searchResults.update {
if (ui.unitFrom.group == UnitGroup.CURRENCY) unitsRepo.updateRates(ui.unitFrom)
val result = unitsRepo.filterUnits(
query = ui.query.text,
unitGroup = ui.unitFrom.group,
favoritesOnly = ui.showFavoritesOnly,
hideBrokenUnits = true,
sorting = ui.sorting,
)
if (result.isEmpty()) UnitSearchResult.Empty else UnitSearchResult.Success(result)
}
}
ui
}
.stateIn(viewModelScope, UnitSelectorUIState.Loading)
fun updateSelectorQuery(value: TextFieldValue) = query.update { value }
fun updateSelectorQuery(value: TextFieldValue) {
query.update { value }
onSearch()
}
fun updateShowFavoritesOnly(value: Boolean) = viewModelScope.launch {
userPrefsRepository.updateUnitConverterFavoritesOnly(value)
onSearch()
}
fun updateSelectedUnitGroup(value: UnitGroup?) = selectedUnitGroup.update { value }
fun favoriteUnit(unit: UnitSearchResultItem) = viewModelScope.launch {
unitsRepo.favorite(unit.basicUnit.id)
onSearch()
}
fun favoriteUnit(unit: AbstractUnit) = viewModelScope.launch(Dispatchers.IO) {
unitsRepo.favorite(unit)
private fun onSearch() {
searchJob?.cancel()
searchJob = viewModelScope.launch {
val prefs = userPrefsRepository.converterPrefs.first()
val result = unitsRepo.filterUnitsAndBatchConvert(
query = query.value.text,
unitGroup = selectedUnitGroup.value ?: return@launch,
favoritesOnly = prefs.unitConverterFavoritesOnly,
sorting = prefs.unitConverterSorting,
unitFromId = unitFromId ?: return@launch,
input = input,
)
searchResults.update { result }
}
}
init {
onSearch()
}
override fun onCleared() {
super.onCleared()
viewModelScope.cancel()
}
}

View File

@ -48,7 +48,7 @@ import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.AssistChip
import com.sadellie.unitto.core.ui.common.FilterChip
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
/**
* Row of chips with [UnitGroup]s. Temporary solution

View File

@ -24,7 +24,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
@Composable
internal fun UnitGroupHeader(modifier: Modifier, unitGroup: UnitGroup) {

View File

@ -19,6 +19,7 @@
package com.sadellie.unitto.feature.converter.components
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -30,57 +31,55 @@ import androidx.compose.ui.tooling.preview.Preview
import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.SearchPlaceholder
import com.sadellie.unitto.data.converter.UnitID
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.AbstractUnit
import com.sadellie.unitto.data.model.unit.NormalUnit
import com.sadellie.unitto.feature.converter.UnitSearchResult
import com.sadellie.unitto.data.converter.UnitSearchResultItem
import com.sadellie.unitto.data.database.UnitsEntity
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.unit.NormalUnit
import java.math.BigDecimal
@Composable
internal fun UnitsList(
modifier: Modifier,
searchResult: UnitSearchResult,
searchResult: Map<UnitGroup, List<UnitSearchResultItem>>,
navigateToUnitGroups: () -> Unit,
currentUnitId: String,
supportLabel: (AbstractUnit) -> String,
onClick: (AbstractUnit) -> Unit,
favoriteUnit: (AbstractUnit) -> Unit,
supportLabel: (UnitSearchResultItem) -> String,
onClick: (UnitSearchResultItem) -> Unit,
favoriteUnit: (UnitSearchResultItem) -> Unit,
) {
Crossfade(
modifier = modifier,
targetState = searchResult,
label = "Units list",
animationSpec = tween(200),
) { result ->
when (result) {
is UnitSearchResult.Success -> LazyColumn(
when {
result.isEmpty() -> SearchPlaceholder(
onButtonClick = navigateToUnitGroups,
supportText = stringResource(R.string.converter_no_results_support),
buttonLabel = stringResource(R.string.open_settings_label),
)
else -> LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
result.units.forEach { (group, units) ->
result.forEach { (group, units) ->
item(group.name) {
UnitGroupHeader(Modifier.animateItemPlacement(), group)
}
items(units, { it.id }) {
items(units, { it.basicUnit.id }) {
BasicUnitListItem(
modifier = Modifier.animateItemPlacement(),
name = stringResource(it.displayName),
name = stringResource(it.basicUnit.displayName),
supportLabel = supportLabel(it),
isFavorite = it.isFavorite,
isSelected = it.id == currentUnitId,
isFavorite = it.stats.isFavorite,
isSelected = it.basicUnit.id == currentUnitId,
onClick = { onClick(it) },
favoriteUnit = { favoriteUnit(it) },
)
}
}
}
UnitSearchResult.Empty -> SearchPlaceholder(
onButtonClick = navigateToUnitGroups,
supportText = stringResource(R.string.converter_no_results_support),
buttonLabel = stringResource(R.string.open_settings_label),
)
UnitSearchResult.Loading -> Unit
}
}
}
@ -89,7 +88,7 @@ internal fun UnitsList(
@Composable
private fun PreviewUnitsList() {
val resources = LocalContext.current.resources
val groupedUnits: Map<UnitGroup, List<AbstractUnit>> = mapOf(
val units: Map<UnitGroup, List<UnitSearchResultItem>> = mapOf(
UnitGroup.LENGTH to listOf(
NormalUnit(UnitID.meter, BigDecimal("1000000000000000000"), UnitGroup.LENGTH, R.string.unit_meter, R.string.unit_meter_short),
NormalUnit(UnitID.kilometer, BigDecimal("1000000000000000000000"), UnitGroup.LENGTH, R.string.unit_kilometer, R.string.unit_kilometer_short),
@ -98,15 +97,16 @@ private fun PreviewUnitsList() {
NormalUnit(UnitID.foot, BigDecimal("304800000000000000"), UnitGroup.LENGTH, R.string.unit_foot, R.string.unit_foot_short),
NormalUnit(UnitID.yard, BigDecimal("914400000000000000"), UnitGroup.LENGTH, R.string.unit_yard, R.string.unit_yard_short),
NormalUnit(UnitID.mile, BigDecimal("1609344000000000000000"), UnitGroup.LENGTH, R.string.unit_mile, R.string.unit_mile_short),
),
)
.map { UnitSearchResultItem(it, UnitsEntity(unitId = it.id), null) },
)
UnitsList(
modifier = Modifier.fillMaxSize(),
searchResult = UnitSearchResult.Success(units = groupedUnits),
searchResult = units,
navigateToUnitGroups = {},
currentUnitId = UnitID.mile,
supportLabel = { resources.getString(it.shortName) },
supportLabel = { resources.getString(it.basicUnit.shortName) },
onClick = {},
favoriteUnit = {},
)

View File

@ -28,11 +28,9 @@ import androidx.navigation.navDeepLink
import com.sadellie.unitto.core.ui.model.DrawerItem
import com.sadellie.unitto.core.ui.unittoComposable
import com.sadellie.unitto.core.ui.unittoNavigation
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.feature.converter.ConverterRoute
import com.sadellie.unitto.feature.converter.ConverterViewModel
import com.sadellie.unitto.feature.converter.CurrencyRateUpdateState
import com.sadellie.unitto.feature.converter.UnitConverterUIState
import com.sadellie.unitto.feature.converter.UnitFromSelectorRoute
import com.sadellie.unitto.feature.converter.UnitToSelectorRoute
@ -47,7 +45,7 @@ internal const val UNIT_TO_ID_ARG = "unitToIdArg"
internal const val INPUT_ARG = "inputArg"
private const val UNIT_FROM_ROUTE = "$UNIT_FROM/{$UNIT_FROM_ID_ARG}/{$UNIT_GROUP_ARG}"
private const val UNIT_TO_ROUTE = "$UNIT_TO/{$UNIT_FROM_ID_ARG}/{$UNIT_TO_ID_ARG}/{$INPUT_ARG}"
private const val UNIT_TO_ROUTE = "$UNIT_TO/{$UNIT_FROM_ID_ARG}/{$UNIT_TO_ID_ARG}/{$UNIT_GROUP_ARG}/{$INPUT_ARG}"
private fun NavHostController.navigateLeft(
unitFromId: String,
unitGroup: UnitGroup,
@ -56,8 +54,9 @@ private fun NavHostController.navigateLeft(
private fun NavHostController.navigateRight(
unitFromId: String,
unitToId: String,
input: String?,
) = navigate("$UNIT_TO/$unitFromId/$unitToId/$input")
unitGroup: UnitGroup,
input: String,
) = navigate("$UNIT_TO/$unitFromId/$unitToId/$unitGroup/${input.ifEmpty { null }}")
fun NavGraphBuilder.converterGraph(
openDrawer: () -> Unit,
@ -80,54 +79,8 @@ fun NavGraphBuilder.converterGraph(
ConverterRoute(
viewModel = parentViewModel,
// Navigation logic is here, but should actually be in ConverterScreen
navigateToLeftScreen = { uiState: UnitConverterUIState ->
when (uiState) {
is UnitConverterUIState.Default ->
navController
.navigateLeft(uiState.unitFrom.id, uiState.unitFrom.group)
is UnitConverterUIState.NumberBase ->
navController
.navigateLeft(uiState.unitFrom.id, uiState.unitFrom.group)
else -> Unit
}
},
navigateToRightScreen = { uiState: UnitConverterUIState ->
when (uiState) {
is UnitConverterUIState.Default -> {
// Don't allow converting if still loading currencies
val convertingCurrencies = uiState.unitFrom.group == UnitGroup.CURRENCY
val currenciesReady =
uiState.currencyRateUpdateState is CurrencyRateUpdateState.Ready
val input: String? = if (convertingCurrencies and !currenciesReady) {
null
} else {
(uiState.calculation?.toPlainString() ?: uiState.input1.text)
.ifEmpty { null }
}
navController.navigateRight(
uiState.unitFrom.id,
uiState.unitTo.id,
input,
)
}
is UnitConverterUIState.NumberBase -> {
val input = uiState.input.text.ifEmpty { null }
navController.navigateRight(
uiState.unitFrom.id,
uiState.unitTo.id,
input,
)
}
UnitConverterUIState.Loading -> Unit
}
},
navigateToLeftScreen = navController::navigateLeft,
navigateToRightScreen = navController::navigateRight,
openDrawer = openDrawer,
)
}
@ -166,6 +119,9 @@ fun NavGraphBuilder.converterGraph(
navArgument(UNIT_TO_ID_ARG) {
type = NavType.StringType
},
navArgument(UNIT_GROUP_ARG) {
type = NavType.EnumType(UnitGroup::class.java)
},
navArgument(INPUT_ARG) {
type = NavType.StringType
nullable = true

View File

@ -1,134 +0,0 @@
/*
* Unitto is a calculator for Android
* Copyright (c) 2023-2024 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.converter
import android.content.Context
import com.sadellie.unitto.core.base.FormatterSymbols
import com.sadellie.unitto.core.base.Token
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import java.math.BigDecimal
@RunWith(RobolectricTestRunner::class)
class ConverterUIStateKtTest {
@Test
fun format() {
val formatterSymbols = FormatterSymbols(Token.SPACE, Token.PERIOD)
var basicValue = BigDecimal("1")
val mContext: Context = RuntimeEnvironment.getApplication().applicationContext
fun String.formatTime() = formatTime(basicValue.multiply(BigDecimal(this)))
.format(mContext, formatterSymbols)
// Edgy things (minus, decimals and zeros)
Assert.assertEquals("28as", "-28".formatTime())
Assert.assertEquals("0.05as", "-0.05".formatTime())
Assert.assertEquals("0", "0".formatTime())
basicValue = BigDecimal("86400000000000000000000")
Assert.assertEquals("28d", "-28".formatTime())
Assert.assertEquals("1h 12m", "-0.05".formatTime())
Assert.assertEquals("0", "0".formatTime())
Assert.assertEquals("0", "-0".formatTime())
// DAYS
basicValue = BigDecimal("86400000000000000000000")
Assert.assertEquals("12h", "0.5".formatTime())
Assert.assertEquals("1h 12m", "0.05".formatTime())
Assert.assertEquals("7m 12s", "0.005".formatTime())
Assert.assertEquals("28d", "28".formatTime())
Assert.assertEquals("90d", "90".formatTime())
Assert.assertEquals("90d 12h", "90.5".formatTime())
Assert.assertEquals("90d 7m 12s", "90.005".formatTime())
// HOURS
basicValue = BigDecimal("3600000000000000000000")
Assert.assertEquals("30m", "0.5".formatTime())
Assert.assertEquals("3m", "0.05".formatTime())
Assert.assertEquals("18s", "0.005".formatTime())
Assert.assertEquals("1d 4h", "28".formatTime())
Assert.assertEquals("3d 18h", "90".formatTime())
Assert.assertEquals("3d 18h 30m", "90.5".formatTime())
Assert.assertEquals("3d 18h 18s", "90.005".formatTime())
// MINUTES
basicValue = BigDecimal("60000000000000000000")
Assert.assertEquals("30s", "0.5".formatTime())
Assert.assertEquals("3s", "0.05".formatTime())
Assert.assertEquals("300ms", "0.005".formatTime())
Assert.assertEquals("28m", "28".formatTime())
Assert.assertEquals("1h 30m", "90".formatTime())
Assert.assertEquals("1h 30m 30s", "90.5".formatTime())
Assert.assertEquals("1h 30m 300ms", "90.005".formatTime())
// SECONDS
basicValue = BigDecimal("1000000000000000000")
Assert.assertEquals("500ms", "0.5".formatTime())
Assert.assertEquals("50ms", "0.05".formatTime())
Assert.assertEquals("5ms", "0.005".formatTime())
Assert.assertEquals("28s", "28".formatTime())
Assert.assertEquals("1m 30s", "90".formatTime())
Assert.assertEquals("1m 30s 500ms", "90.5".formatTime())
Assert.assertEquals("1m 30s 5ms", "90.005".formatTime())
// MILLISECONDS
basicValue = BigDecimal("1000000000000000")
Assert.assertEquals("500µs", "0.5".formatTime())
Assert.assertEquals("50µs", "0.05".formatTime())
Assert.assertEquals("5µs", "0.005".formatTime())
Assert.assertEquals("28ms", "28".formatTime())
Assert.assertEquals("90ms", "90".formatTime())
Assert.assertEquals("90ms 500µs", "90.5".formatTime())
Assert.assertEquals("90ms 5µs", "90.005".formatTime())
// MICROSECONDS
basicValue = BigDecimal("1000000000000")
Assert.assertEquals("500ns", "0.5".formatTime())
Assert.assertEquals("50ns", "0.05".formatTime())
Assert.assertEquals("5ns", "0.005".formatTime())
Assert.assertEquals("28µs", "28".formatTime())
Assert.assertEquals("90µs", "90".formatTime())
Assert.assertEquals("90µs 500ns", "90.5".formatTime())
Assert.assertEquals("90µs 5ns", "90.005".formatTime())
// NANOSECONDS
basicValue = BigDecimal("1000000000")
Assert.assertEquals("500 000 000as", "0.5".formatTime())
Assert.assertEquals("50 000 000as", "0.05".formatTime())
Assert.assertEquals("5 000 000as", "0.005".formatTime())
Assert.assertEquals("28ns", "28".formatTime())
Assert.assertEquals("90ns", "90".formatTime())
Assert.assertEquals("90ns 500 000 000as", "90.5".formatTime())
Assert.assertEquals("90ns 5 000 000as", "90.005".formatTime())
// ATTOSECONDS
basicValue = BigDecimal("1")
Assert.assertEquals("0.5as", "0.5".formatTime())
Assert.assertEquals("0.05as", "0.05".formatTime())
Assert.assertEquals("0.005as", "0.005".formatTime())
Assert.assertEquals("28as", "28".formatTime())
Assert.assertEquals("90as", "90".formatTime())
Assert.assertEquals("90.5as", "90.5".formatTime())
Assert.assertEquals("90.005as", "90.005".formatTime())
}
}

View File

@ -42,8 +42,8 @@ import com.sadellie.unitto.core.ui.common.EmptyScreen
import com.sadellie.unitto.core.ui.common.ListItem
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.ScaffoldWithLargeTopBar
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.userprefs.ConverterPreferences
import com.sadellie.unitto.data.userprefs.ConverterPreferencesImpl
import com.sadellie.unitto.feature.settings.components.AlertDialogWithList

View File

@ -21,7 +21,7 @@ package com.sadellie.unitto.feature.settings.converter
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.model.converter.UnitsListSorting
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch

View File

@ -56,7 +56,7 @@ import com.sadellie.unitto.core.ui.common.EmptyScreen
import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.ScaffoldWithLargeTopBar
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
import org.burnoutcrew.reorderable.ReorderableItem
import org.burnoutcrew.reorderable.detectReorder
import org.burnoutcrew.reorderable.detectReorderAfterLongPress

View File

@ -18,7 +18,7 @@
package com.sadellie.unitto.feature.settings.unitgroups
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
internal sealed class UnitGroupsUIState {
data object Loading : UnitGroupsUIState()

View File

@ -21,7 +21,7 @@ package com.sadellie.unitto.feature.settings.unitgroups
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.converter.UnitGroup
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.map