Add file plugin settings screens
This commit is contained in:
parent
7487b65247
commit
a94cc2ae01
@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
@Composable
|
@Composable
|
||||||
fun PreferenceCategory(
|
fun PreferenceCategory(
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
|
iconPadding: Boolean = true,
|
||||||
content: @Composable ColumnScope.() -> Unit
|
content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
@ -21,7 +22,7 @@ fun PreferenceCategory(
|
|||||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(start = 56.dp),
|
modifier = Modifier.padding(start = if (iconPadding) 56.dp else 0.dp),
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
package de.mm20.launcher2.ui.settings.filesearch
|
package de.mm20.launcher2.ui.settings.filesearch
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.AccountBox
|
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.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.accounts.AccountType
|
import de.mm20.launcher2.accounts.AccountType
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
|
import de.mm20.launcher2.plugin.PluginState
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.Banner
|
import de.mm20.launcher2.ui.component.Banner
|
||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
@ -40,6 +45,13 @@ fun FileSearchSettingsScreen() {
|
|||||||
viewModel.onResume()
|
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
|
val loading by viewModel.loading
|
||||||
PreferenceScreen(title = stringResource(R.string.preference_search_files)) {
|
PreferenceScreen(title = stringResource(R.string.preference_search_files)) {
|
||||||
if (loading == true) {
|
if (loading == true) {
|
||||||
@ -174,6 +186,38 @@ fun FileSearchSettingsScreen() {
|
|||||||
enabled = googleAccount != null
|
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)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import de.mm20.launcher2.accounts.AccountsRepository
|
|||||||
import de.mm20.launcher2.files.settings.FileSearchSettings
|
import de.mm20.launcher2.files.settings.FileSearchSettings
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
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.SharingStarted
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -22,6 +22,7 @@ class FileSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
private val fileSearchSettings: FileSearchSettings by inject()
|
private val fileSearchSettings: FileSearchSettings by inject()
|
||||||
private val accountsRepository: AccountsRepository by inject()
|
private val accountsRepository: AccountsRepository by inject()
|
||||||
private val permissionsManager: PermissionsManager by inject()
|
private val permissionsManager: PermissionsManager by inject()
|
||||||
|
private val pluginService: PluginService by inject()
|
||||||
|
|
||||||
val hasFilePermission = permissionsManager.hasPermission(PermissionGroup.ExternalStorage)
|
val hasFilePermission = permissionsManager.hasPermission(PermissionGroup.ExternalStorage)
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
|
||||||
@ -33,6 +34,14 @@ class FileSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
val googleAvailable = accountsRepository.isSupported(AccountType.Google)
|
val googleAvailable = accountsRepository.isSupported(AccountType.Google)
|
||||||
|
|
||||||
|
val availablePlugins = pluginService.getPluginsWithState(
|
||||||
|
type = PluginType.FileSearch,
|
||||||
|
enabled = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
val enabledPlugins = fileSearchSettings.enabledPlugins
|
||||||
|
|
||||||
|
|
||||||
fun onResume() {
|
fun onResume() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
nextcloudAccount.value =
|
nextcloudAccount.value =
|
||||||
@ -75,4 +84,8 @@ class FileSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
fun login(context: AppCompatActivity, accountType: AccountType) {
|
fun login(context: AppCompatActivity, accountType: AccountType) {
|
||||||
accountsRepository.signin(context, 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
|
package de.mm20.launcher2.ui.settings.plugins
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateColorAsState
|
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.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
|
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
|
||||||
import androidx.compose.material.icons.rounded.Delete
|
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.Info
|
||||||
import androidx.compose.material.icons.rounded.Settings
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
import androidx.compose.material.icons.rounded.Verified
|
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.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -37,7 +41,10 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
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.plugin.PluginType
|
||||||
|
import de.mm20.launcher2.ui.component.Banner
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
@ -55,11 +62,14 @@ fun PluginSettingsScreen(pluginId: String) {
|
|||||||
val pluginPackage by viewModel.pluginPackage.collectAsStateWithLifecycle(null)
|
val pluginPackage by viewModel.pluginPackage.collectAsStateWithLifecycle(null)
|
||||||
val icon by viewModel.icon.collectAsStateWithLifecycle(null)
|
val icon by viewModel.icon.collectAsStateWithLifecycle(null)
|
||||||
val types by viewModel.types.collectAsStateWithLifecycle(emptyList())
|
val types by viewModel.types.collectAsStateWithLifecycle(emptyList())
|
||||||
val states by viewModel.states.collectAsStateWithLifecycle(
|
|
||||||
|
val filePlugins by viewModel.filePlugins.collectAsStateWithLifecycle(
|
||||||
emptyList(),
|
emptyList(),
|
||||||
minActiveState = Lifecycle.State.RESUMED
|
minActiveState = Lifecycle.State.RESUMED
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val enabledFileSearchPlugins by viewModel.enabledFileSearchPlugins.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@ -247,8 +257,45 @@ fun PluginSettingsScreen(pluginId: String) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
AnimatedVisibility(pluginPackage?.enabled == true) {
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,13 @@ import android.net.Uri
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.files.settings.FileSearchSettings
|
||||||
import de.mm20.launcher2.ktx.tryStartActivity
|
import de.mm20.launcher2.ktx.tryStartActivity
|
||||||
import de.mm20.launcher2.plugin.PluginPackage
|
import de.mm20.launcher2.plugin.PluginPackage
|
||||||
import de.mm20.launcher2.plugin.PluginState
|
import de.mm20.launcher2.plugin.PluginState
|
||||||
import de.mm20.launcher2.plugin.PluginType
|
import de.mm20.launcher2.plugin.PluginType
|
||||||
import de.mm20.launcher2.plugins.PluginService
|
import de.mm20.launcher2.plugins.PluginService
|
||||||
|
import de.mm20.launcher2.plugins.PluginWithState
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@ -26,6 +28,7 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
||||||
private val pluginService by inject<PluginService>()
|
private val pluginService by inject<PluginService>()
|
||||||
|
private val fileSearchSettings: FileSearchSettings by inject()
|
||||||
|
|
||||||
private var pluginPackageName = MutableStateFlow<String?>(null)
|
private var pluginPackageName = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
@ -47,11 +50,17 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
it?.plugins?.map { it.type }?.distinct() ?: emptyList()
|
it?.plugins?.map { it.type }?.distinct() ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val states: Flow<List<PluginState?>> = pluginPackage.map {
|
val filePlugins = pluginPackage
|
||||||
it?.plugins?.map {
|
.map {
|
||||||
pluginService.getPluginState(it)
|
it?.plugins?.mapNotNull {
|
||||||
} ?: emptyList()
|
if (it.type == PluginType.FileSearch) {
|
||||||
}
|
val state = pluginService.getPluginState(it)
|
||||||
|
PluginWithState(it, state)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun init(pluginId: String) {
|
fun init(pluginId: String) {
|
||||||
@ -78,4 +87,10 @@ class PluginSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
val plugin = pluginPackage.value ?: return
|
val plugin = pluginPackage.value ?: return
|
||||||
pluginService.uninstallPluginPackage(context, plugin)
|
pluginService.uninstallPluginPackage(context, plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val enabledFileSearchPlugins = fileSearchSettings.enabledPlugins
|
||||||
|
fun setFileSearchPluginEnabled(authority: String, enabled: Boolean) {
|
||||||
|
fileSearchSettings.setPluginEnabled(authority, enabled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -361,7 +361,7 @@ internal class PluginFileDeserializer(
|
|||||||
val id = obj.getString("id")
|
val id = obj.getString("id")
|
||||||
val plugin = pluginRepository.get(authority).firstOrNull() ?: return null
|
val plugin = pluginRepository.get(authority).firstOrNull() ?: return null
|
||||||
if (!plugin.enabled) return null
|
if (!plugin.enabled) return null
|
||||||
val provider = PluginFileProvider(context, plugin)
|
val provider = PluginFileProvider(context, authority)
|
||||||
return provider.getFile(id)
|
return provider.getFile(id)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
|
|||||||
@ -53,9 +53,7 @@ internal class FileRepository(
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
settings.data.combine(filePlugins) { settings, plugins ->
|
settings.data.collectLatest { settings ->
|
||||||
settings to plugins
|
|
||||||
}.collectLatest { (settings, plugins) ->
|
|
||||||
val providers = mutableListOf<FileProvider>()
|
val providers = mutableListOf<FileProvider>()
|
||||||
|
|
||||||
if (settings.localFiles) providers.add(
|
if (settings.localFiles) providers.add(
|
||||||
@ -68,7 +66,7 @@ internal class FileRepository(
|
|||||||
if (settings.nextcloudFiles) providers.add(NextcloudFileProvider(nextcloudClient))
|
if (settings.nextcloudFiles) providers.add(NextcloudFileProvider(nextcloudClient))
|
||||||
if (settings.owncloudFiles) providers.add(OwncloudFileProvider(owncloudClient))
|
if (settings.owncloudFiles) providers.add(OwncloudFileProvider(owncloudClient))
|
||||||
|
|
||||||
for (plugin in plugins) {
|
for (plugin in settings.plugins) {
|
||||||
providers.add(PluginFileProvider(context, plugin))
|
providers.add(PluginFileProvider(context, plugin))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,12 +20,12 @@ import kotlin.coroutines.resume
|
|||||||
|
|
||||||
class PluginFileProvider(
|
class PluginFileProvider(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val plugin: Plugin,
|
private val pluginAuthority: String,
|
||||||
) : FileProvider {
|
) : FileProvider {
|
||||||
override suspend fun search(query: String): List<File> {
|
override suspend fun search(query: String): List<File> {
|
||||||
val uri = Uri.Builder()
|
val uri = Uri.Builder()
|
||||||
.scheme("content")
|
.scheme("content")
|
||||||
.authority(plugin.authority)
|
.authority(pluginAuthority)
|
||||||
.path(SearchPluginContract.Paths.Search)
|
.path(SearchPluginContract.Paths.Search)
|
||||||
.appendQueryParameter(SearchPluginContract.Paths.QueryParam, query)
|
.appendQueryParameter(SearchPluginContract.Paths.QueryParam, query)
|
||||||
.build()
|
.build()
|
||||||
@ -43,14 +43,14 @@ class PluginFileProvider(
|
|||||||
cancellationSignal
|
cancellationSignal
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MM20", "Plugin ${plugin.authority} threw exception")
|
Log.e("MM20", "Plugin ${pluginAuthority} threw exception")
|
||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
it.resume(emptyList())
|
it.resume(emptyList())
|
||||||
return@suspendCancellableCoroutine
|
return@suspendCancellableCoroutine
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
||||||
Log.e("MM20", "Plugin ${plugin.authority} returned null cursor")
|
Log.e("MM20", "Plugin ${pluginAuthority} returned null cursor")
|
||||||
it.resume(emptyList())
|
it.resume(emptyList())
|
||||||
return@suspendCancellableCoroutine
|
return@suspendCancellableCoroutine
|
||||||
}
|
}
|
||||||
@ -65,14 +65,14 @@ class PluginFileProvider(
|
|||||||
context.contentResolver.call(
|
context.contentResolver.call(
|
||||||
Uri.Builder()
|
Uri.Builder()
|
||||||
.scheme("content")
|
.scheme("content")
|
||||||
.authority(plugin.authority)
|
.authority(pluginAuthority)
|
||||||
.build(),
|
.build(),
|
||||||
PluginContract.Methods.GetConfig,
|
PluginContract.Methods.GetConfig,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
) ?: return null
|
) ?: return null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MM20", "Plugin ${plugin.authority} threw exception")
|
Log.e("MM20", "Plugin ${pluginAuthority} threw exception")
|
||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ class PluginFileProvider(
|
|||||||
suspend fun getFile(id: String): File? {
|
suspend fun getFile(id: String): File? {
|
||||||
val uri = Uri.Builder()
|
val uri = Uri.Builder()
|
||||||
.scheme("content")
|
.scheme("content")
|
||||||
.authority(plugin.authority)
|
.authority(pluginAuthority)
|
||||||
.path(SearchPluginContract.Paths.Root)
|
.path(SearchPluginContract.Paths.Root)
|
||||||
.appendPath(id)
|
.appendPath(id)
|
||||||
.build()
|
.build()
|
||||||
@ -109,7 +109,7 @@ class PluginFileProvider(
|
|||||||
val config = getPluginConfig()
|
val config = getPluginConfig()
|
||||||
|
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
Log.e("MM20", "Plugin ${plugin.authority} returned null config")
|
Log.e("MM20", "Plugin ${pluginAuthority} returned null config")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ class PluginFileProvider(
|
|||||||
}?.let { Uri.parse(it) },
|
}?.let { Uri.parse(it) },
|
||||||
storageStrategy = config.storageStrategy,
|
storageStrategy = config.storageStrategy,
|
||||||
isDirectory = directoryIndex?.let { cursor.getInt(it) } == 1,
|
isDirectory = directoryIndex?.let { cursor.getInt(it) } == 1,
|
||||||
authority = plugin.authority,
|
authority = pluginAuthority,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,17 @@ class FileSearchSettings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val enabledPlugins: Flow<Set<String>>
|
||||||
|
get(): Flow<Set<String>> {
|
||||||
|
return context.dataStore.data.map { it.plugins }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEnabledPlugins(enabledPlugins: Set<String>) {
|
||||||
|
updateData {
|
||||||
|
it.copy(plugins = enabledPlugins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun backup(toDir: File) {
|
override suspend fun backup(toDir: File) {
|
||||||
val data = context.dataStore.data.first()
|
val data = context.dataStore.data.first()
|
||||||
val file = File(toDir, "file_search.json")
|
val file = File(toDir, "file_search.json")
|
||||||
@ -109,4 +120,14 @@ class FileSearchSettings(
|
|||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setPluginEnabled(authority: String, enabled: Boolean) {
|
||||||
|
updateData {
|
||||||
|
if (enabled) {
|
||||||
|
it.copy(plugins = it.plugins + authority)
|
||||||
|
} else {
|
||||||
|
it.copy(plugins = it.plugins - authority)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -12,7 +12,6 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import de.mm20.launcher2.ktx.tryStartActivity
|
import de.mm20.launcher2.ktx.tryStartActivity
|
||||||
import de.mm20.launcher2.plugin.Plugin
|
import de.mm20.launcher2.plugin.Plugin
|
||||||
import de.mm20.launcher2.plugin.PluginPackage
|
import de.mm20.launcher2.plugin.PluginPackage
|
||||||
@ -43,7 +42,10 @@ data class PluginWithState(
|
|||||||
interface PluginService {
|
interface PluginService {
|
||||||
fun enablePluginPackage(plugin: PluginPackage)
|
fun enablePluginPackage(plugin: PluginPackage)
|
||||||
fun disablePluginPackage(plugin: PluginPackage)
|
fun disablePluginPackage(plugin: PluginPackage)
|
||||||
fun getPluginsWithState(type: PluginType? = null): Flow<List<PluginWithState>>
|
fun getPluginsWithState(
|
||||||
|
type: PluginType? = null,
|
||||||
|
enabled: Boolean? = null,
|
||||||
|
): Flow<List<PluginWithState>>
|
||||||
|
|
||||||
fun isPluginHostInstalled(): Flow<Boolean>
|
fun isPluginHostInstalled(): Flow<Boolean>
|
||||||
|
|
||||||
@ -127,9 +129,11 @@ internal class PluginServiceImpl(
|
|||||||
|
|
||||||
override fun getPluginsWithState(
|
override fun getPluginsWithState(
|
||||||
type: PluginType?,
|
type: PluginType?,
|
||||||
|
enabled: Boolean?,
|
||||||
): Flow<List<PluginWithState>> {
|
): Flow<List<PluginWithState>> {
|
||||||
return repository.findMany(
|
return repository.findMany(
|
||||||
type = type,
|
type = type,
|
||||||
|
enabled = enabled,
|
||||||
).map {
|
).map {
|
||||||
it.map {
|
it.map {
|
||||||
PluginWithState(
|
PluginWithState(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user