Refactor weather module

This commit is contained in:
MM20
2023-12-22 00:25:41 +01:00
parent bba805ccd6
commit eb1123ab73
21 changed files with 734 additions and 882 deletions
@@ -4,12 +4,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.weather.WeatherLocation
import de.mm20.launcher2.weather.WeatherRepository
import de.mm20.launcher2.weather.settings.WeatherSettings
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.first
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlin.coroutines.coroutineContext
class WeatherLocationSearchDialogVM: ViewModel(), KoinComponent {
private val weatherSettings: WeatherSettings by inject()
private val repository: WeatherRepository by inject()
val isSearchingLocation = mutableStateOf(false)
@@ -27,7 +30,7 @@ class WeatherLocationSearchDialogVM: ViewModel(), KoinComponent {
debounceSearchJob = launch {
delay(1000)
isSearchingLocation.value = true
locationResults.value = repository.lookupLocation(query)
locationResults.value = repository.searchLocations(query).first()
isSearchingLocation.value = false
}
}
@@ -35,7 +38,6 @@ class WeatherLocationSearchDialogVM: ViewModel(), KoinComponent {
fun setLocation(location: WeatherLocation) {
locationResults.value = emptyList()
repository.setAutoLocation(false)
repository.setLocation(location)
weatherSettings.setLocation(location)
}
}
@@ -9,6 +9,7 @@ import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.weather.DailyForecast
import de.mm20.launcher2.weather.Forecast
import de.mm20.launcher2.weather.WeatherRepository
import de.mm20.launcher2.weather.settings.WeatherSettings
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
@@ -21,6 +22,7 @@ import kotlin.math.min
class WeatherWidgetVM : ViewModel(), KoinComponent {
private val weatherRepository: WeatherRepository by inject()
private val weatherSettings: WeatherSettings by inject()
private val permissionsManager: PermissionsManager by inject()
@@ -58,7 +60,7 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
currentForecast.value = getCurrentlySelectedForecast()
}
private val forecastsFlow = weatherRepository.forecasts
private val forecastsFlow = weatherRepository.getDailyForecasts()
/**
* All available forecasts, grouped by day
@@ -106,7 +108,7 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
fun requestLocationPermission(context: AppCompatActivity) {
permissionsManager.requestPermission(context, PermissionGroup.Location)
}
val autoLocation = weatherRepository.autoLocation
val autoLocation = weatherSettings.autoLocation
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }
@@ -18,6 +18,7 @@ import java.security.MessageDigest
fun BuildInfoSettingsScreen() {
val viewModel: BuildInfoSettingsScreenVM = viewModel()
val context = LocalContext.current
val buildFeatures by viewModel.buildFeatures.collectAsState(emptyMap())
PreferenceScreen(title = stringResource(R.string.preference_screen_buildinfo)) {
item {
Preference(title = "Build type", summary = BuildConfig.BUILD_TYPE)
@@ -47,7 +48,7 @@ fun BuildInfoSettingsScreen() {
}
item {
PreferenceCategory(title = "Features") {
for (feature in viewModel.buildFeatures) {
for (feature in buildFeatures) {
Preference(
title = feature.key,
summary = if (feature.value) "YES" else "NO"
@@ -5,6 +5,7 @@ import de.mm20.launcher2.accounts.AccountType
import de.mm20.launcher2.accounts.AccountsRepository
import de.mm20.launcher2.preferences.Settings.WeatherSettings.WeatherProvider
import de.mm20.launcher2.weather.WeatherRepository
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -12,12 +13,14 @@ class BuildInfoSettingsScreenVM : ViewModel(), KoinComponent {
private val accountsRepository: AccountsRepository by inject()
private val weatherRepository: WeatherRepository by inject()
private val availableWeatherProviders = weatherRepository.getAvailableProviders()
private val availableWeatherProviders = weatherRepository.getProviders()
val buildFeatures = mapOf(
"Accounts: Google" to accountsRepository.isSupported(AccountType.Google),
"Weather providers: HERE" to availableWeatherProviders.contains(WeatherProvider.Here),
"Weather providers: Met No" to availableWeatherProviders.contains(WeatherProvider.MetNo),
"Weather providers: OpenWeatherMap" to availableWeatherProviders.contains(WeatherProvider.OpenWeatherMap),
)
val buildFeatures = availableWeatherProviders.map {
mapOf(
"Accounts: Google" to accountsRepository.isSupported(AccountType.Google),
"Weather providers: HERE" to it.any { it.id == "here" },
"Weather providers: Met No" to it.any { it.id == "metno" },
"Weather providers: OpenWeatherMap" to it.any { it.id == "owm" },
)
}
}
@@ -8,20 +8,23 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.Settings.WeatherSettings.WeatherProvider
import de.mm20.launcher2.ui.BuildConfig
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.*
import de.mm20.launcher2.weather.WeatherLocation
import de.mm20.launcher2.weather.WeatherProviderInfo
@Composable
fun WeatherIntegrationSettingsScreen() {
val viewModel: WeatherIntegrationSettingsScreenVM = viewModel()
val context = LocalContext.current
val availableProviders by viewModel.availableProviders.collectAsState(emptyList())
PreferenceScreen(
title = stringResource(R.string.preference_screen_weatherwidget),
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/integrations/weather"
@@ -31,14 +34,8 @@ fun WeatherIntegrationSettingsScreen() {
val weatherProvider by viewModel.weatherProvider.collectAsState()
ListPreference(
title = stringResource(R.string.preference_weather_provider),
items = viewModel.availableProviders.map {
when (it) {
WeatherProvider.MetNo -> stringResource(R.string.provider_metno)
WeatherProvider.OpenWeatherMap -> stringResource(R.string.provider_openweathermap)
WeatherProvider.Here -> stringResource(R.string.provider_here)
WeatherProvider.BrightSky -> stringResource(R.string.provider_brightsky)
else -> "Unknown provider"
} to it
items = availableProviders.map{
it.name to it.id
},
onValueChanged = {
if (it != null) viewModel.setWeatherProvider(it)
@@ -78,7 +75,7 @@ fun WeatherIntegrationSettingsScreen() {
viewModel.setAutoLocation(it)
}
)
val location by viewModel.location
val location by viewModel.location.collectAsStateWithLifecycle()
LocationPreference(
title = stringResource(R.string.preference_location),
value = location,
@@ -104,13 +101,13 @@ fun WeatherIntegrationSettingsScreen() {
@Composable
fun LocationPreference(
title: String,
value: WeatherLocation?,
value: String?,
enabled: Boolean = true
) {
var showDialog by remember { mutableStateOf(false) }
Preference(
title = title,
summary = value?.name,
summary = value,
enabled = enabled,
onClick = {
showDialog = true
@@ -7,13 +7,16 @@ import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings.WeatherSettings
import de.mm20.launcher2.weather.WeatherLocation
import de.mm20.launcher2.weather.WeatherProviderInfo
import de.mm20.launcher2.weather.WeatherRepository
import de.mm20.launcher2.weather.settings.WeatherSettings
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.koin.core.component.KoinComponent
@@ -21,15 +24,16 @@ import org.koin.core.component.inject
class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
private val repository: WeatherRepository by inject()
private val weatherSettings: WeatherSettings by inject()
private val permissionsManager: PermissionsManager by inject()
private val dataStore: LauncherDataStore by inject()
val availableProviders = repository.getAvailableProviders()
val availableProviders = repository.getProviders()
val weatherProvider = repository.selectedProvider
val weatherProvider = weatherSettings.providerId
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setWeatherProvider(provider: WeatherSettings.WeatherProvider) {
repository.selectProvider(provider)
fun setWeatherProvider(provider: String) {
weatherSettings.setProviderId(provider)
}
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }
@@ -43,13 +47,19 @@ class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
}
}
val autoLocation = repository.autoLocation
val autoLocation = weatherSettings.autoLocation
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
fun setAutoLocation(autoLocation: Boolean) {
repository.setAutoLocation(autoLocation)
weatherSettings.setAutoLocation(autoLocation)
}
val location = mutableStateOf<WeatherLocation?>(null)
val location = weatherSettings.autoLocation.flatMapLatest {
if (it) {
repository.getForecasts(limit = 1).map { it.firstOrNull()?.location }
} else {
weatherSettings.location.map { it?.name }
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val hasLocationPermission = permissionsManager.hasPermission(PermissionGroup.Location)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
@@ -59,22 +69,8 @@ class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
}
init {
viewModelScope.launch {
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@WeatherIntegrationSettingsScreenVM.location.value = it
}
}
}
fun clearWeatherData() {
repository.clearForecasts()
repository.deleteForecasts()
}
}