polish issue detail screenshot feedback

This commit is contained in:
Xisheng-Zhao
2026-05-17 17:51:37 +08:00
parent 0e2035a480
commit bf12771908
2 changed files with 183 additions and 166 deletions
@@ -67,6 +67,7 @@ import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Email
import androidx.compose.material.icons.outlined.Flag
import androidx.compose.material.icons.outlined.Folder
import androidx.compose.material.icons.outlined.Image
import androidx.compose.material.icons.outlined.Key
import androidx.compose.material.icons.outlined.KeyboardArrowDown
import androidx.compose.material.icons.outlined.KeyboardArrowRight
@@ -7565,8 +7566,6 @@ private fun IssueDetailHeroCard(
zh: Boolean,
onStatusChange: (String) -> Unit,
onPriorityChange: (String) -> Unit,
onEdit: () -> Unit,
onCopyLink: () -> Unit,
) {
Surface(
modifier = Modifier
@@ -7588,20 +7587,6 @@ private fun IssueDetailHeroCard(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.size(38.dp)
.clip(RoundedCornerShape(12.dp))
.background(MulticaColors.AccentSoft.copy(alpha = 0.38f)),
contentAlignment = Alignment.Center,
) {
Text(
text = issue.identifier.takeLastWhile { it.isDigit() }.take(2).ifBlank { issue.identifier.take(2) },
style = MaterialTheme.typography.labelLarge.copy(fontSize = 13.sp, lineHeight = 16.sp, fontWeight = FontWeight.Bold),
color = MulticaColors.Accent,
maxLines = 1,
)
}
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(
text = issue.identifier,
@@ -7614,9 +7599,9 @@ private fun IssueDetailHeroCard(
Text(
text = issue.title,
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Bold,
fontSize = 22.sp,
lineHeight = 27.sp,
fontWeight = FontWeight.SemiBold,
fontSize = 20.sp,
lineHeight = 24.sp,
),
color = MulticaColors.Text,
maxLines = 3,
@@ -7624,18 +7609,6 @@ private fun IssueDetailHeroCard(
)
}
}
MulticaIconPillButton(
icon = Icons.Outlined.ContentCopy,
contentDescription = "Issue Detail Hero Copy Link",
onClick = onCopyLink,
tone = MulticaButtonTone.Ghost,
)
MulticaIconPillButton(
icon = Icons.Outlined.Edit,
contentDescription = "Issue Detail Hero Edit",
onClick = onEdit,
tone = MulticaButtonTone.Ghost,
)
}
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
item {
@@ -7669,58 +7642,125 @@ private fun IssueDetailCompactMetadata(
) {
val labelSummary = issueLabels.joinToString(", ") { it.name }
.ifBlank { if (zh) "无 Labels" else "No labels" }
CompactInfoPanel(
val dueValue = issue.dueDate?.takeIf { it.isNotBlank() }?.let { shortDate(it) } ?: "-"
val createdValue = issue.createdAt.takeIf { it.isNotBlank() }?.let { shortDate(it) } ?: "-"
val updatedValue = issue.updatedAt.takeIf { it.isNotBlank() }?.let { shortDate(it) } ?: "-"
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(top = 2.dp, bottom = 10.dp)
.padding(top = 2.dp, bottom = 8.dp)
.semantics(mergeDescendants = false) { contentDescription = "Issue Detail Compact Metadata" },
shape = RoundedCornerShape(12.dp),
color = MulticaColors.Surface.copy(alpha = 0.82f),
tonalElevation = 0.dp,
shadowElevation = 0.dp,
border = androidx.compose.foundation.BorderStroke(0.75.dp, MulticaColors.Border.copy(alpha = 0.42f)),
) {
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) {
CompactMetaText(
Column(
modifier = Modifier.padding(horizontal = 13.dp, vertical = 11.dp),
verticalArrangement = Arrangement.spacedBy(9.dp),
) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth()) {
IssueDetailTinyProperty(
label = if (zh) "负责人" else "Assignee",
value = assignee.ifBlank { if (zh) "未分配" else "Unassigned" },
icon = Icons.Outlined.Person,
modifier = Modifier.weight(1f),
contentDescription = "Issue Detail Assignee Metadata Row",
)
CompactMetaText(
IssueDetailTinyProperty(
label = if (zh) "项目" else "Project",
value = project,
icon = Icons.Outlined.Folder,
modifier = Modifier.weight(1f),
contentDescription = "Issue Detail Project Metadata Row",
)
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) {
CompactMetaText(
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth()) {
IssueDetailTinyProperty(
label = if (zh) "优先级" else "Priority",
value = Models.priorityLabel(issue.priority, zh),
icon = Icons.Outlined.Flag,
modifier = Modifier.weight(1f),
contentDescription = "Issue Detail Priority Due Date Metadata Row",
)
CompactMetaText(
IssueDetailTinyProperty(
label = if (zh) "到期" else "Due",
value = issue.dueDate?.takeIf { it.isNotBlank() }?.let { shortDate(it) } ?: "-",
value = dueValue,
icon = Icons.Outlined.CalendarMonth,
modifier = Modifier.weight(1f),
contentDescription = "Issue Detail Priority Due Date Metadata Row",
contentDescription = "Issue Detail Due Date Metadata Row",
)
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) {
CompactMetaText(
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth()) {
IssueDetailTinyProperty(
label = if (zh) "Labels" else "Labels",
value = labelSummary,
icon = Icons.Outlined.Label,
modifier = Modifier.weight(1f),
contentDescription = "Issue Detail Labels Metadata Row",
)
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) {
CompactMetaText(
IssueDetailTinyProperty(
label = if (zh) "创建" else "Created",
value = issue.createdAt.takeIf { it.isNotBlank() }?.let { shortDate(it) } ?: "-",
value = createdValue,
icon = Icons.Outlined.CalendarMonth,
modifier = Modifier.weight(1f),
contentDescription = "Issue Detail Created Metadata Row",
)
CompactMetaText(
}
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth()) {
IssueDetailTinyProperty(
label = if (zh) "更新" else "Updated",
value = issue.updatedAt.takeIf { it.isNotBlank() }?.let { shortDate(it) } ?: "-",
value = updatedValue,
icon = Icons.Outlined.CalendarMonth,
modifier = Modifier.weight(1f),
contentDescription = "Issue Detail Updated Metadata Row",
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
}
@Composable
private fun IssueDetailTinyProperty(
label: String,
value: String,
icon: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
Row(
modifier = modifier.then(
if (contentDescription == null) Modifier else Modifier.semantics(mergeDescendants = true) {
this.contentDescription = contentDescription
}
),
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.Top,
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MulticaColors.TextTertiary,
modifier = Modifier
.padding(top = 1.dp)
.size(13.dp),
)
Column(verticalArrangement = Arrangement.spacedBy(1.dp), modifier = Modifier.weight(1f)) {
Text(
text = label,
style = MaterialTheme.typography.labelSmall.copy(fontSize = 10.sp, lineHeight = 12.sp, fontWeight = FontWeight.SemiBold),
color = MulticaColors.TextTertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = value,
style = MaterialTheme.typography.bodySmall.copy(fontSize = 12.sp, lineHeight = 15.sp, fontWeight = FontWeight.Medium),
color = MulticaColors.Text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
@@ -8508,6 +8548,7 @@ private fun IssueDetailProgressCard(
subtitle: String?,
icon: ImageVector,
tone: MulticaButtonTone,
contentDescription: String? = null,
onClick: (() -> Unit)? = null,
) {
val tint = when (tone) {
@@ -8523,7 +8564,16 @@ private fun IssueDetailProgressCard(
.fillMaxWidth()
.then(clickModifier)
.clip(shape)
.border(0.75.dp, MulticaColors.Border.copy(alpha = 0.52f), shape),
.border(0.75.dp, MulticaColors.Border.copy(alpha = 0.52f), shape)
.semantics(mergeDescendants = true) {
this.contentDescription = contentDescription ?: "Issue Detail Progress Card"
if (onClick != null) {
semanticsOnClick(label = title) {
onClick()
true
}
}
},
shape = shape,
color = MulticaColors.Surface.copy(alpha = 0.72f),
) {
@@ -9724,8 +9774,6 @@ private fun PilotIssueDetail(
zh = zh,
onStatusChange = { updateIssueStatusFromDetail(it) },
onPriorityChange = { updateIssuePriorityFromDetail(it) },
onEdit = { onEditIssue(data.issue) },
onCopyLink = { copyIssueLink() },
)
}
item {
@@ -9760,74 +9808,31 @@ private fun PilotIssueDetail(
MarkdownBlock(data.issue.description)
}
}
if (data.activeTasks.isNotEmpty() || data.runs.isNotEmpty()) {
item {
SectionHeader(if (zh) "Agent 最新进展" else "Agent Latest Progress")
}
item {
IssueDetailWebActionRow(
eyebrow = if (zh) "Agent Live" else "Agent live",
title = if (data.activeTasks.isNotEmpty()) {
if (zh) "正在运行的 Agent 任务" else "Active agent task"
} else if (data.runs.isNotEmpty()) {
val task = data.activeTasks.firstOrNull()
val run = data.runs.firstOrNull()
IssueDetailProgressCard(
title = if (task != null) {
if (zh) "活跃 Agent 任务" else "Active agent task"
} else {
if (zh) "最近 Agent 运行" else "Latest agent run"
} else {
if (zh) "暂无进展更新" else "No progress updates yet"
},
subtitle = if (data.activeTasks.isNotEmpty()) {
if (zh) "${data.activeTasks.size} 个任务正在运行" else "${data.activeTasks.size} active task(s)"
} else if (data.runs.isNotEmpty()) {
if (zh) "${data.runs.size} 条 Agent 运行记录" else "${data.runs.size} agent run(s)"
subtitle = if (task != null) {
listOf(task.status, clean(task.triggerSummary).ifBlank { Models.shortId(task.id) })
.filter { it.isNotBlank() }
.joinToString(" · ")
} else {
if (zh) "Agent live card 对齐 Web activity header" else "Agent live card aligned with the Web activity header"
listOf(run?.status.orEmpty(), run?.startedAt?.let { shortDate(it) }.orEmpty(), clean(run?.error))
.filter { it.isNotBlank() }
.joinToString(" · ")
},
icon = Icons.Outlined.Bolt,
tone = if (data.activeTasks.isNotEmpty()) MulticaButtonTone.Primary else MulticaButtonTone.Ghost,
contentDescription = "Issue Agent Live Web Summary $WEB_SSOT_EXECUTION_LOG_SECTION",
tone = if (task != null || run?.status != "failed") MulticaButtonTone.Primary else MulticaButtonTone.Destructive,
contentDescription = "Issue Agent Progress Compact Row $WEB_SSOT_EXECUTION_LOG_SECTION",
onClick = if (run != null) ({ selectedRun = run }) else null,
)
}
item {
when {
data.activeTasks.isNotEmpty() -> {
val task = data.activeTasks.first()
IssueDetailProgressCard(
title = if (zh) "活跃 Agent 任务" else "Active agent task",
subtitle = listOf(task.status, clean(task.triggerSummary).ifBlank { Models.shortId(task.id) })
.filter { it.isNotBlank() }
.joinToString(" · "),
icon = Icons.Outlined.Bolt,
tone = MulticaButtonTone.Primary,
)
}
data.runs.isNotEmpty() -> {
val run = data.runs.first()
IssueDetailProgressCard(
title = if (zh) "最近 Agent 运行" else "Latest agent run",
subtitle = listOf(run.status, shortDate(run.startedAt), clean(run.error))
.filter { it.isNotBlank() }
.joinToString(" · "),
icon = Icons.Outlined.Bolt,
tone = if (run.status == "failed") MulticaButtonTone.Destructive else MulticaButtonTone.Primary,
onClick = { selectedRun = run },
)
}
!data.timeline?.entries.isNullOrEmpty() -> {
val entry = data.timeline!!.entries.first()
IssueDetailProgressCard(
title = clean(entry.action).ifBlank { if (zh) "最近活动" else "Latest activity" },
subtitle = clean(entry.content).ifBlank { shortDate(entry.createdAt) },
icon = Icons.Outlined.CalendarMonth,
tone = MulticaButtonTone.Ghost,
)
}
else -> {
IssueDetailProgressCard(
title = if (zh) "暂无进展更新" else "No progress updates yet",
subtitle = if (zh) "Agent 运行、评论和活动会优先显示在这里" else "Agent runs, comments, and activity will surface here first",
icon = Icons.Outlined.RadioButtonUnchecked,
tone = MulticaButtonTone.Ghost,
)
}
}
}
item {
Spacer(
@@ -10795,12 +10800,6 @@ private fun IssueCommentInputBar(
.padding(horizontal = 18.dp, vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(MulticaColors.Border)
)
if (pendingAttachments.isNotEmpty()) {
Text(
text = if (zh) {
@@ -10830,17 +10829,21 @@ private fun IssueCommentInputBar(
horizontalArrangement = Arrangement.spacedBy(7.dp),
verticalAlignment = Alignment.Bottom,
) {
MulticaPillButton(
text = if (uploadingAttachment) "..." else if (zh) "" else "Img",
MulticaIconPillButton(
icon = Icons.Outlined.Image,
contentDescription = "Issue Comment Attach Image",
onClick = onAttachImage,
tone = MulticaButtonTone.Secondary,
contentDescription = "Issue Comment Attach Image",
enabled = !uploadingAttachment,
modifier = Modifier.size(38.dp),
)
MulticaPillButton(
text = "+",
MulticaIconPillButton(
icon = Icons.Outlined.Add,
contentDescription = "Issue Comment Attach",
onClick = onAttach,
tone = MulticaButtonTone.Ghost,
contentDescription = "Issue Comment Attach",
enabled = !uploadingAttachment,
modifier = Modifier.size(38.dp),
)
MulticaPillButton(
text = "@",
@@ -10852,7 +10855,9 @@ private fun IssueCommentInputBar(
value = draft,
onValueChange = onDraftChange,
modifier = Modifier.weight(1f),
label = if (zh) "评论" else "Comment",
label = "",
placeholder = if (zh) "评论" else "Comment",
showLabel = false,
contentDescription = "Issue Comment Input",
minLines = 1,
maxLines = 4,
@@ -1596,6 +1596,8 @@ fun MulticaCupertinoTextField(
label: String,
modifier: Modifier = Modifier,
contentDescription: String? = null,
placeholder: String = label,
showLabel: Boolean = true,
enabled: Boolean = true,
singleLine: Boolean = false,
minLines: Int = 1,
@@ -1609,6 +1611,7 @@ fun MulticaCupertinoTextField(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(5.dp),
) {
if (showLabel && label.isNotBlank()) {
Text(
text = label,
style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp, lineHeight = 15.sp, fontWeight = FontWeight.SemiBold),
@@ -1616,6 +1619,7 @@ fun MulticaCupertinoTextField(
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
CupertinoBorderedTextField(
value = value,
onValueChange = onValueChange,
@@ -1624,7 +1628,11 @@ fun MulticaCupertinoTextField(
singleLine = singleLine,
minLines = minLines,
maxLines = maxLines,
placeholder = { Text(label, color = MulticaColors.TextTertiary) },
placeholder = {
if (placeholder.isNotBlank()) {
Text(placeholder, color = MulticaColors.TextTertiary)
}
},
textStyle = MaterialTheme.typography.bodyLarge.copy(fontSize = 16.sp, lineHeight = 21.sp, color = MulticaColors.TextPrimary),
shape = RoundedCornerShape(10.dp),
)
@@ -1639,6 +1647,8 @@ fun MulticaTextField(
label: String,
modifier: Modifier = Modifier,
contentDescription: String? = null,
placeholder: String = label,
showLabel: Boolean = true,
enabled: Boolean = true,
singleLine: Boolean = false,
minLines: Int = 1,
@@ -1650,6 +1660,8 @@ fun MulticaTextField(
label = label,
modifier = modifier,
contentDescription = contentDescription,
placeholder = placeholder,
showLabel = showLabel,
enabled = enabled,
singleLine = singleLine,
minLines = minLines,