Add file plugin settings screens
This commit is contained in:
+2
-1
@@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp
|
||||
@Composable
|
||||
fun PreferenceCategory(
|
||||
title: String? = null,
|
||||
iconPadding: Boolean = true,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
Column {
|
||||
@@ -21,7 +22,7 @@ fun PreferenceCategory(
|
||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 56.dp),
|
||||
modifier = Modifier.padding(start = if (iconPadding) 56.dp else 0.dp),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
|
||||
+45
-1
@@ -1,12 +1,14 @@
|
||||
package de.mm20.launcher2.ui.settings.filesearch
|
||||
|
||||
import android.app.PendingIntent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.AccountBox
|
||||
import androidx.compose.material.icons.rounded.ErrorOutline
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -19,10 +21,13 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.accounts.AccountType
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.plugin.PluginState
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.Banner
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
@@ -40,6 +45,13 @@ fun FileSearchSettingsScreen() {
|
||||
viewModel.onResume()
|
||||
}
|
||||
}
|
||||
|
||||
val plugins by viewModel.availablePlugins.collectAsStateWithLifecycle(
|
||||
emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED,
|
||||
)
|
||||
val enabledPlugins by viewModel.enabledPlugins.collectAsStateWithLifecycle(null)
|
||||
|
||||
val loading by viewModel.loading
|
||||
PreferenceScreen(title = stringResource(R.string.preference_search_files)) {
|
||||
if (loading == true) {
|
||||
@@ -174,6 +186,38 @@ fun FileSearchSettingsScreen() {
|
||||
enabled = googleAccount != null
|
||||
)
|
||||
}
|
||||
for (plugin in plugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: "You need to setup this plugin first",
|
||||
icon = Icons.Rounded.ErrorOutline,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text("Set up")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setPluginEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-2
@@ -10,9 +10,9 @@ import de.mm20.launcher2.accounts.AccountsRepository
|
||||
import de.mm20.launcher2.files.settings.FileSearchSettings
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -22,6 +22,7 @@ class FileSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val fileSearchSettings: FileSearchSettings by inject()
|
||||
private val accountsRepository: AccountsRepository by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val pluginService: PluginService by inject()
|
||||
|
||||
val hasFilePermission = permissionsManager.hasPermission(PermissionGroup.ExternalStorage)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||
@@ -33,6 +34,14 @@ class FileSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
val googleAvailable = accountsRepository.isSupported(AccountType.Google)
|
||||
|
||||
val availablePlugins = pluginService.getPluginsWithState(
|
||||
type = PluginType.FileSearch,
|
||||
enabled = true,
|
||||
)
|
||||
|
||||
val enabledPlugins = fileSearchSettings.enabledPlugins
|
||||
|
||||
|
||||
fun onResume() {
|
||||
viewModelScope.launch {
|
||||
nextcloudAccount.value =
|
||||
@@ -75,4 +84,8 @@ class FileSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
fun login(context: AppCompatActivity, accountType: AccountType) {
|
||||
accountsRepository.signin(context, accountType)
|
||||
}
|
||||
|
||||
fun setPluginEnabled(authority: String, enabled: Boolean) {
|
||||
fileSearchSettings.setPluginEnabled(authority, enabled)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.mm20.launcher2.ui.settings.plugins
|
||||
|
||||
import android.app.PendingIntent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
@@ -16,6 +17,8 @@ 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.ErrorOutline
|
||||
import androidx.compose.material.icons.rounded.FileCopy
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.material.icons.rounded.Verified
|
||||
@@ -25,6 +28,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -37,7 +41,10 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.component.Banner
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
@@ -55,11 +62,14 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
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(
|
||||
|
||||
val filePlugins by viewModel.filePlugins.collectAsStateWithLifecycle(
|
||||
emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
|
||||
val enabledFileSearchPlugins by viewModel.enabledFileSearchPlugins.collectAsStateWithLifecycle(null)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -247,8 +257,45 @@ fun PluginSettingsScreen(pluginId: String) {
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(pluginPackage?.enabled == true) {
|
||||
PreferenceCategory {
|
||||
|
||||
if (filePlugins.isNotEmpty()) {
|
||||
PreferenceCategory(
|
||||
"File search",
|
||||
iconPadding = false,
|
||||
) {
|
||||
for (plugin in filePlugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
Banner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = state.message ?: "You need to setup this plugin first",
|
||||
icon = Icons.Rounded.ErrorOutline,
|
||||
primaryAction = {
|
||||
TextButton(onClick = {
|
||||
try {
|
||||
state.setupActivity.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}) {
|
||||
Text("Set up")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = plugin.plugin.label,
|
||||
enabled = enabledFileSearchPlugins != null && state is PluginState.Ready,
|
||||
summary = (state as? PluginState.Ready)?.text
|
||||
?: (state as? PluginState.SetupRequired)?.message
|
||||
?: plugin.plugin.description,
|
||||
value = enabledFileSearchPlugins?.contains(plugin.plugin.authority) == true && state is PluginState.Ready,
|
||||
onValueChanged = {
|
||||
viewModel.setFileSearchPluginEnabled(plugin.plugin.authority, it)
|
||||
},
|
||||
iconPadding = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-5
@@ -7,11 +7,13 @@ import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.files.settings.FileSearchSettings
|
||||
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 de.mm20.launcher2.plugins.PluginWithState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -26,6 +28,7 @@ import org.koin.core.component.inject
|
||||
|
||||
class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val pluginService by inject<PluginService>()
|
||||
private val fileSearchSettings: FileSearchSettings by inject()
|
||||
|
||||
private var pluginPackageName = MutableStateFlow<String?>(null)
|
||||
|
||||
@@ -47,11 +50,17 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
it?.plugins?.map { it.type }?.distinct() ?: emptyList()
|
||||
}
|
||||
|
||||
val states: Flow<List<PluginState?>> = pluginPackage.map {
|
||||
it?.plugins?.map {
|
||||
pluginService.getPluginState(it)
|
||||
} ?: emptyList()
|
||||
}
|
||||
val filePlugins = pluginPackage
|
||||
.map {
|
||||
it?.plugins?.mapNotNull {
|
||||
if (it.type == PluginType.FileSearch) {
|
||||
val state = pluginService.getPluginState(it)
|
||||
PluginWithState(it, state)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
|
||||
fun init(pluginId: String) {
|
||||
@@ -78,4 +87,10 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
val plugin = pluginPackage.value ?: return
|
||||
pluginService.uninstallPluginPackage(context, plugin)
|
||||
}
|
||||
|
||||
|
||||
val enabledFileSearchPlugins = fileSearchSettings.enabledPlugins
|
||||
fun setFileSearchPluginEnabled(authority: String, enabled: Boolean) {
|
||||
fileSearchSettings.setPluginEnabled(authority, enabled)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user