Skip to content

Commit 64c2c25

Browse files
authored
Merge pull request #11 from rusq/back
Picklist: Back button
2 parents 45a957e + 834fc9c commit 64c2c25

File tree

4 files changed

+126
-35
lines changed

4 files changed

+126
-35
lines changed

errors.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package tbcomctl
2+
3+
import "errors"
4+
5+
// ErrType is the type of error returned by the callback functions.
6+
type ErrType int
7+
8+
const (
9+
TErrNoChange ErrType = iota // there has been no change to the selection
10+
TErrRetry // error of this type will ask user to retry the selection or input
11+
TInputError // tell user that there was an input error (user-error)
12+
)
13+
14+
// Error is the type of error returned by the input-processing callback functions
15+
type Error struct {
16+
Alert bool
17+
Msg string
18+
Type ErrType
19+
}
20+
21+
func (e *Error) Error() string { return e.Msg }
22+
23+
var (
24+
// ErrRetry should be returned by CallbackFunc if the retry should be performed.
25+
ErrRetry = &Error{Type: TErrRetry, Msg: "retry", Alert: true}
26+
// ErrNoChange should be returned if the user picked the same value as before, and no update needed.
27+
ErrNoChange = &Error{Type: TErrNoChange, Msg: "no change"}
28+
)
29+
30+
// BackPressed is a special type of error indicating that callback handler should call the previous handler.
31+
var BackPressed = errors.New("back")

examples/picklist/main.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,29 @@ func main() {
3939
p2 := tbcomctl.NewPicklistText(
4040
"2",
4141
"second picklist",
42-
[]string{"5", "6", "7", "8"},
42+
[]string{"5", "6", "7", "8", "back"},
43+
func(ctx context.Context, c tb.Context) error {
44+
fmt.Println(tbcomctl.Sdump(c.Callback()))
45+
if c.Data() == "back" {
46+
return tbcomctl.BackPressed
47+
}
48+
return nil
49+
},
50+
tbcomctl.PickOptBtnPattern([]uint{1, 2, 1, 1}),
51+
)
52+
p3 := tbcomctl.NewPicklistText(
53+
"3",
54+
"picklist with back button option",
55+
[]string{"9", "A", "B", "C"},
4356
func(ctx context.Context, c tb.Context) error {
4457
fmt.Println(tbcomctl.Sdump(c.Callback()))
4558
return nil
4659
},
4760
tbcomctl.PickOptBtnPattern([]uint{1, 2, 1}),
61+
tbcomctl.PickOptBtnBackWithText("option back"),
4862
)
4963
m := tbcomctl.NewMessageText("msg", "all ok")
50-
form := tbcomctl.NewForm(p1, p2, m).
64+
form := tbcomctl.NewForm(p1, p2, p3, m).
5165
SetOverwrite(true).
5266
SetRemoveButtons(true)
5367
b.Handle("/picklist", form.Handler)

picklist.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package tbcomctl
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"runtime/trace"
78
"strings"
89

10+
"github.com/rusq/dlog"
911
tb "gopkg.in/tucnak/telebot.v3"
1012
)
1113

@@ -21,9 +23,11 @@ type Picklist struct {
2123
removeButtons bool
2224
noUpdate bool
2325
msgChoose bool
26+
backBtn bool
2427

25-
vFn ValuesFunc
26-
cbFn BtnCallbackFunc
28+
vFn ValuesFunc
29+
cbFn BtnCallbackFunc
30+
backTxtFn TextFunc
2731

2832
btnPattern []uint
2933
}
@@ -102,6 +106,19 @@ func PickOptBtnPattern(pattern []uint) PicklistOption {
102106
}
103107
}
104108

109+
func PickOptBtnBack(textFn TextFunc) PicklistOption {
110+
return func(p *Picklist) {
111+
p.backBtn = true
112+
p.backTxtFn = textFn
113+
}
114+
}
115+
116+
func PickOptBtnBackWithText(s string) PicklistOption {
117+
return PickOptBtnBack(func(ctx context.Context, u *tb.User) (string, error) {
118+
return s, nil
119+
})
120+
}
121+
105122
// NewPicklist creates a new picklist.
106123
func NewPicklist(name string, textFn TextFunc, valuesFn ValuesFunc, callbackFn BtnCallbackFunc, opts ...PicklistOption) *Picklist {
107124
if textFn == nil || valuesFn == nil || callbackFn == nil {
@@ -116,6 +133,9 @@ func NewPicklist(name string, textFn TextFunc, valuesFn ValuesFunc, callbackFn B
116133
for _, opt := range opts {
117134
opt(p)
118135
}
136+
if p.backBtn {
137+
p.btnPattern = append(p.btnPattern, 1)
138+
}
119139
return p
120140
}
121141

@@ -169,12 +189,30 @@ func (p *Picklist) Callback(c tb.Context) error {
169189
p.logCallback(cb)
170190

171191
var resp tb.CallbackResponse
192+
193+
if p.backBtn {
194+
// back button is enabled, check if the callback data contains back button text.
195+
txt, err := p.backTxtFn(ctx, c.Sender())
196+
if err != nil {
197+
trace.Logf(ctx, "back button", "err=%s", err)
198+
}
199+
if c.Data() == txt {
200+
trace.Log(ctx, "callback", "back is pressed (option)")
201+
return p.handleBackButton(ctx, c)
202+
}
203+
}
204+
172205
err := p.cbFn(WithController(ctx, p), c)
173206
if err != nil {
207+
if errors.Is(err, BackPressed) {
208+
// user callback function might return "back button is pressed" as well
209+
trace.Log(ctx, "callback", "back is pressed (user)")
210+
return p.handleBackButton(ctx, c)
211+
}
174212
if e, ok := err.(*Error); !ok {
175213
p.editMsg(ctx, c)
176214
if err := c.Respond(&tb.CallbackResponse{Text: err.Error(), ShowAlert: true}); err != nil {
177-
trace.Logf(ctx, "respond", err.Error())
215+
trace.Log(ctx, "respond", err.Error())
178216
}
179217
p.unregister(c.Sender(), cb.Message.ID)
180218
return e
@@ -255,6 +293,13 @@ func (p *Picklist) format(u *tb.User, text string) string {
255293
}
256294

257295
func (p *Picklist) inlineMarkup(c tb.Context, values []string) *tb.ReplyMarkup {
296+
if p.backBtn {
297+
txt, err := p.backTxtFn(context.Background(), c.Sender())
298+
if err != nil {
299+
dlog.Debugf("backTextFn returned an error: %s", err)
300+
}
301+
values = append(values, txt)
302+
}
258303
if len(p.btnPattern) == 0 {
259304
return ButtonMarkup(c, values, p.maxButtons, p.Callback)
260305
}
@@ -292,8 +337,18 @@ func convertToMsg(cb *tb.Callback) *tb.Message {
292337

293338
func (p *Picklist) nextHandler(c tb.Context) error {
294339
if p.next != nil {
295-
// this call is part of the pipeline
296340
return p.next.Handler(c)
297341
}
298342
return nil
299343
}
344+
345+
func (p *Picklist) handleBackButton(ctx context.Context, c tb.Context) error {
346+
if err := c.Respond(&tb.CallbackResponse{}); err != nil {
347+
trace.Log(ctx, "respond", err.Error())
348+
}
349+
c.Set(BackPressed.Error(), true)
350+
if p.prev != nil {
351+
return p.prev.Handler(c)
352+
}
353+
return nil
354+
}

tbcomctl.go

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -133,29 +133,6 @@ type ErrFunc func(ctx context.Context, m *tb.Message, err error)
133133
// ErrRetry if the retry should be performed.
134134
type BtnCallbackFunc func(ctx context.Context, c tb.Context) error
135135

136-
type ErrType int
137-
138-
const (
139-
TErrNoChange ErrType = iota
140-
TErrRetry
141-
TInputError
142-
)
143-
144-
type Error struct {
145-
Alert bool
146-
Msg string
147-
Type ErrType
148-
}
149-
150-
func (e *Error) Error() string { return e.Msg }
151-
152-
var (
153-
// ErrRetry should be returned by CallbackFunc if the retry should be performed.
154-
ErrRetry = &Error{Type: TErrRetry, Msg: "retry", Alert: true}
155-
// ErrNoChange should be returned if the user picked the same value as before, and no update needed.
156-
ErrNoChange = &Error{Type: TErrNoChange, Msg: "no change"}
157-
)
158-
159136
var hasher = sha1.New
160137

161138
func hash(s string) string {
@@ -387,12 +364,8 @@ func (c *commonCtl) setOverwrite(b bool) {
387364
func (c *commonCtl) sendOrEdit(ct tb.Context, txt string, sendOpts ...interface{}) (*tb.Message, error) {
388365
var outbound *tb.Message
389366
var err error
390-
if c.overwrite && c.prev != nil {
391-
msgID, ok := c.prev.OutgoingID(ct.Sender().Recipient())
392-
if !ok {
393-
return nil, fmt.Errorf("%s can't find previous message ID for %s", caller(2), Userinfo(ct.Sender()))
394-
395-
}
367+
msgID, ok := c.getPreviousMsgID(ct)
368+
if c.overwrite && ok {
396369
prevMsg := tb.Message{ID: msgID, Chat: ct.Chat()}
397370
outbound, err = ct.Bot().Edit(&prevMsg,
398371
txt,
@@ -403,3 +376,21 @@ func (c *commonCtl) sendOrEdit(ct tb.Context, txt string, sendOpts ...interface{
403376
}
404377
return outbound, err
405378
}
379+
380+
func (c *commonCtl) getPreviousMsgID(ct tb.Context) (int, bool) {
381+
backPressed, ok := ct.Get(BackPressed.Error()).(bool)
382+
if ok && backPressed {
383+
ct.Set(BackPressed.Error(), false) // reset the context value
384+
if c.next == nil {
385+
// internal error
386+
return 0, false
387+
}
388+
return c.next.OutgoingID(ct.Sender().Recipient())
389+
}
390+
// back not pressed
391+
if c.prev == nil {
392+
return 0, false
393+
}
394+
return c.prev.OutgoingID(ct.Sender().Recipient())
395+
396+
}

0 commit comments

Comments
 (0)