feat(ui): vendor 图标系统 + 应用图标占位
- 新增 tools/gen_icons 生成 7 张 vendor 占位 PNG + 256x256 应用图标 - internal/ui/vendor_icons.go 用 //go:embed 加载并按 Authenticator.Name() 查表 - entry 行圆环中心叠 16dp vendor 图标,HOTP 直接用图标占位 - 图标为通用色块 + 首字母,无第三方商标;用户可按文件名替换为真实 logo - assets/README 说明替换方式与 Windows .syso 嵌入步骤
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"image"
|
||||
_ "image/png"
|
||||
|
||||
"git.wxccs.org/iceking2nd/winauth-go/internal/global"
|
||||
)
|
||||
|
||||
//go:embed assets/vendor_icons/*.png assets/app_icon.png
|
||||
var assetsFS embed.FS
|
||||
|
||||
// vendorIcons holds the decoded vendor PNGs keyed by the authenticator
|
||||
// Name() return value. Built once at first lookup so we pay the decode
|
||||
// cost once per process instead of per frame.
|
||||
var vendorIcons = map[string]image.Image{}
|
||||
|
||||
func loadVendorIcons() {
|
||||
const fn = "internal.ui.loadVendorIcons"
|
||||
files := []string{"google", "microsoft", "okta", "hotp", "battlenet", "steam", "qr"}
|
||||
for _, name := range files {
|
||||
data, err := assetsFS.ReadFile("assets/vendor_icons/" + name + ".png")
|
||||
if err != nil {
|
||||
global.Log.WithField("func", fn).WithError(err).
|
||||
WithField("vendor", name).Warn("vendor icon missing from embed")
|
||||
continue
|
||||
}
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
global.Log.WithField("func", fn).WithError(err).
|
||||
WithField("vendor", name).Warn("vendor icon decode failed")
|
||||
continue
|
||||
}
|
||||
vendorIcons[name] = img
|
||||
}
|
||||
}
|
||||
|
||||
// vendorIconFor returns the icon image bound to the given authenticator
|
||||
// vendor key ("google", "microsoft", ...). Falls back to the generic
|
||||
// "qr" tile when no specific icon is registered, and nil when even that
|
||||
// is unavailable (only happens if the embed is broken).
|
||||
func vendorIconFor(vendor string) image.Image {
|
||||
if len(vendorIcons) == 0 {
|
||||
loadVendorIcons()
|
||||
}
|
||||
if img, ok := vendorIcons[vendor]; ok {
|
||||
return img
|
||||
}
|
||||
return vendorIcons["qr"]
|
||||
}
|
||||
|
||||
// AppIcon returns the decoded application icon, or nil if the asset is
|
||||
// missing. Currently unused at runtime — Gio v0.7 has no API to set a
|
||||
// window icon, so this exists so callers (e.g. a future tray bucket)
|
||||
// can reuse the same bitmap.
|
||||
func AppIcon() image.Image {
|
||||
data, err := assetsFS.ReadFile("assets/app_icon.png")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return img
|
||||
}
|
||||
Reference in New Issue
Block a user