From 1433108ec265ab6a0b0972aa14b1784428be8fdb Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Fri, 11 Apr 2025 03:49:22 -0700 Subject: [PATCH] feat(PluginsScreen): improve layout internals --- .../ui/components/ResetToDefaultButton.kt | 15 +- .../ui/screens/plugins/PluginsModel.kt | 2 +- .../ui/screens/plugins/PluginsScreen.kt | 220 ++++++------------ .../screens/plugins/components/PluginCard.kt | 22 +- .../plugins/components/PluginSearch.kt | 46 ++++ .../plugins/components/PluginsError.kt | 35 +++ .../screens/plugins/components/PluginsNone.kt | 31 +++ .../dialogs/UninstallPluginDialog.kt | 48 ++++ app/src/main/res/drawable/ic_search.xml | 9 - app/src/main/res/values/strings.xml | 4 +- 10 files changed, 258 insertions(+), 174 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginSearch.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsError.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsNone.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/dialogs/UninstallPluginDialog.kt delete mode 100644 app/src/main/res/drawable/ic_search.xml diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/ResetToDefaultButton.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/ResetToDefaultButton.kt index 4650830..a4b64ea 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/ResetToDefaultButton.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/ResetToDefaultButton.kt @@ -1,11 +1,13 @@ package com.aliucord.manager.ui.components -import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.* +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.unit.dp import com.aliucord.manager.R import com.aliucord.manager.ui.util.mirrorVertically @@ -15,13 +17,20 @@ fun ResetToDefaultButton( onClick: () -> Unit, modifier: Modifier = Modifier, ) { - AnimatedVisibility(visible = enabled, modifier = modifier) { + AnimatedVisibility( + visible = enabled, + enter = fadeIn() + slideInHorizontally(), + exit = fadeOut() + slideOutHorizontally(), + modifier = modifier, + ) { IconButton(onClick = onClick) { Icon( painter = painterResource(R.drawable.ic_refresh), tint = MaterialTheme.colorScheme.secondary, contentDescription = stringResource(R.string.action_reset_default), - modifier = Modifier.mirrorVertically(), + modifier = Modifier + .mirrorVertically() + .size(26.dp), ) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsModel.kt index c9cdaa2..37f1912 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsModel.kt @@ -92,7 +92,7 @@ class PluginsModel( val deleteSuccess = try { File(pluginPath).delete() } catch (t: Throwable) { - t.printStackTrace() + Log.e(BuildConfig.TAG, "Failed to delete plugin", t) false } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt index 0ed0530..e136f63 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/PluginsScreen.kt @@ -9,25 +9,21 @@ import android.os.Parcelable 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.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.* import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LifecycleResumeEffect import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.koin.getScreenModel import com.aliucord.manager.R import com.aliucord.manager.ui.components.BackButton -import com.aliucord.manager.ui.screens.plugins.components.Changelog -import com.aliucord.manager.ui.screens.plugins.components.PluginCard +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 kotlinx.collections.immutable.ImmutableList import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @@ -39,7 +35,6 @@ class PluginsScreen : Screen, Parcelable { @Composable override fun Content() { val model = getScreenModel() - val plugins = model.filteredPlugins.collectAsState() // Refresh plugins list on activity resume or when this initially opens LifecycleResumeEffect(Unit) { @@ -63,156 +58,81 @@ class PluginsScreen : Screen, Parcelable { ) } - Scaffold( - topBar = { - TopAppBar( - title = { Text(stringResource(R.string.plugins_title)) }, - navigationIcon = { BackButton() }, - ) - } - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - PluginSearch( - currentFilter = model.searchText.collectAsState().value, - onFilterChange = model::setSearchText, - modifier = Modifier.fillMaxWidth(), - ) + 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, + ) + } +} - if (model.error) { - Box( - modifier = Modifier.fillMaxSize() - ) { - 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, - ) - } +@Composable +fun PluginsScreenContent( + searchText: State, + setSearchText: (String) -> Unit, + isError: Boolean, + plugins: ImmutableList, + 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()) } - } else if (plugins.value.isNotEmpty()) { - LazyColumn( - contentPadding = PaddingValues(bottom = 15.dp, top = 6.dp), - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - items(items = plugins.value) { plugin -> + + plugins.isNotEmpty() -> { + items( + items = plugins, + contentType = { "PLUGIN" }, + key = { it.path }, + ) { plugin -> PluginCard( plugin = plugin, - onClickDelete = { model.showUninstallDialog(plugin) }, - onClickShowChangelog = { model.showChangelogDialog(plugin) }, - onSetEnabled = { model.setPluginEnabled(pluginName = plugin.manifest.name, it) } + onClickDelete = { onPluginUninstall(plugin) }, + onClickShowChangelog = { onPluginChangelog(plugin) }, + onSetEnabled = { onPluginToggle(plugin.manifest.name, it) }, ) } } - } else { - Box( - modifier = Modifier.fillMaxSize() - ) { - 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)) - } + + else -> item(key = "PLUGINS_NONE") { + PluginsNone(modifier = Modifier.fillParentMaxSize()) } } } } } } - -@Composable -private fun PluginSearch( - currentFilter: String, - onFilterChange: (String) -> Unit, - modifier: Modifier = Modifier, -) { - val focusManager = LocalFocusManager.current - - OutlinedTextField( - value = currentFilter, - onValueChange = onFilterChange, - singleLine = true, - shape = RoundedCornerShape(12.dp), - label = { Text(stringResource(R.string.action_search)) }, - trailingIcon = { - val isFilterBlank by remember { derivedStateOf { currentFilter.isEmpty() } } - - if (!isFilterBlank) { - IconButton(onClick = { onFilterChange("") }) { - Icon( - painter = painterResource(R.drawable.ic_close), - contentDescription = stringResource(R.string.action_clear) - ) - } - } else { - Icon( - painter = painterResource(R.drawable.ic_search), - contentDescription = stringResource(R.string.plugins_search) - ) - } - }, - keyboardOptions = KeyboardOptions(autoCorrectEnabled = false, imeAction = ImeAction.Search), - keyboardActions = KeyboardActions { focusManager.clearFocus() }, - modifier = modifier, - ) -} - -@Composable -private fun UninstallPluginDialog( - pluginName: String, - onConfirm: () -> Unit, - onDismiss: () -> Unit, -) { - AlertDialog( - onDismissRequest = onDismiss, - icon = { - Icon( - painter = painterResource(R.drawable.ic_delete_forever), - contentDescription = stringResource(R.string.action_uninstall) - ) - }, - title = { - Text("${stringResource(R.string.action_uninstall)} $pluginName") - }, - text = { - Text(stringResource(R.string.plugins_delete_plugin_body)) - }, - confirmButton = { - Button( - onClick = onConfirm, - ) { - Text(stringResource(android.R.string.ok)) - } - }, - dismissButton = { - Button( - onClick = onDismiss, - colors = ButtonDefaults.buttonColors( - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - containerColor = MaterialTheme.colorScheme.secondaryContainer - ) - ) { - Text(stringResource(android.R.string.cancel)) - } - } - ) -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginCard.kt index 1fc4a14..2190e7e 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginCard.kt @@ -36,10 +36,10 @@ fun PluginCard( 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), - verticalAlignment = Alignment.CenterVertically ) { Column { // Name @@ -107,21 +107,19 @@ fun PluginCard( ) { // Description Text( + text = plugin.manifest.description, + style = MaterialTheme.typography.bodyMedium, modifier = Modifier .heightIn(max = 150.dp, min = 40.dp) .padding(bottom = 20.dp), - text = plugin.manifest.description, - style = MaterialTheme.typography.bodyMedium ) Row( horizontalArrangement = Arrangement.spacedBy(10.dp) ) { IconButton( + onClick = { uriHandler.openUri(plugin.manifest.repositoryUrl) }, modifier = Modifier.size(25.dp), - onClick = { - uriHandler.openUri(plugin.manifest.repositoryUrl) - } ) { Icon( modifier = Modifier.fillMaxSize(), @@ -131,10 +129,13 @@ fun PluginCard( } if (plugin.manifest.changelog != null) { - IconButton(modifier = Modifier.size(25.dp), onClick = onClickShowChangelog) { + IconButton( + onClick = onClickShowChangelog, + modifier = Modifier.size(25.dp), + ) { Icon( painter = painterResource(R.drawable.ic_history), - contentDescription = stringResource(R.string.plugins_view_changelog, plugin.manifest.name), + contentDescription = stringResource(R.string.plugins_view_changelog), modifier = Modifier.fillMaxSize(), ) } @@ -142,7 +143,10 @@ fun PluginCard( Spacer(Modifier.weight(1f, true)) - IconButton(modifier = Modifier.size(25.dp), onClick = onClickDelete) { + IconButton( + onClick = onClickDelete, + modifier = Modifier.size(25.dp), + ) { Icon( modifier = Modifier.fillMaxSize(), painter = painterResource(R.drawable.ic_delete_forever), diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginSearch.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginSearch.kt new file mode 100644 index 0000000..9844ab6 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginSearch.kt @@ -0,0 +1,46 @@ +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.R +import com.aliucord.manager.ui.components.ResetToDefaultButton + +@Composable +fun PluginSearch( + currentFilter: State, + 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, + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsError.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsError.kt new file mode 100644 index 0000000..4285ed0 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsError.kt @@ -0,0 +1,35 @@ +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 com.aliucord.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, + ) + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsNone.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsNone.kt new file mode 100644 index 0000000..f1f87f8 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/PluginsNone.kt @@ -0,0 +1,31 @@ +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 com.aliucord.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)) + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/dialogs/UninstallPluginDialog.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/dialogs/UninstallPluginDialog.kt new file mode 100644 index 0000000..3fe09e9 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/plugins/components/dialogs/UninstallPluginDialog.kt @@ -0,0 +1,48 @@ +package com.aliucord.manager.ui.screens.plugins.components.dialogs + +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.aliucord.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, + ) + }, + title = { + Text(stringResource(R.string.plugins_delete_plugin, pluginName)) + }, + text = { + Text(stringResource(R.string.plugins_delete_plugin_body)) + }, + confirmButton = { + Button( + onClick = onConfirm, + ) { + Text(stringResource(android.R.string.ok)) + } + }, + dismissButton = { + Button( + onClick = onDismiss, + colors = ButtonDefaults.buttonColors( + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) { + Text(stringResource(android.R.string.cancel)) + } + } + ) +} diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml deleted file mode 100644 index 7670aa4..0000000 --- a/app/src/main/res/drawable/ic_search.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a1d270..d5a1d0b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,9 +60,9 @@ Light Plugins - View changelog for %1$s + View changelog + Uninstall %s Are you sure you want to uninstall this plugin? - Search Plugins No plugins installed! Changelog Media §plugin§%1$s§ v%2$s