feat(ui): Battle.Net 序列号/恢复码展示 + 热键复制确认
- dialog_bnet_info.go: 新增 Battle.Net 信息对话框,显示序列号 和恢复码,支持一键复制恢复码 - row_menu: 新增 rowActionBnetInfo 动作,仅对 Battle.Net 条目可见 - 热键行为确认:autoType 已自动复制到剪贴板 + 键入前台窗口 - i18n: action_bnet_info / dialog_bnet_info_title / bnet_info_intro / hint_bnet_restore_code_secret 三语翻译
This commit is contained in:
@@ -580,3 +580,16 @@ other = "Beim Windows-Start starten"
|
||||
|
||||
[hint_auto_start]
|
||||
other = "Trägt die Anwendung in den Windows-Autostart-Registrierungsschlüssel ein."
|
||||
|
||||
[action_bnet_info]
|
||||
other = "Seriennummer & Wiederherstellungscode..."
|
||||
|
||||
[dialog_bnet_info_title]
|
||||
other = "Battle.Net-Seriennummer & Wiederherstellungscode"
|
||||
|
||||
[bnet_info_intro]
|
||||
other = "Bewahre den Wiederherstellungscode geheim — jeder, der sowohl Seriennummer als auch Wiederherstellungscode besitzt, kann den vollen Kontozugriff wiederherstellen."
|
||||
|
||||
[hint_bnet_restore_code_secret]
|
||||
other = "Teile den Wiederherstellungscode niemals. Er ist die einzige Möglichkeit, diesen Authentifikator wiederherzustellen."
|
||||
|
||||
|
||||
@@ -582,3 +582,16 @@ other = "Launch at Windows startup"
|
||||
|
||||
[hint_auto_start]
|
||||
other = "Adds the application to the Windows startup registry key."
|
||||
|
||||
[action_bnet_info]
|
||||
other = "Serial & restore code..."
|
||||
|
||||
[dialog_bnet_info_title]
|
||||
other = "Battle.Net serial & restore code"
|
||||
|
||||
[bnet_info_intro]
|
||||
other = "Keep the restore code secret — anyone who has both the serial and the restore code can recover full account access."
|
||||
|
||||
[hint_bnet_restore_code_secret]
|
||||
other = "Never share the restore code. It is the only way to recover this authenticator."
|
||||
|
||||
|
||||
@@ -580,3 +580,16 @@ other = "开机自启动"
|
||||
|
||||
[hint_auto_start]
|
||||
other = "将程序添加到 Windows 启动注册表项。"
|
||||
|
||||
[action_bnet_info]
|
||||
other = "序列号与恢复码..."
|
||||
|
||||
[dialog_bnet_info_title]
|
||||
other = "战网序列号与恢复码"
|
||||
|
||||
[bnet_info_intro]
|
||||
other = "请妥善保管恢复码 —— 任何同时拥有序列号和恢复码的人都能恢复完整的账号访问权限。"
|
||||
|
||||
[hint_bnet_restore_code_secret]
|
||||
other = "切勿泄露恢复码。它是恢复此验证器的唯一途径。"
|
||||
|
||||
|
||||
+16
-1
@@ -127,6 +127,7 @@ type appState struct {
|
||||
renameDlg *renameDialog
|
||||
confirmDelDlg *confirmDeleteDialog
|
||||
iconPickerDlg *iconPickerDialog
|
||||
bnetInfoDlg *bnetInfoDialog
|
||||
rowTargetIdx int
|
||||
|
||||
store *store
|
||||
@@ -497,7 +498,13 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
total := len(st.entries)
|
||||
st.mu.Unlock()
|
||||
st.rowTargetIdx = moreTargetIdx
|
||||
st.rowMenu = newRowActionMenu(moreTargetIdx, total)
|
||||
vendor := ""
|
||||
st.mu.Lock()
|
||||
if moreTargetIdx >= 0 && moreTargetIdx < len(st.entries) {
|
||||
vendor = st.entries[moreTargetIdx].Auth.Name()
|
||||
}
|
||||
st.mu.Unlock()
|
||||
st.rowMenu = newRowActionMenu(moreTargetIdx, total, vendor)
|
||||
}
|
||||
|
||||
// First-run welcome dialog. Routed before all other dialogs so the
|
||||
@@ -743,6 +750,14 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
}
|
||||
st.mu.Unlock()
|
||||
st.iconPickerDlg = newIconPickerDialog(current)
|
||||
case rowActionBnetInfo:
|
||||
st.mu.Lock()
|
||||
if idx >= 0 && idx < len(st.entries) {
|
||||
if bn, ok := st.entries[idx].Auth.(*authenticator.BattleNetAuthenticator); ok {
|
||||
st.bnetInfoDlg = newBnetInfoDialog(bn)
|
||||
}
|
||||
}
|
||||
st.mu.Unlock()
|
||||
}
|
||||
w.Invalidate()
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
|
||||
"git.wxccs.org/iceking2nd/winauth-go/internal/authenticator"
|
||||
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
|
||||
"git.wxccs.org/iceking2nd/winauth-go/internal/win32"
|
||||
)
|
||||
|
||||
// bnetInfoDialog shows the Battle.Net serial number and restore code.
|
||||
// The restore code can be copied to the clipboard.
|
||||
type bnetInfoDialog struct {
|
||||
serial string
|
||||
restoreCode string
|
||||
copyBtn widget.Clickable
|
||||
closeBtn widget.Clickable
|
||||
copied bool
|
||||
}
|
||||
|
||||
func newBnetInfoDialog(bn *authenticator.BattleNetAuthenticator) *bnetInfoDialog {
|
||||
return &bnetInfoDialog{
|
||||
serial: bn.Serial,
|
||||
restoreCode: bn.RestoreCode(),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *bnetInfoDialog) Layout(
|
||||
gtx layout.Context, th *material.Theme,
|
||||
onClose func(),
|
||||
) layout.Dimensions {
|
||||
if d.closeBtn.Clicked(gtx) {
|
||||
onClose()
|
||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||
}
|
||||
if d.copyBtn.Clicked(gtx) {
|
||||
if err := win32.SetClipboardText(d.restoreCode); err == nil {
|
||||
d.copied = true
|
||||
}
|
||||
}
|
||||
|
||||
body := func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.Body2(th, i18n.T("bnet_info_intro")).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(material.Body2(th, i18n.T("label_serial")+": ").Layout),
|
||||
layout.Rigid(material.Body1(th, d.serial).Layout),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
layout.Rigid(material.Body2(th, i18n.T("label_restore_code")+": ").Layout),
|
||||
layout.Rigid(material.Body1(th, d.restoreCode).Layout),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
copyLabel := i18n.T("btn_copy")
|
||||
if d.copied {
|
||||
copyLabel = i18n.T("msg_copied")
|
||||
}
|
||||
return material.Button(th, &d.copyBtn, copyLabel).Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(material.Caption(th, i18n.T("hint_bnet_restore_code_secret")).Layout),
|
||||
)
|
||||
}
|
||||
|
||||
return modalCardCancel(gtx, th, i18n.T("dialog_bnet_info_title"),
|
||||
i18n.T("btn_close"), &d.closeBtn, body)
|
||||
}
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
rowActionMoveUp
|
||||
rowActionMoveDown
|
||||
rowActionChangeIcon
|
||||
rowActionBnetInfo
|
||||
)
|
||||
|
||||
// rowActionMenu is the small popup opened by the ⋯ button on each entry
|
||||
@@ -29,20 +30,23 @@ type rowActionMenu struct {
|
||||
idx int
|
||||
canMoveUp bool
|
||||
canMoveDown bool
|
||||
isBnet bool
|
||||
|
||||
renameBtn widget.Clickable
|
||||
deleteBtn widget.Clickable
|
||||
moveUpBtn widget.Clickable
|
||||
moveDownBtn widget.Clickable
|
||||
changeIconBtn widget.Clickable
|
||||
bnetInfoBtn widget.Clickable
|
||||
cancelBtn widget.Clickable
|
||||
}
|
||||
|
||||
func newRowActionMenu(idx, total int) *rowActionMenu {
|
||||
func newRowActionMenu(idx, total int, vendor string) *rowActionMenu {
|
||||
return &rowActionMenu{
|
||||
idx: idx,
|
||||
canMoveUp: idx > 0,
|
||||
canMoveDown: idx < total-1,
|
||||
isBnet: vendor == "battlenet",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +111,7 @@ func (m *rowActionMenu) Layout(gtx layout.Context, th *material.Theme) layout.Di
|
||||
row(&m.renameBtn, i18n.T("action_rename"), true),
|
||||
row(&m.deleteBtn, i18n.T("action_delete"), true),
|
||||
row(&m.changeIconBtn, i18n.T("action_change_icon"), true),
|
||||
row(&m.bnetInfoBtn, i18n.T("action_bnet_info"), m.isBnet),
|
||||
row(&m.moveUpBtn, i18n.T("action_move_up"), m.canMoveUp),
|
||||
row(&m.moveDownBtn, i18n.T("action_move_down"), m.canMoveDown),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
|
||||
Reference in New Issue
Block a user