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" ) // importLegacyDialog drives the "Import legacy WinAuth XML..." flow. // It is intentionally minimal: a path field, an optional password field // for entries the user encrypted in the original C# WinAuth, and Import // + Cancel buttons. DPAPI-encrypted entries decrypt transparently on // Windows; on other platforms they are skipped with a warning in the // log. type importLegacyDialog struct { pathEd widget.Editor pwEd widget.Editor importBt widget.Clickable cancelBt widget.Clickable errorMsg string } func newImportLegacyDialog() *importLegacyDialog { d := &importLegacyDialog{} d.pathEd.SingleLine = true d.pwEd.SingleLine = true d.pwEd.Mask = '*' return d } // importLegacyResult is what the dialog reports to its caller. type importLegacyResult struct { cfg *config.Config cancel bool } // Layout returns dimensions and reports outcomes via onDone. onDone is // called with cancel=true on Cancel and with a non-nil cfg on success; // validation errors are kept inside the dialog so the user can retry. func (d *importLegacyDialog) Layout( gtx layout.Context, th *material.Theme, onDone func(importLegacyResult), ) layout.Dimensions { if d.cancelBt.Clicked(gtx) { d.wipe() onDone(importLegacyResult{cancel: true}) return layout.Dimensions{Size: gtx.Constraints.Max} } if d.importBt.Clicked(gtx) { path := d.pathEd.Text() if path == "" { d.errorMsg = i18n.T("msg_empty_import_path") } else { password := []byte(d.pwEd.Text()) cfg, err := config.LoadLegacyXML(path, password) for i := range password { password[i] = 0 } switch { case errors.Is(err, config.ErrLegacyPasswordRequired): d.errorMsg = i18n.T("msg_legacy_password_required") case errors.Is(err, config.ErrLegacyPasswordWrong): d.errorMsg = i18n.T("msg_legacy_password_wrong") case err != nil: d.errorMsg = fmt.Sprintf(i18n.T("msg_import_failed"), err.Error()) global.Log.WithField("func", "internal.ui.importLegacyDialog.Layout"). WithError(err).Warn("legacy import failed") default: d.wipe() onDone(importLegacyResult{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("import_legacy_intro")).Layout), layout.Rigid(layout.Spacer{Height: 8}.Layout), layout.Rigid(labeledEditor(th, i18n.T("label_import_path"), &d.pathEd, "C:\\Users\\...\\winauth.xml")), layout.Rigid(labeledEditor(th, i18n.T("label_password"), &d.pwEd, "")), layout.Rigid(errorLabel(th, d.errorMsg)), ) } return modalCard(gtx, th, i18n.T("dialog_import_legacy_title"), i18n.T("btn_import"), i18n.T("btn_cancel"), &d.importBt, &d.cancelBt, body) } // wipe clears the password editor so the plaintext bytes are not kept // after the dialog closes. func (d *importLegacyDialog) wipe() { d.pwEd.SetText("") }