package ui import ( "context" "fmt" "sync" "time" "gioui.org/layout" "gioui.org/widget" "gioui.org/widget/material" "git.wxccs.org/iceking2nd/winauth-go/internal/authenticator" "git.wxccs.org/iceking2nd/winauth-go/internal/global" "git.wxccs.org/iceking2nd/winauth-go/internal/i18n" ) // addBattleNetDialog drives the Battle.Net mobile-service enrollment flow. // Unlike Google/Microsoft/Okta the secret is server-issued, so the user // only chooses a display name and an optional region override; on Enroll // we kick off a background goroutine and disable the button while waiting. type addBattleNetDialog struct { nameEd widget.Editor regionEd widget.Editor // empty = auto okBtn widget.Clickable cancelBtn widget.Clickable errorMsg string mu sync.Mutex pending bool result *authenticator.BattleNetAuthenticator resultEr error invalidate func() // set by Layout on first call } func newAddBattleNetDialog(invalidate func()) *addBattleNetDialog { d := &addBattleNetDialog{invalidate: invalidate} d.nameEd.SingleLine = true d.regionEd.SingleLine = true return d } func (d *addBattleNetDialog) Layout( gtx layout.Context, th *material.Theme, onDone func(authenticator.Authenticator, string), ) layout.Dimensions { const fn = "internal.ui.addBattleNetDialog.Layout" d.mu.Lock() pending := d.pending finished := !pending && (d.result != nil || d.resultEr != nil) res := d.result resErr := d.resultEr d.mu.Unlock() if finished { if resErr != nil { d.errorMsg = fmt.Sprintf(i18n.T("msg_enroll_failed"), resErr.Error()) // clear the latched result so retry works d.mu.Lock() d.result, d.resultEr = nil, nil d.mu.Unlock() } else if res != nil { name := d.nameEd.Text() if name == "" { name = i18n.T("vendor_battlenet") } onDone(res, name) return layout.Dimensions{Size: gtx.Constraints.Max} } } if d.okBtn.Clicked(gtx) && !pending { d.mu.Lock() d.pending = true d.errorMsg = i18n.T("msg_enrolling") d.mu.Unlock() region := d.regionEd.Text() go func(region string) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() b := authenticator.NewBattleNetAuthenticator() err := b.Enroll(ctx, region) d.mu.Lock() d.pending = false if err != nil { global.Log.WithField("func", fn).WithError(err).Warn("battle.net enroll failed") d.resultEr = err } else { d.result = b } d.mu.Unlock() if d.invalidate != nil { d.invalidate() } }(region) } if d.cancelBtn.Clicked(gtx) && !pending { onDone(nil, "") return layout.Dimensions{Size: gtx.Constraints.Max} } body := func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(labeledEditor(th, i18n.T("label_name"), &d.nameEd, i18n.T("vendor_battlenet"))), layout.Rigid(labeledEditor(th, i18n.T("label_region"), &d.regionEd, i18n.T("region_auto"))), layout.Rigid(errorLabel(th, d.errorMsg)), ) } // disable buttons while pending by swallowing inputs (no Gio-native // "disabled" flag on material.Button — we just gate via the pending check // above before reacting to clicks) return modalCard(gtx, th, i18n.T("dialog_add_battlenet_title"), i18n.T("btn_enroll"), i18n.T("btn_cancel"), &d.okBtn, &d.cancelBtn, body) }