package gio import ( "fmt" "strings" "gioui.org/layout" "gioui.org/unit" "gioui.org/widget/material" "softbox.local/core/application" ) const unsafeIconCacheMessage = "检测到不安全的图标缓存项。该缓存项未被使用,本次请求没有继续远端获取或自动修复。请完全退出 SoftBox 后,按故障排查文档由管理员人工处理。" 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(6), 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 { return layout.Flex{Alignment: layout.Middle}.Layout( gtx, layout.Rigid(material.Body1(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) }), layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.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(64), unit.Dp(7), ) }) }), layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { return layout.Center.Layout(gtx, material.Body1(theme, item.Name).Layout) }), layout.Rigid(layout.Spacer{Height: unit.Dp(3)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { label := material.Caption( 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(10)}.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(func(gtx layout.Context) layout.Dimensions { label := material.Caption(theme, "实际安装/启动操作将在后续用例接入") 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(8)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return outlinedPanel( gtx, shellColors.destructive, shellColors.surface, unit.Dp(6), layout.UniformInset(unit.Dp(10)), 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(3)}.Layout), layout.Rigid(material.Body2(theme, unsafeIconCacheMessage).Layout), layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.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) } func detailField( gtx layout.Context, theme *material.Theme, labelText string, value string, ) layout.Dimensions { return layout.Inset{Bottom: unit.Dp(8)}.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(2)}.Layout), layout.Rigid(material.Caption(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 } }