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