218 lines
6.5 KiB
Go
218 lines
6.5 KiB
Go
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
|
||
}
|
||
}
|