* feat(#500): tap-to-reveal detail dialog for truncated cards Add shared DetailDialog composable and wire tap-to-open full metadata dialogs across Skills (installed + hub), MCP servers, Plugins, Toolsets, and Webhooks screens. Action buttons stay on the card (sticky); tapping the card body reveals untruncated description + key metadata. Refs: #500 * fix(#500): resolve compile errors from missing runtime imports + dialog scope - add missing androidx.compose.runtime.mutableStateOf/remember imports in InstalledSkillsTab + HubSkillsTab (caused 'Unresolved reference' cascade on compileDebugKotlin) - move PluginsScreen detail dialog inside the composable function scope (was nested in OrphanPluginCard -> showDetail unresolved) - DetailDialog rows type: Pair<String?,String?> -> Pair<String,String?> (key always non-null; fixes type-inference failure) - drop unnecessary safe call on non-null label Verified locally with the Android SDK: ./gradlew :app:compileDebugKotlin -> BUILD SUCCESSFUL ./gradlew :app:testDebugUnitTest -> BUILD SUCCESSFUL Refs: #500 * refactor(#500): extract toDetailRows() mappers + semantic status tones; wire SkillsScreen hub browse - add DetailRows.kt with @Composable extension mappers for Skill/HubSkill/ McpServer/PluginInfo/Toolset/WebhookSubscription -> List<DetailRow> - DetailDialog now renders status rows via LocalHermesStatusColors (review #2): SUCCESS/NEUTRAL/WARNING/ERROR/INFO tones instead of plain onSurfaceVariant - wire SkillsScreen.HubBrowseView/HubSkillCard tap -> DetailDialog (same UX as other screens) — user request - remove JVM-signature-clashing Pair overload; drop unused Color import - add detail_dialog_trust_level string (HubSkill has no version field) - verified: compileDebugKotlin + testDebugUnitTest PASS locally w/ ANDROID_HOME * fix(#500): full hub description in dialog + no name truncation - HubBrowseView (both SkillsScreen + HubSkillsTab): on card tap, fetch previewHubSkill(identifier) to get the FULL SkillContentResponse.content (search snippet was truncated); dialog shows full content once loaded, with a spinner while previewing. Falls back to snippet if no identifier. - SkillsViewModel: add hubPreviewContent/isHubPreviewing/hubPreviewError state + previewHubSkill()/clearHubPreview() (uses existing preview endpoint) - DetailDialog: title now wraps fully (removed maxLines=2 ellipsis cap) so long skill names are never cut off - verified: compileDebugKotlin PASS + ktlint clean (local ANDROID_HOME)
This commit is contained in:
11 files changed
+522
-13
No files matched your search
@@ -1,5 +1,6 @@
|
||||
package com.m57.hermescontrol.ui.plugins
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -51,6 +52,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.PluginInfo
|
||||
import com.m57.hermescontrol.theme.LocalHermesStatusColors
|
||||
import com.m57.hermescontrol.ui.common.DetailDialog
|
||||
import com.m57.hermescontrol.ui.common.EmptyState
|
||||
import com.m57.hermescontrol.ui.common.ErrorState
|
||||
import com.m57.hermescontrol.ui.common.HermesScaffold
|
||||
@@ -60,6 +62,7 @@ import com.m57.hermescontrol.ui.common.SearchBar
|
||||
import com.m57.hermescontrol.ui.common.ToastEffect
|
||||
import com.m57.hermescontrol.ui.common.listContentPadding
|
||||
import com.m57.hermescontrol.ui.common.listItemSpacing
|
||||
import com.m57.hermescontrol.ui.common.toDetailRows
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -71,6 +74,7 @@ fun PluginsScreen(
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
var query by remember { mutableStateOf("") }
|
||||
var showDetail by remember { mutableStateOf<PluginInfo?>(null) }
|
||||
|
||||
val filteredPlugins =
|
||||
remember(query, state.plugins) {
|
||||
@@ -176,7 +180,12 @@ fun PluginsScreen(
|
||||
|
||||
// Plugin list
|
||||
items(filteredPlugins, key = { it.name }) { plugin ->
|
||||
PluginCard(plugin = plugin, state = state, viewModel = viewModel)
|
||||
PluginCard(
|
||||
plugin = plugin,
|
||||
state = state,
|
||||
viewModel = viewModel,
|
||||
onClick = { showDetail = plugin },
|
||||
)
|
||||
}
|
||||
|
||||
// Orphan dashboard plugins section
|
||||
@@ -191,7 +200,7 @@ fun PluginsScreen(
|
||||
)
|
||||
}
|
||||
items(state.orphanPlugins, key = { "orphan-${it.name}" }) { plugin ->
|
||||
OrphanPluginCard(plugin = plugin)
|
||||
OrphanPluginCard(plugin = plugin, onClick = { showDetail = plugin })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,6 +208,14 @@ fun PluginsScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showDetail?.let { plugin ->
|
||||
DetailDialog(
|
||||
title = plugin.name,
|
||||
rows = plugin.toDetailRows(),
|
||||
onDismiss = { showDetail = null },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -422,11 +439,12 @@ private fun PluginCard(
|
||||
plugin: PluginInfo,
|
||||
state: PluginsUiState,
|
||||
viewModel: PluginsViewModel,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val busy = state.rowBusy == plugin.name
|
||||
val statusColors = LocalHermesStatusColors.current
|
||||
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Card(modifier = Modifier.fillMaxWidth().clickable(onClick = onClick)) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
// Header row: name + toggle
|
||||
Row(
|
||||
@@ -588,8 +606,11 @@ private fun PluginCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OrphanPluginCard(plugin: PluginInfo) {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
private fun OrphanPluginCard(
|
||||
plugin: PluginInfo,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Card(modifier = Modifier.fillMaxWidth().clickable(onClick = onClick)) {
|
||||
Row(
|
||||
modifier = Modifier.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
||||
Reference in new issue
Block a user