Delete app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins directory
This commit is contained in:
parent
2d213f46e8
commit
96400eb22c
8 changed files with 0 additions and 821 deletions
|
|
@ -1,203 +0,0 @@
|
|||
package com.aliucord.manager.ui.screens.plugins
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.*
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import com.aliucord.manager.manager.PathManager
|
||||
import com.aliucord.manager.ui.screens.plugins.model.PluginItem
|
||||
import com.aliucord.manager.ui.screens.plugins.model.PluginManifest
|
||||
import com.aliucord.manager.ui.util.emptyImmutableList
|
||||
import com.aliucord.manager.ui.util.toUnsafeImmutable
|
||||
import com.aliucord.manager.util.*
|
||||
import com.github.diamondminer88.zip.ZipReader
|
||||
import dev.goon.manager.BuildConfig
|
||||
import dev.goon.manager.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.*
|
||||
import java.io.File
|
||||
import kotlin.time.Duration
|
||||
|
||||
class PluginsModel(
|
||||
private val context: Application,
|
||||
private val paths: PathManager,
|
||||
private val json: Json,
|
||||
) : ScreenModel {
|
||||
private val plugins = MutableStateFlow<ImmutableList<PluginItem>>(emptyImmutableList())
|
||||
private val aliucordJsonMutex = Mutex()
|
||||
|
||||
var error by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var showChangelogDialog by mutableStateOf<PluginItem?>(null)
|
||||
private set
|
||||
|
||||
var showUninstallDialog by mutableStateOf<PluginItem?>(null)
|
||||
private set
|
||||
|
||||
val searchText: StateFlow<String>
|
||||
private field = MutableStateFlow("")
|
||||
|
||||
val filteredPlugins: StateFlow<ImmutableList<PluginItem>> = searchText
|
||||
.combine(plugins) { searchText, plugins ->
|
||||
if (searchText.isBlank()) {
|
||||
plugins
|
||||
} else {
|
||||
plugins.filter { plugin ->
|
||||
plugin.manifest.name.contains(searchText, ignoreCase = true)
|
||||
|| plugin.manifest.description.contains(searchText, ignoreCase = true)
|
||||
|| plugin.manifest.authors.any { (name) -> name.contains(searchText, ignoreCase = true) }
|
||||
}.toUnsafeImmutable()
|
||||
}
|
||||
}.stateIn(
|
||||
scope = screenModelScope + Dispatchers.Default,
|
||||
started = SharingStarted.WhileSubscribed(replayExpiration = Duration.ZERO),
|
||||
initialValue = plugins.value,
|
||||
)
|
||||
|
||||
fun setSearchText(search: String) {
|
||||
searchText.value = search
|
||||
}
|
||||
|
||||
fun showChangelogDialog(plugin: PluginItem) {
|
||||
showChangelogDialog = plugin
|
||||
}
|
||||
|
||||
fun hideChangelogDialog() {
|
||||
showChangelogDialog = null
|
||||
}
|
||||
|
||||
fun showUninstallDialog(plugin: PluginItem) {
|
||||
showUninstallDialog = plugin
|
||||
}
|
||||
|
||||
fun hideUninstallDialog() {
|
||||
showUninstallDialog = null
|
||||
}
|
||||
|
||||
fun uninstallPlugin(plugin: PluginItem) = screenModelScope.launchIO {
|
||||
if (!plugins.value.any { it.path == plugin.path }) {
|
||||
mainThread { hideUninstallDialog() }
|
||||
return@launchIO
|
||||
}
|
||||
|
||||
val deleteSuccess = try {
|
||||
File(plugin.path).delete()
|
||||
} catch (t: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to delete plugin", t)
|
||||
false
|
||||
}
|
||||
|
||||
if (!deleteSuccess) {
|
||||
mainThread {
|
||||
hideUninstallDialog()
|
||||
context.showToast(R.string.plugins_error)
|
||||
}
|
||||
return@launchIO
|
||||
}
|
||||
|
||||
plugins.update { (it - plugin).toUnsafeImmutable() }
|
||||
mainThread { hideUninstallDialog() }
|
||||
}
|
||||
|
||||
fun setPluginEnabled(pluginName: String, enabled: Boolean) = screenModelScope.launchIO {
|
||||
aliucordJsonMutex.withLock {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
try {
|
||||
val settings = paths.coreSettingsFile.inputStream()
|
||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||
.toMutableMap()
|
||||
|
||||
settings["AC_PM_$pluginName"] = JsonPrimitive(enabled)
|
||||
|
||||
paths.coreSettingsFile.outputStream()
|
||||
.use { json.encodeToStream(settings, it) }
|
||||
} catch (t: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to write Aliucord.json", t)
|
||||
mainThread { error = true }
|
||||
}
|
||||
}
|
||||
|
||||
mainThread {
|
||||
plugins.value.forEach {
|
||||
if (it.manifest.name == pluginName)
|
||||
it.enabled = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by screen to load initial data
|
||||
fun refreshData() = screenModelScope.launchIO {
|
||||
loadPlugins()
|
||||
loadPluginsEnabled()
|
||||
}
|
||||
|
||||
private suspend fun loadPluginsEnabled() {
|
||||
if (!paths.coreSettingsFile.exists()) return
|
||||
|
||||
aliucordJsonMutex.withLock {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
val pluginToggles = try {
|
||||
paths.coreSettingsFile.inputStream()
|
||||
.use { Json.decodeFromStream<Map<String, JsonElement>>(it) }
|
||||
.filterKeys { it.startsWith("AC_PM_") }
|
||||
.mapKeys { it.key.substring("AC_PM_".length) }
|
||||
.mapValues { it.value.jsonPrimitive.boolean }
|
||||
} catch (t: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to load Aliucord.json", t)
|
||||
mainThread { error = true }
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
mainThread {
|
||||
plugins.value.forEach {
|
||||
if (!pluginToggles.getOrDefault(it.manifest.name, true))
|
||||
it.enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadPlugins() {
|
||||
try {
|
||||
if (!paths.pluginsDir.exists() && !paths.pluginsDir.mkdirs())
|
||||
throw IllegalStateException("Failed to create plugins directory")
|
||||
|
||||
val pluginFiles = paths.pluginsDir.listFiles { file -> file.extension == "zip" }
|
||||
?: throw IllegalStateException("Failed to read plugins directory")
|
||||
|
||||
val pluginItems = pluginFiles.map {
|
||||
PluginItem(
|
||||
manifest = loadPluginManifest(it),
|
||||
path = it.absolutePath,
|
||||
)
|
||||
}
|
||||
|
||||
plugins.value = pluginItems.toUnsafeImmutable()
|
||||
} catch (t: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to load plugins", t)
|
||||
mainThread { error = true }
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPluginManifest(pluginFile: File): PluginManifest {
|
||||
return ZipReader(pluginFile).use {
|
||||
val manifest = it.openEntry("manifest.json")
|
||||
?: throw Exception("Plugin ${pluginFile.name} has no manifest")
|
||||
|
||||
try {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
json.decodeFromStream(manifest.read().inputStream())
|
||||
} catch (t: Throwable) {
|
||||
throw Exception("Failed to parse plugin manifest for ${pluginFile.name}", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screens.plugins
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.koinScreenModel
|
||||
import com.aliucord.manager.ui.components.BackButton
|
||||
import com.aliucord.manager.ui.screens.plugins.components.*
|
||||
import com.aliucord.manager.ui.screens.plugins.components.dialogs.UninstallPluginDialog
|
||||
import com.aliucord.manager.ui.screens.plugins.model.PluginItem
|
||||
import com.aliucord.manager.ui.util.paddings.*
|
||||
import dev.goon.manager.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class PluginsScreen : Screen, Parcelable {
|
||||
@IgnoredOnParcel
|
||||
override val key = "Plugins"
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = koinScreenModel<PluginsModel>()
|
||||
|
||||
// Refresh plugins list on activity resume or when this initially opens
|
||||
LifecycleResumeEffect(Unit) {
|
||||
model.refreshData()
|
||||
|
||||
onPauseOrDispose {}
|
||||
}
|
||||
|
||||
model.showUninstallDialog?.let { plugin ->
|
||||
UninstallPluginDialog(
|
||||
pluginName = plugin.manifest.name,
|
||||
onConfirm = { model.uninstallPlugin(plugin) },
|
||||
onDismiss = model::hideUninstallDialog
|
||||
)
|
||||
}
|
||||
|
||||
model.showChangelogDialog?.let { plugin ->
|
||||
Changelog(
|
||||
plugin = plugin,
|
||||
onDismiss = model::hideChangelogDialog
|
||||
)
|
||||
}
|
||||
|
||||
PluginsScreenContent(
|
||||
searchText = model.searchText.collectAsState(),
|
||||
setSearchText = model::setSearchText,
|
||||
isError = model.error,
|
||||
plugins = model.filteredPlugins.collectAsState().value,
|
||||
onPluginUninstall = model::showUninstallDialog,
|
||||
onPluginChangelog = model::showChangelogDialog,
|
||||
onPluginToggle = model::setPluginEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PluginsScreenContent(
|
||||
searchText: State<String>,
|
||||
setSearchText: (String) -> Unit,
|
||||
isError: Boolean,
|
||||
plugins: ImmutableList<PluginItem>,
|
||||
onPluginUninstall: (PluginItem) -> Unit,
|
||||
onPluginChangelog: (PluginItem) -> Unit,
|
||||
onPluginToggle: (name: String, enabled: Boolean) -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.plugins_title)) },
|
||||
navigationIcon = { BackButton() },
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues.exclude(PaddingValuesSides.Bottom))
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
PluginSearch(
|
||||
currentFilter = searchText,
|
||||
onFilterChange = setSearchText,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = paddingValues
|
||||
.exclude(PaddingValuesSides.Horizontal + PaddingValuesSides.Top)
|
||||
.add(PaddingValues(vertical = 12.dp)),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
when {
|
||||
isError -> item(key = "ERROR") {
|
||||
PluginsError(modifier = Modifier.fillParentMaxSize())
|
||||
}
|
||||
|
||||
plugins.isNotEmpty() -> {
|
||||
items(
|
||||
items = plugins,
|
||||
contentType = { "PLUGIN" },
|
||||
key = { it.path },
|
||||
) { plugin ->
|
||||
PluginCard(
|
||||
plugin = plugin,
|
||||
onClickDelete = { onPluginUninstall(plugin) },
|
||||
onClickShowChangelog = { onPluginChangelog(plugin) },
|
||||
onSetEnabled = { onPluginToggle(plugin.manifest.name, it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> item(key = "PLUGINS_NONE") {
|
||||
PluginsNone(modifier = Modifier.fillParentMaxSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screens.plugins.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.aliucord.manager.ui.screens.plugins.model.PluginItem
|
||||
import dev.goon.manager.R
|
||||
|
||||
private val hyperLinkPattern = Regex("\\[(.+?)]\\((.+?\\))")
|
||||
|
||||
@Suppress("RegExpRedundantEscape") // It is very much not redundant and causes a crash lol
|
||||
private val headerStylePattern = Regex("\\{(improved|added|fixed)( marginTop)?\\}")
|
||||
|
||||
@Composable
|
||||
private fun AnnotatedString.Builder.MarkdownHyperlink(content: String) {
|
||||
var idx = 0
|
||||
|
||||
with(hyperLinkPattern.toPattern().matcher(content)) {
|
||||
while (find()) {
|
||||
val start = start()
|
||||
val end = end()
|
||||
val title = group(1)!!
|
||||
val url = group(2)!!
|
||||
|
||||
append(content.substring(idx, start))
|
||||
|
||||
// @formatter:off
|
||||
pushLink(LinkAnnotation.Url(
|
||||
url,
|
||||
TextLinkStyles(SpanStyle(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
textDecoration = TextDecoration.Underline
|
||||
))
|
||||
))
|
||||
append(title)
|
||||
pop()
|
||||
// @formatter:on
|
||||
|
||||
idx = end
|
||||
}
|
||||
}
|
||||
|
||||
if (idx < content.length) append(content.substring(idx))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Changelog(
|
||||
plugin: PluginItem,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_history),
|
||||
contentDescription = stringResource(R.string.plugins_view_changelog, plugin.manifest.name)
|
||||
)
|
||||
},
|
||||
title = { Text(plugin.manifest.name) },
|
||||
text = {
|
||||
Column {
|
||||
plugin.manifest.changelogMedia?.let { mediaUrl ->
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 90.dp)
|
||||
.clip(RoundedCornerShape(14.dp)),
|
||||
model = mediaUrl,
|
||||
contentDescription = stringResource(R.string.plugins_changelog_media)
|
||||
)
|
||||
}
|
||||
|
||||
LazyColumn {
|
||||
items(plugin.manifest.changelog!!.lines()) {
|
||||
var line = it.trim()
|
||||
|
||||
if (line.isNotEmpty()) {
|
||||
when (line[0]) {
|
||||
'#' -> {
|
||||
do {
|
||||
line = line.substring(1)
|
||||
} while (line.startsWith("#"))
|
||||
|
||||
Text(
|
||||
text = line.trimStart(),
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
'*' -> {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 2.dp),
|
||||
text = buildAnnotatedString {
|
||||
withStyle(
|
||||
SpanStyle(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
) {
|
||||
append("● ")
|
||||
}
|
||||
|
||||
MarkdownHyperlink(line.substring(1))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
when {
|
||||
line.endsWith("marginTop}") -> {
|
||||
val color = MaterialTheme.colorScheme.onSurface
|
||||
|
||||
Text(
|
||||
text = line,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = color,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
line.all { c -> c == '=' } -> {} // Discord ignores =======
|
||||
else -> {
|
||||
Text(buildAnnotatedString {
|
||||
MarkdownHyperlink(line)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.action_close))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Juby210 & zt
|
||||
* Licensed under the Open Software License version 3.0
|
||||
*/
|
||||
|
||||
package com.aliucord.manager.ui.screens.plugins.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.ui.screens.plugins.model.PluginItem
|
||||
import dev.goon.manager.R
|
||||
|
||||
@Composable
|
||||
fun PluginCard(
|
||||
plugin: PluginItem,
|
||||
onClickDelete: () -> Unit,
|
||||
onClickShowChangelog: () -> Unit,
|
||||
onSetEnabled: (Boolean) -> Unit,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
ElevatedCard {
|
||||
// Header
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clickable { onSetEnabled(!plugin.enabled) }
|
||||
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 14.dp),
|
||||
) {
|
||||
Column {
|
||||
// Name
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(plugin.manifest.name)
|
||||
}
|
||||
append(" v")
|
||||
append(plugin.manifest.version)
|
||||
}
|
||||
)
|
||||
|
||||
// Authors
|
||||
val authors = buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
|
||||
for ((idx, author) in plugin.manifest.authors.withIndex()) {
|
||||
if (idx > 0) append(", ")
|
||||
|
||||
if (author.hyperlink) pushLink(
|
||||
LinkAnnotation.Url(
|
||||
url = author.discordUrl,
|
||||
styles = TextLinkStyles(
|
||||
SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
append(author.name)
|
||||
if (author.hyperlink) pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = authors,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
// Toggle Switch
|
||||
Switch(
|
||||
checked = plugin.enabled,
|
||||
onCheckedChange = { onSetEnabled(!plugin.enabled) }
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier
|
||||
.alpha(0.3f)
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Description
|
||||
Text(
|
||||
text = plugin.manifest.description,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier
|
||||
.heightIn(max = 150.dp, min = 40.dp)
|
||||
.padding(bottom = 20.dp),
|
||||
)
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { uriHandler.openUri(plugin.manifest.repositoryUrl) },
|
||||
modifier = Modifier.size(25.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
painter = painterResource(R.drawable.ic_account_github_white_24dp),
|
||||
contentDescription = stringResource(R.string.github)
|
||||
)
|
||||
}
|
||||
|
||||
if (plugin.manifest.changelog != null) {
|
||||
IconButton(
|
||||
onClick = onClickShowChangelog,
|
||||
modifier = Modifier.size(25.dp),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_history),
|
||||
contentDescription = stringResource(R.string.plugins_view_changelog),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
IconButton(
|
||||
onClick = onClickDelete,
|
||||
modifier = Modifier.size(25.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
painter = painterResource(R.drawable.ic_delete_forever),
|
||||
contentDescription = stringResource(R.string.action_uninstall),
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package com.aliucord.manager.ui.screens.plugins.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.ui.components.ResetToDefaultButton
|
||||
import dev.goon.manager.R
|
||||
|
||||
@Composable
|
||||
fun PluginSearch(
|
||||
currentFilter: State<String>,
|
||||
onFilterChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier.Companion,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
OutlinedTextField(
|
||||
value = currentFilter.value,
|
||||
onValueChange = onFilterChange,
|
||||
singleLine = true,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
label = { Text(stringResource(R.string.action_search)) },
|
||||
trailingIcon = {
|
||||
val isFilterBlank by remember { derivedStateOf { currentFilter.value.isEmpty() } }
|
||||
|
||||
ResetToDefaultButton(
|
||||
enabled = !isFilterBlank,
|
||||
onClick = { onFilterChange("") },
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
imeAction = ImeAction.Companion.Search
|
||||
),
|
||||
keyboardActions = KeyboardActions { focusManager.clearFocus() },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package com.aliucord.manager.ui.screens.plugins.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.goon.manager.R
|
||||
|
||||
@Composable
|
||||
fun PluginsError(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_warning),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.plugins_error),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.aliucord.manager.ui.screens.plugins.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import dev.goon.manager.R
|
||||
|
||||
@Composable
|
||||
fun PluginsNone(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_extension_off),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(stringResource(R.string.plugins_none_installed))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.aliucord.manager.ui.screens.plugins.components.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.goon.manager.R
|
||||
|
||||
@Composable
|
||||
fun UninstallPluginDialog(
|
||||
pluginName: String,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_delete_forever),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(stringResource(R.string.plugins_delete_plugin, pluginName))
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.plugins_delete_plugin_body, pluginName),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = onConfirm,
|
||||
) {
|
||||
Text(stringResource(R.string.action_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(
|
||||
onClick = onDismiss,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue