cmd/tailscale: add progress indicator to sign in buttons

For tailscale/tailscale#481

Signed-off-by: Elias Naur <mail@eliasnaur.com>
pull/3/head
Elias Naur 4 years ago
parent e3befb01d8
commit 22851bfa05

@ -44,7 +44,7 @@ type App struct {
// mu protects the following fields.
mu sync.Mutex
// netState is the most recent network state.
netState NetworkState
netState BackendState
// browseURL is set whenever the backend wants to
// browse.
browseURL *string
@ -54,14 +54,14 @@ type App struct {
type clientState struct {
browseURL string
net NetworkState
backend BackendState
// query is the search query, in lowercase.
query string
Peers []UIPeer
}
type NetworkState struct {
type BackendState struct {
State ipn.State
NetworkMap *controlclient.NetworkMap
LostInternet bool
@ -160,9 +160,12 @@ func (a *App) runBackend() error {
notifications <- n
})
}()
var cfg *router.Config
var state NetworkState
var service jni.Object
var (
cfg *router.Config
state BackendState
service jni.Object
signingIn bool
)
for {
select {
case err := <-startErr:
@ -215,6 +218,7 @@ func (a *App) runBackend() error {
a.notify(state)
}
if u := n.BrowseToURL; u != nil {
signingIn = false
a.setURL(*u)
}
if m := n.NetMap; m != nil {
@ -234,7 +238,10 @@ func (a *App) runBackend() error {
case e := <-a.backend:
switch e := e.(type) {
case ReauthEvent:
go b.backend.StartLoginInteractive()
if !signingIn {
go b.backend.StartLoginInteractive()
signingIn = true
}
case LogoutEvent:
go b.backend.Logout()
case ConnectEvent:
@ -374,7 +381,7 @@ func (a *App) notifyVPNClosed() {
}
}
func (a *App) notify(state NetworkState) {
func (a *App) notify(state BackendState) {
a.mu.Lock()
a.netState = state
a.mu.Unlock()
@ -420,11 +427,12 @@ func (a *App) runUI() error {
a.request(ConnectEvent{Enable: false})
case <-a.updates:
a.mu.Lock()
oldState := state.net.State
state.net = a.netState
oldState := state.backend.State
state.backend = a.netState
if a.browseURL != nil {
state.browseURL = *a.browseURL
a.browseURL = nil
ui.signinType = noSignin
}
if a.prefs != nil {
ui.enabled.Value = a.prefs.WantRunning
@ -434,7 +442,7 @@ func (a *App) runUI() error {
a.updateState(peer, state)
w.Invalidate()
if peer != 0 {
newState := state.net.State
newState := state.backend.State
// Start VPN if we just logged in.
if oldState <= ipn.Stopped && newState > ipn.Stopped {
if err := a.callVoidMethod(peer, "prepareVPN", "()V"); err != nil {
@ -444,7 +452,7 @@ func (a *App) runUI() error {
}
case peer = <-onPeerCreated:
w.Invalidate()
if state.net.State > ipn.Stopped {
if state.backend.State > ipn.Stopped {
if err := a.callVoidMethod(peer, "prepareVPN", "()V"); err != nil {
return err
}
@ -459,7 +467,7 @@ func (a *App) runUI() error {
return nil
})
case <-vpnPrepared:
if state.net.State > ipn.Stopped {
if state.backend.State > ipn.Stopped {
if err := a.callVoidMethod(a.appCtx, "startVPN", "()V"); err != nil {
return err
}
@ -502,7 +510,7 @@ func (a *App) updateState(javaPeer jni.Object, state *clientState) {
}
state.Peers = nil
netMap := state.net.NetworkMap
netMap := state.backend.NetworkMap
if netMap == nil {
return
}
@ -534,7 +542,7 @@ func (a *App) updateState(javaPeer jni.Object, state *clientState) {
name = strings.ToUpper(name)
peers = append(peers, UIPeer{Owner: u, Name: name})
}
myID := state.net.NetworkMap.User
myID := state.backend.NetworkMap.User
sort.Slice(peers, func(i, j int) bool {
lhs, rhs := peers[i], peers[j]
if lu, ru := lhs.Owner, rhs.Owner; ru != lu {

@ -51,6 +51,8 @@ type UI struct {
// googleSignin is the button for native Google Sign-in
googleSignin widget.Clickable
signinType signinType
self widget.Clickable
peers []widget.Clickable
@ -86,6 +88,8 @@ type UI struct {
events []UIEvent
}
type signinType uint8
// An UIPeer is either a peer or a section header
// with the user information.
type UIPeer struct {
@ -108,6 +112,12 @@ const (
keyShowIntro = "ui.showintro"
)
const (
noSignin signinType = iota
webSignin
googleSignin
)
type (
C = layout.Context
D = layout.Dimensions
@ -175,7 +185,7 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
ui.menu.show = !ui.menu.show
}
netmap := state.net.NetworkMap
netmap := state.backend.NetworkMap
var localName, localAddr string
var expiry time.Time
if netmap != nil {
@ -186,12 +196,16 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
}
}
if ui.googleSignin.Clicked() {
ui.events = append(ui.events, GoogleAuthEvent{})
}
if ui.signinType == noSignin {
if ui.googleSignin.Clicked() {
ui.signinType = googleSignin
ui.events = append(ui.events, GoogleAuthEvent{})
}
if ui.webSignin.Clicked() {
ui.events = append(ui.events, ReauthEvent{})
if ui.webSignin.Clicked() {
ui.signinType = webSignin
ui.events = append(ui.events, ReauthEvent{})
}
}
if ui.menuClicked(&ui.menu.copy) && localAddr != "" {
@ -215,7 +229,7 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
const numHeaders = 5
n := numHeaders + len(state.Peers)
needsLogin := state.net.State == ipn.NeedsLogin
needsLogin := state.backend.State == ipn.NeedsLogin
ui.root.Layout(gtx, n, func(gtx C, idx int) D {
var in layout.Inset
if idx == n-1 {
@ -226,14 +240,14 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
return in.Layout(gtx, func(gtx C) D {
switch idx {
case 0:
return ui.layoutTop(gtx, sysIns, &state.net)
return ui.layoutTop(gtx, sysIns, &state.backend)
case 1:
if netmap == nil || state.net.State < ipn.Stopped {
if netmap == nil || state.backend.State < ipn.Stopped {
return D{}
}
return ui.layoutLocal(gtx, sysIns, localName, localAddr)
case 2:
if state.net.State < ipn.Stopped {
if state.backend.State < ipn.Stopped {
return D{}
}
return ui.layoutSearchbar(gtx, sysIns)
@ -241,9 +255,9 @@ func (ui *UI) layout(gtx layout.Context, sysIns system.Insets, state *clientStat
if !needsLogin {
return D{}
}
return ui.layoutSignIn(gtx)
return ui.layoutSignIn(gtx, &state.backend)
case 4:
if needsLogin || !state.net.LostInternet {
if needsLogin || !state.backend.LostInternet {
return D{}
}
return ui.layoutDisconnected(gtx)
@ -309,7 +323,7 @@ func (d *Dismiss) Dismissed(gtx layout.Context) bool {
}
// layoutSignIn lays out the sign in button(s).
func (ui *UI) layoutSignIn(gtx layout.Context) layout.Dimensions {
func (ui *UI) layoutSignIn(gtx layout.Context, state *BackendState) layout.Dimensions {
return layout.Inset{Top: unit.Dp(48), Left: unit.Dp(48), Right: unit.Dp(48)}.Layout(gtx, func(gtx C) D {
const (
textColor = 0x555555
@ -328,24 +342,26 @@ func (ui *UI) layoutSignIn(gtx layout.Context) layout.Dimensions {
signin := material.ButtonLayout(ui.theme, &ui.googleSignin)
signin.Background = rgb(white)
return border.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Px(2)).Layout(gtx, func(gtx C) D {
return signin.Layout(gtx, func(gtx C) D {
gtx.Constraints.Max.Y = gtx.Px(unit.Dp(48))
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Inset{Right: unit.Dp(4)}.Layout(gtx, func(gtx C) D {
return drawImage(gtx, ui.icons.google, unit.Dp(16))
})
}),
layout.Rigid(func(gtx C) D {
return layout.Inset{Top: unit.Dp(10), Bottom: unit.Dp(10)}.Layout(gtx, func(gtx C) D {
l := material.Body2(ui.theme, "Sign in with Google")
l.Color = rgb(textColor)
return l.Layout(gtx)
})
}),
)
return ui.withLoader(gtx, ui.signinType == googleSignin, func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Px(2)).Layout(gtx, func(gtx C) D {
return signin.Layout(gtx, func(gtx C) D {
gtx.Constraints.Max.Y = gtx.Px(unit.Dp(48))
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Inset{Right: unit.Dp(4)}.Layout(gtx, func(gtx C) D {
return drawImage(gtx, ui.icons.google, unit.Dp(16))
})
}),
layout.Rigid(func(gtx C) D {
return layout.Inset{Top: unit.Dp(10), Bottom: unit.Dp(10)}.Layout(gtx, func(gtx C) D {
l := material.Body2(ui.theme, "Sign in with Google")
l.Color = rgb(textColor)
return l.Layout(gtx)
})
}),
)
})
})
})
})
@ -356,13 +372,15 @@ func (ui *UI) layoutSignIn(gtx layout.Context) layout.Dimensions {
if !enableGoogleSignin {
label = "Sign in"
}
return border.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Px(2)).Layout(gtx, func(gtx C) D {
signin := material.Button(ui.theme, &ui.webSignin, label)
signin.Background = rgb(signinColor)
signin.Color = rgb(textColor)
signin.Background = rgb(white)
return signin.Layout(gtx)
return ui.withLoader(gtx, ui.signinType == webSignin, func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Px(2)).Layout(gtx, func(gtx C) D {
signin := material.Button(ui.theme, &ui.webSignin, label)
signin.Background = rgb(signinColor)
signin.Color = rgb(textColor)
signin.Background = rgb(white)
return signin.Layout(gtx)
})
})
})
}),
@ -370,6 +388,27 @@ func (ui *UI) layoutSignIn(gtx layout.Context) layout.Dimensions {
})
}
func (ui *UI) withLoader(gtx layout.Context, loading bool, w layout.Widget) layout.Dimensions {
cons := gtx.Constraints
return layout.Stack{Alignment: layout.W}.Layout(gtx,
layout.Stacked(func(gtx C) D {
gtx.Constraints = cons
return w(gtx)
}),
layout.Stacked(func(gtx C) D {
if !loading {
return D{}
}
return layout.Inset{Left: unit.Dp(16)}.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min = image.Point{
X: gtx.Px(unit.Dp(16)),
}
return material.Loader(ui.theme).Layout(gtx)
})
}),
)
}
// layoutDisconnected lays out the "please connect to the internet"
// message.
func (ui *UI) layoutDisconnected(gtx layout.Context) layout.Dimensions {
@ -624,7 +663,7 @@ func (ui *UI) layoutSection(gtx layout.Context, sysIns system.Insets, title stri
}
// layoutTop lays out the top controls: toggle, status and menu dots.
func (ui *UI) layoutTop(gtx layout.Context, sysIns system.Insets, state *NetworkState) layout.Dimensions {
func (ui *UI) layoutTop(gtx layout.Context, sysIns system.Insets, state *BackendState) layout.Dimensions {
in := layout.Inset{
Top: unit.Dp(16),
Bottom: unit.Dp(16),

@ -4,7 +4,7 @@ go 1.14
require (
eliasnaur.com/font v0.0.0-20200617114307-e02d32decb4b
gioui.org v0.0.0-20200630184435-6ef1ff7cfbfb
gioui.org v0.0.0-20200709135439-29f820caaac9
gioui.org/cmd v0.0.0-20200622185735-5bd0ecea5e43
github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/tailscale/wireguard-go v0.0.0-20200615180905-687c10194779

@ -3,8 +3,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
eliasnaur.com/font v0.0.0-20200617114307-e02d32decb4b h1:J9r7EuPdhvBTafg34EqrObAm/bDEaDh7LvhKJPGficE=
eliasnaur.com/font v0.0.0-20200617114307-e02d32decb4b/go.mod h1:CYwJpIhpzVfoHpFXGlXjSx9mXMWtHt4XXmZb6RjumRc=
gioui.org v0.0.0-20200622101735-5368743478e0/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU=
gioui.org v0.0.0-20200630184435-6ef1ff7cfbfb h1:+jJBzbEtW03w0+fAfhPCwmN0/8xN1CJ94lbfV2eSKhs=
gioui.org v0.0.0-20200630184435-6ef1ff7cfbfb/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU=
gioui.org v0.0.0-20200709135439-29f820caaac9 h1:dFKWoxIzFlW2e8WTMWYOvGDAosSruMHnS66UBpzdYeU=
gioui.org v0.0.0-20200709135439-29f820caaac9/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU=
gioui.org/cmd v0.0.0-20200622185735-5bd0ecea5e43 h1:Wj8OoCIw06dNSSSPAAipnmcG7dbFn+7Et9IY37e1HBU=
gioui.org/cmd v0.0.0-20200622185735-5bd0ecea5e43/go.mod h1:KrsGUGWoPetiRyuDmOd/GTNCBFi2u4UbESTFXZ5YqXY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

Loading…
Cancel
Save