cchat-gtk/internal/ui/service/auth/auth.go

268 lines
5.3 KiB
Go
Raw Normal View History

2020-05-26 06:51:06 +00:00
package auth
import (
"html"
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/dialog"
"github.com/diamondburned/cchat/text"
2020-05-26 06:51:06 +00:00
"github.com/gotk3/gotk3/gtk"
)
type Dialog struct {
2020-07-10 23:26:07 +00:00
*dialog.Modal
2020-05-26 06:51:06 +00:00
Auther cchat.Authenticator
onAuth func(cchat.Session)
stack *gtk.Stack // dialog stack
scroll *gtk.ScrolledWindow
body *gtk.Box
label *gtk.Label
// filled on spin()
request *Request
}
// NewDialog makes a new authentication dialog. Auth() is called when the user
// is authenticated successfully inside the Gtk main thread.
func NewDialog(name text.Rich, auther cchat.Authenticator, auth func(cchat.Session)) *Dialog {
2020-05-26 06:51:06 +00:00
label, _ := gtk.LabelNew("")
2020-10-26 03:36:00 +00:00
label.SetMarginStart(10)
label.SetMarginEnd(10)
label.SetMarginTop(10)
label.SetMarginBottom(10)
2020-05-26 06:51:06 +00:00
label.Show()
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
box.Show()
box.Add(label)
sw, _ := gtk.ScrolledWindowNew(nil, nil)
sw.Show()
sw.Add(box)
spinner, _ := gtk.SpinnerNew()
spinner.Show()
spinner.Start()
spinner.SetSizeRequest(50, 50)
stack, _ := gtk.StackNew()
stack.Show()
stack.SetVExpand(true)
stack.SetHExpand(true)
stack.AddNamed(sw, "main")
stack.AddNamed(spinner, "spinner")
d := &Dialog{
Auther: auther,
onAuth: auth,
stack: stack,
scroll: sw,
body: box,
label: label,
}
2020-07-10 23:26:07 +00:00
d.Modal = dialog.NewModal(stack, "Log in to "+name.Content, "Log in", d.ok)
d.Modal.SetDefaultSize(400, 300)
2020-05-26 06:51:06 +00:00
d.spin(nil)
d.Show()
return d
}
func (d *Dialog) runOnAuth(ses cchat.Session) {
// finalize
d.Destroy()
d.onAuth(ses)
}
func (d *Dialog) spin(err error) {
// Print the error.
if err != nil {
d.label.SetMarkup(`<span color="red">` + html.EscapeString(err.Error()) + `</span>`)
} else {
d.label.SetText("")
}
// Restore the old widget states.
d.stack.SetVisibleChildName("main")
d.Dialog.SetSensitive(true)
2020-10-26 03:36:00 +00:00
form := d.Auther.AuthenticateForm()
// See if we need to remove the current request page. We should keep
// everything the same if the key matches, as then forms aren't reset.
if d.request != nil {
if d.request.equalEntries(form) {
return
}
d.body.Remove(d.request)
}
d.request = NewRequest(form)
2020-05-26 06:51:06 +00:00
d.body.Add(d.request)
}
2020-07-10 23:26:07 +00:00
func (d *Dialog) ok(m *dialog.Modal) {
2020-05-26 06:51:06 +00:00
// Disable the buttons.
d.Dialog.SetSensitive(false)
// Switch to the spinner screen.
d.stack.SetVisibleChildName("spinner")
// Get the values of all fields.
var values = d.request.values()
gts.Async(func() (func(), error) {
s, err := d.Auther.Authenticate(values)
if err != nil {
return func() { d.spin(err) }, nil
}
return func() { d.runOnAuth(s) }, nil
})
}
type Request struct {
*gtk.Grid
2020-10-26 03:36:00 +00:00
entries []cchat.AuthenticateEntry
2020-05-26 06:51:06 +00:00
labels []*gtk.Label
2020-10-26 03:36:00 +00:00
texts []Texter
2020-05-26 06:51:06 +00:00
}
func NewRequest(authEntries []cchat.AuthenticateEntry) *Request {
grid, _ := gtk.GridNew()
grid.Show()
2020-06-04 23:00:41 +00:00
grid.SetRowSpacing(7)
grid.SetColumnHomogeneous(true)
grid.SetColumnSpacing(5)
2020-05-26 06:51:06 +00:00
req := &Request{
Grid: grid,
2020-10-26 03:36:00 +00:00
entries: authEntries,
2020-05-26 06:51:06 +00:00
labels: make([]*gtk.Label, len(authEntries)),
2020-10-26 03:36:00 +00:00
texts: make([]Texter, len(authEntries)),
2020-05-26 06:51:06 +00:00
}
2020-10-26 03:36:00 +00:00
for i, authEntry := range req.entries {
label, texter := newEntry(authEntry)
2020-05-26 06:51:06 +00:00
req.labels[i] = label
2020-10-26 03:36:00 +00:00
req.texts[i] = texter
2020-05-26 06:51:06 +00:00
grid.Attach(label, 0, i, 1, 1)
2020-10-26 03:36:00 +00:00
grid.Attach(texter, 1, i, 3, 1) // triple the width
2020-05-26 06:51:06 +00:00
}
return req
}
2020-10-26 03:36:00 +00:00
// equalEntries compares the current request with a list of entries. It returns
// false if there are inequalities.
func (r *Request) equalEntries(entries []cchat.AuthenticateEntry) bool {
if len(r.entries) != len(entries) {
return false
}
for i, entry := range r.entries {
if entry != entries[i] {
return false
}
}
return true
}
2020-05-26 06:51:06 +00:00
func (r *Request) values() []string {
var values = make([]string, len(r.entries))
2020-10-26 03:36:00 +00:00
for i, texter := range r.texts {
values[i] = texter.GetText()
2020-05-26 06:51:06 +00:00
}
return values
}
2020-06-04 23:00:41 +00:00
func newEntry(authEntry cchat.AuthenticateEntry) (*gtk.Label, Texter) {
2020-05-26 06:51:06 +00:00
label, _ := gtk.LabelNew(authEntry.Name)
label.Show()
label.SetXAlign(1) // right align
2020-06-04 23:00:41 +00:00
label.SetJustify(gtk.JUSTIFY_RIGHT)
label.SetLineWrap(true)
2020-05-26 06:51:06 +00:00
2020-06-04 23:00:41 +00:00
var texter Texter
if authEntry.Multiline {
texter = NewMultilineInput()
} else {
var input = NewEntryInput()
if authEntry.Secret {
input.SetInputPurpose(gtk.INPUT_PURPOSE_PASSWORD)
input.SetVisibility(false)
input.SetInvisibleChar('●')
} else {
// usually; this is just an assumption
input.SetInputPurpose(gtk.INPUT_PURPOSE_EMAIL)
}
texter = input
}
return label, texter
}
type Texter interface {
gtk.IWidget
GetText() string
SetText(string)
}
type EntryInput struct {
*gtk.Entry
}
var _ Texter = (*EntryInput)(nil)
func NewEntryInput() EntryInput {
2020-05-26 06:51:06 +00:00
input, _ := gtk.EntryNew()
2020-06-04 23:00:41 +00:00
input.SetVAlign(gtk.ALIGN_CENTER)
2020-05-26 06:51:06 +00:00
input.Show()
2020-06-04 23:00:41 +00:00
return EntryInput{
input,
2020-05-26 06:51:06 +00:00
}
2020-06-04 23:00:41 +00:00
}
func (i EntryInput) GetText() (text string) {
text, _ = i.Entry.GetText()
return
}
type MultilineInput struct {
*gtk.TextView
Buffer *gtk.TextBuffer
}
var _ Texter = (*MultilineInput)(nil)
func NewMultilineInput() MultilineInput {
view, _ := gtk.TextViewNew()
view.SetWrapMode(gtk.WRAP_WORD_CHAR)
view.SetEditable(true)
view.Show()
buf, _ := view.GetBuffer()
return MultilineInput{view, buf}
}
func (i MultilineInput) GetText() (text string) {
start, end := i.Buffer.GetBounds()
text, _ = i.Buffer.GetText(start, end, true)
return
}
2020-05-26 06:51:06 +00:00
2020-06-04 23:00:41 +00:00
func (i MultilineInput) SetText(text string) {
i.Buffer.SetText(text)
2020-05-26 06:51:06 +00:00
}