feat(ui): 接入 vendor 真实图标库 + 图标选择器
- assets/icons/ 共 80+ 张真实品牌图标嵌入 UI 包 - entry 新增 IconKey 字段,持久化到 config.Entry.IconName - iconPickerDialog 网格布局图标选择器 - Add 完成后自动弹出图标选择器,Skip 则用 vendor 占位图 - ⋯ 菜单新增 "Change icon..." 项 - vendorIconFor 解析顺序:catalog → vendor 占位 → qr 兜底
This commit is contained in:
+36
-3
@@ -56,6 +56,11 @@ type entry struct {
|
||||
Auth authenticator.Authenticator
|
||||
Code string
|
||||
|
||||
// IconKey selects a specific brand icon from the embedded catalog
|
||||
// (e.g. "GitHubIcon", "DropboxIcon"). Empty means fall back to the
|
||||
// vendor-name placeholder ("google", "steam", …).
|
||||
IconKey string
|
||||
|
||||
// Hotkey is the user-configured global shortcut string ("Ctrl+Alt+G")
|
||||
// or "" if none is set.
|
||||
Hotkey string
|
||||
@@ -106,6 +111,7 @@ type appState struct {
|
||||
rowMenu *rowActionMenu
|
||||
renameDlg *renameDialog
|
||||
confirmDelDlg *confirmDeleteDialog
|
||||
iconPickerDlg *iconPickerDialog
|
||||
rowTargetIdx int
|
||||
|
||||
store *store
|
||||
@@ -144,7 +150,7 @@ func (st *appState) snapshotEntries() []config.Entry {
|
||||
defer st.mu.Unlock()
|
||||
out := make([]config.Entry, 0, len(st.entries))
|
||||
for _, en := range st.entries {
|
||||
out = append(out, entryFromAuthenticator(en.Name, en.Auth, en.Hotkey))
|
||||
out = append(out, entryFromAuthenticator(en.Name, en.Auth, en.Hotkey, en.IconKey))
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -285,7 +291,7 @@ func (st *appState) absorbConfig(cfg *config.Config) {
|
||||
global.Log.WithField("func", fn).WithError(err).Warn("skip entry")
|
||||
continue
|
||||
}
|
||||
st.entries = append(st.entries, &entry{Name: e.Name, Auth: a, Hotkey: e.Hotkey})
|
||||
st.entries = append(st.entries, &entry{Name: e.Name, Auth: a, Hotkey: e.Hotkey, IconKey: e.IconName})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +311,7 @@ func (st *appState) mergeImportedConfig(cfg *config.Config) {
|
||||
global.Log.WithField("func", fn).WithError(err).Warn("skip imported entry")
|
||||
continue
|
||||
}
|
||||
st.entries = append(st.entries, &entry{Name: e.Name, Auth: a, Hotkey: e.Hotkey})
|
||||
st.entries = append(st.entries, &entry{Name: e.Name, Auth: a, Hotkey: e.Hotkey, IconKey: e.IconName})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,8 +614,13 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
if added != nil {
|
||||
st.mu.Lock()
|
||||
st.entries = append(st.entries, &entry{Name: name, Auth: added})
|
||||
idx := len(st.entries) - 1
|
||||
st.mu.Unlock()
|
||||
st.store.Push()
|
||||
// Auto-open icon picker so the user can choose a brand
|
||||
// icon for the entry right after adding it.
|
||||
st.rowTargetIdx = idx
|
||||
st.iconPickerDlg = newIconPickerDialog("")
|
||||
}
|
||||
st.dialog = nil
|
||||
w.Invalidate()
|
||||
@@ -656,6 +667,14 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
st.moveEntry(idx, -1)
|
||||
case rowActionMoveDown:
|
||||
st.moveEntry(idx, +1)
|
||||
case rowActionChangeIcon:
|
||||
st.mu.Lock()
|
||||
current := ""
|
||||
if idx >= 0 && idx < len(st.entries) {
|
||||
current = st.entries[idx].IconKey
|
||||
}
|
||||
st.mu.Unlock()
|
||||
st.iconPickerDlg = newIconPickerDialog(current)
|
||||
}
|
||||
w.Invalidate()
|
||||
} else {
|
||||
@@ -680,6 +699,20 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
w.Invalidate()
|
||||
})
|
||||
}
|
||||
if st.iconPickerDlg != nil {
|
||||
return st.iconPickerDlg.Layout(gtx, th, func(iconKey string, cancel bool) {
|
||||
if !cancel {
|
||||
st.mu.Lock()
|
||||
if st.rowTargetIdx >= 0 && st.rowTargetIdx < len(st.entries) {
|
||||
st.entries[st.rowTargetIdx].IconKey = iconKey
|
||||
}
|
||||
st.mu.Unlock()
|
||||
st.store.Push()
|
||||
}
|
||||
st.iconPickerDlg = nil
|
||||
w.Invalidate()
|
||||
})
|
||||
}
|
||||
|
||||
if st.tradesDialog != nil {
|
||||
return st.tradesDialog.Layout(gtx, th)
|
||||
|
||||
Reference in New Issue
Block a user