Files
soft_quay/app-modern/ui/gio/shell_detail.go
T

247 lines
7.3 KiB
Go
Raw Normal View History

2026-07-18 14:23:02 +08:00
package gio
import (
"fmt"
"strings"
"gioui.org/layout"
"gioui.org/unit"
"gioui.org/widget/material"
"softbox.local/core/application"
"softbox.local/core/domain"
)
const unsafeIconCacheMessage = "检测到不安全的图标缓存项。该缓存项未被使用,本次请求没有继续远端获取或自动修复。请完全退出 SoftBox 后,按故障排查文档由管理员人工处理。"
2026-07-18 14:23:02 +08:00
func (shell *AppShell) layoutDetail(
gtx layout.Context,
theme *material.Theme,
item application.CatalogListItem,
) layout.Dimensions {
shell.detailRendered = true
return panel(
gtx,
shellColors.muted,
unit.Dp(10),
layout.UniformInset(unit.Dp(16)),
func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Alignment: layout.Middle}.Layout(
gtx,
layout.Rigid(material.H6(theme, "软件详情").Layout),
layout.Flexed(1, layout.Spacer{}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return shell.layoutFilterButton(
gtx,
theme,
&shell.closeDetail,
"关闭",
false,
)
}),
)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return shell.layoutUnsafeIconCacheFailure(gtx, theme, item.ID)
}),
2026-07-18 14:23:02 +08:00
layout.Rigid(layout.Spacer{Height: unit.Dp(16)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return shell.layoutAppIcon(
gtx,
theme,
item.ID,
item.Name,
unit.Dp(72),
unit.Dp(12),
)
})
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Center.Layout(gtx, material.H6(theme, item.Name).Layout)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
label := material.Body2(
theme,
fmt.Sprintf("%s · %s", item.ID, item.Version),
)
label.Color = shellColors.secondary
return layout.Center.Layout(gtx, label.Layout)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(16)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return detailField(gtx, theme, "状态", statusLabel(item.Status))
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return detailField(gtx, theme, "分类", fallbackText(item.Category, "未分类"))
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return detailField(
gtx,
theme,
"标签",
fallbackText(strings.Join(item.Tags, " · "), "无"),
)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return detailField(
gtx,
theme,
"简介",
fallbackText(item.Description, "暂无简介"),
)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if item.Reason == "" {
return layout.Dimensions{}
}
return detailField(gtx, theme, "不可用原因", reasonLabel(item.Reason))
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if item.Tutorial == "" {
return layout.Dimensions{}
}
return detailField(gtx, theme, "教程", item.Tutorial)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if item.Homepage == "" {
return layout.Dimensions{}
}
return detailField(gtx, theme, "主页", item.Homepage)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
label := material.Caption(theme, actionLabel(item)+";实际操作将在后续用例接入")
label.Color = shellColors.secondary
return label.Layout(gtx)
}),
)
},
)
}
func (shell *AppShell) layoutUnsafeIconCacheFailure(
gtx layout.Context,
theme *material.Theme,
appID string,
) layout.Dimensions {
failure, exists := shell.iconFailures[appID]
if !exists || failure.Code != application.IconFailureUnsafe ||
failure.Identity.AppID != appID {
return layout.Dimensions{}
}
return layout.Inset{Top: unit.Dp(12)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return outlinedPanel(
gtx,
shellColors.destructive,
shellColors.surface,
unit.Dp(8),
layout.UniformInset(unit.Dp(12)),
func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
title := material.Body1(theme, "图标缓存安全警告")
title.Color = shellColors.destructive
return title.Layout(gtx)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
layout.Rigid(material.Body2(theme, unsafeIconCacheMessage).Layout),
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
diagnostic := material.Caption(theme, unsafeIconCacheDiagnostic(failure))
diagnostic.Color = shellColors.secondary
return diagnostic.Layout(gtx)
}),
)
},
)
})
}
func unsafeIconCacheDiagnostic(failure iconFailureState) string {
return fmt.Sprintf(
"诊断码:%s\n应用 ID:%s\n缓存定位符:%s",
failure.Code,
failure.Identity.AppID,
unsafeIconCacheLocator(failure.Identity),
)
}
func unsafeIconCacheLocator(identity application.IconEventIdentity) string {
digest := strings.TrimPrefix(identity.Reference, "sha256:")
return fmt.Sprintf("%s-%d.icon", digest, identity.DPI)
}
2026-07-18 14:23:02 +08:00
func actionLabel(item application.CatalogListItem) string {
if item.Reason != "" || item.Status == domain.StatusIncompatible {
return "查看不可用原因"
}
switch item.Status {
case domain.StatusInstalled:
return "查看或启动"
case domain.StatusUpdateAvailable:
return "查看更新"
case domain.StatusRunning:
return "查看运行状态"
case domain.StatusQueued,
domain.StatusDownloading,
domain.StatusVerifying,
domain.StatusExtracting,
domain.StatusInstalling:
return "查看任务"
case domain.StatusFailed, domain.StatusRollbackPending:
return "查看恢复选项"
default:
if item.Installable {
return "查看并安装"
}
return "查看详情"
}
}
func detailField(
gtx layout.Context,
theme *material.Theme,
labelText string,
value string,
) layout.Dimensions {
return layout.Inset{Bottom: unit.Dp(12)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
label := material.Caption(theme, labelText)
label.Color = shellColors.secondary
return label.Layout(gtx)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(3)}.Layout),
layout.Rigid(material.Body2(theme, value).Layout),
)
})
}
func fallbackText(value, fallback string) string {
if value == "" {
return fallback
}
return value
}
func reasonLabel(reason string) string {
switch reason {
case "deprecated":
return "软件已停止发布,不能新装或更新"
case "minimum_os":
return "当前 Windows 版本低于最低要求"
case "architecture":
return "没有适用于当前系统架构的软件包"
default:
return reason
}
}