Refactor weather to use new datastore preferences
This commit is contained in:
parent
d18feccfe4
commit
2b3323bf7f
@ -1,39 +1,24 @@
|
|||||||
package de.mm20.launcher2.ui.settings.weather
|
package de.mm20.launcher2.ui.settings.weather
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings.WeatherSettings
|
import de.mm20.launcher2.preferences.Settings.WeatherSettings
|
||||||
import de.mm20.launcher2.weather.WeatherLocation
|
import de.mm20.launcher2.weather.WeatherLocation
|
||||||
import de.mm20.launcher2.weather.WeatherProvider
|
import de.mm20.launcher2.weather.WeatherRepository
|
||||||
import de.mm20.launcher2.weather.brightsky.BrightskyProvider
|
|
||||||
import de.mm20.launcher2.weather.here.HereProvider
|
|
||||||
import de.mm20.launcher2.weather.metno.MetNoProvider
|
|
||||||
import de.mm20.launcher2.weather.openweathermap.OpenWeatherMapProvider
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
class WeatherScreenVM(private val context: Application) : AndroidViewModel(context), KoinComponent {
|
class WeatherScreenVM : ViewModel(), KoinComponent {
|
||||||
val dataStore: LauncherDataStore by inject()
|
private val repository: WeatherRepository by inject()
|
||||||
|
private val dataStore: LauncherDataStore by inject()
|
||||||
val weatherProvider = MutableLiveData<WeatherSettings.WeatherProvider?>(null)
|
|
||||||
fun setWeatherProvider(provider: WeatherSettings.WeatherProvider) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
dataStore.updateData {
|
|
||||||
it.toBuilder()
|
|
||||||
.setWeather(it.weather.toBuilder().setProvider(provider))
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }.asLiveData()
|
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }.asLiveData()
|
||||||
fun setImperialUnits(imperialUnits: Boolean) {
|
fun setImperialUnits(imperialUnits: Boolean) {
|
||||||
@ -46,30 +31,23 @@ class WeatherScreenVM(private val context: Application) : AndroidViewModel(conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var provider: WeatherProvider<out WeatherLocation>? = null
|
val weatherProvider = repository.selectedProvider.asLiveData()
|
||||||
set(value) {
|
fun setWeatherProvider(provider: WeatherSettings.WeatherProvider) {
|
||||||
field = value
|
repository.selectProvider(provider)
|
||||||
if (value != null) {
|
}
|
||||||
val autoLocation = value.autoLocation
|
|
||||||
this.autoLocation.postValue(autoLocation)
|
|
||||||
location.postValue(if (autoLocation) value.getLastLocation() else value.getLocation())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val autoLocation = MutableLiveData(false)
|
val autoLocation = repository.autoLocation.asLiveData()
|
||||||
fun setAutoLocation(autoLocation: Boolean) {
|
fun setAutoLocation(autoLocation: Boolean) {
|
||||||
provider?.autoLocation = autoLocation
|
repository.setAutoLocation(autoLocation)
|
||||||
location.postValue(if (autoLocation) provider?.getLastLocation() else provider?.getLocation())
|
|
||||||
this.autoLocation.postValue(autoLocation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val location = MutableLiveData<WeatherLocation?>(null)
|
val location = MutableLiveData<WeatherLocation?>(null)
|
||||||
fun setLocation(location: WeatherLocation) {
|
fun setLocation(location: WeatherLocation) {
|
||||||
provider?.setLocation(location)
|
locationResults.postValue(emptyList())
|
||||||
this.location.postValue(location)
|
repository.setLocation(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var debounceSearchJob : Job? = null
|
private var debounceSearchJob: Job? = null
|
||||||
suspend fun searchLocation(query: String) {
|
suspend fun searchLocation(query: String) {
|
||||||
debounceSearchJob?.cancelAndJoin()
|
debounceSearchJob?.cancelAndJoin()
|
||||||
if (query.isBlank()) {
|
if (query.isBlank()) {
|
||||||
@ -80,11 +58,8 @@ class WeatherScreenVM(private val context: Application) : AndroidViewModel(conte
|
|||||||
withContext(coroutineContext) {
|
withContext(coroutineContext) {
|
||||||
debounceSearchJob = launch {
|
debounceSearchJob = launch {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
Log.d("MM20", "Searching for $query")
|
|
||||||
val provider = provider ?: return@launch
|
|
||||||
isSearchingLocation.value = true
|
isSearchingLocation.value = true
|
||||||
val results = provider
|
locationResults.value = repository.lookupLocation(query)
|
||||||
locationResults.value = results.lookupLocation(query)
|
|
||||||
isSearchingLocation.value = false
|
isSearchingLocation.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,14 +70,14 @@ class WeatherScreenVM(private val context: Application) : AndroidViewModel(conte
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dataStore.data.map { it.weather.provider }.collectLatest {
|
val autoLocation = repository.autoLocation
|
||||||
weatherProvider.postValue(it)
|
val location = repository.location
|
||||||
provider = when (it) {
|
val lastLocation = repository.lastLocation
|
||||||
WeatherSettings.WeatherProvider.OpenWeatherMap -> OpenWeatherMapProvider(context)
|
combine(autoLocation, lastLocation, location) { autoLoc, lastLoc, loc ->
|
||||||
WeatherSettings.WeatherProvider.Here -> HereProvider(context)
|
if (autoLoc) lastLoc
|
||||||
WeatherSettings.WeatherProvider.BrightSky -> BrightskyProvider(context)
|
else loc
|
||||||
else -> MetNoProvider(context)
|
}.collectLatest {
|
||||||
}
|
this@WeatherScreenVM.location.postValue(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
package de.mm20.launcher2.weather
|
package de.mm20.launcher2.weather
|
||||||
|
|
||||||
|
import de.mm20.launcher2.preferences.Settings.WeatherSettings
|
||||||
|
import de.mm20.launcher2.weather.brightsky.BrightskyProvider
|
||||||
|
import de.mm20.launcher2.weather.here.HereProvider
|
||||||
|
import de.mm20.launcher2.weather.metno.MetNoProvider
|
||||||
|
import de.mm20.launcher2.weather.openweathermap.OpenWeatherMapProvider
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val weatherModule = module {
|
val weatherModule = module {
|
||||||
single { WeatherRepository(androidContext(), get()) }
|
single<WeatherRepository> { WeatherRepositoryImpl(androidContext(), get(), get()) }
|
||||||
|
factory { (selectedProvider: WeatherSettings.WeatherProvider) ->
|
||||||
|
when (selectedProvider) {
|
||||||
|
WeatherSettings.WeatherProvider.OpenWeatherMap -> OpenWeatherMapProvider(androidContext())
|
||||||
|
WeatherSettings.WeatherProvider.Here -> HereProvider(androidContext())
|
||||||
|
WeatherSettings.WeatherProvider.BrightSky -> BrightskyProvider(androidContext())
|
||||||
|
else -> MetNoProvider(androidContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,23 +2,95 @@ package de.mm20.launcher2.weather
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.datastore.dataStore
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import kotlinx.coroutines.flow.map
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import de.mm20.launcher2.preferences.Settings.WeatherSettings
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.get
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class WeatherRepository(
|
interface WeatherRepository {
|
||||||
val context: Context,
|
val forecasts: Flow<List<DailyForecast>>
|
||||||
val database: AppDatabase,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val forecasts = database.weatherDao().getForecasts()
|
suspend fun lookupLocation(query: String): List<WeatherLocation>
|
||||||
.map { it.map { Forecast(it) } }
|
|
||||||
.map {
|
val lastLocation: Flow<WeatherLocation?>
|
||||||
groupForecastsPerDay(it)
|
val location: Flow<WeatherLocation?>
|
||||||
|
val autoLocation: Flow<Boolean>
|
||||||
|
|
||||||
|
fun setLocation(location: WeatherLocation)
|
||||||
|
fun setAutoLocation(autoLocation: Boolean)
|
||||||
|
fun setLastLocation(lastLocation: WeatherLocation?)
|
||||||
|
|
||||||
|
fun selectProvider(provider: WeatherSettings.WeatherProvider)
|
||||||
|
|
||||||
|
val selectedProvider: Flow<WeatherSettings.WeatherProvider>
|
||||||
|
}
|
||||||
|
|
||||||
|
class WeatherRepositoryImpl(
|
||||||
|
private val context: Context,
|
||||||
|
private val database: AppDatabase,
|
||||||
|
private val dataStore: LauncherDataStore,
|
||||||
|
) : WeatherRepository, KoinComponent {
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Main + Job())
|
||||||
|
|
||||||
|
private var provider: WeatherProvider<out WeatherLocation>
|
||||||
|
|
||||||
|
override val selectedProvider = dataStore.data.map { it.weather.provider }
|
||||||
|
|
||||||
|
override val forecasts: Flow<List<DailyForecast>>
|
||||||
|
get() = database.weatherDao().getForecasts()
|
||||||
|
.map { it.map { Forecast(it) } }
|
||||||
|
.map {
|
||||||
|
groupForecastsPerDay(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val lastLocation = MutableStateFlow<WeatherLocation?>(null)
|
||||||
|
override val location = MutableStateFlow<WeatherLocation?>(null)
|
||||||
|
override val autoLocation = MutableStateFlow(false)
|
||||||
|
|
||||||
|
override fun setLocation(location: WeatherLocation) {
|
||||||
|
provider.setLocation(location)
|
||||||
|
this.location.value = location
|
||||||
|
provider.resetLastUpdate()
|
||||||
|
requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAutoLocation(autoLocation: Boolean) {
|
||||||
|
provider.autoLocation = autoLocation
|
||||||
|
this.autoLocation.value = autoLocation
|
||||||
|
provider.resetLastUpdate()
|
||||||
|
requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLastLocation(lastLocation: WeatherLocation?) {
|
||||||
|
this.lastLocation.value = lastLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun lookupLocation(query: String): List<WeatherLocation> {
|
||||||
|
return provider.lookupLocation(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun selectProvider(provider: WeatherSettings.WeatherProvider) {
|
||||||
|
scope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setWeather(
|
||||||
|
it.weather.toBuilder()
|
||||||
|
.setProvider(provider)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val weatherRequest =
|
val weatherRequest =
|
||||||
@ -28,6 +100,28 @@ class WeatherRepository(
|
|||||||
"weather",
|
"weather",
|
||||||
ExistingPeriodicWorkPolicy.KEEP, weatherRequest
|
ExistingPeriodicWorkPolicy.KEEP, weatherRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
provider = runBlocking {
|
||||||
|
val selectedProvider = selectedProvider.first()
|
||||||
|
get { parametersOf(selectedProvider) }
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
var providerSetting: WeatherSettings.WeatherProvider? = null
|
||||||
|
selectedProvider.collectLatest {
|
||||||
|
if (it != providerSetting) {
|
||||||
|
providerSetting = it
|
||||||
|
provider = get { parametersOf(it) }
|
||||||
|
location.value = provider.getLocation()
|
||||||
|
lastLocation.value = provider.getLastLocation()
|
||||||
|
autoLocation.value = provider.autoLocation
|
||||||
|
provider.resetLastUpdate()
|
||||||
|
requestUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun groupForecastsPerDay(forecasts: List<Forecast>): List<DailyForecast> {
|
private fun groupForecastsPerDay(forecasts: List<Forecast>): List<DailyForecast> {
|
||||||
@ -71,30 +165,36 @@ class WeatherRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun requestUpdate(context: Context) {
|
private fun requestUpdate() {
|
||||||
val provider = WeatherProvider.getInstance(context) ?: return
|
val weatherRequest = OneTimeWorkRequest.Builder(WeatherUpdateWorker::class.java)
|
||||||
if (provider.isUpdateRequired()) {
|
.addTag("weather")
|
||||||
val weatherRequest = OneTimeWorkRequest.Builder(WeatherUpdateWorker::class.java)
|
.build()
|
||||||
.addTag("weather")
|
WorkManager.getInstance(context).enqueue(weatherRequest)
|
||||||
.build()
|
|
||||||
WorkManager.getInstance(context).enqueue(weatherRequest)
|
|
||||||
} else {
|
|
||||||
Log.d("MM20", "No weather update required")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WeatherUpdateWorker(val context: Context, params: WorkerParameters) :
|
class WeatherUpdateWorker(val context: Context, params: WorkerParameters) :
|
||||||
CoroutineWorker(context, params) {
|
CoroutineWorker(context, params), KoinComponent {
|
||||||
|
val repository: WeatherRepository by inject()
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val provider = WeatherProvider.getInstance(context) ?: return Result.failure()
|
Log.d("MM20", "Requesting weather data")
|
||||||
if (!provider.isAvailable()) return Result.failure()
|
val providerPref = repository.selectedProvider.first()
|
||||||
if (!provider.isUpdateRequired()) return Result.failure()
|
val provider: WeatherProvider<out WeatherLocation> = get { parametersOf(providerPref) }
|
||||||
|
if (!provider.isAvailable()) {
|
||||||
|
Log.d("MM20", "Weather provider is not available")
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
if (!provider.isUpdateRequired()) {
|
||||||
|
Log.d("MM20", "No weather update required")
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
val weatherData = provider.fetchNewWeatherData()
|
val weatherData = provider.fetchNewWeatherData()
|
||||||
return if (weatherData == null) {
|
return if (weatherData == null) {
|
||||||
Log.d("MM20", "Weather update failed")
|
Log.d("MM20", "Weather update failed")
|
||||||
Result.retry()
|
Result.retry()
|
||||||
} else {
|
} else {
|
||||||
|
repository.setLastLocation(provider.getLastLocation())
|
||||||
Log.d("MM20", "Weather update succeeded")
|
Log.d("MM20", "Weather update succeeded")
|
||||||
AppDatabase.getInstance(applicationContext).weatherDao()
|
AppDatabase.getInstance(applicationContext).weatherDao()
|
||||||
.replaceAll(weatherData.map { it.toDatabaseEntity() })
|
.replaceAll(weatherData.map { it.toDatabaseEntity() })
|
||||||
|
|||||||
@ -16,6 +16,5 @@ class WeatherViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun requestUpdate(context: Context) {
|
fun requestUpdate(context: Context) {
|
||||||
repository.requestUpdate(context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user