package ui import ( "image/color" "time" "gioui.org/layout" "gioui.org/unit" "git.wxccs.org/iceking2nd/winauth-go/internal/authenticator" ) // entryProgressRing draws the per-row TOTP countdown ring. For HOTP // entries it returns a same-sized blank box so the rows still line up. func entryProgressRing(gtx layout.Context, en *entry) layout.Dimensions { const ringDp = 22 if en.Auth == nil || en.Auth.Name() == "hotp" { return layout.Dimensions{Size: gtx.Constraints.Constrain( layout.Spacer{Width: unit.Dp(ringDp), Height: unit.Dp(ringDp)}.Layout(gtx).Size, )} } period := totpPeriod(en.Auth) if period <= 0 { period = authenticator.DefaultPeriod } now := time.Now().Unix() elapsed := now % int64(period) remaining := int64(period) - elapsed progress := float32(remaining) / float32(period) fg := color.NRGBA{R: 0x10, G: 0x70, B: 0xff, A: 0xff} if remaining <= 5 { fg = color.NRGBA{R: 0xd0, G: 0x30, B: 0x30, A: 0xff} } bg := color.NRGBA{R: 0xd8, G: 0xd8, B: 0xd8, A: 0xff} return progressRing{ Size: unit.Dp(ringDp), Stroke: unit.Dp(2.5), Progress: progress, Color: fg, BgColor: bg, }.Layout(gtx) } // totpPeriod extracts the configured period from any authenticator whose // underlying Base we can reach. Returns 0 if the authenticator does not // expose one (in which case callers fall back to the default). func totpPeriod(a authenticator.Authenticator) int { type periodGetter interface{ GetPeriod() int } if pg, ok := a.(periodGetter); ok { return pg.GetPeriod() } // All current TOTP-like authenticators (Google/Microsoft/Okta/Steam/ // BattleNet) embed authenticator.Base whose default period is 30s, // matching the C# original. Hard-code that here. return authenticator.DefaultPeriod }