Add plugin settings screen
This commit is contained in:
@@ -143,6 +143,7 @@ dependencies {
|
||||
implementation(project(":libs:g-services"))
|
||||
implementation(project(":libs:owncloud"))
|
||||
implementation(project(":services:accounts"))
|
||||
implementation(project(":services:plugins"))
|
||||
implementation(project(":services:backup"))
|
||||
implementation(project(":data:search-actions"))
|
||||
implementation(project(":services:global-actions"))
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user