Add preference to disable currency converter
This commit is contained in:
parent
9e012f1a23
commit
558d12bb8a
@ -4,24 +4,36 @@ import android.content.Context
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class CurrencyRepository(val context: Context) {
|
class CurrencyRepository(
|
||||||
|
private val context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
init {
|
fun enableCurrencyUpdateWorker() {
|
||||||
val currencyWorker = PeriodicWorkRequest.Builder(ExchangeRateWorker::class.java, 60, TimeUnit.MINUTES)
|
val currencyWorker =
|
||||||
|
PeriodicWorkRequest.Builder(ExchangeRateWorker::class.java, 60, TimeUnit.MINUTES)
|
||||||
.build()
|
.build()
|
||||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork("ExchangeRates",
|
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||||
ExistingPeriodicWorkPolicy.REPLACE, currencyWorker)
|
"ExchangeRates",
|
||||||
|
ExistingPeriodicWorkPolicy.REPLACE, currencyWorker
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun convertCurrency(fromCurrency: String, value: Double, toCurrency: String? = null): List<Pair<String, Double>> {
|
fun disableCurrencyUpdateWorker() {
|
||||||
|
WorkManager.getInstance(context).cancelUniqueWork("ExchangeRates")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertCurrency(
|
||||||
|
fromCurrency: String,
|
||||||
|
value: Double,
|
||||||
|
toCurrency: String? = null
|
||||||
|
): List<Pair<String, Double>> {
|
||||||
|
|
||||||
return withContext<List<Pair<String, Double>>>(Dispatchers.IO) {
|
return withContext<List<Pair<String, Double>>>(Dispatchers.IO) {
|
||||||
val dao = AppDatabase.getInstance(context)
|
val dao = AppDatabase.getInstance(context)
|
||||||
.currencyDao()
|
.currencyDao()
|
||||||
|
|
||||||
val from = Currency(dao.getCurrency(fromCurrency) ?: return@withContext emptyList())
|
val from = Currency(dao.getCurrency(fromCurrency) ?: return@withContext emptyList())
|
||||||
|
|
||||||
@ -38,7 +50,10 @@ class CurrencyRepository(val context: Context) {
|
|||||||
} else {
|
} else {
|
||||||
val to = Currency(dao.getCurrency(toCurrency) ?: return@withContext emptyList())
|
val to = Currency(dao.getCurrency(toCurrency) ?: return@withContext emptyList())
|
||||||
if (from.lastUpdate != to.lastUpdate) {
|
if (from.lastUpdate != to.lastUpdate) {
|
||||||
Log.w("MM20", "Exchange rate update dates do not match: $fromCurrency, $toCurrency")
|
Log.w(
|
||||||
|
"MM20",
|
||||||
|
"Exchange rate update dates do not match: $fromCurrency, $toCurrency"
|
||||||
|
)
|
||||||
return@withContext emptyList()
|
return@withContext emptyList()
|
||||||
}
|
}
|
||||||
listOf(toCurrency to value * to.value / from.value)
|
listOf(toCurrency to value * to.value / from.value)
|
||||||
|
|||||||
@ -525,6 +525,8 @@
|
|||||||
<string name="preference_search_calculator_summary">Evaluate mathematical terms</string>
|
<string name="preference_search_calculator_summary">Evaluate mathematical terms</string>
|
||||||
<string name="preference_search_unitconverter">Unit converter</string>
|
<string name="preference_search_unitconverter">Unit converter</string>
|
||||||
<string name="preference_search_unitconverter_summary">Usage: 1.5 kg or 4 cm >> in</string>
|
<string name="preference_search_unitconverter_summary">Usage: 1.5 kg or 4 cm >> in</string>
|
||||||
|
<string name="preference_search_currencyconverter">Currency converter</string>
|
||||||
|
<string name="preference_search_currencyconverter_summary">Periodically download exchange rates to convert currencies</string>
|
||||||
<string name="preference_search_wikipedia">Wikipedia</string>
|
<string name="preference_search_wikipedia">Wikipedia</string>
|
||||||
<string name="preference_search_wikipedia_summary">Search the free encyclopedia</string>
|
<string name="preference_search_wikipedia_summary">Search the free encyclopedia</string>
|
||||||
<string name="preference_search_websites">Websites</string>
|
<string name="preference_search_websites">Websites</string>
|
||||||
|
|||||||
@ -87,6 +87,7 @@ fun createFactorySettings(context: Context): Settings {
|
|||||||
Settings.UnitConverterSearchSettings
|
Settings.UnitConverterSearchSettings
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.setEnabled(true)
|
.setEnabled(true)
|
||||||
|
.setCurrencies(true)
|
||||||
)
|
)
|
||||||
.setWikipediaSearch(
|
.setWikipediaSearch(
|
||||||
Settings.WikipediaSearchSettings
|
Settings.WikipediaSearchSettings
|
||||||
|
|||||||
@ -15,6 +15,9 @@ class Migration_5_6: VersionedMigration(5, 6) {
|
|||||||
.setLightScheme(DefaultLightCustomColorScheme)
|
.setLightScheme(DefaultLightCustomColorScheme)
|
||||||
.setDarkScheme(DefaultDarkCustomColorScheme)
|
.setDarkScheme(DefaultDarkCustomColorScheme)
|
||||||
)
|
)
|
||||||
|
).setUnitConverterSearch(
|
||||||
|
builder.unitConverterSearch.toBuilder()
|
||||||
|
.setCurrencies(true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,6 +136,7 @@ message Settings {
|
|||||||
|
|
||||||
message UnitConverterSearchSettings {
|
message UnitConverterSearchSettings {
|
||||||
bool enabled = 1;
|
bool enabled = 1;
|
||||||
|
bool currencies = 2;
|
||||||
}
|
}
|
||||||
UnitConverterSearchSettings unit_converter_search = 13;
|
UnitConverterSearchSettings unit_converter_search = 13;
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import de.mm20.launcher2.ui.settings.license.LicenseScreen
|
|||||||
import de.mm20.launcher2.ui.settings.main.MainSettingsScreen
|
import de.mm20.launcher2.ui.settings.main.MainSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.musicwidget.MusicWidgetSettingsScreen
|
import de.mm20.launcher2.ui.settings.musicwidget.MusicWidgetSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
|
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.weatherwidget.WeatherWidgetSettingsScreen
|
import de.mm20.launcher2.ui.settings.weatherwidget.WeatherWidgetSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.websearch.WebSearchSettingsScreen
|
import de.mm20.launcher2.ui.settings.websearch.WebSearchSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.widgets.WidgetsSettingsScreen
|
import de.mm20.launcher2.ui.settings.widgets.WidgetsSettingsScreen
|
||||||
@ -98,6 +99,9 @@ class SettingsActivity : BaseActivity() {
|
|||||||
composable("settings/search") {
|
composable("settings/search") {
|
||||||
SearchSettingsScreen()
|
SearchSettingsScreen()
|
||||||
}
|
}
|
||||||
|
composable("settings/search/unitconverter") {
|
||||||
|
UnitConverterSettingsScreen()
|
||||||
|
}
|
||||||
composable("settings/search/wikipedia") {
|
composable("settings/search/wikipedia") {
|
||||||
WikipediaSettingsScreen()
|
WikipediaSettingsScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,13 +128,16 @@ fun SearchSettingsScreen() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val unitConverter by viewModel.unitConverter.observeAsState()
|
val unitConverter by viewModel.unitConverter.observeAsState()
|
||||||
SwitchPreference(
|
PreferenceWithSwitch(
|
||||||
title = stringResource(R.string.preference_search_unitconverter),
|
title = stringResource(R.string.preference_search_unitconverter),
|
||||||
summary = stringResource(R.string.preference_search_unitconverter_summary),
|
summary = stringResource(R.string.preference_search_unitconverter_summary),
|
||||||
icon = Icons.Rounded.Loop,
|
icon = Icons.Rounded.Loop,
|
||||||
value = unitConverter == true,
|
switchValue = unitConverter == true,
|
||||||
onValueChanged = {
|
onSwitchChanged = {
|
||||||
viewModel.setUnitConverter(it)
|
viewModel.setUnitConverter(it)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/search/unitconverter")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.unitconverter
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Loop
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||||
|
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreenVM
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UnitConverterSettingsScreen() {
|
||||||
|
val viewModel: UnitConverterSettingsScreenVM = viewModel()
|
||||||
|
PreferenceScreen(title = stringResource(R.string.preference_search_unitconverter)) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
val unitConverter by viewModel.unitConverter.observeAsState()
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.preference_search_unitconverter),
|
||||||
|
summary = stringResource(R.string.preference_search_unitconverter_summary),
|
||||||
|
value = unitConverter == true,
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.setUnitConverter(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val currencyConverter by viewModel.currencyConverter.observeAsState()
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.preference_search_currencyconverter),
|
||||||
|
summary = stringResource(R.string.preference_search_currencyconverter_summary),
|
||||||
|
enabled = unitConverter != false,
|
||||||
|
value = currencyConverter == true,
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.setCurrencyConverter(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.unitconverter
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class UnitConverterSettingsScreenVM: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val dataStore: LauncherDataStore by inject()
|
||||||
|
|
||||||
|
val unitConverter = dataStore.data.map { it.unitConverterSearch.enabled }.asLiveData()
|
||||||
|
fun setUnitConverter(unitConverter: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setUnitConverterSearch(
|
||||||
|
it.unitConverterSearch.toBuilder()
|
||||||
|
.setEnabled(unitConverter)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val currencyConverter = dataStore.data.map { it.unitConverterSearch.currencies }.asLiveData()
|
||||||
|
fun setCurrencyConverter(currencyConverter: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setUnitConverterSearch(
|
||||||
|
it.unitConverterSearch.toBuilder()
|
||||||
|
.setCurrencies(currencyConverter)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,5 +7,5 @@ import org.koin.dsl.module
|
|||||||
|
|
||||||
val unitConverterModule = module {
|
val unitConverterModule = module {
|
||||||
single { CurrencyRepository(androidContext()) }
|
single { CurrencyRepository(androidContext()) }
|
||||||
single<UnitConverterRepository> { UnitConverterRepositoryImpl(androidContext()) }
|
single<UnitConverterRepository> { UnitConverterRepositoryImpl(androidContext(), get()) }
|
||||||
}
|
}
|
||||||
@ -2,40 +2,59 @@ package de.mm20.launcher2.unitconverter
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import de.mm20.launcher2.currencies.CurrencyRepository
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.data.UnitConverter
|
import de.mm20.launcher2.search.data.UnitConverter
|
||||||
import de.mm20.launcher2.unitconverter.converters.*
|
import de.mm20.launcher2.unitconverter.converters.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
interface UnitConverterRepository {
|
interface UnitConverterRepository {
|
||||||
fun search(query:String): Flow<UnitConverter?>
|
fun search(query: String): Flow<UnitConverter?>
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class UnitConverterRepositoryImpl(val context: Context) : UnitConverterRepository, KoinComponent {
|
internal class UnitConverterRepositoryImpl(
|
||||||
|
private val context: Context,
|
||||||
|
private val currencyRepository: CurrencyRepository,
|
||||||
|
) : UnitConverterRepository, KoinComponent {
|
||||||
private val dataStore: LauncherDataStore by inject()
|
private val dataStore: LauncherDataStore by inject()
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
val unitConverter = MutableLiveData<UnitConverter?>()
|
val unitConverter = MutableLiveData<UnitConverter?>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
dataStore.data.map { it.unitConverterSearch }.distinctUntilChanged().collectLatest {
|
||||||
|
if (it.enabled && it.currencies) currencyRepository.enableCurrencyUpdateWorker()
|
||||||
|
else currencyRepository.disableCurrencyUpdateWorker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun search(query: String): Flow<UnitConverter?> = channelFlow {
|
override fun search(query: String): Flow<UnitConverter?> = channelFlow {
|
||||||
if (query.isBlank()) {
|
if (query.isBlank()) {
|
||||||
send(null)
|
send(null)
|
||||||
return@channelFlow
|
return@channelFlow
|
||||||
}
|
}
|
||||||
dataStore.data.map { it.unitConverterSearch.enabled }.collectLatest {
|
dataStore.data.map { it.unitConverterSearch }.collectLatest {
|
||||||
if (it) {
|
if (it.enabled) {
|
||||||
send(queryUnitConverter(query))
|
send(queryUnitConverter(query, it.currencies))
|
||||||
} else {
|
} else {
|
||||||
send(null)
|
send(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun queryUnitConverter(query: String): UnitConverter? {
|
private suspend fun queryUnitConverter(
|
||||||
|
query: String,
|
||||||
|
includeCurrencies: Boolean
|
||||||
|
): UnitConverter? {
|
||||||
if (!query.matches(Regex("[0-9,.:]+ [^\\s]+")) && !query.matches(Regex("[0-9,.:]+ [^\\s]+ >> [^\\s]+"))) return null
|
if (!query.matches(Regex("[0-9,.:]+ [^\\s]+")) && !query.matches(Regex("[0-9,.:]+ [^\\s]+ >> [^\\s]+"))) return null
|
||||||
val valueStr: String
|
val valueStr: String
|
||||||
val unitStr: String
|
val unitStr: String
|
||||||
@ -49,18 +68,19 @@ internal class UnitConverterRepositoryImpl(val context: Context) : UnitConverter
|
|||||||
val value = valueStr.toDoubleOrNull() ?: valueStr.replace(',', '.').toDoubleOrNull()
|
val value = valueStr.toDoubleOrNull() ?: valueStr.replace(',', '.').toDoubleOrNull()
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
val converters = listOf(
|
val converters = mutableListOf(
|
||||||
lazy { MassConverter(context) },
|
MassConverter(context),
|
||||||
lazy { LengthConverter(context) },
|
LengthConverter(context),
|
||||||
lazy { CurrencyConverter() },
|
DataConverter(context),
|
||||||
lazy { DataConverter(context) },
|
TimeConverter(context),
|
||||||
lazy { TimeConverter(context) },
|
VelocityConverter(context),
|
||||||
lazy { VelocityConverter(context) },
|
AreaConverter(context),
|
||||||
lazy { AreaConverter(context) },
|
TemperatureConverter(context)
|
||||||
lazy { TemperatureConverter(context) }
|
|
||||||
)
|
)
|
||||||
for (conv in converters) {
|
|
||||||
val converter = conv.value
|
if (includeCurrencies) converters.add(CurrencyConverter(currencyRepository))
|
||||||
|
|
||||||
|
for (converter in converters) {
|
||||||
if (!converter.isValidUnit(unitStr)) continue
|
if (!converter.isValidUnit(unitStr)) continue
|
||||||
if (targetUnitStr != null && !converter.isValidUnit(targetUnitStr)) continue
|
if (targetUnitStr != null && !converter.isValidUnit(targetUnitStr)) continue
|
||||||
return converter.convert(context, unitStr, value, targetUnitStr)
|
return converter.convert(context, unitStr, value, targetUnitStr)
|
||||||
|
|||||||
@ -12,14 +12,13 @@ import java.text.DecimalFormat
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.Currency as JCurrency
|
import java.util.Currency as JCurrency
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import org.koin.core.component.KoinComponent
|
|
||||||
import org.koin.core.component.inject
|
|
||||||
|
|
||||||
class CurrencyConverter : Converter, KoinComponent {
|
class CurrencyConverter(
|
||||||
|
private val repository: CurrencyRepository,
|
||||||
|
) : Converter {
|
||||||
|
|
||||||
override val dimension: Dimension = Dimension.Currency
|
override val dimension: Dimension = Dimension.Currency
|
||||||
|
|
||||||
private val repository: CurrencyRepository by inject()
|
|
||||||
|
|
||||||
private val topCurrencies = arrayOf("USD", "EUR", "JPY", "GBP", "AUD")
|
private val topCurrencies = arrayOf("USD", "EUR", "JPY", "GBP", "AUD")
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user