Add plugin settings screen

This commit is contained in:
MM20
2023-11-06 19:30:41 +01:00
parent 7c20d541cd
commit e7ae751340
20 changed files with 398 additions and 85 deletions
@@ -5,11 +5,13 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -18,7 +20,8 @@ import androidx.compose.ui.unit.dp
fun LargeMessage(
modifier: Modifier = Modifier,
icon: ImageVector,
text: String
text: String,
color: Color = LocalContentColor.current
) {
Column(
modifier = modifier,
@@ -30,12 +33,14 @@ fun LargeMessage(
contentDescription = null,
modifier = Modifier
.padding(bottom = 24.dp)
.size(64.dp)
.size(64.dp),
tint = color
)
Text(
text,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
color = color
)
}
}
@@ -2,21 +2,23 @@ package de.mm20.launcher2.ui.settings
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.core.view.WindowCompat
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.composable
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import de.mm20.launcher2.licenses.AppLicense
import de.mm20.launcher2.licenses.OpenSourceLicenses
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.ui.base.BaseActivity
import de.mm20.launcher2.ui.base.ProvideSettings
import de.mm20.launcher2.ui.locals.LocalNavController
@@ -45,6 +47,7 @@ import de.mm20.launcher2.ui.settings.license.LicenseScreen
import de.mm20.launcher2.ui.settings.log.LogScreen
import de.mm20.launcher2.ui.settings.main.MainSettingsScreen
import de.mm20.launcher2.ui.settings.media.MediaIntegrationSettingsScreen
import de.mm20.launcher2.ui.settings.plugins.PluginsSettingsScreen
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
import de.mm20.launcher2.ui.settings.searchactions.SearchActionsSettingsScreen
import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen
@@ -53,20 +56,17 @@ import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsScreen
import de.mm20.launcher2.ui.settings.wikipedia.WikipediaSettingsScreen
import de.mm20.launcher2.ui.theme.LauncherTheme
import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
import org.koin.android.ext.android.inject
import java.net.URLDecoder
import java.util.UUID
class SettingsActivity : BaseActivity() {
private val dataStore: LauncherDataStore by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
val navController = rememberAnimatedNavController()
val navController = rememberNavController()
LaunchedEffect(intent) {
intent.getStringExtra(EXTRA_ROUTE)
@@ -80,13 +80,21 @@ class SettingsActivity : BaseActivity() {
ProvideSettings {
LauncherTheme {
OverlayHost {
AnimatedNavHost(
NavHost(
navController = navController,
startDestination = "settings",
exitTransition = { fadeOut(tween(300, 300)) },
enterTransition = { fadeIn(tween(200)) },
popEnterTransition = { fadeIn(tween(0)) },
popExitTransition = { fadeOut(tween(200)) },
exitTransition = {
fadeOut() + scaleOut(targetScale = 0.5f)
},
enterTransition = {
slideInHorizontally { it }
},
popEnterTransition = {
fadeIn() + scaleIn(initialScale = 0.5f)
},
popExitTransition = {
slideOutHorizontally { it }
},
) {
composable("settings") {
MainSettingsScreen()
@@ -156,6 +164,9 @@ class SettingsActivity : BaseActivity() {
composable("settings/integrations") {
IntegrationsSettingsScreen()
}
composable("settings/plugins") {
PluginsSettingsScreen()
}
composable("settings/about") {
AboutSettingsScreen()
}
@@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.settings.main
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Apps
import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.Extension
import androidx.compose.material.icons.rounded.Gesture
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Info
@@ -74,6 +75,14 @@ fun MainSettingsScreen() {
navController?.navigate("settings/integrations")
}
)
Preference(
icon = Icons.Rounded.Extension,
title = stringResource(id = R.string.preference_screen_plugins),
summary = stringResource(id = R.string.preference_screen_plugins_summary),
onClick = {
navController?.navigate("settings/plugins")
}
)
Preference(
icon = Icons.Rounded.SettingsBackupRestore,
title = stringResource(id = R.string.preference_screen_backup),
@@ -0,0 +1,119 @@
package de.mm20.launcher2.ui.settings.plugins
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Extension
import androidx.compose.material.icons.rounded.ExtensionOff
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
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.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.LargeMessage
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
@Composable
fun PluginsSettingsScreen() {
val viewModel: PluginsSettingsScreenVM = viewModel()
val hostInstalled by viewModel.hostInstalled.collectAsState(null)
val hasPermission by viewModel.hasPermission.collectAsState(null)
val context = LocalContext.current
val plugins by viewModel.plugins.collectAsState(null)
PreferenceScreen(title = stringResource(R.string.preference_screen_plugins)) {
when {
hostInstalled == false -> {
item {
Column(
modifier = Modifier
.fillMaxWidth()
.fillParentMaxHeight()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
LargeMessage(
icon = Icons.Rounded.ExtensionOff,
text = stringResource(R.string.plugin_host_not_installed),
color = MaterialTheme.colorScheme.secondary
)
}
}
}
hasPermission == false -> {
item {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
MissingPermissionBanner(
text = stringResource(R.string.missing_permission_plugins),
onClick = { viewModel.requestPermission(context) }
)
}
}
}
plugins?.isEmpty() == true -> {
item {
Column(
modifier = Modifier
.fillMaxWidth()
.fillParentMaxHeight()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
LargeMessage(
icon = Icons.Rounded.Extension,
text = stringResource(R.string.no_plugins_installed),
color = MaterialTheme.colorScheme.secondary
)
}
}
}
plugins != null -> {
items(plugins!!) { item ->
val icon by remember(item.plugin.authority) {
viewModel.getIcon(item.plugin)
}.collectAsState(null)
Preference(
title = { Text(item.plugin.label) },
summary = item.plugin.description?.let { { Text(it) } },
controls = {
Switch(checked = item.plugin.enabled, onCheckedChange = {
viewModel.setPluginEnabled(item.plugin, it)
})
},
icon = {
AsyncImage(model = icon, contentDescription = null, modifier = Modifier.size(36.dp))
},
onClick = {
viewModel.setPluginEnabled(item.plugin, !item.plugin.enabled)
}
)
}
}
}
}
}
@@ -0,0 +1,42 @@
package de.mm20.launcher2.ui.settings.plugins
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.plugin.Plugin
import de.mm20.launcher2.plugins.PluginService
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class PluginsSettingsScreenVM : ViewModel(), KoinComponent {
private val pluginService: PluginService by inject()
private val permissionsManager: PermissionsManager by inject()
val hostInstalled = pluginService.isPluginHostInstalled()
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Plugins)
val plugins = hasPermission.flatMapLatest {
if (it) pluginService.getPluginsWithState() else emptyFlow()
}
fun setPluginEnabled(plugin: Plugin, value: Boolean) {
if (value) {
pluginService.enablePlugin(plugin)
} else {
pluginService.disablePlugin(plugin)
}
}
fun requestPermission(context: Context) {
permissionsManager.requestPermission(context as AppCompatActivity, PermissionGroup.Plugins)
}
fun getIcon(plugin: Plugin) = flow {
emit(pluginService.getPluginIcon(plugin))
}
}