send.go raw
1 package gui
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7 "time"
8
9 "github.com/p9c/p9/pkg/amt"
10 "github.com/p9c/p9/pkg/btcaddr"
11
12 "github.com/atotto/clipboard"
13
14 l "github.com/p9c/p9/pkg/gel/gio/layout"
15 "github.com/p9c/p9/pkg/gel/gio/text"
16
17 "github.com/p9c/p9/pkg/gel"
18 "github.com/p9c/p9/pkg/chainhash"
19 )
20
21 type SendPage struct {
22 wg *WalletGUI
23 inputWidth, break1 float32
24 }
25
26 func (wg *WalletGUI) GetSendPage() (sp *SendPage) {
27 sp = &SendPage{
28 wg: wg,
29 inputWidth: 17,
30 break1: 48,
31 }
32 wg.inputs["sendAddress"].SetPasteFunc = sp.pasteFunction
33 wg.inputs["sendAmount"].SetPasteFunc = sp.pasteFunction
34 wg.inputs["sendMessage"].SetPasteFunc = sp.pasteFunction
35 return
36 }
37
38 func (sp *SendPage) Fn(gtx l.Context) l.Dimensions {
39 wg := sp.wg
40 return wg.Responsive(
41 wg.Size.Load(), gel.Widgets{
42 {
43 Widget: sp.SmallList,
44 },
45 {
46 Size: sp.break1,
47 Widget: sp.MediumList,
48 },
49 },
50 ).Fn(gtx)
51 }
52
53 func (sp *SendPage) SmallList(gtx l.Context) l.Dimensions {
54 wg := sp.wg
55 smallWidgets := []l.Widget{
56 wg.Flex().Rigid(wg.balanceCard()).Fn,
57 sp.InputMessage(),
58 sp.AddressInput(),
59 sp.AmountInput(),
60 sp.MessageInput(),
61 wg.Flex().
62 Flexed(
63 1,
64 sp.SendButton(),
65 ).
66 Rigid(
67 wg.Inset(0.5, gel.EmptySpace(0, 0)).Fn,
68 ).
69 Rigid(
70 sp.PasteButton(),
71 ).
72 Rigid(
73 wg.Inset(0.5, gel.EmptySpace(0, 0)).Fn,
74 ).
75 Rigid(
76 sp.SaveButton(),
77 ).Fn,
78 sp.AddressbookHeader(),
79 }
80 smallWidgets = append(smallWidgets, sp.GetAddressbookHistoryCards("DocBg")...)
81 le := func(gtx l.Context, index int) l.Dimensions {
82 return wg.Inset(
83 0.25,
84 smallWidgets[index],
85 ).Fn(gtx)
86 }
87 return wg.lists["send"].
88 Vertical().
89 Length(len(smallWidgets)).
90 ListElement(le).Fn(gtx)
91 }
92
93 func (sp *SendPage) InputMessage() l.Widget {
94 return sp.wg.Body2("Enter or paste the details for a payment").Alignment(text.Start).Fn
95 }
96
97 func (sp *SendPage) MediumList(gtx l.Context) l.Dimensions {
98 wg := sp.wg
99 sendFormWidget := []l.Widget{
100 wg.balanceCard(),
101 sp.InputMessage(),
102 sp.AddressInput(),
103 sp.AmountInput(),
104 sp.MessageInput(),
105 wg.Flex().
106 Flexed(
107 1,
108 sp.SendButton(),
109 ).
110 Rigid(
111 wg.Inset(0.5, gel.EmptySpace(0, 0)).Fn,
112 ).
113 Rigid(
114 sp.PasteButton(),
115 ).
116 Rigid(
117 wg.Inset(0.5, gel.EmptySpace(0, 0)).Fn,
118 ).
119 Rigid(
120 sp.SaveButton(),
121 ).Fn,
122 }
123 sendLE := func(gtx l.Context, index int) l.Dimensions {
124 return wg.Inset(0.25, sendFormWidget[index]).Fn(gtx)
125 }
126 var historyWidget []l.Widget
127 historyWidget = append(historyWidget, sp.GetAddressbookHistoryCards("DocBg")...)
128 historyLE := func(gtx l.Context, index int) l.Dimensions {
129 return wg.Inset(
130 0.25,
131 historyWidget[index],
132 ).Fn(gtx)
133 }
134 return wg.Flex().AlignStart().
135 Rigid(
136 func(gtx l.Context) l.Dimensions {
137 gtx.Constraints.Max.X =
138 int(wg.TextSize.V * sp.inputWidth)
139 // gtx.Constraints.Min.X = int(wg.TextSize.True * sp.inputWidth)
140
141 return wg.VFlex().AlignStart().
142 Rigid(
143 wg.lists["sendMedium"].
144 Vertical().
145 Length(len(sendFormWidget)).
146 ListElement(sendLE).Fn,
147 ).Fn(gtx)
148 },
149 ).
150 // Rigid(wg.Inset(0.25, gel.EmptySpace(0, 0)).Fn).
151 Flexed(
152 1,
153 wg.VFlex().AlignStart().
154 Rigid(
155 sp.AddressbookHeader(),
156 ).
157 Rigid(
158 wg.lists["sendAddresses"].
159 Vertical().
160 Length(len(historyWidget)).
161 ListElement(historyLE).Fn,
162 ).Fn,
163 ).Fn(gtx)
164 }
165
166 func (sp *SendPage) AddressInput() l.Widget {
167 return func(gtx l.Context) l.Dimensions {
168 wg := sp.wg
169 return wg.inputs["sendAddress"].Fn(gtx)
170 }
171 }
172
173 func (sp *SendPage) AmountInput() l.Widget {
174 return func(gtx l.Context) l.Dimensions {
175 wg := sp.wg
176 return wg.inputs["sendAmount"].Fn(gtx)
177 }
178 }
179
180 func (sp *SendPage) MessageInput() l.Widget {
181 return func(gtx l.Context) l.Dimensions {
182 wg := sp.wg
183 return wg.inputs["sendMessage"].Fn(gtx)
184 }
185 }
186
187 func (sp *SendPage) SendButton() l.Widget {
188 return func(gtx l.Context) l.Dimensions {
189 wg := sp.wg
190 if wg.inputs["sendAmount"].GetText() == "" || wg.inputs["sendMessage"].GetText() == "" ||
191 wg.inputs["sendAddress"].GetText() == "" {
192 gtx.Queue = nil
193 }
194 return wg.ButtonLayout(
195 wg.clickables["sendSend"].
196 SetClick(
197 func() {
198 D.Ln("clicked send button")
199 go func() {
200 if wg.WalletAndClientRunning() {
201 var amount float64
202 var am amt.Amount
203 var e error
204 if amount, e = strconv.ParseFloat(
205 wg.inputs["sendAmount"].GetText(),
206 64,
207 ); !E.Chk(e) {
208 if am, e = amt.NewAmount(amount); E.Chk(e) {
209 D.Ln(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", e)
210 // todo: indicate this to the user somehow
211 return
212 }
213 } else {
214 // todo: indicate this to the user somehow
215 return
216 }
217 var addr btcaddr.Address
218 if addr, e = btcaddr.Decode(
219 wg.inputs["sendAddress"].GetText(),
220 wg.cx.ActiveNet,
221 ); E.Chk(e) {
222 D.Ln(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", e)
223 D.Ln("invalid address")
224 // TODO: indicate this to the user somehow
225 return
226 }
227 if e = wg.WalletClient.WalletPassphrase(wg.cx.Config.WalletPass.V(), 5); E.Chk(e) {
228 D.Ln(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", e)
229 return
230 }
231 var txid *chainhash.Hash
232 if txid, e = wg.WalletClient.SendToAddress(addr, am); E.Chk(e) {
233 // TODO: indicate send failure to user somehow
234 D.Ln(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", e)
235 return
236 }
237 wg.RecentTransactions(10, "recent")
238 wg.RecentTransactions(-1, "history")
239 wg.Invalidate()
240 D.Ln("transaction successful", txid)
241 sp.saveForm(txid.String())
242 select {
243 case <-time.After(time.Second * 5):
244 case <-wg.quit:
245 }
246 }
247 }()
248 },
249 ),
250 ).
251 Background("Primary").
252 Embed(
253 wg.Inset(
254 0.5,
255 wg.H6("send").Color("Light").Fn,
256 ).
257 Fn,
258 ).
259 Fn(gtx)
260 }
261 }
262 func (sp *SendPage) saveForm(txid string) {
263 wg := sp.wg
264 D.Ln("processing form data to save")
265 amtS := wg.inputs["sendAmount"].GetText()
266 var e error
267 var amount float64
268 if amount, e = strconv.ParseFloat(amtS, 64); E.Chk(e) {
269 return
270 }
271 if amount == 0 {
272 return
273 }
274 var ua amt.Amount
275 if ua, e = amt.NewAmount(amount); E.Chk(e) {
276 return
277 }
278 msg := wg.inputs["sendMessage"].GetText()
279 if msg == "" {
280 return
281 }
282 addr := wg.inputs["sendAddress"].GetText()
283 var ad btcaddr.Address
284 if ad, e = btcaddr.Decode(addr, wg.cx.ActiveNet); E.Chk(e) {
285 return
286 }
287 wg.State.sendAddresses = append(
288 wg.State.sendAddresses, AddressEntry{
289 Address: ad.EncodeAddress(),
290 Label: msg,
291 Amount: ua,
292 Created: time.Now(),
293 TxID: txid,
294 },
295 )
296 // prevent accidental double clicks recording the same entry again
297 wg.inputs["sendAmount"].SetText("")
298 wg.inputs["sendMessage"].SetText("")
299 wg.inputs["sendAddress"].SetText("")
300 }
301
302 func (sp *SendPage) SaveButton() l.Widget {
303 return func(gtx l.Context) l.Dimensions {
304 wg := sp.wg
305 if wg.inputs["sendAmount"].GetText() == "" || wg.inputs["sendMessage"].GetText() == "" ||
306 wg.inputs["sendAddress"].GetText() == "" {
307 gtx.Queue = nil
308 }
309 return wg.ButtonLayout(
310 wg.clickables["sendSave"].
311 SetClick(
312 func() { sp.saveForm("") },
313 ),
314 ).
315 Background("Primary").
316 Embed(
317 wg.Inset(
318 0.5,
319 wg.H6("save").Color("Light").Fn,
320 ).
321 Fn,
322 ).
323 Fn(gtx)
324 }
325 }
326
327 func (sp *SendPage) PasteButton() l.Widget {
328 return func(gtx l.Context) l.Dimensions {
329 wg := sp.wg
330 return wg.ButtonLayout(
331 wg.clickables["sendFromRequest"].
332 SetClick(func() { sp.pasteFunction() }),
333 ).
334 Background("Primary").
335 Embed(
336 wg.Inset(
337 0.5,
338 wg.H6("paste").Color("Light").Fn,
339 ).
340 Fn,
341 ).
342 Fn(gtx)
343 }
344 }
345
346 func (sp *SendPage) pasteFunction() (b bool) {
347 wg := sp.wg
348 D.Ln("clicked paste button")
349 var urn string
350 var e error
351 if urn, e = clipboard.ReadAll(); E.Chk(e) {
352 return
353 }
354 if !strings.HasPrefix(urn, "parallelcoin:") {
355 if e = clipboard.WriteAll(urn); E.Chk(e) {
356 }
357 return
358 }
359 split1 := strings.Split(urn, "parallelcoin:")
360 split2 := strings.Split(split1[1], "?")
361 addr := split2[0]
362 var ua btcaddr.Address
363 if ua, e = btcaddr.Decode(addr, wg.cx.ActiveNet); E.Chk(e) {
364 return
365 }
366 _ = ua
367 b = true
368 wg.inputs["sendAddress"].SetText(addr)
369 if len(split2) <= 1 {
370 return
371 }
372 split3 := strings.Split(split2[1], "&")
373 for i := range split3 {
374 var split4 []string
375 split4 = strings.Split(split3[i], "=")
376 D.Ln(split4)
377 if len(split4) > 1 {
378 switch split4[0] {
379 case "amount":
380 wg.inputs["sendAmount"].SetText(split4[1])
381 // D.Ln("############ amount", split4[1])
382 case "message", "label":
383 msg := split4[i]
384 if len(msg) > 64 {
385 msg = msg[:64]
386 }
387 wg.inputs["sendMessage"].SetText(msg)
388 // D.Ln("############ message", split4[1])
389 }
390 }
391 }
392 return
393 }
394
395 func (sp *SendPage) AddressbookHeader() l.Widget {
396 wg := sp.wg
397 return wg.Flex().AlignStart().
398 Rigid(
399 wg.Inset(
400 0.25,
401 wg.H5("Send Address Book").Fn,
402 ).Fn,
403 ).Fn
404 }
405
406 func (sp *SendPage) GetAddressbookHistoryCards(bg string) (widgets []l.Widget) {
407 wg := sp.wg
408 avail := len(wg.sendAddressbookClickables)
409 req := len(wg.State.sendAddresses)
410 if req > avail {
411 for i := 0; i < req-avail; i++ {
412 wg.sendAddressbookClickables = append(wg.sendAddressbookClickables, wg.WidgetPool.GetClickable())
413 }
414 }
415 for x := range wg.State.sendAddresses {
416 j := x
417 i := len(wg.State.sendAddresses) - 1 - x
418 widgets = append(
419 widgets, func(gtx l.Context) l.Dimensions {
420 return wg.ButtonLayout(
421 wg.sendAddressbookClickables[i].SetClick(
422 func() {
423 sendText := fmt.Sprintf(
424 "parallelcoin:%s?amount=%8.8f&message=%s",
425 wg.State.sendAddresses[i].Address,
426 wg.State.sendAddresses[i].Amount.ToDUO(),
427 wg.State.sendAddresses[i].Label,
428 )
429 D.Ln("clicked send address list item", j)
430 if e := clipboard.WriteAll(sendText); E.Chk(e) {
431 }
432 },
433 ),
434 ).
435 Background(bg).
436 Embed(
437 wg.Inset(
438 0.25,
439 wg.VFlex().AlignStart().
440 Rigid(
441 wg.Flex().AlignBaseline().
442 Rigid(
443 wg.Caption(wg.State.sendAddresses[i].Address).
444 Font("go regular").Fn,
445 ).
446 Flexed(
447 1,
448 wg.Body1(wg.State.sendAddresses[i].Amount.String()).
449 Alignment(text.End).Fn,
450 ).
451 Fn,
452 ).
453 Rigid(
454 wg.Inset(
455 0.25,
456 wg.Body1(wg.State.sendAddresses[i].Label).MaxLines(1).Fn,
457 ).Fn,
458 ).
459 Rigid(
460 gel.If(
461 wg.State.sendAddresses[i].TxID != "",
462 func(ctx l.Context) l.Dimensions {
463 for j := range wg.txHistoryList {
464 if wg.txHistoryList[j].TxID == wg.State.sendAddresses[i].TxID {
465 return wg.Flex().Flexed(
466 1,
467 wg.VFlex().
468 Rigid(
469 wg.Flex().Flexed(
470 1,
471 wg.Caption(wg.State.sendAddresses[i].TxID).MaxLines(1).Fn,
472 ).Fn,
473 ).
474 Rigid(
475 wg.Body1(
476 fmt.Sprint(
477 "Confirmations: ",
478 wg.txHistoryList[j].Confirmations,
479 ),
480 ).Fn,
481 ).Fn,
482 ).Fn(gtx)
483 }
484 }
485 return func(ctx l.Context) l.Dimensions { return l.Dimensions{} }(gtx)
486 },
487 func(ctx l.Context) l.Dimensions { return l.Dimensions{} },
488 ),
489 ).
490 Fn,
491 ).
492 Fn,
493 ).Fn(gtx)
494 },
495 )
496 }
497 return
498 }
499