Add weather plugin specific settings
This commit is contained in:
parent
f984f68b34
commit
35ff1bce1a
@ -20,6 +20,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
|
||||
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Error
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
@ -40,6 +41,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
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.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@ -48,7 +50,9 @@ import coil.compose.AsyncImage
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.plugin.PluginState
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
@ -76,6 +80,11 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
|
||||
val weatherPlugins by viewModel.weatherPlugins.collectAsStateWithLifecycle(
|
||||
emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
|
||||
val requestPermissionStarter =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
@ -87,6 +96,10 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
null
|
||||
)
|
||||
|
||||
val weatherProviderId by viewModel.weatherProvider.collectAsStateWithLifecycle(
|
||||
null
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@ -286,7 +299,7 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
AnimatedVisibility(pluginPackage?.enabled == true && hasPermission == true) {
|
||||
if (filePlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
"File search",
|
||||
stringResource(R.string.plugin_type_filesearch),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in filePlugins) {
|
||||
@ -294,7 +307,7 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: "You need to setup this plugin first",
|
||||
text = state.message ?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
@ -311,7 +324,7 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
} else if (state is PluginState.Error) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = "This plugin isn't working correctly",
|
||||
text = stringResource(R.string.plugin_state_error),
|
||||
icon = Icons.Rounded.Error,
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
)
|
||||
@ -334,6 +347,58 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (weatherPlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
stringResource(R.string.plugin_type_weather),
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in weatherPlugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else if (state is PluginState.Error) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.plugin_state_error),
|
||||
icon = Icons.Rounded.Error,
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = state is PluginState.Ready && weatherProviderId != plugin.plugin.authority,
|
||||
iconPadding = false,
|
||||
summary = if (weatherProviderId != plugin.plugin.authority) {
|
||||
stringResource(R.string.plugin_weather_provider_enable)
|
||||
} else {
|
||||
stringResource(R.string.plugin_weather_provider_enabled)
|
||||
}
|
||||
)
|
||||
}
|
||||
Preference(
|
||||
title = stringResource(R.string.widget_config_weather_integration_settings),
|
||||
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
||||
onClick = {
|
||||
navController?.navigate("settings/integrations/weather")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import de.mm20.launcher2.plugin.PluginState
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import de.mm20.launcher2.plugins.PluginWithState
|
||||
import de.mm20.launcher2.weather.settings.WeatherSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@ -30,6 +31,7 @@ import org.koin.core.component.inject
|
||||
class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val pluginService by inject<PluginService>()
|
||||
private val fileSearchSettings: FileSearchSettings by inject()
|
||||
private val weatherSettings: WeatherSettings by inject()
|
||||
|
||||
private var pluginPackageName = MutableStateFlow<String?>(null)
|
||||
|
||||
@ -70,6 +72,11 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
it.filter { it.plugin.type == PluginType.FileSearch }
|
||||
}
|
||||
|
||||
val weatherPlugins = states
|
||||
.map {
|
||||
it.filter { it.plugin.type == PluginType.Weather }
|
||||
}
|
||||
|
||||
|
||||
fun init(pluginId: String) {
|
||||
this.pluginPackageName.value = pluginId
|
||||
@ -101,4 +108,9 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
fun setFileSearchPluginEnabled(authority: String, enabled: Boolean) {
|
||||
fileSearchSettings.setPluginEnabled(authority, enabled)
|
||||
}
|
||||
|
||||
val weatherProvider = weatherSettings.providerId
|
||||
fun setWeatherProvider(providerId: String) {
|
||||
weatherSettings.setProviderId(providerId)
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,27 @@
|
||||
package de.mm20.launcher2.ui.settings.weather
|
||||
|
||||
import android.app.PendingIntent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
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.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.plugin.PluginState
|
||||
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.Banner
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.preferences.*
|
||||
import de.mm20.launcher2.weather.WeatherLocation
|
||||
@ -25,12 +34,36 @@ fun WeatherIntegrationSettingsScreen() {
|
||||
|
||||
val availableProviders by viewModel.availableProviders.collectAsState(emptyList())
|
||||
|
||||
val pluginState by viewModel.weatherProviderPluginState.collectAsStateWithLifecycle(
|
||||
null,
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
|
||||
PreferenceScreen(
|
||||
title = stringResource(R.string.preference_screen_weatherwidget),
|
||||
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/integrations/weather"
|
||||
) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
val state = pluginState?.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: stringResource(R.string.plugin_state_setup_required),
|
||||
icon = Icons.Rounded.Info,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.plugin_action_setup))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
val weatherProvider by viewModel.weatherProvider.collectAsState()
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_weather_provider),
|
||||
|
||||
@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.weather.WeatherLocation
|
||||
import de.mm20.launcher2.weather.WeatherProviderInfo
|
||||
@ -15,9 +16,11 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMap
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
@ -25,6 +28,7 @@ import org.koin.core.component.inject
|
||||
class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val repository: WeatherRepository by inject()
|
||||
private val weatherSettings: WeatherSettings by inject()
|
||||
private val pluginService: PluginService by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
@ -36,6 +40,10 @@ class WeatherIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
weatherSettings.setProviderId(provider)
|
||||
}
|
||||
|
||||
val weatherProviderPluginState = weatherProvider.flatMapLatest {
|
||||
it?.let { pluginService.getPluginWithState(it) } ?: flowOf(null)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
|
||||
val imperialUnits = dataStore.data.map { it.weather.imperialUnits }
|
||||
fun setImperialUnits(imperialUnits: Boolean) {
|
||||
viewModelScope.launch {
|
||||
|
||||
@ -852,4 +852,11 @@
|
||||
<string name="shortcut_label_unavailable">Unavailable</string>
|
||||
<!-- %1$s: app name -->
|
||||
<string name="shortcut_unavailable_description">This shortcut is unavailable because %1$s isn\'t the default launcher</string>
|
||||
<string name="plugin_state_error">This plugin isn\'t working correctly</string>
|
||||
<string name="plugin_state_setup_required">You need to setup this plugin first</string>
|
||||
<string name="plugin_action_setup">Set up</string>
|
||||
<string name="plugin_type_filesearch">File search</string>
|
||||
<string name="plugin_type_weather">Weather provider</string>
|
||||
<string name="plugin_weather_provider_enable">Set as weather provider</string>
|
||||
<string name="plugin_weather_provider_enabled">Currently set as weather provider</string>
|
||||
</resources>
|
||||
@ -25,7 +25,7 @@ interface PluginDao {
|
||||
): Flow<List<PluginEntity>>
|
||||
|
||||
@Query("SELECT * FROM Plugins WHERE authority = :authority")
|
||||
fun get(authority: String): Flow<PluginEntity>
|
||||
fun get(authority: String): Flow<PluginEntity?>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertMany(plugins: List<PluginEntity>)
|
||||
|
||||
@ -31,7 +31,7 @@ internal class PluginRepositoryImpl(
|
||||
}
|
||||
|
||||
override fun get(authority: String): Flow<Plugin?> {
|
||||
return dao.get(authority).map { Plugin(it) }
|
||||
return dao.get(authority).map { it?.let { Plugin(it) } }
|
||||
}
|
||||
|
||||
override fun insertMany(plugins: List<Plugin>): Job {
|
||||
|
||||
@ -46,6 +46,10 @@ interface PluginService {
|
||||
enabled: Boolean? = null,
|
||||
): Flow<List<PluginWithState>>
|
||||
|
||||
fun getPluginWithState(
|
||||
authority: String,
|
||||
): Flow<PluginWithState?>
|
||||
|
||||
fun getPluginPackages(): Flow<List<PluginPackage>>
|
||||
fun getPluginPackage(packageName: String): Flow<PluginPackage?>
|
||||
suspend fun getPluginState(plugin: Plugin): PluginState?
|
||||
@ -132,6 +136,17 @@ internal class PluginServiceImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPluginWithState(authority: String): Flow<PluginWithState?> {
|
||||
return repository.get(authority).map {
|
||||
it?.let {
|
||||
PluginWithState(
|
||||
plugin = it,
|
||||
state = getPluginState(it),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPluginState(plugin: Plugin): PluginState {
|
||||
val bundle =
|
||||
try {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user