fd4d756823
- qr/export.go: EntryToOtpAuth + OtpAuth.URI,将 config.Entry 反向 转换为标准 otpauth:// URI(Battle.Net/Steam 标记为 partial) - dialog_export.go: Tab 切换对话框,otpauth URI 列表可复制 + 加密备份文件导出(独立密码,0o600 权限) - dialog_restore_backup.go: 从 .winauth.bak 恢复,密码验证后合并 - settings_menu: 新增 Export / Restore Backup 菜单项 - i18n: en/zh-CN/de 三语翻译
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package ui
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gioui.org/layout"
|
|
"gioui.org/widget"
|
|
"gioui.org/widget/material"
|
|
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/config"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/global"
|
|
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
|
|
)
|
|
|
|
// restoreBackupDialog loads an encrypted .winauth.bak file produced by the
|
|
// export dialog. The user supplies the path and the backup's password; on
|
|
// success the loaded entries are handed back to the caller for merging.
|
|
type restoreBackupDialog struct {
|
|
pathEd widget.Editor
|
|
pwEd widget.Editor
|
|
restoreB widget.Clickable
|
|
cancelB widget.Clickable
|
|
|
|
errorMsg string
|
|
}
|
|
|
|
func newRestoreBackupDialog() *restoreBackupDialog {
|
|
d := &restoreBackupDialog{}
|
|
d.pathEd.SingleLine = true
|
|
d.pwEd.SingleLine = true
|
|
d.pwEd.Mask = '*'
|
|
return d
|
|
}
|
|
|
|
// restoreBackupResult carries the outcome of the dialog.
|
|
type restoreBackupResult struct {
|
|
cfg *config.Config
|
|
cancel bool
|
|
}
|
|
|
|
func (d *restoreBackupDialog) Layout(
|
|
gtx layout.Context, th *material.Theme,
|
|
onDone func(restoreBackupResult),
|
|
) layout.Dimensions {
|
|
if d.cancelB.Clicked(gtx) {
|
|
d.wipe()
|
|
onDone(restoreBackupResult{cancel: true})
|
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
|
}
|
|
if d.restoreB.Clicked(gtx) {
|
|
path := d.pathEd.Text()
|
|
if path == "" {
|
|
d.errorMsg = i18n.T("msg_empty_backup_path")
|
|
} else {
|
|
pw := []byte(d.pwEd.Text())
|
|
cfg, err := config.LoadYAML(path, pw)
|
|
for i := range pw {
|
|
pw[i] = 0
|
|
}
|
|
switch {
|
|
case errors.Is(err, config.ErrPasswordRequired):
|
|
d.errorMsg = i18n.T("msg_password_required")
|
|
case errors.Is(err, config.ErrPasswordWrong):
|
|
d.errorMsg = i18n.T("msg_password_wrong")
|
|
case err != nil:
|
|
d.errorMsg = fmt.Sprintf(i18n.T("msg_restore_failed"), err.Error())
|
|
global.Log.WithField("func", "internal.ui.restoreBackupDialog.Layout").
|
|
WithError(err).Warn("restore backup failed")
|
|
default:
|
|
d.wipe()
|
|
onDone(restoreBackupResult{cfg: cfg})
|
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
|
}
|
|
}
|
|
}
|
|
|
|
body := func(gtx layout.Context) layout.Dimensions {
|
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
|
layout.Rigid(material.Body2(th, i18n.T("restore_backup_intro")).Layout),
|
|
layout.Rigid(layout.Spacer{Height: 8}.Layout),
|
|
layout.Rigid(labeledEditor(th, i18n.T("label_backup_path"), &d.pathEd, "backup.winauth.bak")),
|
|
layout.Rigid(labeledEditor(th, i18n.T("label_password"), &d.pwEd, "")),
|
|
layout.Rigid(errorLabel(th, d.errorMsg)),
|
|
)
|
|
}
|
|
return modalCard(gtx, th, i18n.T("dialog_restore_backup_title"),
|
|
i18n.T("btn_restore"), i18n.T("btn_cancel"),
|
|
&d.restoreB, &d.cancelB, body)
|
|
}
|
|
|
|
func (d *restoreBackupDialog) wipe() {
|
|
d.pwEd.SetText("")
|
|
}
|