package main import ( "context" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" "completed/internal/client" "completed/internal/model" ) // guiApp holds the window and the single client all screens share. It's a // small base: log in, see your games in a table, refresh. Adding/editing // games is for a later pass. type guiApp struct { client *client.Client window fyne.Window } func newGUIApp(c *client.Client, w fyne.Window) *guiApp { return &guiApp{client: c, window: w} } // showLogin builds and displays the login form. It's the window's initial // content. func (a *guiApp) showLogin() { email := widget.NewEntry() email.SetPlaceHolder("email") password := widget.NewPasswordEntry() password.SetPlaceHolder("password") status := widget.NewLabel("") form := widget.NewForm( widget.NewFormItem("Email", email), widget.NewFormItem("Password", password), ) form.SubmitText = "Log in" form.OnSubmit = func() { if err := validateLogin(email.Text, password.Text); err != nil { status.SetText(err.Error()) return } status.SetText("Logging in...") a.login(email.Text, password.Text, status) } a.window.SetContent(container.NewVBox( widget.NewLabelWithStyle("Completed", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), form, status, )) } // login runs the login request off the UI goroutine, then either shows the // games screen or reports the error back on the login form - both via // fyne.Do, since Fyne widgets may only be touched from the UI goroutine. func (a *guiApp) login(email, password string, status *widget.Label) { go func() { _, err := a.client.Login(context.Background(), email, password) if err != nil { fyne.Do(func() { status.SetText(err.Error()) }) return } fyne.Do(a.showGames) }() } // showGames displays the games table and fetches the current list. func (a *guiApp) showGames() { var games []model.Game table := widget.NewTable( func() (int, int) { return gameTableSize(games) }, func() fyne.CanvasObject { return widget.NewLabel("") }, func(id widget.TableCellID, obj fyne.CanvasObject) { obj.(*widget.Label).SetText(gameCellText(games, id.Row, id.Col)) }, ) for i := range gameColumns { table.SetColumnWidth(i, 120) } status := widget.NewLabel("Loading...") refresh := widget.NewButton("Refresh", func() { a.fetchGames(table, &games, status) }) a.window.SetContent(container.NewBorder( container.NewVBox( widget.NewLabelWithStyle("Completed", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), container.NewHBox(refresh, status), ), nil, nil, nil, table, )) a.fetchGames(table, &games, status) } func (a *guiApp) fetchGames(table *widget.Table, games *[]model.Game, status *widget.Label) { go func() { result, err := a.client.ListGames(context.Background()) fyne.Do(func() { if err != nil { status.SetText("Error: " + err.Error()) dialog.ShowError(err, a.window) return } *games = result status.SetText("") table.Refresh() }) }() }