From 2b3323bf7fad72bd4a64ddb85fbe6f520c3082cf Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 2 Jan 2022 15:52:17 +0100 Subject: [PATCH] Refactor weather to use new datastore preferences --- .../ui/settings/weather/WeatherScreenVM.kt | 73 +++------ .../java/de/mm20/launcher2/weather/Module.kt | 15 +- .../launcher2/weather/WeatherRepository.kt | 148 +++++++++++++++--- .../launcher2/weather/WeatherViewModel.kt | 1 - 4 files changed, 162 insertions(+), 75 deletions(-) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt index 914a3532..c286fe6e 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt @@ -1,39 +1,24 @@ 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.ViewModel import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.Settings.WeatherSettings import de.mm20.launcher2.weather.WeatherLocation -import de.mm20.launcher2.weather.WeatherProvider -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 de.mm20.launcher2.weather.WeatherRepository import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import org.koin.core.component.KoinComponent import org.koin.core.component.inject import kotlin.coroutines.coroutineContext -class WeatherScreenVM(private val context: Application) : AndroidViewModel(context), KoinComponent { - val dataStore: LauncherDataStore by inject() - - val weatherProvider = MutableLiveData(null) - fun setWeatherProvider(provider: WeatherSettings.WeatherProvider) { - viewModelScope.launch { - dataStore.updateData { - it.toBuilder() - .setWeather(it.weather.toBuilder().setProvider(provider)) - .build() - } - } - } +class WeatherScreenVM : ViewModel(), KoinComponent { + private val repository: WeatherRepository by inject() + private val dataStore: LauncherDataStore by inject() val imperialUnits = dataStore.data.map { it.weather.imperialUnits }.asLiveData() fun setImperialUnits(imperialUnits: Boolean) { @@ -46,30 +31,23 @@ class WeatherScreenVM(private val context: Application) : AndroidViewModel(conte } } - private var provider: WeatherProvider? = null - set(value) { - field = value - if (value != null) { - val autoLocation = value.autoLocation - this.autoLocation.postValue(autoLocation) - location.postValue(if (autoLocation) value.getLastLocation() else value.getLocation()) - } - } + val weatherProvider = repository.selectedProvider.asLiveData() + fun setWeatherProvider(provider: WeatherSettings.WeatherProvider) { + repository.selectProvider(provider) + } - val autoLocation = MutableLiveData(false) + val autoLocation = repository.autoLocation.asLiveData() fun setAutoLocation(autoLocation: Boolean) { - provider?.autoLocation = autoLocation - location.postValue(if (autoLocation) provider?.getLastLocation() else provider?.getLocation()) - this.autoLocation.postValue(autoLocation) + repository.setAutoLocation(autoLocation) } val location = MutableLiveData(null) fun setLocation(location: WeatherLocation) { - provider?.setLocation(location) - this.location.postValue(location) + locationResults.postValue(emptyList()) + repository.setLocation(location) } - private var debounceSearchJob : Job? = null + private var debounceSearchJob: Job? = null suspend fun searchLocation(query: String) { debounceSearchJob?.cancelAndJoin() if (query.isBlank()) { @@ -80,11 +58,8 @@ class WeatherScreenVM(private val context: Application) : AndroidViewModel(conte withContext(coroutineContext) { debounceSearchJob = launch { delay(1000) - Log.d("MM20", "Searching for $query") - val provider = provider ?: return@launch isSearchingLocation.value = true - val results = provider - locationResults.value = results.lookupLocation(query) + locationResults.value = repository.lookupLocation(query) isSearchingLocation.value = false } } @@ -95,14 +70,14 @@ class WeatherScreenVM(private val context: Application) : AndroidViewModel(conte init { viewModelScope.launch { - dataStore.data.map { it.weather.provider }.collectLatest { - weatherProvider.postValue(it) - provider = when (it) { - WeatherSettings.WeatherProvider.OpenWeatherMap -> OpenWeatherMapProvider(context) - WeatherSettings.WeatherProvider.Here -> HereProvider(context) - WeatherSettings.WeatherProvider.BrightSky -> BrightskyProvider(context) - else -> MetNoProvider(context) - } + val autoLocation = repository.autoLocation + val location = repository.location + val lastLocation = repository.lastLocation + combine(autoLocation, lastLocation, location) { autoLoc, lastLoc, loc -> + if (autoLoc) lastLoc + else loc + }.collectLatest { + this@WeatherScreenVM.location.postValue(it) } } } diff --git a/weather/src/main/java/de/mm20/launcher2/weather/Module.kt b/weather/src/main/java/de/mm20/launcher2/weather/Module.kt index 8db832d0..95869296 100644 --- a/weather/src/main/java/de/mm20/launcher2/weather/Module.kt +++ b/weather/src/main/java/de/mm20/launcher2/weather/Module.kt @@ -1,8 +1,21 @@ 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.dsl.module val weatherModule = module { - single { WeatherRepository(androidContext(), get()) } + single { 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()) + } + } } \ No newline at end of file diff --git a/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt b/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt index 59af3e49..dbf3eaa5 100644 --- a/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt +++ b/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt @@ -2,23 +2,95 @@ package de.mm20.launcher2.weather import android.content.Context import android.util.Log +import androidx.datastore.dataStore import androidx.work.* import de.mm20.launcher2.database.AppDatabase -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import de.mm20.launcher2.preferences.LauncherDataStore +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.concurrent.TimeUnit -class WeatherRepository( - val context: Context, - val database: AppDatabase, -) { +interface WeatherRepository { + val forecasts: Flow> - val forecasts = database.weatherDao().getForecasts() - .map { it.map { Forecast(it) } } - .map { - groupForecastsPerDay(it) + suspend fun lookupLocation(query: String): List + + val lastLocation: Flow + val location: Flow + val autoLocation: Flow + + fun setLocation(location: WeatherLocation) + fun setAutoLocation(autoLocation: Boolean) + fun setLastLocation(lastLocation: WeatherLocation?) + + fun selectProvider(provider: WeatherSettings.WeatherProvider) + + val selectedProvider: Flow +} + +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 + + override val selectedProvider = dataStore.data.map { it.weather.provider } + + override val forecasts: Flow> + get() = database.weatherDao().getForecasts() + .map { it.map { Forecast(it) } } + .map { + groupForecastsPerDay(it) + } + + override val lastLocation = MutableStateFlow(null) + override val location = MutableStateFlow(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 { + return provider.lookupLocation(query) + } + + override fun selectProvider(provider: WeatherSettings.WeatherProvider) { + scope.launch { + dataStore.updateData { + it.toBuilder() + .setWeather( + it.weather.toBuilder() + .setProvider(provider) + ) + .build() + } } + } init { val weatherRequest = @@ -28,6 +100,28 @@ class WeatherRepository( "weather", 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): List { @@ -71,30 +165,36 @@ class WeatherRepository( } - fun requestUpdate(context: Context) { - val provider = WeatherProvider.getInstance(context) ?: return - if (provider.isUpdateRequired()) { - val weatherRequest = OneTimeWorkRequest.Builder(WeatherUpdateWorker::class.java) - .addTag("weather") - .build() - WorkManager.getInstance(context).enqueue(weatherRequest) - } else { - Log.d("MM20", "No weather update required") - } + private fun requestUpdate() { + val weatherRequest = OneTimeWorkRequest.Builder(WeatherUpdateWorker::class.java) + .addTag("weather") + .build() + WorkManager.getInstance(context).enqueue(weatherRequest) } } class WeatherUpdateWorker(val context: Context, params: WorkerParameters) : - CoroutineWorker(context, params) { + CoroutineWorker(context, params), KoinComponent { + val repository: WeatherRepository by inject() + override suspend fun doWork(): Result { - val provider = WeatherProvider.getInstance(context) ?: return Result.failure() - if (!provider.isAvailable()) return Result.failure() - if (!provider.isUpdateRequired()) return Result.failure() + Log.d("MM20", "Requesting weather data") + val providerPref = repository.selectedProvider.first() + val provider: WeatherProvider = 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() return if (weatherData == null) { Log.d("MM20", "Weather update failed") Result.retry() } else { + repository.setLastLocation(provider.getLastLocation()) Log.d("MM20", "Weather update succeeded") AppDatabase.getInstance(applicationContext).weatherDao() .replaceAll(weatherData.map { it.toDatabaseEntity() }) diff --git a/weather/src/main/java/de/mm20/launcher2/weather/WeatherViewModel.kt b/weather/src/main/java/de/mm20/launcher2/weather/WeatherViewModel.kt index 94d1fd7b..1e29982b 100644 --- a/weather/src/main/java/de/mm20/launcher2/weather/WeatherViewModel.kt +++ b/weather/src/main/java/de/mm20/launcher2/weather/WeatherViewModel.kt @@ -16,6 +16,5 @@ class WeatherViewModel(application: Application) : AndroidViewModel(application) } fun requestUpdate(context: Context) { - repository.requestUpdate(context) } } \ No newline at end of file