Add preference to disable currency converter

This commit is contained in:
MM20 2022-04-16 21:35:13 +02:00
parent 9e012f1a23
commit 558d12bb8a
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
12 changed files with 176 additions and 39 deletions

View File

@ -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)

View File

@ -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 &gt;&gt; in</string> <string name="preference_search_unitconverter_summary">Usage: 1.5 kg or 4 cm &gt;&gt; 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>

View File

@ -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

View File

@ -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)
) )
} }
} }

View File

@ -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;

View File

@ -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()
} }

View File

@ -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")
} }
) )

View File

@ -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)
}
)
}
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -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()) }
} }

View File

@ -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)

View File

@ -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")