Group plugins by package
This commit is contained in:
@@ -15,6 +15,7 @@ import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.HelpOutline
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
@@ -80,9 +81,6 @@ fun PreferenceScreen(
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
val systemUiController = rememberSystemUiController()
|
||||
systemUiController.setStatusBarColor(MaterialTheme.colorScheme.surface)
|
||||
systemUiController.setNavigationBarColor(Color.Black)
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -124,7 +122,7 @@ fun PreferenceScreen(
|
||||
activity?.onBackPressed()
|
||||
}
|
||||
}) {
|
||||
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
||||
Icon(imageVector = Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package de.mm20.launcher2.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
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.material3.MaterialTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
@@ -21,6 +25,7 @@ import de.mm20.launcher2.licenses.AppLicense
|
||||
import de.mm20.launcher2.licenses.OpenSourceLicenses
|
||||
import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
|
||||
import de.mm20.launcher2.ui.overlays.OverlayHost
|
||||
@@ -47,6 +52,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.PluginSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.plugins.PluginsSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.searchactions.SearchActionsSettingsScreen
|
||||
@@ -79,6 +85,15 @@ class SettingsActivity : BaseActivity() {
|
||||
) {
|
||||
ProvideSettings {
|
||||
LauncherTheme {
|
||||
val systemBarColor = MaterialTheme.colorScheme.surfaceDim
|
||||
val systemBarColorAlt = MaterialTheme.colorScheme.onSurface
|
||||
val isDarkTheme = LocalDarkTheme.current
|
||||
LaunchedEffect(isDarkTheme, systemBarColor, systemBarColorAlt) {
|
||||
enableEdgeToEdge(
|
||||
if (isDarkTheme) SystemBarStyle.dark(systemBarColor.toArgb())
|
||||
else SystemBarStyle.light(systemBarColor.toArgb(), systemBarColorAlt.toArgb())
|
||||
)
|
||||
}
|
||||
OverlayHost {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
@@ -167,6 +182,9 @@ class SettingsActivity : BaseActivity() {
|
||||
composable("settings/plugins") {
|
||||
PluginsSettingsScreen()
|
||||
}
|
||||
composable("settings/plugins/{id}") {
|
||||
PluginSettingsScreen(it.arguments?.getString("id") ?: return@composable)
|
||||
}
|
||||
composable("settings/about") {
|
||||
AboutSettingsScreen()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
package de.mm20.launcher2.ui.settings.plugins
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.material.icons.rounded.Verified
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
|
||||
@Composable
|
||||
fun PluginSettingsScreen(pluginId: String) {
|
||||
val navController = LocalNavController.current
|
||||
val activity = LocalContext.current as AppCompatActivity
|
||||
val context = LocalContext.current
|
||||
val viewModel: PluginSettingsScreenVM = viewModel()
|
||||
LaunchedEffect(pluginId) {
|
||||
viewModel.init(pluginId)
|
||||
}
|
||||
|
||||
val pluginPackage by viewModel.pluginPackage.collectAsStateWithLifecycle(null)
|
||||
val icon by viewModel.icon.collectAsStateWithLifecycle(null)
|
||||
val types by viewModel.types.collectAsStateWithLifecycle(emptyList())
|
||||
val states by viewModel.states.collectAsStateWithLifecycle(
|
||||
emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
if (navController?.navigateUp() != true) {
|
||||
activity.onBackPressed()
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (pluginPackage?.settings != null) {
|
||||
IconButton(onClick = {
|
||||
pluginPackage?.settings?.let {
|
||||
activity.startActivity(it)
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Settings,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = {
|
||||
viewModel.openAppInfo(context)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Info,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
viewModel.uninstall(context)
|
||||
navController?.navigateUp()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Delete,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(it)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 12.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(end = 12.dp)
|
||||
.size(48.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
pluginPackage?.label ?: "",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
if (pluginPackage?.isOfficial == true) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.secondary,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
)
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Text(
|
||||
"Official",
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondary,
|
||||
)
|
||||
Icon(
|
||||
Icons.Rounded.Verified, null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.onSecondary,
|
||||
)
|
||||
}
|
||||
} else if (pluginPackage?.author != null) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
pluginPackage!!.author!!,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pluginPackage?.description?.let {
|
||||
Text(
|
||||
text = it,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
top = 16.dp,
|
||||
bottom = 24.dp
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(bottom = 24.dp, start = 12.dp, end = 12.dp)
|
||||
) {
|
||||
for (type in types) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(end = 4.dp)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.tertiaryContainer,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
)
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
when (type) {
|
||||
PluginType.FileSearch -> Icons.AutoMirrored.Rounded.InsertDriveFile
|
||||
},
|
||||
null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
)
|
||||
Text(
|
||||
when (type) {
|
||||
PluginType.FileSearch -> "File search"
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
val surfaceColor by animateColorAsState(
|
||||
if (pluginPackage?.enabled == true) {
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceContainer
|
||||
}
|
||||
)
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
color = surfaceColor,
|
||||
) {
|
||||
SwitchPreference(
|
||||
enabled = pluginPackage != null,
|
||||
iconPadding = false,
|
||||
title = "Enable plugin",
|
||||
value = pluginPackage?.enabled == true,
|
||||
onValueChanged = {
|
||||
viewModel.setPluginEnabled(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(pluginPackage?.enabled == true) {
|
||||
PreferenceCategory {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package de.mm20.launcher2.ui.settings.plugins
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.plugin.PluginPackage
|
||||
import de.mm20.launcher2.plugin.PluginState
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val pluginService by inject<PluginService>()
|
||||
|
||||
private var pluginPackageName = MutableStateFlow<String?>(null)
|
||||
|
||||
val pluginPackage: StateFlow<PluginPackage?> = pluginPackageName.flatMapLatest {
|
||||
if (it == null) {
|
||||
emptyFlow()
|
||||
} else {
|
||||
pluginService.getPluginPackage(it)
|
||||
}
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(100), null)
|
||||
|
||||
val icon: Flow<Drawable?> = pluginPackage
|
||||
.distinctUntilChangedBy { it?.packageName }
|
||||
.map {
|
||||
it?.let { pluginService.getPluginPackageIcon(it) }
|
||||
}
|
||||
|
||||
val types: Flow<List<PluginType>> = pluginPackage.map {
|
||||
it?.plugins?.map { it.type }?.distinct() ?: emptyList()
|
||||
}
|
||||
|
||||
val states: Flow<List<PluginState?>> = pluginPackage.map {
|
||||
it?.plugins?.map {
|
||||
pluginService.getPluginState(it)
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
|
||||
fun init(pluginId: String) {
|
||||
this.pluginPackageName.value = pluginId
|
||||
}
|
||||
|
||||
fun setPluginEnabled(enabled: Boolean) {
|
||||
val plugin = pluginPackage.value ?: return
|
||||
if (enabled) {
|
||||
pluginService.enablePluginPackage(plugin)
|
||||
} else {
|
||||
pluginService.disablePluginPackage(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
fun openAppInfo(context: Context) {
|
||||
val plugin = pluginPackage.value ?: return
|
||||
context.tryStartActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.parse("package:${plugin.packageName}")
|
||||
})
|
||||
}
|
||||
|
||||
fun uninstall(context: Context) {
|
||||
val plugin = pluginPackage.value ?: return
|
||||
pluginService.uninstallPluginPackage(context, plugin)
|
||||
}
|
||||
}
|
||||
+63
-23
@@ -2,15 +2,16 @@ package de.mm20.launcher2.ui.settings.plugins
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.material.icons.rounded.Verified
|
||||
import androidx.compose.material3.Icon
|
||||
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
|
||||
@@ -23,19 +24,25 @@ 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.plugin.PluginPackage
|
||||
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.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
|
||||
@Composable
|
||||
fun PluginsSettingsScreen() {
|
||||
val viewModel: PluginsSettingsScreenVM = viewModel()
|
||||
val navController = LocalNavController.current
|
||||
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)
|
||||
val pluginPackages by viewModel.pluginPackages.collectAsState(null)
|
||||
val enabledPackages by viewModel.enabledPluginPackages.collectAsState(emptyList())
|
||||
val disabledPackages by viewModel.disabledPluginPackages.collectAsState(emptyList())
|
||||
PreferenceScreen(title = stringResource(R.string.preference_screen_plugins)) {
|
||||
when {
|
||||
hostInstalled == false -> {
|
||||
@@ -73,7 +80,7 @@ fun PluginsSettingsScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
plugins?.isEmpty() == true -> {
|
||||
pluginPackages?.isEmpty() == true -> {
|
||||
item {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -92,28 +99,61 @@ fun PluginsSettingsScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
else -> {
|
||||
if (enabledPackages.isNotEmpty()) {
|
||||
item {
|
||||
PreferenceCategory("Enabled") {
|
||||
for (plugin in enabledPackages) {
|
||||
PluginPreference(viewModel, plugin)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (disabledPackages.isNotEmpty()) {
|
||||
item {
|
||||
PreferenceCategory("Installed") {
|
||||
for (plugin in disabledPackages) {
|
||||
PluginPreference(viewModel, plugin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PluginPreference(viewModel: PluginsSettingsScreenVM, plugin: PluginPackage) {
|
||||
val navController = LocalNavController.current
|
||||
val icon by remember(plugin.packageName) {
|
||||
viewModel.getIcon(plugin)
|
||||
}.collectAsState(null)
|
||||
Preference(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(plugin.label)
|
||||
if (plugin.isOfficial) {
|
||||
Icon(
|
||||
Icons.Rounded.Verified, null,
|
||||
modifier = Modifier.padding(start = 4.dp).size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
summary = plugin.description?.let { { Text(it) } },
|
||||
icon = {
|
||||
AsyncImage(
|
||||
model = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp)
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
navController?.navigate("settings/plugins/${plugin.packageName}")
|
||||
}
|
||||
)
|
||||
}
|
||||
+16
-13
@@ -3,13 +3,16 @@ package de.mm20.launcher2.ui.settings.plugins
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.ktx.normalize
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.plugin.Plugin
|
||||
import de.mm20.launcher2.plugin.PluginPackage
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@@ -20,23 +23,23 @@ class PluginsSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val hostInstalled = pluginService.isPluginHostInstalled()
|
||||
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Plugins)
|
||||
val plugins = hasPermission.flatMapLatest {
|
||||
if (it) pluginService.getPluginsWithState() else emptyFlow()
|
||||
val pluginPackages = pluginService
|
||||
.getPluginPackages()
|
||||
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(100), 1)
|
||||
|
||||
val enabledPluginPackages = pluginPackages.mapLatest {
|
||||
it.filter { it.enabled }.sortedBy { it.label }
|
||||
}
|
||||
|
||||
fun setPluginEnabled(plugin: Plugin, value: Boolean) {
|
||||
if (value) {
|
||||
pluginService.enablePlugin(plugin)
|
||||
} else {
|
||||
pluginService.disablePlugin(plugin)
|
||||
}
|
||||
val disabledPluginPackages = pluginPackages.mapLatest {
|
||||
it.filter { !it.enabled }.sortedBy { it.label }
|
||||
}
|
||||
|
||||
fun requestPermission(context: Context) {
|
||||
permissionsManager.requestPermission(context as AppCompatActivity, PermissionGroup.Plugins)
|
||||
}
|
||||
|
||||
fun getIcon(plugin: Plugin) = flow {
|
||||
emit(pluginService.getPluginIcon(plugin))
|
||||
fun getIcon(plugin: PluginPackage) = flow {
|
||||
emit(pluginService.getPluginPackageIcon(plugin))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user