fix: several bugs and inconsistencies

This commit is contained in:
particle-box
2026-02-06 10:41:31 +05:30
parent 6c1072171d
commit ce67a4f2a7
5 changed files with 182 additions and 24 deletions

View File

@@ -206,8 +206,45 @@ fun ScriptCatalog(root: ScriptingRootSection) {
) {
item {
if (isLoading) {
Box(modifier = Modifier.fillMaxWidth().padding(8.dp), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
Box(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
contentAlignment = Alignment.Center
) {
Surface(
shape = RoundedCornerShape(16.dp),
color = Color.White.copy(alpha = 0.06f),
tonalElevation = 0.dp,
shadowElevation = 0.dp,
border = BorderStroke(
1.dp,
Brush.linearGradient(
listOf(
PurrfectPalette.glowPrimary.copy(alpha = 0.45f),
PurrfectPalette.glowSecondary.copy(alpha = 0.30f)
)
)
)
) {
Row(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
CircularProgressIndicator(
modifier = Modifier.size(18.dp),
strokeWidth = 2.dp,
color = Color.White
)
Text(
text = translation["loading"] ?: "Loading...",
color = Color.White,
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp
)
}
}
}
} else if (allScripts.isEmpty() && repositories.isNotEmpty()) {
Box(

View File

@@ -547,6 +547,8 @@
"no_settings_for_module": "This module does not have any settings",
"open_module_failed": "Failed to open module file",
"open_scripts_folder_button": "Open scripts folder",
"loaded_script": "Loaded script {name}",
"unloaded_script": "Unloaded script {name}",
"script_already_installed": "Script already installed",
"select_folder_button": "Choose Folder",
"select_scripts_folder_toast": "Please select a scripts folder first",
@@ -558,6 +560,7 @@
"no_repos_added": "No repositories added",
"repo_list_info": "Find repositories here:",
"link_text": "Repository list",
"loading": "Loading catalog...",
"script_already_installed": "Script already installed",
"script_downloaded": "Script downloaded",
"could_not_create_file": "Could not create file",
@@ -3530,6 +3533,11 @@
"toast_open_snap_failed": "Failed to open snap",
"toast_mark_message_read_failed": "Failed to mark message as read. Check logs for more details",
"toast_open_conversation_first": "You must open a conversation first",
"conversation_toolbox": {
"title": "Conversation Toolbox",
"loaded_script": "Loaded Script",
"failed_to_load": "Failed to load: {message}"
},
"toast_open_link_failed": "Failed to open link",
"ai_response_style": {
"casual": "Casual",

View File

@@ -2,7 +2,17 @@ package me.eternal.purrfectsnap.common.scripting.ui
import android.app.Activity
import android.app.AlertDialog
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Surface
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import me.eternal.purrfectsnap.common.scripting.bindings.AbstractBinding
import me.eternal.purrfectsnap.common.scripting.bindings.BindingSide
import me.eternal.purrfectsnap.common.scripting.ktx.contextScope
@@ -125,13 +135,46 @@ class InterfaceManager : AbstractBinding("interface-manager", BindingSide.COMMON
@JSFunction fun createAlertDialog(activity: Activity, builder: (AlertDialog.Builder) -> Unit, callback: (interfaceBuilder: InterfaceBuilder, alertDialog: AlertDialog) -> Unit): AlertDialog {
return createComposeAlertDialog(activity, builder = builder) { alertDialog ->
ScriptInterface(interfaceBuilder = remember {
val interfaceBuilder = remember {
InterfaceBuilder().also {
contextScope {
callback(it, alertDialog)
}
}
})
}
val shape = RoundedCornerShape(22.dp)
Surface(
modifier = Modifier
.fillMaxWidth()
.border(
width = 1.dp,
brush = Brush.linearGradient(
listOf(
Color(0xFF8C7BFF).copy(alpha = 0.52f),
Color(0xFF5FD8FF).copy(alpha = 0.30f)
)
),
shape = shape
),
color = Color.Transparent,
shape = shape
) {
androidx.compose.foundation.layout.Column(
modifier = Modifier
.background(
brush = Brush.linearGradient(
listOf(
Color(0xFF2A2452).copy(alpha = 0.97f),
Color(0xFF1A143A).copy(alpha = 0.94f),
)
),
shape = shape
)
.padding(10.dp)
) {
ScriptInterface(interfaceBuilder = interfaceBuilder)
}
}
}
}

View File

@@ -1,18 +1,27 @@
package me.eternal.purrfectsnap.common.scripting.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.OutlinedButton
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.foundation.interaction.MutableInteractionSource
import kotlinx.coroutines.launch
import me.eternal.purrfectsnap.common.logger.AbstractLogger
import me.eternal.purrfectsnap.common.scripting.ui.components.Node
@@ -66,7 +75,7 @@ private fun DrawNode(node: Node) {
Text(
text = cachedAttributes["label"] as String,
fontSize = (cachedAttributes["fontSize"]?.toString()?.toInt() ?: 14).sp,
color = (cachedAttributes["color"] as? Long)?.let { Color(it) } ?: Color.Unspecified
color = (cachedAttributes["color"] as? Long)?.let { Color(it) } ?: Color.White
)
}
@@ -130,7 +139,15 @@ private fun DrawNode(node: Node) {
node.setAttribute("state", state)
(cachedAttributes["callback"] as? (Boolean) -> Unit)?.let { it(state) }
}
}
},
colors = androidx.compose.material3.SwitchDefaults.colors(
checkedThumbColor = Color.White,
checkedTrackColor = Color(0xFF8C7BFF).copy(alpha = 0.66f),
uncheckedThumbColor = Color.White.copy(alpha = 0.9f),
uncheckedTrackColor = Color.White.copy(alpha = 0.20f),
checkedBorderColor = Color.Transparent,
uncheckedBorderColor = Color.Transparent
)
)
}
NodeType.SLIDER -> {
@@ -148,26 +165,55 @@ private fun DrawNode(node: Node) {
},
valueRange = (cachedAttributes["min"] as Int).toFloat()..(cachedAttributes["max"] as Int).toFloat(),
steps = cachedAttributes["step"] as Int,
colors = SliderDefaults.colors(
thumbColor = Color(0xFF8C7BFF),
activeTrackColor = Color(0xFF8C7BFF).copy(alpha = 0.72f),
inactiveTrackColor = Color.White.copy(alpha = 0.20f),
)
)
}
NodeType.BUTTON -> {
OutlinedButton(onClick = {
runCallbackSafe {
(cachedAttributes["callback"] as? () -> Unit)?.let { it() }
}
}) {
NodeLabel()
val buttonShape = RoundedCornerShape(999.dp)
Box(
modifier = Modifier
.clip(buttonShape)
.background(Color.White.copy(alpha = 0.08f), buttonShape)
.border(1.dp, Color.White.copy(alpha = 0.20f), buttonShape)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
onClick = {
runCallbackSafe {
(cachedAttributes["callback"] as? () -> Unit)?.let { it() }
}
}
)
.padding(horizontal = 16.dp, vertical = 9.dp),
contentAlignment = Alignment.Center
) {
Text(
text = cachedAttributes["label"] as String,
color = Color.White,
fontSize = (cachedAttributes["fontSize"]?.toString()?.toInt() ?: 14).sp,
fontWeight = FontWeight.SemiBold
)
}
}
NodeType.TEXT_INPUT -> {
var textInputValue by remember {
mutableStateOf(cachedAttributes["value"].toString())
}
TextField(
val inputShape = RoundedCornerShape(14.dp)
BasicTextField(
value = textInputValue,
readOnly = cachedAttributes["readonly"] as? Boolean ?: false,
singleLine = cachedAttributes["singleLine"] as? Boolean ?: true,
maxLines = cachedAttributes["maxLines"] as? Int ?: 1,
textStyle = TextStyle(
color = Color.White,
fontSize = 14.sp
),
cursorBrush = SolidColor(Color(0xFF8C7BFF)),
onValueChange = { value ->
runCallbackSafe {
textInputValue = value
@@ -175,7 +221,24 @@ private fun DrawNode(node: Node) {
(cachedAttributes["callback"] as? (String) -> Unit)?.let { it(value) }
}
},
placeholder = { Text(cachedAttributes["placeholder"].toString()) }
decorationBox = { innerTextField ->
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White.copy(alpha = 0.08f), inputShape)
.border(1.dp, Color.White.copy(alpha = 0.18f), inputShape)
.padding(horizontal = 12.dp, vertical = 10.dp)
) {
if (textInputValue.isEmpty()) {
Text(
text = cachedAttributes["placeholder"].toString(),
color = Color.White.copy(alpha = 0.6f),
fontSize = 14.sp
)
}
innerTextField()
}
}
)
}
else -> {}

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -25,7 +26,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
@@ -99,7 +99,9 @@ class ConversationToolbox : Feature("Conversation Toolbox") {
val interfaceManager = getBinding(InterfaceManager::class)?.takeIf {
it.hasInterface(EnumScriptInterface.CONVERSATION_TOOLBOX)
} ?: return@eachModule
addComposable("\uD83D\uDCDC ${moduleInfo.displayName}") { alertDialog, conversationId ->
addComposable(
"\uD83D\uDCDC ${context.translation["conversation_toolbox.loaded_script"]}: ${moduleInfo.displayName}"
) { alertDialog, conversationId ->
ScriptInterface(remember {
interfaceManager.buildInterface(EnumScriptInterface.CONVERSATION_TOOLBOX, mapOf(
"alertDialog" to alertDialog,
@@ -143,7 +145,7 @@ class ConversationToolbox : Feature("Conversation Toolbox") {
.verticalScroll(rememberScrollState())
) {
Text(
"Conversation Toolbox",
context.translation["conversation_toolbox.title"],
fontSize = 18.sp,
modifier = Modifier
.fillMaxWidth()
@@ -160,14 +162,16 @@ class ConversationToolbox : Feature("Conversation Toolbox") {
Column(
modifier = Modifier
.fillMaxWidth()
.shadow(10.dp, itemShape, clip = true)
.clip(itemShape)
.background(Color.White.copy(alpha = 0.06f), itemShape)
.border(1.dp, Color.White.copy(alpha = 0.10f), itemShape)
) {
Row(
modifier = Modifier
.clickable { expandedComposableCache[title] = !expanded }
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { expandedComposableCache[title] = !expanded }
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
@@ -196,7 +200,10 @@ class ConversationToolbox : Feature("Conversation Toolbox") {
composable(alertDialog, openedConversationId)
}.onFailure { throwable ->
Text(
"Failed to load: ${throwable.message}",
context.translation.format(
"conversation_toolbox.failed_to_load",
"message" to (throwable.message ?: "unknown error")
),
color = PurrfectOverlayPalette.textSecondary,
fontSize = 12.sp
)