From e2b99b7b36d79a7855c09d0dc5201e875ae28f57 Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Fri, 6 Aug 2021 02:16:56 +0100 Subject: [PATCH 01/27] clean up wallet/commands.go - remove unused methods - remove multiwallet methods that can be called directly from pages since pages already have multiwallet in the Load struct. --- ui/load/ticket.go | 28 ++ ui/load/vsp.go | 29 +- ui/load/wallet.go | 13 + ui/page/settings_page.go | 4 +- ui/page/statistics_page.go | 4 +- ui/page/tickets/list_page.go | 2 +- ui/page/tickets/overview.go | 110 ++++-- ui/page/tickets/utils.go | 11 +- wallet/commands.go | 625 +---------------------------------- wallet/wallet.go | 4 +- wallet/wallet_suite_test.go | 13 - wallet/wallet_test.go | 89 ----- 12 files changed, 161 insertions(+), 771 deletions(-) create mode 100644 ui/load/ticket.go delete mode 100644 wallet/wallet_suite_test.go delete mode 100644 wallet/wallet_test.go diff --git a/ui/load/ticket.go b/ui/load/ticket.go new file mode 100644 index 000000000..4d762e394 --- /dev/null +++ b/ui/load/ticket.go @@ -0,0 +1,28 @@ +package load + +import "github.com/planetdecred/dcrlibwallet" + +func (wl *WalletLoad) AllLiveTickets() ([]dcrlibwallet.Transaction, error) { + var txs []dcrlibwallet.Transaction + wallets := wl.MultiWallet.AllWallets() + for _, w := range wallets { + immatureTx, err := w.GetTransactionsRaw(0, 0, dcrlibwallet.TxFilterImmature, true) + if err != nil { + return txs, err + } + + txs = append(txs, immatureTx...) + + liveTxs, err := w.GetTransactionsRaw(0, 0, dcrlibwallet.TxFilterLive, true) + if err != nil { + return txs, err + } + + txs = append(txs, liveTxs...) + } + + return txs, nil +} + +func (wl *WalletLoad) + diff --git a/ui/load/vsp.go b/ui/load/vsp.go index 7753d2f66..e872d37a5 100644 --- a/ui/load/vsp.go +++ b/ui/load/vsp.go @@ -61,6 +61,33 @@ func getVSPInfo(url string) (*dcrlibwallet.VspInfoResponse, error) { return &vspInfoResponse, nil } +// getInitVSPInfo returns the list information of the VSP +func getInitVSPInfo(url string) (map[string]*dcrlibwallet.VspInfoResponse, error) { + rq := new(http.Client) + resp, err := rq.Get((url)) + if err != nil { + return nil, err + } + + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("non 200 response from server: %v", string(b)) + } + + var vspInfoResponse map[string]*dcrlibwallet.VspInfoResponse + err = json.Unmarshal(b, &vspInfoResponse) + if err != nil { + return nil, err + } + + return vspInfoResponse, nil +} + func (wl *WalletLoad) GetVSPList() { var valueOut struct { Remember string @@ -80,7 +107,7 @@ func (wl *WalletLoad) GetVSPList() { } } - l, _ := wallet.GetInitVSPInfo("https://api.decred.org/?c=vsp") + l, _ := getInitVSPInfo("https://api.decred.org/?c=vsp") for h, v := range l { if strings.Contains(wl.Wallet.Net, v.Network) { loadedVSP = append(loadedVSP, wallet.VSPInfo{ diff --git a/ui/load/wallet.go b/ui/load/wallet.go index 791fd1402..a9c23e8cb 100644 --- a/ui/load/wallet.go +++ b/ui/load/wallet.go @@ -2,6 +2,7 @@ package load import ( "errors" + "fmt" "sort" "github.com/decred/dcrd/dcrutil" @@ -107,3 +108,15 @@ func (wl *WalletLoad) HDPrefix() string { return "" } } + +func (wl *WalletLoad) WalletDirectory() string { + return fmt.Sprintf("%s/%s", wl.Wallet.Root, wl.Wallet.Net) +} + +func (wl *WalletLoad) DataSize() string { + v, err := wl.MultiWallet.RootDirFileSizeInBytes() + if err != nil { + return "Unknown" + } + return fmt.Sprintf("%f GB", float64(v)*1e-9) +} \ No newline at end of file diff --git a/ui/page/settings_page.go b/ui/page/settings_page.go index 012f9513f..4a94f19eb 100644 --- a/ui/page/settings_page.go +++ b/ui/page/settings_page.go @@ -479,7 +479,7 @@ func (pg *SettingsPage) Handle() { pg.showSPVPeerDialog() return } - pg.wal.RemoveUserConfigValueForKey(specificPeerKey) + pg.WL.MultiWallet.DeleteUserConfigValueForKey(specificPeerKey) } for pg.updateConnectToPeer.Clicked() { @@ -498,7 +498,7 @@ func (pg *SettingsPage) Handle() { pg.showUserAgentDialog() return } - pg.wal.RemoveUserConfigValueForKey(userAgentKey) + pg.WL.MultiWallet.DeleteUserConfigValueForKey(userAgentKey) } select { diff --git a/ui/page/statistics_page.go b/ui/page/statistics_page.go index 83b606fce..9c38429b1 100644 --- a/ui/page/statistics_page.go +++ b/ui/page/statistics_page.go @@ -103,9 +103,9 @@ func (pg *StatPage) layoutStats(gtx C) D { pg.Theme.Separator().Layout, item("Best block age", pg.WL.Info.LastSyncTime), pg.Theme.Separator().Layout, - item("Wallet data directory", pg.WL.Wallet.WalletDirectory()), + item("Wallet data directory", pg.WL.WalletDirectory()), pg.Theme.Separator().Layout, - item("Wallet data", pg.WL.Wallet.DataSize()), + item("Wallet data", pg.WL.DataSize()), pg.Theme.Separator().Layout, item("Transactions", fmt.Sprintf("%d", (*pg.txs).Total)), pg.Theme.Separator().Layout, diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 467f602ef..28de25f53 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -189,7 +189,7 @@ func (pg *ListPage) Layout(gtx C) D { func (pg *ListPage) ticketListLayout(gtx C, tickets []wallet.Ticket) D { return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { - st := ticketStatusIcon(pg.Load, tickets[index].Info.Status) + st := ticketStatusProfile(pg.Load, tickets[index].Info.Status) if st == nil { return D{} } diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 4b68847ad..2695db5a0 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" + "github.com/planetdecred/dcrlibwallet" + "gioui.org/layout" "gioui.org/unit" "gioui.org/widget" @@ -39,6 +41,9 @@ type Page struct { toTickets decredmaterial.TextAndIconButton toTicketsActivity decredmaterial.TextAndIconButton ticketTooltips []tooltips + + stakingOverview *dcrlibwallet.StakingOverview + liveTickets []dcrlibwallet.Transaction } func NewTicketPage(l *load.Load) *Page { @@ -65,6 +70,7 @@ func NewTicketPage(l *load.Load) *Page { pg.toTicketsActivity.Color = l.Theme.Color.Primary pg.toTicketsActivity.BackgroundColor = l.Theme.Color.Surface + pg.stakingOverview = new(dcrlibwallet.StakingOverview) return pg } @@ -74,6 +80,25 @@ func (pg *Page) ID() string { func (pg *Page) OnResume() { pg.ticketPrice = dcrutil.Amount(pg.WL.TicketPrice()).String() + wallets := pg.WL.MultiWallet.AllWallets() + + for _, w := range wallets { + ov, _ := w.StakingOverview() + pg.stakingOverview.All += ov.All + pg.stakingOverview.Expired += ov.Expired + pg.stakingOverview.Immature += ov.Immature + pg.stakingOverview.Live += ov.Live + pg.stakingOverview.Revoked += ov.Revoked + pg.stakingOverview.Voted += ov.Voted + } + + lt, err := pg.WL.AllLiveTickets() + if err != nil { + fmt.Printf("ERROR FETCHING LIVE TICKETS %v \n", err.Error()) + pg.CreateToast(err.Error(), false) + } + pg.liveTickets = lt + fmt.Printf("LIVE TICKETS %v \n", lt) go pg.WL.GetVSPList() // TODO: automatic ticket purchase functionality pg.autoPurchaseEnabled.Disabled() @@ -92,7 +117,7 @@ func (pg *Page) Layout(gtx layout.Context) layout.Dimensions { return pg.ticketsActivitySection(gtx) }, func(ctx layout.Context) layout.Dimensions { - return pg.stackingRecordSection(gtx) + return pg.stakingRecordSection(gtx) }, } @@ -176,38 +201,55 @@ func (pg *Page) ticketPriceSection(gtx layout.Context) layout.Dimensions { }) } +func (pg *Page) stakingCounts() []struct { + Status string + Count int +} { + return []struct { + Status string + Count int + }{ + {"IMMATURE", pg.stakingOverview.Immature}, + {"LIVE", pg.stakingOverview.Live}, + {"VOTED", pg.stakingOverview.Voted}, + {"EXPIRED", pg.stakingOverview.Expired}, + {"REVOKED", pg.stakingOverview.Revoked}, + } +} + func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { return pg.pageSections(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Inset{Bottom: values.MarginPadding14}.Layout(gtx, func(gtx C) D { - tit := pg.Theme.Label(values.TextSize14, "Live Tickets") - tit.Color = pg.Theme.Color.Gray2 - return pg.titleRow(gtx, tit.Layout, func(gtx C) D { - ticketLiveCounter := (*pg.tickets).LiveCounter + title := pg.Theme.Label(values.TextSize14, "Live Tickets") + title.Color = pg.Theme.Color.Gray2 + return pg.titleRow(gtx, title.Layout, func(gtx C) D { var elements []layout.FlexChild - for i := 0; i < len(ticketLiveCounter); i++ { - item := ticketLiveCounter[i] - elements = append(elements, layout.Rigid(func(gtx C) D { - return layout.Inset{Right: values.MarginPadding14}.Layout(gtx, func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - st := ticketStatusIcon(pg.Load, item.Status) - if st == nil { - return layout.Dimensions{} - } - return st.icon.Layout16dp(gtx) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{Left: values.MarginPadding4}.Layout(gtx, func(gtx C) D { - label := pg.Theme.Label(values.TextSize14, fmt.Sprintf("%d", item.Count)) - label.Color = pg.Theme.Color.DeepBlue - return label.Layout(gtx) - }) - }), - ) - }) - })) + for i := 0; i < len(pg.stakingCounts()); i++ { + item := pg.stakingCounts()[i] + if item.Status == LIVE || item.Status == IMMATURE { + elements = append(elements, layout.Rigid(func(gtx C) D { + return layout.Inset{Right: values.MarginPadding14}.Layout(gtx, func(gtx C) D { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + st := ticketStatusProfile(pg.Load, item.Status) + if st == nil { + return layout.Dimensions{} + } + return st.icon.Layout16dp(gtx) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{Left: values.MarginPadding4}.Layout(gtx, func(gtx C) D { + label := pg.Theme.Label(values.TextSize14, fmt.Sprintf("%d", item.Count)) + label.Color = pg.Theme.Color.DeepBlue + return label.Layout(gtx) + }) + }), + ) + }) + })) + } } elements = append(elements, layout.Rigid(pg.toTickets.Layout)) return layout.Flex{Alignment: layout.Middle}.Layout(gtx, elements...) @@ -262,7 +304,7 @@ func (pg *Page) ticketsActivitySection(gtx layout.Context) layout.Dimensions { }) } -func (pg *Page) stackingRecordSection(gtx layout.Context) layout.Dimensions { +func (pg *Page) stakingRecordSection(gtx layout.Context) layout.Dimensions { return pg.pageSections(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -275,19 +317,19 @@ func (pg *Page) stackingRecordSection(gtx layout.Context) layout.Dimensions { }) }), layout.Rigid(func(gtx C) D { - stackingRecords := (*pg.tickets).StackingRecordCounter + counts := pg.stakingCounts() return decredmaterial.GridWrap{ Axis: layout.Horizontal, Alignment: layout.End, - }.Layout(gtx, len(stackingRecords), func(gtx layout.Context, i int) layout.Dimensions { - item := stackingRecords[i] + }.Layout(gtx, len(counts), func(gtx layout.Context, i int) layout.Dimensions { + count := counts[i] width := unit.Value{U: unit.UnitDp, V: 118} gtx.Constraints.Min.X = gtx.Px(width) return layout.Inset{Bottom: values.MarginPadding16}.Layout(gtx, func(gtx C) D { return layout.Flex{}.Layout(gtx, layout.Rigid(func(gtx C) D { - st := ticketStatusIcon(pg.Load, item.Status) + st := ticketStatusProfile(pg.Load, count.Status) if st == nil { return layout.Dimensions{} } @@ -297,13 +339,13 @@ func (pg *Page) stackingRecordSection(gtx layout.Context) layout.Dimensions { return layout.Inset{Left: values.MarginPadding4}.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - label := pg.Theme.Label(values.TextSize16, fmt.Sprintf("%d", item.Count)) + label := pg.Theme.Label(values.TextSize16, fmt.Sprintf("%d", count.Count)) label.Color = pg.Theme.Color.DeepBlue return label.Layout(gtx) }), layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding40}.Layout(gtx, func(gtx C) D { - txt := pg.Theme.Label(values.TextSize12, strings.Title(strings.ToLower(item.Status))) + txt := pg.Theme.Label(values.TextSize12, strings.Title(strings.ToLower(count.Status))) txt.Color = pg.Theme.Color.Gray2 return txt.Layout(gtx) }) diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 070f20ab9..4709a85c5 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -41,7 +41,12 @@ var ( durationDesc = "" ) -func ticketStatusIcon(l *load.Load, ticketStatus string) *struct { +const ( + LIVE = "LIVE" + IMMATURE = "IMMATURE" +) + +func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { icon *decredmaterial.Image color color.NRGBA background color.NRGBA @@ -189,7 +194,7 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid func ticketCard(gtx layout.Context, l *load.Load, t *wallet.Ticket, tooltip tooltips) layout.Dimensions { var itemWidth int - st := ticketStatusIcon(l, t.Info.Status) + st := ticketStatusProfile(l, t.Info.Status) if st == nil { return layout.Dimensions{} } @@ -362,7 +367,7 @@ func ticketActivityRow(gtx layout.Context, l *load.Load, t wallet.Ticket, index return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - st := ticketStatusIcon(l, t.Info.Status) + st := ticketStatusProfile(l, t.Info.Status) if st == nil { return layout.Dimensions{} } diff --git a/wallet/commands.go b/wallet/commands.go index db6e2543b..cf8cf9eea 100644 --- a/wallet/commands.go +++ b/wallet/commands.go @@ -1,175 +1,17 @@ package wallet import ( - "encoding/base64" - "encoding/json" "fmt" - "io/ioutil" "math" - "net/http" "sort" "strconv" "time" - "golang.org/x/sync/errgroup" - "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/dcrutil/v3" "github.com/planetdecred/dcrlibwallet" ) -// CreateWallet creates a new wallet with the given parameters. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) CreateWallet(name, passphrase string, errChan chan error) { - go func() { - var resp Response - wall, err := wal.multi.CreateNewWallet(name, passphrase, dcrlibwallet.PassphraseTypePass) - sendErr := func(err error) { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(MultiWalletError{ - Message: "Could not create wallet", - Err: err, - }) - } - if err != nil { - sendErr(err) - return - } - seeds, err := wall.DecryptSeed([]byte(passphrase)) - if err != nil { - sendErr(err) - return - } - - resp.Resp = CreatedSeed{ - Seed: seeds, - } - wal.Send <- resp - }() -} - -// RestoreWallet restores a wallet with the given parameters. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) RestoreWallet(seed, passphrase string, errChan chan error) { - go func() { - var resp Response - _, err := wal.multi.RestoreWallet("wallet", seed, passphrase, dcrlibwallet.PassphraseTypePass) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(MultiWalletError{ - Message: "Could not restore wallet", - Err: err, - }) - return - } - resp.Resp = Restored{} - wal.Send <- resp - }() -} - -// DeleteWallet deletes a wallet. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) DeleteWallet(walletID int, passphrase []byte, errChan chan error) { - log.Debug("Deleting Wallet") - go func() { - var resp Response - log.Debugf("Wallet %d: %+v", walletID, wal.multi.WalletWithID(walletID)) - err := wal.multi.DeleteWallet(walletID, passphrase) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(InternalWalletError{ - Message: "Could not delete wallet", - Affected: []int{walletID}, - Err: err, - }) - return - } - resp.Resp = DeletedWallet{ - ID: walletID, - } - wal.Send <- resp - }() -} - -// AddAccount adds an account to a wallet. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) AddAccount(walletID int, name string, pass []byte, errChan chan error, onCreate func(*dcrlibwallet.Account)) { - go func() { - var resp Response - wall := wal.multi.WalletWithID(walletID) - if wall == nil { - go func() { - errChan <- ErrIDNotExist - }() - resp.Err = ErrIDNotExist - wal.Send <- Response{ - Resp: AddedAccount{}, - Err: ErrIDNotExist, - } - return - } - - id, err := wall.CreateNewAccount(name, pass) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(InternalWalletError{ - Message: "Could not create account", - Affected: []int{walletID}, - Err: err, - }) - return - } - - acct, err := wall.GetAccount(id) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(InternalWalletError{ - Message: "Could not fetch newly created account", - Affected: []int{walletID}, - Err: err, - }) - return - } - onCreate(acct) - - resp.Resp = AddedAccount{ - ID: id, - } - wal.Send <- resp - }() -} - -// CreateTransaction creates a TxAuthor with the given parameters. -// The created TxAuthor will have to have a destination added before broadcasting. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) CreateTransaction(walletID int, accountID int32, errChan chan error) { - go func() { - var resp Response - txAuthor, err := wal.multi.NewUnsignedTx(walletID, accountID) - if err != nil { - errChan <- err - return - } - resp.Resp = txAuthor - wal.Send <- resp - }() -} - // transactionStatus accepts the bestBlockHeight, transactionBlockHeight returns a transaction status // which could be confirmed/pending and confirmations count func transactionStatus(bestBlockHeight, txnBlockHeight int32) (string, int32) { @@ -180,31 +22,6 @@ func transactionStatus(bestBlockHeight, txnBlockHeight int32) (string, int32) { return "pending", confirmations } -// BroadcastTransaction broadcasts the transaction built with txAuthor to the network. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) BroadcastTransaction(txAuthor *dcrlibwallet.TxAuthor, passphrase []byte, errChan chan error) { - go func() { - var resp Response - - txHash, err := txAuthor.Broadcast(passphrase) - if err != nil { - errChan <- fmt.Errorf("error broadcasting transaction: %s", err.Error()) - return - } - - hash, err := chainhash.NewHash(txHash) - if err != nil { - errChan <- fmt.Errorf("error parsing successful transaction hash: %s", err.Error()) - return - } - - resp.Resp = &Broadcast{ - TxHash: hash.String(), - } - wal.Send <- resp - }() -} - // GetAllTransactions collects a per-wallet slice of transactions fitting the parameters. // It is non-blocking and sends its result or any error to wal.Send. func (wal *Wallet) GetAllTransactions(offset, limit, txfilter int32) { @@ -432,230 +249,6 @@ func (wal *Wallet) GetMultiWallet() *dcrlibwallet.MultiWallet { return wal.multi } -func (wal *Wallet) SignMessage(walletID int, passphrase []byte, address, message string, errChan chan error) { - go func() { - var resp Response - - wall := wal.multi.WalletWithID(walletID) - if wall == nil { - resp.Err = ErrIDNotExist - wal.Send <- resp - return - } - - signedMessageBytes, err := wall.SignMessage(passphrase, address, message) - if err != nil { - go func() { - errChan <- err - }() - wal.Send <- resp - return - } - - resp.Resp = &Signature{ - Signature: base64.StdEncoding.EncodeToString(signedMessageBytes), - } - - wal.Send <- resp - }() -} - -// RenameWallet renames the wallet identified by walletID. -func (wal *Wallet) RenameWallet(walletID int, name string, errChan chan error) { - go func() { - var resp Response - err := wal.multi.RenameWallet(walletID, name) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(MultiWalletError{ - Message: "Could not rename wallet", - Err: err, - }) - return - } - resp.Resp = Renamed{ - ID: walletID, - } - wal.Send <- resp - }() -} - -// ImportWatchOnlyWallet imports a watch only wallet with the given parameters. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) ImportWatchOnlyWallet(name, extendedPublicKey string) error { - var g errgroup.Group - g.Go(func() error { - _, err := wal.multi.CreateWatchOnlyWallet(name, extendedPublicKey) - if err != nil { - return fmt.Errorf("error importing watch only wallet: %s", err.Error()) - } - return nil - }) - - if err := g.Wait(); err != nil { - return err - } - - return nil -} - -// ChangeWalletPassphrase changes the spending passphrase of the wallet identified by walletID. -func (wal *Wallet) ChangeWalletPassphrase(walletID int, oldPrivatePassphrase, newPrivatePassphrase string, errChan chan error) { - go func() { - var resp Response - err := wal.multi.ChangePrivatePassphraseForWallet(walletID, []byte(oldPrivatePassphrase), []byte(newPrivatePassphrase), dcrlibwallet.PassphraseTypePass) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(InternalWalletError{ - Message: "Could not change password", - Affected: []int{walletID}, - Err: err, - }) - return - } - - resp.Resp = &ChangePassword{ - ID: walletID, - } - }() -} - -func (wal *Wallet) OpenWallets(passphrase string, errChan chan error) { - go func() { - var resp Response - err := wal.multi.OpenWallets([]byte(passphrase)) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(MultiWalletError{ - Message: "Could not open wallets", - Err: err, - }) - return - } - - resp.Resp = OpenWallet{} - wal.Send <- resp - }() -} - -func (wal *Wallet) SetStartupPassphrase(passphrase string, errChan chan error) { - go func() { - var resp Response - err := wal.multi.SetStartupPassphrase([]byte(passphrase), dcrlibwallet.PassphraseTypePass) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(MultiWalletError{ - Message: "Could not set up startup passphrase", - Err: err, - }) - return - } - resp.Resp = &StartupPassphrase{ - Msg: "Startup password set", - } - wal.Send <- resp - }() -} - -func (wal *Wallet) ChangeStartupPassphrase(oldPrivatePassphrase, newPrivatePassphrase string, errChan chan error) { - go func() { - var resp Response - err := wal.multi.ChangeStartupPassphrase([]byte(oldPrivatePassphrase), []byte(newPrivatePassphrase), dcrlibwallet.PassphraseTypePass) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(MultiWalletError{ - Message: "Could not change startup passphrase", - Err: err, - }) - return - } - - resp.Resp = &StartupPassphrase{ - Msg: "Startup password changed", - } - wal.Send <- resp - }() -} - -func (wal *Wallet) RemoveStartupPassphrase(passphrase string, errChan chan error) { - go func() { - var resp Response - err := wal.multi.RemoveStartupPassphrase([]byte(passphrase)) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(MultiWalletError{ - Message: "Could not remove startup passphrase", - Err: err, - }) - return - } - resp.Resp = &StartupPassphrase{ - Msg: "Startup password removed", - } - wal.Send <- resp - }() -} - -// IsStartupSecuritySet checks if start up password is set -func (wal *Wallet) IsStartupSecuritySet() bool { - return wal.multi.IsStartupSecuritySet() -} - -// RenameAccount renames the acct of wallet with id walletID. -func (wal *Wallet) RenameAccount(walletID int, acct int32, name string, errChan chan<- error) { - go func() { - var resp Response - wall := wal.multi.WalletWithID(walletID) - if wall == nil { - go func() { - errChan <- ErrIDNotExist - }() - resp.Err = ErrIDNotExist - wal.Send <- Response{ - Resp: UpdatedAccount{}, - Err: ErrIDNotExist, - } - return - } - - err := wall.RenameAccount(acct, name) - if err != nil { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(InternalWalletError{ - Message: "Could not rename account", - Affected: []int{walletID}, - Err: err, - }) - return - } - resp.Resp = UpdatedAccount{ - ID: acct, - } - wal.Send <- resp - }() -} - func (wal *Wallet) GetAllProposals() { var resp Response go func() { @@ -755,10 +348,6 @@ func (wal *Wallet) ReadStringConfigValueForKey(key string) string { return wal.multi.ReadStringConfigValueForKey(key) } -func (wal *Wallet) RemoveUserConfigValueForKey(key string) { - wal.multi.DeleteUserConfigValueForKey(key) -} - func (wal *Wallet) LoadedWalletsCount() int32 { return wal.multi.LoadedWalletsCount() } @@ -797,128 +386,6 @@ func divMod(numerator, denominator int64) (quotient, remainder int64) { return } -// AllUnspentOutputs get all unspent outputs by walletID and acct -func (wal *Wallet) AllUnspentOutputs(walletID int, acct int32) { - go func() { - var resp Response - wall := wal.multi.WalletWithID(walletID) - if wall == nil { - resp.Err = ErrIDNotExist - wal.Send <- Response{ - Resp: UnspentOutputs{}, - Err: ErrIDNotExist, - } - return - } - utxos, err := wall.UnspentOutputs(acct) - if err != nil { - resp.Err = err - wal.Send <- ResponseError(InternalWalletError{ - Message: "Could not get unspent outputs", - Affected: []int{walletID, int(acct)}, - Err: err, - }) - return - } - - var list []*UnspentOutput - for _, utxo := range utxos { - item := UnspentOutput{ - UTXO: *utxo, - Amount: dcrutil.Amount(utxo.Amount).String(), - DateTime: dcrlibwallet.ExtractDateOrTime(utxo.ReceiveTime), - } - list = append(list, &item) - } - resp.Resp = &UnspentOutputs{ - List: list, - } - wal.Send <- resp - }() -} - -// IsAccountMixerConfigSet check the wallet have account mixer config set -func (wal *Wallet) IsAccountMixerConfigSet(walletID int) bool { - wall := wal.multi.WalletWithID(walletID) - if wall == nil { - return false - } - return wall.ReadBoolConfigValueForKey(dcrlibwallet.AccountMixerConfigSet, false) -} - -// SetupAccountMixer setup account mixer with the given parameters. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) SetupAccountMixer(walletID int, walletPassphrase string, errChan chan error) { - go func() { - var resp Response - - wall := wal.multi.WalletWithID(walletID) - if wall == nil { - resp.Err = ErrIDNotExist - wal.Send <- Response{ - Resp: SetupAccountMixer{}, - Err: ErrIDNotExist, - } - return - } - - var err error - var mixedAcctNumber int32 - var unmixedAcctNumber int32 - mixedAcct := "mixed" - unmixedAcct := "unmixed" - - sendErr := func(err error) { - go func() { - errChan <- err - }() - resp.Err = err - wal.Send <- ResponseError(InternalWalletError{ - Message: "Could not set account mixer", - Err: err, - Affected: []int{walletID}, - }) - } - - if !wall.HasAccount(mixedAcct) { - mixedAcctNumber, err = wall.CreateNewAccount(mixedAcct, []byte(walletPassphrase)) - if err != nil { - sendErr(err) - return - } - } else { - mixedAcctNumber, err = wall.AccountNumber(mixedAcct) - if err != nil { - sendErr(err) - return - } - } - - if !wall.HasAccount(unmixedAcct) { - unmixedAcctNumber, err = wall.CreateNewAccount(unmixedAcct, []byte(walletPassphrase)) - if err != nil { - sendErr(err) - return - } - } else { - unmixedAcctNumber, err = wall.AccountNumber(unmixedAcct) - if err != nil { - sendErr(err) - return - } - } - - err = wall.SetAccountMixerConfig(mixedAcctNumber, unmixedAcctNumber, walletPassphrase) - if err != nil { - sendErr(err) - return - } - - resp.Resp = SetupAccountMixer{} - wal.Send <- resp - }() -} - // GetAllTickets collects a per-wallet slice of tickets fitting the parameters. // It is non-blocking and sends its result or any error to wal.Send. func (wal *Wallet) GetAllTickets() { @@ -1100,94 +567,4 @@ func getUnconfirmedPurchases(wall dcrlibwallet.Wallet, tickets []Ticket) (unconf } return -} - -func (wal *Wallet) StartAccountMixer(walletID int, walletPassphrase string, errChan chan error) { - err := wal.multi.StartAccountMixer(walletID, walletPassphrase) - if err != nil { - go func() { - errChan <- err - }() - } -} - -func (wal *Wallet) StopAccountMixer(walletID int, errChan chan error) { - err := wal.multi.StopAccountMixer(walletID) - if err != nil { - go func() { - errChan <- err - }() - } -} - -func (wal *Wallet) IsAccountMixerActive(walletID int) bool { - wall := wal.multi.WalletWithID(walletID) - if wall == nil { - return false - } - return wall.IsAccountMixerActive() -} - -func (wal *Wallet) AllWallets() []*dcrlibwallet.Wallet { - return wal.multi.AllWallets() -} - -func (wal *Wallet) ReadMixerConfigValueForKey(key string, walletID int) int32 { - wallet := wal.multi.WalletWithID(walletID) - if wallet != nil { - return wallet.ReadInt32ConfigValueForKey(key, -1) - } - return 0 -} - -// GetInitVSPInfo returns the list information of the VSP -func GetInitVSPInfo(url string) (map[string]*dcrlibwallet.VspInfoResponse, error) { - rq := new(http.Client) - resp, err := rq.Get((url)) - if err != nil { - return nil, err - } - - b, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("non 200 response from server: %v", string(b)) - } - - var vspInfoResponse map[string]*dcrlibwallet.VspInfoResponse - err = json.Unmarshal(b, &vspInfoResponse) - if err != nil { - return nil, err - } - - return vspInfoResponse, nil -} - -func (wal *Wallet) WalletDirectory() string { - return fmt.Sprintf("%s/%s", wal.root, wal.Net) -} - -func (wal *Wallet) DataSize() string { - v, err := wal.multi.RootDirFileSizeInBytes() - if err != nil { - return "Unknown" - } - return fmt.Sprintf("%f GB", float64(v)*1e-9) -} - -// GetAccountName returns the account name or 'external' if it does not belong to the wallet -func (wal *Wallet) GetAccountName(walletID int, accountNumber int32) string { - wallet := wal.multi.WalletWithID(walletID) - if wallet == nil { - return "external" - } - account, err := wallet.GetAccount(accountNumber) - if err != nil { - return "external" - } - return account.Name -} +} \ No newline at end of file diff --git a/wallet/wallet.go b/wallet/wallet.go index 31dfcc44c..efbfcf0eb 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -16,7 +16,7 @@ const syncID = "godcr" // Wallet represents the wallet back end of the app type Wallet struct { multi *dcrlibwallet.MultiWallet - root, Net string + Root, Net string Send chan Response Sync chan SyncStatusUpdate confirms int32 @@ -30,7 +30,7 @@ func NewWallet(root string, net string, send chan Response, confirms int32) (*Wa return nil, fmt.Errorf(`root directory or network cannot be ""`) } wal := &Wallet{ - root: root, + Root: root, Net: net, Sync: make(chan SyncStatusUpdate, 2), Send: send, diff --git a/wallet/wallet_suite_test.go b/wallet/wallet_suite_test.go deleted file mode 100644 index a843ac130..000000000 --- a/wallet/wallet_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package wallet_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestWallet(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Wallet Suite") -} diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go deleted file mode 100644 index 6120f5ea1..000000000 --- a/wallet/wallet_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package wallet_test - -import ( - "fmt" - "os" - "time" - - "github.com/decred/dcrd/dcrutil" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - . "github.com/planetdecred/godcr/wallet" -) - -const ( - testnet = "testnet3" -) - -func getTestDir() string { - now := time.Now().UTC().Unix() - testDir := fmt.Sprintf(".godcr_test_%d", now) - _, err := os.Stat(testDir) - i := 1 - for err == nil { - testDir = fmt.Sprintf(".godcr_test_%d_%d", now, i) - _, err = os.Stat(testDir) - i++ - } - err = os.Mkdir(testDir, os.ModePerm) - Expect(err).To(BeNil()) - return testDir -} - -var ( - wal *Wallet -) -var _ = BeforeSuite(func() { - var err error - wal, err = NewWallet(getTestDir(), testnet, make(chan Response), 2) - Expect(err).To(BeNil()) - resp := <-wal.Send - Expect(resp.Resp).To(BeAssignableToTypeOf(LoadedWallets{})) - tempChan := make(chan error) - wal.CreateWallet("123", "password", tempChan) - go func() { - err := <-tempChan - Expect(err).To(BeNil()) - }() - resp = <-wal.Send - Expect(resp.Resp).To(BeAssignableToTypeOf(CreatedSeed{})) -}) - -var _ = AfterSuite(func() { - wal.Shutdown() -}) - -var _ = Describe("Wallet", func() { - It("can get the multi wallet info", func() { - wal.GetMultiWalletInfo() - info := <-wal.Send - Expect(info.Resp).To(BeAssignableToTypeOf(MultiWalletInfo{})) - inf := info.Resp.(MultiWalletInfo) - Expect(inf.LoadedWallets).To(BeEquivalentTo(1)) - Expect(inf.TotalBalance).To(BeEquivalentTo(dcrutil.Amount(0).String())) - Expect(inf.Synced).To(Equal(false)) - }) - It("can rename a wallet", func() { - tempChan := make(chan error) - wal.RenameWallet(1, "random", tempChan) - go func() { - err := <-tempChan - Expect(err).To(BeNil()) - }() - resp := <-wal.Send - Expect(resp.Resp).To(BeAssignableToTypeOf(Renamed{})) - }) - It("can get the current address", func() { - addr, err := wal.CurrentAddress(1, 0) - Expect(err).To(BeNil()) - Expect(wal.IsAddressValid(addr)).To(Equal(true)) - addr2, err := wal.CurrentAddress(1, 0) - Expect(err).To(BeNil()) - Expect(addr).To(Equal(addr2)) - }) - It("can create a new address", func() { - addr, err := wal.NextAddress(1, 0) - Expect(err).To(BeNil()) - Expect(wal.IsAddressValid(addr)).To(Equal(true)) - }) -}) From 7ee9ecfb15de3771af1f49290893e969aea0f8d9 Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Mon, 9 Aug 2021 12:27:21 +0100 Subject: [PATCH 02/27] add ticket type and methods to load package --- ui/load/ticket.go | 106 ++++++++++++++++++++++++++++--- ui/page/tickets/activity_page.go | 6 +- ui/page/tickets/list_page.go | 101 +++++++++++++++++++---------- ui/page/tickets/overview.go | 84 +++++++++++------------- ui/page/tickets/utils.go | 11 +--- wallet/commands.go | 5 ++ 6 files changed, 213 insertions(+), 100 deletions(-) diff --git a/ui/load/ticket.go b/ui/load/ticket.go index 4d762e394..fd1e18d75 100644 --- a/ui/load/ticket.go +++ b/ui/load/ticket.go @@ -1,28 +1,118 @@ package load -import "github.com/planetdecred/dcrlibwallet" +import ( + "fmt" + "github.com/decred/dcrd/dcrutil" + "github.com/planetdecred/dcrlibwallet" + "math" + "time" +) -func (wl *WalletLoad) AllLiveTickets() ([]dcrlibwallet.Transaction, error) { - var txs []dcrlibwallet.Transaction +type Ticket struct { + Status string + Fee string + Amount string + DateTime string + MonthDay string + DaysBehind string + WalletName string +} + +const( + StakingLive = "LIVE" + StakingImmature = "IMMATURE" +) + +func (wl *WalletLoad) StakingOverviewAllWallets() *dcrlibwallet.StakingOverview { + overview := new(dcrlibwallet.StakingOverview) + for _, w := range wl.MultiWallet.AllWallets() { + ov, _ := w.StakingOverview() + overview.All += ov.All + overview.Expired += ov.Expired + overview.Immature += ov.Immature + overview.Live += ov.Live + overview.Revoked += ov.Revoked + overview.Voted += ov.Voted + } + + return overview +} + +func calculateDaysBehind(lastHeaderTime int64) string { + diff := time.Since(time.Unix(lastHeaderTime, 0)) + daysBehind := int(math.Round(diff.Hours() / 24)) + if daysBehind < 1 { + return "<1 day" + } else if daysBehind == 1 { + return "1 day" + } else { + return fmt.Sprintf("%d days", daysBehind) + } +} + +func transactionToTicket(tx dcrlibwallet.Transaction, status, walletName string) Ticket { + return Ticket{ + Status: status, + Amount: dcrutil.Amount(tx.Amount).String(), + DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), + MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), + DaysBehind: calculateDaysBehind(tx.Timestamp), + Fee: dcrutil.Amount(tx.Fee).String(), + WalletName: walletName, + } +} + +func statusFromFilter(txFilter int32) string { + switch txFilter { + case dcrlibwallet.TxFilterImmature: + return StakingImmature + case dcrlibwallet.TxFilterLive: + return StakingLive + } + + return "" +} + +func transactionsToTickets(txs []dcrlibwallet.Transaction, status, walletName string) []Ticket { + var tickets []Ticket + for _, tx := range txs { + tickets = append(tickets, transactionToTicket(tx, status, walletName)) + } + + return tickets +} + +func (wl *WalletLoad) GetTickets (walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { + var tickets []Ticket + + w := wl.MultiWallet.WalletWithID(walletID) + txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) + if err != nil { + return tickets, err + } + + return transactionsToTickets(txs, statusFromFilter(txFilter), w.Name), nil +} + +func (wl *WalletLoad) AllLiveTickets() ([]Ticket, error) { + var txs []Ticket wallets := wl.MultiWallet.AllWallets() + for _, w := range wallets { immatureTx, err := w.GetTransactionsRaw(0, 0, dcrlibwallet.TxFilterImmature, true) if err != nil { return txs, err } - txs = append(txs, immatureTx...) + txs = append(txs, transactionsToTickets(immatureTx, StakingImmature, w.Name)...) liveTxs, err := w.GetTransactionsRaw(0, 0, dcrlibwallet.TxFilterLive, true) if err != nil { return txs, err } - txs = append(txs, liveTxs...) + txs = append(txs, transactionsToTickets(liveTxs, StakingLive, w.Name)...) } return txs, nil } - -func (wl *WalletLoad) - diff --git a/ui/page/tickets/activity_page.go b/ui/page/tickets/activity_page.go index 18fbe1dff..4810cc2bc 100644 --- a/ui/page/tickets/activity_page.go +++ b/ui/page/tickets/activity_page.go @@ -119,10 +119,10 @@ func (pg *ActivityPage) Layout(gtx layout.Context) layout.Dimensions { return components.UniformPadding(gtx, body) } -func filterTickets(tickets []wallet.Ticket, f func(string) bool) []wallet.Ticket { - t := make([]wallet.Ticket, 0) +func filterTickets(tickets []load.Ticket, f func(string) bool) []load.Ticket { + t := make([]load.Ticket, 0) for _, v := range tickets { - if f(v.Info.Status) { + if f(v.Status) { t = append(t, v) } } diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 28de25f53..085332c8f 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -1,21 +1,17 @@ package tickets import ( - "image/color" - "sort" - "strings" - "time" - "gioui.org/layout" "gioui.org/text" "gioui.org/widget" + "image/color" + "strings" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" "github.com/planetdecred/godcr/ui/values" - "github.com/planetdecred/godcr/wallet" ) const listPageID = "TicketsList" @@ -23,7 +19,7 @@ const listPageID = "TicketsList" type ListPage struct { *load.Load - tickets **wallet.Tickets + tickets []load.Ticket ticketsList layout.List filterSorter int isGridView bool @@ -42,7 +38,6 @@ type ListPage struct { func newListPage(l *load.Load) *ListPage { pg := &ListPage{ Load: l, - tickets: l.WL.Tickets, ticketsList: layout.List{Axis: layout.Vertical}, toggleViewType: new(widget.Clickable), isGridView: true, @@ -56,7 +51,6 @@ func newListPage(l *load.Load) *ListPage { {Text: "Immature"}, {Text: "Live"}, {Text: "Voted"}, - {Text: "Missed"}, {Text: "Expired"}, {Text: "Revoked"}, }, 1) @@ -69,8 +63,42 @@ func (pg *ListPage) ID() string { } func (pg *ListPage) OnResume() { - pg.wallets = pg.WL.SortedWalletList() - components.CreateOrUpdateWalletDropDown(pg.Load, &pg.walletDropDown, pg.wallets) +} + +func (pg *ListPage) fetchTickets() { + var txFilter int32 + switch pg.ticketTypeDropDown.Selected() { + case "All": + txFilter = dcrlibwallet.TxFilterStaking + case "Immature": + txFilter = dcrlibwallet.TxFilterImmature + case "Live": + txFilter = dcrlibwallet.TxFilterLive + case "Voted": + txFilter = dcrlibwallet.TxFilterVoted + case "Expired": + txFilter = dcrlibwallet.TxFilterExpired + case "Revoked": + txFilter = dcrlibwallet.TxFilterRevoked + default: + return + } + + var newestFirst bool + switch pg.orderDropDown.Selected() { + case values.StrNewest: + newestFirst = true + case values.StrOldest: + newestFirst = false + } + + selectedWalletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID + tickets, err := pg.WL.GetTickets(selectedWalletID, txFilter, newestFirst) + if err != nil { + pg.CreateToast(err.Error(), false) + } else { + pg.tickets = tickets + } } func (pg *ListPage) Layout(gtx C) D { @@ -100,7 +128,7 @@ func (pg *ListPage) Layout(gtx C) D { return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { return pg.Theme.Card().Layout(gtx, func(gtx C) D { gtx.Constraints.Min = gtx.Constraints.Max - + var tickets []load.Ticket if pg.ticketTypeDropDown.SelectedIndex()-1 != -1 { tickets = filterTickets(tickets, func(ticketStatus string) bool { return ticketStatus == strings.ToUpper(pg.ticketTypeDropDown.Selected()) @@ -187,9 +215,32 @@ func (pg *ListPage) Layout(gtx C) D { return components.UniformPadding(gtx, body) } -func (pg *ListPage) ticketListLayout(gtx C, tickets []wallet.Ticket) D { +func (pg *ListPage) dropDowns(gtx layout.Context) layout.Dimensions { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return pg.walletDropDown.Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding5, + }.Layout(gtx, pg.ticketTypeDropDown.Layout) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding5, + }.Layout(gtx, pg.orderDropDown.Layout) + }), + ) + }), + ) +} + +func (pg *ListPage) ticketListLayout(gtx C, tickets []load.Ticket) D { return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { - st := ticketStatusProfile(pg.Load, tickets[index].Info.Status) + st := ticketStatusProfile(pg.Load, tickets[index].Status) if st == nil { return D{} } @@ -247,7 +298,7 @@ func (pg *ListPage) ticketListLayout(gtx C, tickets []wallet.Ticket) D { l := func(gtx C) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - txt := pg.Theme.Label(values.MarginPadding14, tickets[index].Info.Status) + txt := pg.Theme.Label(values.MarginPadding14, tickets[index].Status) txt.Color = st.color return txt.Layout(gtx) }), @@ -280,7 +331,7 @@ func (pg *ListPage) ticketListLayout(gtx C, tickets []wallet.Ticket) D { }) } -func (pg *ListPage) ticketListGridLayout(gtx C, tickets []wallet.Ticket) D { +func (pg *ListPage) ticketListGridLayout(gtx C, tickets []load.Ticket) D { // TODO: GridWrap's items not able to scroll vertically, will update when it fixed return layout.Center.Layout(gtx, func(gtx C) D { return pg.ticketsList.Layout(gtx, 1, func(gtx C, index int) D { @@ -304,27 +355,9 @@ func (pg *ListPage) ticketListGridLayout(gtx C, tickets []wallet.Ticket) D { } func (pg *ListPage) Handle() { - if pg.toggleViewType.Clicked() { pg.isGridView = !pg.isGridView } - - sortSelection := pg.orderDropDown.SelectedIndex() - if pg.filterSorter != sortSelection { - pg.filterSorter = sortSelection - newestFirst := pg.filterSorter == 0 - for _, wal := range pg.wallets { - tickets := (*pg.tickets).Confirmed[wal.ID] - sort.SliceStable(tickets, func(i, j int) bool { - backTime := time.Unix(tickets[j].Info.Ticket.Timestamp, 0) - frontTime := time.Unix(tickets[i].Info.Ticket.Timestamp, 0) - if newestFirst { - return backTime.Before(frontTime) - } - return frontTime.Before(backTime) - }) - } - } } func (pg *ListPage) OnClose() {} diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 2695db5a0..75f572b27 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -43,7 +43,7 @@ type Page struct { ticketTooltips []tooltips stakingOverview *dcrlibwallet.StakingOverview - liveTickets []dcrlibwallet.Transaction + liveTickets []load.Ticket } func NewTicketPage(l *load.Load) *Page { @@ -80,17 +80,7 @@ func (pg *Page) ID() string { func (pg *Page) OnResume() { pg.ticketPrice = dcrutil.Amount(pg.WL.TicketPrice()).String() - wallets := pg.WL.MultiWallet.AllWallets() - - for _, w := range wallets { - ov, _ := w.StakingOverview() - pg.stakingOverview.All += ov.All - pg.stakingOverview.Expired += ov.Expired - pg.stakingOverview.Immature += ov.Immature - pg.stakingOverview.Live += ov.Live - pg.stakingOverview.Revoked += ov.Revoked - pg.stakingOverview.Voted += ov.Voted - } + pg.stakingOverview = pg.WL.StakingOverviewAllWallets() lt, err := pg.WL.AllLiveTickets() if err != nil { @@ -98,7 +88,7 @@ func (pg *Page) OnResume() { pg.CreateToast(err.Error(), false) } pg.liveTickets = lt - fmt.Printf("LIVE TICKETS %v \n", lt) + fmt.Printf("LIVE TICKETS %+v \n", lt) go pg.WL.GetVSPList() // TODO: automatic ticket purchase functionality pg.autoPurchaseEnabled.Disabled() @@ -113,9 +103,9 @@ func (pg *Page) Layout(gtx layout.Context) layout.Dimensions { func(ctx layout.Context) layout.Dimensions { return pg.ticketsLiveSection(gtx) }, - func(ctx layout.Context) layout.Dimensions { - return pg.ticketsActivitySection(gtx) - }, + //func(ctx layout.Context) layout.Dimensions { + // return pg.ticketsActivitySection(gtx) + //}, func(ctx layout.Context) layout.Dimensions { return pg.stakingRecordSection(gtx) }, @@ -228,7 +218,7 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { var elements []layout.FlexChild for i := 0; i < len(pg.stakingCounts()); i++ { item := pg.stakingCounts()[i] - if item.Status == LIVE || item.Status == IMMATURE { + if item.Status == load.StakingLive || item.Status == load.StakingImmature { elements = append(elements, layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding14}.Layout(gtx, func(gtx C) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, @@ -268,9 +258,9 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { }) } - return pg.ticketsLive.Layout(gtx, len(tickets), func(gtx C, index int) D { + return pg.ticketsLive.Layout(gtx, len(pg.liveTickets), func(gtx C, index int) D { return layout.Inset{Right: values.MarginPadding8}.Layout(gtx, func(gtx C) D { - return ticketCard(gtx, pg.Load, &tickets[index], pg.ticketTooltips[index]) + return ticketCard(gtx, pg.Load, pg.liveTickets[index], pg.ticketTooltips[index]) }) }) }), @@ -278,31 +268,31 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { }) } -func (pg *Page) ticketsActivitySection(gtx layout.Context) layout.Dimensions { - tickets := (*pg.tickets).RecentActivity - if len(tickets) == 0 { - return layout.Dimensions{} - } - - return pg.pageSections(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Bottom: values.MarginPadding14, - }.Layout(gtx, func(gtx C) D { - tit := pg.Theme.Label(values.TextSize14, "Recent Activity") - tit.Color = pg.Theme.Color.Gray2 - return pg.titleRow(gtx, tit.Layout, pg.toTicketsActivity.Layout) - }) - }), - layout.Rigid(func(gtx C) D { - return pg.ticketsActivity.Layout(gtx, len(tickets), func(gtx C, index int) D { - return ticketActivityRow(gtx, pg.Load, tickets[index], index) - }) - }), - ) - }) -} +//func (pg *Page) ticketsActivitySection(gtx layout.Context) layout.Dimensions { +// //tickets := (*pg.tickets).RecentActivity +// if len(tickets) == 0 { +// return layout.Dimensions{} +// } +// +// return pg.pageSections(gtx, func(gtx C) D { +// return layout.Flex{Axis: layout.Vertical}.Layout(gtx, +// layout.Rigid(func(gtx C) D { +// return layout.Inset{ +// Bottom: values.MarginPadding14, +// }.Layout(gtx, func(gtx C) D { +// title := pg.Theme.Label(values.TextSize14, "Recent Activity") +// title.Color = pg.Theme.Color.Gray2 +// return pg.titleRow(gtx, title.Layout, pg.toTicketsActivity.Layout) +// }) +// }), +// layout.Rigid(func(gtx C) D { +// return pg.ticketsActivity.Layout(gtx, len(tickets), func(gtx C, index int) D { +// return ticketActivityRow(gtx, pg.Load, tickets[index], index) +// }) +// }), +// ) +// }) +//} func (pg *Page) stakingRecordSection(gtx layout.Context) layout.Dimensions { return pg.pageSections(gtx, func(gtx C) D { @@ -311,9 +301,9 @@ func (pg *Page) stakingRecordSection(gtx layout.Context) layout.Dimensions { return layout.Inset{ Bottom: values.MarginPadding14, }.Layout(gtx, func(gtx C) D { - tit := pg.Theme.Label(values.TextSize14, "Staking Record") - tit.Color = pg.Theme.Color.Gray2 - return pg.titleRow(gtx, tit.Layout, func(gtx C) D { return layout.Dimensions{} }) + title := pg.Theme.Label(values.TextSize14, "Staking Record") + title.Color = pg.Theme.Color.Gray2 + return pg.titleRow(gtx, title.Layout, func(gtx C) D { return layout.Dimensions{} }) }) }), layout.Rigid(func(gtx C) D { diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 4709a85c5..ff1147ca6 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -41,11 +41,6 @@ var ( durationDesc = "" ) -const ( - LIVE = "LIVE" - IMMATURE = "IMMATURE" -) - func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { icon *decredmaterial.Image color color.NRGBA @@ -192,9 +187,9 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { } // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid -func ticketCard(gtx layout.Context, l *load.Load, t *wallet.Ticket, tooltip tooltips) layout.Dimensions { +func ticketCard(gtx layout.Context, l *load.Load, t load.Ticket, tooltip *decredmaterial.Tooltip) layout.Dimensions { var itemWidth int - st := ticketStatusProfile(l, t.Info.Status) + st := ticketStatusProfile(l, t.Status) if st == nil { return layout.Dimensions{} } @@ -278,7 +273,7 @@ func ticketCard(gtx layout.Context, l *load.Load, t *wallet.Ticket, tooltip tool layout.Rigid(func(gtx C) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - txt := l.Theme.Label(values.MarginPadding14, t.Info.Status) + txt := l.Theme.Label(values.MarginPadding14, t.Status) txt.Color = st.color txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tooltip.statusTooltip, func(gtx C) D { diff --git a/wallet/commands.go b/wallet/commands.go index cf8cf9eea..57ea8f169 100644 --- a/wallet/commands.go +++ b/wallet/commands.go @@ -134,6 +134,11 @@ func walletSyncStatus(isWaiting bool, walletBestBlock, bestBlockHeight int32) st return "synced" } +// CancelSync cancels the SPV sync +func (wal *Wallet) CancelSync() { + go wal.multi.CancelSync() +} + // GetMultiWalletInfo gets bulk information about the loaded wallets. // Information regarding transactions is collected with respect to wal.confirms as the // number of required confirmations for said transactions. From e85361f71aac16ce71df75b3e8937b0dfe99fe4b Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Wed, 18 Aug 2021 16:22:38 +0100 Subject: [PATCH 03/27] clean up ticket list page - add ticket methods for fetch different ticket types in the load package --- ui/load/ticket.go | 118 ++++++++++++++++++++++--------- ui/load/wallet.go | 6 +- ui/page/settings_page.go | 2 +- ui/page/tickets/activity_page.go | 107 +++++++++++++--------------- ui/page/tickets/list_page.go | 82 ++++++++++++++------- ui/page/tickets/overview.go | 5 +- wallet/commands.go | 5 -- wallet/wallet.go | 2 +- 8 files changed, 198 insertions(+), 129 deletions(-) diff --git a/ui/load/ticket.go b/ui/load/ticket.go index fd1e18d75..7faa1b0b1 100644 --- a/ui/load/ticket.go +++ b/ui/load/ticket.go @@ -2,25 +2,32 @@ package load import ( "fmt" - "github.com/decred/dcrd/dcrutil" - "github.com/planetdecred/dcrlibwallet" "math" + "sort" "time" + + "github.com/decred/dcrd/dcrutil" + "github.com/planetdecred/dcrlibwallet" ) type Ticket struct { - Status string + Status string Fee string Amount string DateTime string MonthDay string DaysBehind string WalletName string + + timestamp int64 } -const( +const ( StakingLive = "LIVE" StakingImmature = "IMMATURE" + StakingExpired = "EXPIRED" + StakingRevoked = "REVOKED" + StakingVoted = "VOTED" ) func (wl *WalletLoad) StakingOverviewAllWallets() *dcrlibwallet.StakingOverview { @@ -50,29 +57,36 @@ func calculateDaysBehind(lastHeaderTime int64) string { } } -func transactionToTicket(tx dcrlibwallet.Transaction, status, walletName string) Ticket { - return Ticket{ - Status: status, - Amount: dcrutil.Amount(tx.Amount).String(), - DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), - MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), - DaysBehind: calculateDaysBehind(tx.Timestamp), - Fee: dcrutil.Amount(tx.Fee).String(), - WalletName: walletName, - } -} - -func statusFromFilter(txFilter int32) string { +func filterToStatus(txFilter int32) string { switch txFilter { case dcrlibwallet.TxFilterImmature: return StakingImmature case dcrlibwallet.TxFilterLive: return StakingLive + case dcrlibwallet.TxFilterExpired: + return StakingExpired + case dcrlibwallet.TxFilterRevoked: + return StakingRevoked + case dcrlibwallet.TxFilterVoted: + return StakingVoted } return "" } +func transactionToTicket(tx dcrlibwallet.Transaction, status, walletName string) Ticket { + return Ticket{ + Status: status, + Amount: dcrutil.Amount(tx.Amount).String(), + DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), + MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), + DaysBehind: calculateDaysBehind(tx.Timestamp), + Fee: dcrutil.Amount(tx.Fee).String(), + WalletName: walletName, + timestamp: tx.Timestamp, + } +} + func transactionsToTickets(txs []dcrlibwallet.Transaction, status, walletName string) []Ticket { var tickets []Ticket for _, tx := range txs { @@ -82,37 +96,73 @@ func transactionsToTickets(txs []dcrlibwallet.Transaction, status, walletName st return tickets } -func (wl *WalletLoad) GetTickets (walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { +func (wl *WalletLoad) getAllTickets(walletID int, newestFirst bool) ([]Ticket, error) { + w := wl.MultiWallet.WalletWithID(walletID) var tickets []Ticket + addTickets := func(txFilter int32, newestFirst bool) error { + txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) + if err != nil { + return err + } + + tickets = append(tickets, transactionsToTickets(txs, filterToStatus(txFilter), w.Name)...) + return nil + } + + filters := []int32{ + dcrlibwallet.TxFilterImmature, + dcrlibwallet.TxFilterLive, + dcrlibwallet.TxFilterVoted, + dcrlibwallet.TxFilterExpired, + dcrlibwallet.TxFilterRevoked, + } + + for _, filter := range filters { + err := addTickets(filter, newestFirst) + if err != nil { + return nil, err + } + } + + sort.SliceStable(tickets, func(i, j int) bool { + if newestFirst { + return tickets[i].timestamp > tickets[j].timestamp + } + return tickets[i].timestamp < tickets[j].timestamp + }) + + return tickets, nil +} + +func (wl *WalletLoad) GetTickets(walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { + if txFilter == dcrlibwallet.TxFilterStaking { + return wl.getAllTickets(walletID, newestFirst) + } + w := wl.MultiWallet.WalletWithID(walletID) txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) if err != nil { - return tickets, err + return nil, err } - return transactionsToTickets(txs, statusFromFilter(txFilter), w.Name), nil + return transactionsToTickets(txs, filterToStatus(txFilter), w.Name), nil } func (wl *WalletLoad) AllLiveTickets() ([]Ticket, error) { - var txs []Ticket + var tickets []Ticket wallets := wl.MultiWallet.AllWallets() + liveTicketFilters := []int32{dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} for _, w := range wallets { - immatureTx, err := w.GetTransactionsRaw(0, 0, dcrlibwallet.TxFilterImmature, true) - if err != nil { - return txs, err + for _, filter := range liveTicketFilters { + tx, err := w.GetTransactionsRaw(0, 0, filter, true) + if err != nil { + return tickets, err + } + tickets = append(tickets, transactionsToTickets(tx, filterToStatus(filter), w.Name)...) } - - txs = append(txs, transactionsToTickets(immatureTx, StakingImmature, w.Name)...) - - liveTxs, err := w.GetTransactionsRaw(0, 0, dcrlibwallet.TxFilterLive, true) - if err != nil { - return txs, err - } - - txs = append(txs, transactionsToTickets(liveTxs, StakingLive, w.Name)...) } - return txs, nil + return tickets, nil } diff --git a/ui/load/wallet.go b/ui/load/wallet.go index a9c23e8cb..ceaa4e74e 100644 --- a/ui/load/wallet.go +++ b/ui/load/wallet.go @@ -46,8 +46,8 @@ func (wl *WalletLoad) SortedWalletList() []*dcrlibwallet.Wallet { func (wl *WalletLoad) TotalWalletsBalance() (dcrutil.Amount, error) { totalBalance := int64(0) - for _, wallet := range wl.MultiWallet.AllWallets() { - accountsResult, err := wallet.GetAccountsRaw() + for _, w := range wl.MultiWallet.AllWallets() { + accountsResult, err := w.GetAccountsRaw() if err != nil { return -1, err } @@ -119,4 +119,4 @@ func (wl *WalletLoad) DataSize() string { return "Unknown" } return fmt.Sprintf("%f GB", float64(v)*1e-9) -} \ No newline at end of file +} diff --git a/ui/page/settings_page.go b/ui/page/settings_page.go index 4a94f19eb..7211a488d 100644 --- a/ui/page/settings_page.go +++ b/ui/page/settings_page.go @@ -546,7 +546,7 @@ func (pg *SettingsPage) showUserAgentDialog() { } func (pg *SettingsPage) updateSettingOptions() { - isPassword := pg.wal.IsStartupSecuritySet() + isPassword := pg.WL.MultiWallet.IsStartupSecuritySet() pg.startupPassword.SetChecked(false) pg.isStartupPassword = false if isPassword { diff --git a/ui/page/tickets/activity_page.go b/ui/page/tickets/activity_page.go index 4810cc2bc..fadf8259e 100644 --- a/ui/page/tickets/activity_page.go +++ b/ui/page/tickets/activity_page.go @@ -2,16 +2,14 @@ package tickets import ( "sort" - "strings" "time" "gioui.org/layout" - "gioui.org/text" + "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" - "github.com/planetdecred/godcr/ui/values" "github.com/planetdecred/godcr/wallet" ) @@ -29,7 +27,8 @@ type ActivityPage struct { wallets []*dcrlibwallet.Wallet - backButton decredmaterial.IconButton + backButton decredmaterial.IconButton + ticketsBeta []load.Ticket } func newTicketActivityPage(l *load.Load) *ActivityPage { @@ -65,58 +64,54 @@ func (pg *ActivityPage) OnResume() { } func (pg *ActivityPage) Layout(gtx layout.Context) layout.Dimensions { - body := func(gtx C) D { - page := components.SubPage{ - Load: pg.Load, - Title: "Ticket activity", - BackButton: pg.backButton, - Back: func() { - pg.PopFragment() - }, - Body: func(gtx C) D { - walletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID - tickets := (*pg.tickets).Confirmed[walletID] - return layout.Stack{Alignment: layout.N}.Layout(gtx, - layout.Expanded(func(gtx C) D { - return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { - return pg.Theme.Card().Layout(gtx, func(gtx C) D { - gtx.Constraints.Min = gtx.Constraints.Max - if pg.ticketTypeDropDown.SelectedIndex()-1 != -1 { - tickets = filterTickets(tickets, func(ticketStatus string) bool { - return ticketStatus == strings.ToUpper(pg.ticketTypeDropDown.Selected()) - }) - } - - if len(tickets) == 0 { - txt := pg.Theme.Body1("No tickets yet") - txt.Color = pg.Theme.Color.Gray2 - txt.Alignment = text.Middle - return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { return txt.Layout(gtx) }) - } - return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { - return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { - return ticketActivityRow(gtx, pg.Load, tickets[index], index) - }) - }) - }) - }) - }), - layout.Expanded(func(gtx C) D { - return pg.walletDropDown.Layout(gtx, 0, false) - }), - layout.Expanded(func(gtx C) D { - return pg.orderDropDown.Layout(gtx, 0, true) - }), - layout.Expanded(func(gtx C) D { - return pg.ticketTypeDropDown.Layout(gtx, pg.orderDropDown.Width+10, true) - }), - ) - }, - } - return page.Layout(gtx) - } - - return components.UniformPadding(gtx, body) + return D{} + //components.CreateOrUpdateWalletDropDown(pg.Load, &pg.walletDropDown, pg.wallets) + //body := func(gtx C) D { + // page := components.SubPage{ + // Load: pg.Load, + // Title: "Ticket activity", + // BackButton: pg.backButton, + // Back: func() { + // pg.PopFragment() + // }, + // Body: func(gtx C) D { + // walletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID + // tickets := (*pg.tickets).Confirmed[walletID] + // return layout.Stack{Alignment: layout.N}.Layout(gtx, + // layout.Expanded(func(gtx C) D { + // return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { + // return pg.Theme.Card().Layout(gtx, func(gtx C) D { + // gtx.Constraints.Min = gtx.Constraints.Max + // if pg.ticketTypeDropDown.SelectedIndex()-1 != -1 { + // tickets = filterTickets(pg.ticketsBeta, func(ticketStatus string) bool { + // return ticketStatus == strings.ToUpper(pg.ticketTypeDropDown.Selected()) + // }) + // } + // + // if len(tickets) == 0 { + // txt := pg.Theme.Body1("No tickets yet") + // txt.Color = pg.Theme.Color.Gray2 + // txt.Alignment = text.Middle + // return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { return txt.Layout(gtx) }) + // } + // return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { + // return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { + // return ticketActivityRow(gtx, pg.Load, tickets[index], index) + // }) + // }) + // }) + // }) + // }), + // layout.Stacked(func(gtx C) D { + // return pg.dropDowns(gtx) + // }), + // ) + // }, + // } + // return page.Layout(gtx) + //} + // + //return components.UniformPadding(gtx, body) } func filterTickets(tickets []load.Ticket, f func(string) bool) []load.Ticket { diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 085332c8f..5c7c4e062 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -1,17 +1,18 @@ package tickets import ( + "context" + "image/color" + "gioui.org/layout" "gioui.org/text" "gioui.org/widget" - "image/color" - "strings" - "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" "github.com/planetdecred/godcr/ui/values" + "github.com/planetdecred/godcr/wallet" ) const listPageID = "TicketsList" @@ -19,6 +20,9 @@ const listPageID = "TicketsList" type ListPage struct { *load.Load + ctx context.Context // page context + ctxCancel context.CancelFunc + tickets []load.Ticket ticketsList layout.List filterSorter int @@ -63,6 +67,36 @@ func (pg *ListPage) ID() string { } func (pg *ListPage) OnResume() { + pg.ctx, pg.ctxCancel = context.WithCancel(context.TODO()) + pg.wallets = pg.WL.SortedWalletList() + components.CreateOrUpdateWalletDropDown(pg.Load, &pg.walletDropDown, pg.wallets) + pg.listenForTxNotifications() + pg.fetchTickets() +} + +func (pg *ListPage) listenForTxNotifications() { + go func() { + for { + var notification interface{} + + select { + case notification = <-pg.Receiver.NotificationsUpdate: + default: + if components.ContextDone(pg.ctx) { + return + } + } + + switch n := notification.(type) { + case wallet.NewTransaction: + selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] + if selectedWallet.ID == n.Transaction.WalletID { + pg.fetchTickets() + pg.RefreshWindow() + } + } + } + }() } func (pg *ListPage) fetchTickets() { @@ -84,14 +118,7 @@ func (pg *ListPage) fetchTickets() { return } - var newestFirst bool - switch pg.orderDropDown.Selected() { - case values.StrNewest: - newestFirst = true - case values.StrOldest: - newestFirst = false - } - + newestFirst := pg.orderDropDown.SelectedIndex() == 0 selectedWalletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID tickets, err := pg.WL.GetTickets(selectedWalletID, txFilter, newestFirst) if err != nil { @@ -128,14 +155,7 @@ func (pg *ListPage) Layout(gtx C) D { return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { return pg.Theme.Card().Layout(gtx, func(gtx C) D { gtx.Constraints.Min = gtx.Constraints.Max - var tickets []load.Ticket - if pg.ticketTypeDropDown.SelectedIndex()-1 != -1 { - tickets = filterTickets(tickets, func(ticketStatus string) bool { - return ticketStatus == strings.ToUpper(pg.ticketTypeDropDown.Selected()) - }) - } - - if len(tickets) == 0 { + if len(pg.tickets) == 0 { txt := pg.Theme.Body1("No tickets yet") txt.Color = pg.Theme.Color.Gray2 txt.Alignment = text.Middle @@ -143,9 +163,9 @@ func (pg *ListPage) Layout(gtx C) D { } return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { if pg.isGridView { - return pg.ticketListGridLayout(gtx, tickets) + return pg.ticketListGridLayout(gtx, pg.tickets) } - return pg.ticketListLayout(gtx, tickets) + return pg.ticketListLayout(gtx, pg.tickets) }) }) }) @@ -218,9 +238,7 @@ func (pg *ListPage) Layout(gtx C) D { func (pg *ListPage) dropDowns(gtx layout.Context) layout.Dimensions { gtx.Constraints.Min.X = gtx.Constraints.Max.X return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return pg.walletDropDown.Layout(gtx) - }), + layout.Rigid(pg.walletDropDown.Layout), layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -358,6 +376,20 @@ func (pg *ListPage) Handle() { if pg.toggleViewType.Clicked() { pg.isGridView = !pg.isGridView } + + for pg.orderDropDown.Changed() { + pg.fetchTickets() + } + + for pg.walletDropDown.Changed() { + pg.fetchTickets() + } + + for pg.ticketTypeDropDown.Changed() { + pg.fetchTickets() + } } -func (pg *ListPage) OnClose() {} +func (pg *ListPage) OnClose() { + pg.ctxCancel() +} diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 75f572b27..0a02820fa 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -4,13 +4,12 @@ import ( "fmt" "strings" - "github.com/planetdecred/dcrlibwallet" - "gioui.org/layout" "gioui.org/unit" "gioui.org/widget" "github.com/decred/dcrd/dcrutil" + "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" @@ -84,11 +83,9 @@ func (pg *Page) OnResume() { lt, err := pg.WL.AllLiveTickets() if err != nil { - fmt.Printf("ERROR FETCHING LIVE TICKETS %v \n", err.Error()) pg.CreateToast(err.Error(), false) } pg.liveTickets = lt - fmt.Printf("LIVE TICKETS %+v \n", lt) go pg.WL.GetVSPList() // TODO: automatic ticket purchase functionality pg.autoPurchaseEnabled.Disabled() diff --git a/wallet/commands.go b/wallet/commands.go index 57ea8f169..8a95f318c 100644 --- a/wallet/commands.go +++ b/wallet/commands.go @@ -323,11 +323,6 @@ func (wal *Wallet) RescanBlocks(walletID int) error { return wal.multi.RescanBlocks(walletID) } -// CancelSync cancels the SPV sync -func (wal *Wallet) CancelSync() { - go wal.multi.CancelSync() -} - func (wal *Wallet) IsSyncingProposals() bool { return wal.multi.Politeia.IsSyncing() } diff --git a/wallet/wallet.go b/wallet/wallet.go index efbfcf0eb..5aef2be08 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -45,7 +45,7 @@ func (wal *Wallet) InitMultiWallet() error { if wal.Net == dcrlibwallet.Testnet3 { politeiaHost = dcrlibwallet.PoliteiaTestnetHost } - multiWal, err := dcrlibwallet.NewMultiWallet(wal.root, "bdb", wal.Net, politeiaHost) + multiWal, err := dcrlibwallet.NewMultiWallet(wal.Root, "bdb", wal.Net, politeiaHost) if err != nil { return err } From e9ebe207a771f9751729b4ded2dacda32ead3fa8 Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Thu, 19 Aug 2021 18:55:03 +0100 Subject: [PATCH 04/27] resolve inactive button bug on ticket purchase modal --- ui/load/ticket.go | 1 + ui/page/tickets/activity_page.go | 148 ++++++++++++++---------------- ui/page/tickets/list_page.go | 2 +- ui/page/tickets/overview.go | 2 +- ui/page/tickets/purchase_modal.go | 4 + ui/page/tickets/utils.go | 7 +- wallet/commands.go | 2 +- 7 files changed, 80 insertions(+), 86 deletions(-) diff --git a/ui/load/ticket.go b/ui/load/ticket.go index 7faa1b0b1..121ffc412 100644 --- a/ui/load/ticket.go +++ b/ui/load/ticket.go @@ -42,6 +42,7 @@ func (wl *WalletLoad) StakingOverviewAllWallets() *dcrlibwallet.StakingOverview overview.Voted += ov.Voted } + wl.MultiWallet.GetLowestBlockTimestamp() return overview } diff --git a/ui/page/tickets/activity_page.go b/ui/page/tickets/activity_page.go index fadf8259e..8d5ca3a87 100644 --- a/ui/page/tickets/activity_page.go +++ b/ui/page/tickets/activity_page.go @@ -1,23 +1,22 @@ package tickets import ( - "sort" - "time" - "gioui.org/layout" + "gioui.org/text" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" "github.com/planetdecred/godcr/wallet" + "github.com/planetdecred/godcr/ui/values" ) const ActivityPageID = "TicketsActivity" type ActivityPage struct { *load.Load - tickets **wallet.Tickets + tickets []load.Ticket ticketsList layout.List filterSorter int @@ -27,14 +26,12 @@ type ActivityPage struct { wallets []*dcrlibwallet.Wallet - backButton decredmaterial.IconButton - ticketsBeta []load.Ticket + backButton decredmaterial.IconButton } func newTicketActivityPage(l *load.Load) *ActivityPage { pg := &ActivityPage{ Load: l, - tickets: l.WL.Tickets, ticketsList: layout.List{Axis: layout.Vertical}, } pg.orderDropDown = components.CreateOrderDropDown(l) @@ -64,84 +61,75 @@ func (pg *ActivityPage) OnResume() { } func (pg *ActivityPage) Layout(gtx layout.Context) layout.Dimensions { - return D{} - //components.CreateOrUpdateWalletDropDown(pg.Load, &pg.walletDropDown, pg.wallets) - //body := func(gtx C) D { - // page := components.SubPage{ - // Load: pg.Load, - // Title: "Ticket activity", - // BackButton: pg.backButton, - // Back: func() { - // pg.PopFragment() - // }, - // Body: func(gtx C) D { - // walletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID - // tickets := (*pg.tickets).Confirmed[walletID] - // return layout.Stack{Alignment: layout.N}.Layout(gtx, - // layout.Expanded(func(gtx C) D { - // return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { - // return pg.Theme.Card().Layout(gtx, func(gtx C) D { - // gtx.Constraints.Min = gtx.Constraints.Max - // if pg.ticketTypeDropDown.SelectedIndex()-1 != -1 { - // tickets = filterTickets(pg.ticketsBeta, func(ticketStatus string) bool { - // return ticketStatus == strings.ToUpper(pg.ticketTypeDropDown.Selected()) - // }) - // } - // - // if len(tickets) == 0 { - // txt := pg.Theme.Body1("No tickets yet") - // txt.Color = pg.Theme.Color.Gray2 - // txt.Alignment = text.Middle - // return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { return txt.Layout(gtx) }) - // } - // return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { - // return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { - // return ticketActivityRow(gtx, pg.Load, tickets[index], index) - // }) - // }) - // }) - // }) - // }), - // layout.Stacked(func(gtx C) D { - // return pg.dropDowns(gtx) - // }), - // ) - // }, - // } - // return page.Layout(gtx) - //} - // - //return components.UniformPadding(gtx, body) -} - -func filterTickets(tickets []load.Ticket, f func(string) bool) []load.Ticket { - t := make([]load.Ticket, 0) - for _, v := range tickets { - if f(v.Status) { - t = append(t, v) + components.CreateOrUpdateWalletDropDown(pg.Load, &pg.walletDropDown, pg.wallets) + body := func(gtx C) D { + page := components.SubPage{ + Load: pg.Load, + Title: "Ticket activity", + BackButton: pg.backButton, + Back: func() { + pg.PopFragment() + }, + Body: func(gtx C) D { + return layout.Stack{Alignment: layout.N}.Layout(gtx, + layout.Expanded(func(gtx C) D { + return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { + return pg.Theme.Card().Layout(gtx, func(gtx C) D { + gtx.Constraints.Min = gtx.Constraints.Max + if len(pg.tickets) == 0 { + txt := pg.Theme.Body1("No tickets yet") + txt.Color = pg.Theme.Color.Gray2 + txt.Alignment = text.Middle + return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { return txt.Layout(gtx) }) + } + return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { + return pg.ticketsList.Layout(gtx, len(pg.tickets), func(gtx C, index int) D { + return ticketActivityRow(gtx, pg.Load, pg.tickets[index], index) + }) + }) + }) + }) + }), + layout.Stacked(func(gtx C) D { + return pg.dropDowns(gtx) + }), + ) + }, } + return page.Layout(gtx) } - return t + + return components.UniformPadding(gtx, body) } -func (pg *ActivityPage) Handle() { +func (pg *ActivityPage) dropDowns(gtx layout.Context) layout.Dimensions { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return pg.walletDropDown.Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding5, + }.Layout(gtx, func(gtx C) D { + return pg.ticketTypeDropDown.Layout(gtx) + }) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding5, + }.Layout(gtx, func(gtx C) D { + return pg.orderDropDown.Layout(gtx) + }) + }), + ) + }), + ) +} - sortSelection := pg.orderDropDown.SelectedIndex() - if pg.filterSorter != sortSelection { - pg.filterSorter = sortSelection - newestFirst := pg.filterSorter == 0 - for _, wal := range pg.wallets { - tickets := (*pg.tickets).Confirmed[wal.ID] - sort.SliceStable(tickets, func(i, j int) bool { - backTime := time.Unix(tickets[j].Info.Ticket.Timestamp, 0) - frontTime := time.Unix(tickets[i].Info.Ticket.Timestamp, 0) - if newestFirst { - return backTime.Before(frontTime) - } - return frontTime.Before(backTime) - }) - } - } +func (pg *ActivityPage) Handle() { } diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 5c7c4e062..0973bd536 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -122,7 +122,7 @@ func (pg *ListPage) fetchTickets() { selectedWalletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID tickets, err := pg.WL.GetTickets(selectedWalletID, txFilter, newestFirst) if err != nil { - pg.CreateToast(err.Error(), false) + pg.Toast.NotifyError(err.Error()) } else { pg.tickets = tickets } diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 0a02820fa..5cde91152 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -83,7 +83,7 @@ func (pg *Page) OnResume() { lt, err := pg.WL.AllLiveTickets() if err != nil { - pg.CreateToast(err.Error(), false) + pg.Toast.NotifyError(err.Error()) } pg.liveTickets = lt go pg.WL.GetVSPList() diff --git a/ui/page/tickets/purchase_modal.go b/ui/page/tickets/purchase_modal.go index 8d4daa2e2..144848420 100644 --- a/ui/page/tickets/purchase_modal.go +++ b/ui/page/tickets/purchase_modal.go @@ -159,6 +159,10 @@ func (tp *ticketPurchaseModal) canPurchase() bool { return false } + if tp.ticketCount() < 1 { + return false + } + return true } diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index ff1147ca6..b19e43dac 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -9,6 +9,7 @@ import ( "gioui.org/gesture" "gioui.org/layout" "gioui.org/unit" + "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" @@ -358,11 +359,11 @@ func ticketCard(gtx layout.Context, l *load.Load, t load.Ticket, tooltip *decred } // ticketActivityRow layouts out ticket info, display ticket activities on the tickets_page and tickets_activity_page -func ticketActivityRow(gtx layout.Context, l *load.Load, t wallet.Ticket, index int) layout.Dimensions { +func ticketActivityRow(gtx layout.Context, l *load.Load, t load.Ticket, index int) layout.Dimensions { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - st := ticketStatusProfile(l, t.Info.Status) + st := ticketStatusProfile(l, t.Status) if st == nil { return layout.Dimensions{} } @@ -389,7 +390,7 @@ func ticketActivityRow(gtx layout.Context, l *load.Load, t wallet.Ticket, index }.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - labelStatus := l.Theme.Label(values.TextSize18, strings.Title(strings.ToLower(t.Info.Status))) + labelStatus := l.Theme.Label(values.TextSize18, strings.Title(strings.ToLower(t.Status))) labelStatus.Color = l.Theme.Color.DeepBlue labelDaysBehind := l.Theme.Label(values.TextSize14, t.DaysBehind) diff --git a/wallet/commands.go b/wallet/commands.go index 8a95f318c..4616c9351 100644 --- a/wallet/commands.go +++ b/wallet/commands.go @@ -567,4 +567,4 @@ func getUnconfirmedPurchases(wall dcrlibwallet.Wallet, tickets []Ticket) (unconf } return -} \ No newline at end of file +} From eac64b2c9aefdb915baa1ccc528bbf613a41b84a Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Thu, 19 Aug 2021 20:34:01 +0100 Subject: [PATCH 05/27] remove global ticket implementation --- ui/load/load.go | 1 - ui/load/wallet.go | 1 - ui/page/tickets/overview.go | 3 - ui/state.go | 11 +-- ui/window.go | 4 - wallet/commands.go | 183 ------------------------------------ wallet/responses.go | 35 ------- 7 files changed, 4 insertions(+), 234 deletions(-) diff --git a/ui/load/load.go b/ui/load/load.go index 3afa63d97..c596a2d1f 100644 --- a/ui/load/load.go +++ b/ui/load/load.go @@ -95,7 +95,6 @@ func NewLoad() (*Load, error) { SyncStatus: new(wallet.SyncStatus), Transactions: new(wallet.Transactions), UnspentOutputs: new(wallet.UnspentOutputs), - Tickets: new(*wallet.Tickets), VspInfo: new(wallet.VSP), Proposals: new(wallet.Proposals), diff --git a/ui/load/wallet.go b/ui/load/wallet.go index ceaa4e74e..8d894d945 100644 --- a/ui/load/wallet.go +++ b/ui/load/wallet.go @@ -23,7 +23,6 @@ type WalletLoad struct { Transactions *wallet.Transactions Transaction *wallet.Transaction BroadcastResult wallet.Broadcast - Tickets **wallet.Tickets VspInfo *wallet.VSP UnspentOutputs *wallet.UnspentOutputs Wallet *wallet.Wallet diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 5cde91152..8ea143916 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -14,7 +14,6 @@ import ( "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" "github.com/planetdecred/godcr/ui/values" - "github.com/planetdecred/godcr/wallet" ) type ( @@ -33,7 +32,6 @@ type Page struct { purchaseTicket decredmaterial.Button - tickets **wallet.Tickets ticketPrice string autoPurchaseEnabled *decredmaterial.Switch @@ -48,7 +46,6 @@ type Page struct { func NewTicketPage(l *load.Load) *Page { pg := &Page{ Load: l, - tickets: l.WL.Tickets, ticketsLive: &layout.List{Axis: layout.Horizontal}, ticketsActivity: &layout.List{Axis: layout.Vertical}, diff --git a/ui/state.go b/ui/state.go index 242126f3e..e321630ba 100644 --- a/ui/state.go +++ b/ui/state.go @@ -49,10 +49,10 @@ func (win *Window) updateStates(update interface{}) { return case *wallet.UnspentOutputs: win.walletUnspentOutputs = e - case *wallet.Tickets: - win.states.loading = false - win.walletTickets = e - return + //case *wallet.Tickets: + // win.states.loading = false + // win.walletTickets = e + // return case *wallet.VSPInfo: win.states.loading = false win.vspInfo.List = append(win.vspInfo.List, *e) @@ -87,14 +87,11 @@ func (win *Window) updateStates(update interface{}) { win.notifyOnSuccess(update.(*wallet.StartupPassphrase).Msg) case wallet.SetupAccountMixer: win.notifyOnSuccess("Mixer setup completed") - case *wallet.TicketPurchase: - win.notifyOnSuccess("Ticket(s) purchased, attempting to pay fee") } win.states.loading = true win.wallet.GetMultiWalletInfo() win.wallet.GetAllTransactions(0, 0, 0) - win.wallet.GetAllTickets() win.wallet.GetAllProposals() op.InvalidateOp{}.Add(win.ops) log.Debugf("Updated with multiwallet info: %+v\n and window state %+v", win.walletInfo, win.states) diff --git a/ui/window.go b/ui/window.go index 28b768309..22e0032bb 100644 --- a/ui/window.go +++ b/ui/window.go @@ -30,7 +30,6 @@ type Window struct { walletTransactions *wallet.Transactions walletTransaction *wallet.Transaction walletAccount *wallet.Account - walletTickets *wallet.Tickets vspInfo *wallet.VSP proposals *wallet.Proposals selectedProposal *dcrlibwallet.Proposal @@ -91,7 +90,6 @@ func CreateWindow(wal *wallet.Wallet, internalLog chan string) (*Window, *app.Wi win.walletTransactions = new(wallet.Transactions) win.walletUnspentOutputs = new(wallet.UnspentOutputs) win.walletAcctMixerStatus = make(chan *wallet.AccountMixer) - win.walletTickets = new(wallet.Tickets) win.vspInfo = new(wallet.VSP) win.proposals = new(wallet.Proposals) win.proposal = make(chan *wallet.Proposal) @@ -128,7 +126,6 @@ func (win *Window) NewLoad() (*load.Load, error) { SyncStatus: win.walletSyncStatus, Transactions: win.walletTransactions, UnspentOutputs: win.walletUnspentOutputs, - Tickets: &win.walletTickets, VspInfo: win.vspInfo, BroadcastResult: win.broadcastResult, Proposals: win.proposals, @@ -324,7 +321,6 @@ func (win *Window) Loop(w *app.Window, shutdown chan int) { win.updateConnectedPeers(update.ConnectedPeers) case wallet.BlockAttached: if win.walletInfo.Synced { - win.wallet.GetAllTickets() win.wallet.GetMultiWalletInfo() win.updateSyncProgress(update.BlockInfo) } diff --git a/wallet/commands.go b/wallet/commands.go index 4616c9351..72d713ee2 100644 --- a/wallet/commands.go +++ b/wallet/commands.go @@ -385,186 +385,3 @@ func divMod(numerator, denominator int64) (quotient, remainder int64) { remainder = numerator % denominator return } - -// GetAllTickets collects a per-wallet slice of tickets fitting the parameters. -// It is non-blocking and sends its result or any error to wal.Send. -func (wal *Wallet) GetAllTickets() { - go func() { - log.Info("fetching all tickets") - var resp Response - wallets, err := wal.wallets() - if err != nil { - resp.Err = err - wal.Send <- resp - return - } - - var liveRecentTickets []Ticket - var recentActivity []Ticket - - tickets := make(map[int][]Ticket) - unconfirmedTickets := make(map[int][]UnconfirmedPurchase) - - stackingRecordCounter := []struct { - Status string - Count int - }{ - {"UNMINED", 0}, - {"IMMATURE", 0}, - {"LIVE", 0}, - {"VOTED", 0}, - {"MISSED", 0}, - {"EXPIRED", 0}, - {"REVOKED", 0}, - } - - liveCounter := []struct { - Status string - Count int - }{ - {"UNMINED", 0}, - {"IMMATURE", 0}, - {"LIVE", 0}, - } - - for _, wall := range wallets { - ticketsInfo, err := wall.GetTicketsForBlockHeightRange(0, wall.GetBestBlock(), math.MaxInt32) - if err != nil { - resp.Err = err - wal.Send <- resp - return - } - - for _, tinfo := range ticketsInfo { - if tinfo.Status == "UNKNOWN" { - continue - } - - var amount dcrutil.Amount - for _, output := range tinfo.Ticket.MyOutputs { - amount += output.Amount - } - info := Ticket{ - Info: *tinfo, - DateTime: time.Unix(tinfo.Ticket.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), - MonthDay: time.Unix(tinfo.Ticket.Timestamp, 0).Format("Jan 2"), - DaysBehind: calculateDaysBehind(tinfo.Ticket.Timestamp), - Amount: amount.String(), - Fee: tinfo.Ticket.Fee.String(), - WalletName: wall.Name, - } - tickets[wall.ID] = append(tickets[wall.ID], info) - - for i := range liveCounter { - if liveCounter[i].Status == tinfo.Status { - liveCounter[i].Count++ - } - } - - if tinfo.Status == "UNMINED" || tinfo.Status == "IMMATURE" || tinfo.Status == "LIVE" { - liveRecentTickets = append(liveRecentTickets, info) - } - - recentActivity = append(recentActivity, info) - - for i := range stackingRecordCounter { - if stackingRecordCounter[i].Status == tinfo.Status { - stackingRecordCounter[i].Count++ - } - } - } - - sort.SliceStable(tickets[wall.ID], func(i, j int) bool { - backTime := time.Unix(tickets[wall.ID][j].Info.Ticket.Timestamp, 0) - frontTime := time.Unix(tickets[wall.ID][i].Info.Ticket.Timestamp, 0) - return backTime.Before(frontTime) - }) - - unconfirmedTicketPurchases, err := getUnconfirmedPurchases(wall, tickets[wall.ID]) - if err != nil { - resp.Err = err - wal.Send <- resp - return - } - unconfirmedTickets[wall.ID] = unconfirmedTicketPurchases - } - - sort.SliceStable(liveRecentTickets, func(i, j int) bool { - backTime := time.Unix(liveRecentTickets[j].Info.Ticket.Timestamp, 0) - frontTime := time.Unix(liveRecentTickets[i].Info.Ticket.Timestamp, 0) - return backTime.Before(frontTime) - }) - - recentLimit := 5 - if len(liveRecentTickets) > recentLimit { - liveRecentTickets = liveRecentTickets[:recentLimit] - } - - sort.SliceStable(recentActivity, func(i, j int) bool { - backTime := time.Unix(recentActivity[j].Info.Ticket.Timestamp, 0) - frontTime := time.Unix(recentActivity[i].Info.Ticket.Timestamp, 0) - return backTime.Before(frontTime) - }) - - if len(recentActivity) > recentLimit { - recentActivity = recentActivity[:recentLimit] - } - - resp.Resp = &Tickets{ - Confirmed: tickets, - Unconfirmed: unconfirmedTickets, - RecentActivity: recentActivity, - StackingRecordCounter: stackingRecordCounter, - LiveRecent: liveRecentTickets, - LiveCounter: liveCounter, - } - wal.Send <- resp - }() -} - -func getUnconfirmedPurchases(wall dcrlibwallet.Wallet, tickets []Ticket) (unconfirmed []UnconfirmedPurchase, err error) { - contains := func(slice []Ticket, item string) bool { - set := make(map[string]struct{}, len(slice)) - for _, s := range slice { - set[s.Info.Ticket.Hash.String()] = struct{}{} - } - - _, ok := set[item] - return ok - } - - txs, err := wall.GetTransactionsRaw(0, 0, dcrlibwallet.TxFilterAll, true) - if err != nil { - return - } - - ticketTxs := make(map[int][]dcrlibwallet.Transaction) - for _, txn := range txs { - if txn.Type == dcrlibwallet.TxTypeTicketPurchase { - ticketTxs[wall.ID] = append(ticketTxs[wall.ID], txn) - } - } - - if len(tickets) == len(ticketTxs) { - return - } - - for _, txn := range ticketTxs[wall.ID] { - var amount int64 - for _, output := range txn.Outputs { - amount += output.Amount - } - - if !contains(tickets, txn.Hash) { - unconfirmed = append(unconfirmed, UnconfirmedPurchase{ - Hash: txn.Hash, - Status: "UNCONFIRMED", - DateTime: dcrlibwallet.ExtractDateOrTime(txn.Timestamp), - BlockHeight: txn.BlockHeight, - Amount: dcrutil.Amount(amount).String(), - }) - } - } - - return -} diff --git a/wallet/responses.go b/wallet/responses.go index 51e31b0a6..42565655f 100644 --- a/wallet/responses.go +++ b/wallet/responses.go @@ -187,41 +187,6 @@ type UnspentOutputs struct { // SetupAccountMixer is sent when finished setup the wallet account mixer type SetupAccountMixer struct{} -type Ticket struct { - Info dcrlibwallet.TicketInfo - Fee string - Amount string - DateTime string - MonthDay string - DaysBehind string - WalletName string -} - -type UnconfirmedPurchase struct { - Hash string - Status string - DateTime string - BlockHeight int32 - Amount string -} - -type Tickets struct { - Confirmed map[int][]Ticket - Unconfirmed map[int][]UnconfirmedPurchase - LiveRecent []Ticket - LiveCounter []struct { - Status string - Count int - } - RecentActivity []Ticket - StackingRecordCounter []struct { - Status string - Count int - } -} - -type TicketPurchase struct{} - type Balance struct { Total int64 Spendable int64 From e048abf8eeaed0f177f32b92972afbd244f6ff12 Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Thu, 19 Aug 2021 20:53:00 +0100 Subject: [PATCH 06/27] cleanup code --- ui/page/tickets/list_page.go | 6 ++---- ui/state.go | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 0973bd536..4bf6e89eb 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -81,10 +81,8 @@ func (pg *ListPage) listenForTxNotifications() { select { case notification = <-pg.Receiver.NotificationsUpdate: - default: - if components.ContextDone(pg.ctx) { - return - } + case <-pg.ctx.Done(): + return } switch n := notification.(type) { diff --git a/ui/state.go b/ui/state.go index e321630ba..465abcc12 100644 --- a/ui/state.go +++ b/ui/state.go @@ -49,10 +49,6 @@ func (win *Window) updateStates(update interface{}) { return case *wallet.UnspentOutputs: win.walletUnspentOutputs = e - //case *wallet.Tickets: - // win.states.loading = false - // win.walletTickets = e - // return case *wallet.VSPInfo: win.states.loading = false win.vspInfo.List = append(win.vspInfo.List, *e) From 2da18920aab7cef941d414d79c4946453ddca4c1 Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Wed, 25 Aug 2021 10:29:59 +0100 Subject: [PATCH 07/27] use dcrlibwallet staking PR --- go.mod | 6 +- go.sum | 4 +- ui/load/ticket.go | 336 +++++++++++++++---------------- ui/page/tickets/activity_page.go | 2 +- ui/page/tickets/list_page.go | 23 ++- ui/page/tickets/overview.go | 25 ++- ui/page/tickets/utils.go | 124 +++++++++++- wallet/commands.go | 1 - 8 files changed, 318 insertions(+), 203 deletions(-) diff --git a/go.mod b/go.mod index bec7968d4..09a4a56d9 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/PuerkitoBio/goquery v1.6.1 github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d github.com/decred/dcrd/chaincfg v1.5.2 // indirect - github.com/decred/dcrd/chaincfg/chainhash v1.0.3-0.20200921185235-6d75c7ec1199 github.com/decred/dcrd/dcrutil v1.4.0 github.com/decred/dcrd/dcrutil/v2 v2.0.1 github.com/decred/dcrd/dcrutil/v3 v3.0.0 @@ -30,4 +29,7 @@ require ( golang.org/x/text v0.3.3 ) -replace github.com/decred/dcrdata/txhelpers/v4 => github.com/decred/dcrdata/txhelpers/v4 v4.0.0-20200108145420-f82113e7e212 +replace ( + github.com/decred/dcrdata/txhelpers/v4 => github.com/decred/dcrdata/txhelpers/v4 v4.0.0-20200108145420-f82113e7e212 + github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210820141537-cc670554081e +) diff --git a/go.sum b/go.sum index a49a1d7c7..a36aa2981 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIo github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210820141537-cc670554081e h1:/tWa+uP8Gt/4RRXNDgRUALgEWnevC3f91A7hpP1igLY= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210820141537-cc670554081e/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.4.8 h1:Rpmta4xZ/MgZnriKNd24iZMhGpP5dvUcs/uqfBapKZY= github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -681,8 +683,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/planetdecred/dcrlibwallet v1.6.1-0.20210816165030-bb3af17a746a h1:loa8rSQsMWs4mGL4wW0Vzvw9kmH7aoO7A/SL4cE+KOM= -github.com/planetdecred/dcrlibwallet v1.6.1-0.20210816165030-bb3af17a746a/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/ui/load/ticket.go b/ui/load/ticket.go index 121ffc412..008630058 100644 --- a/ui/load/ticket.go +++ b/ui/load/ticket.go @@ -1,169 +1,169 @@ package load - -import ( - "fmt" - "math" - "sort" - "time" - - "github.com/decred/dcrd/dcrutil" - "github.com/planetdecred/dcrlibwallet" -) - -type Ticket struct { - Status string - Fee string - Amount string - DateTime string - MonthDay string - DaysBehind string - WalletName string - - timestamp int64 -} - -const ( - StakingLive = "LIVE" - StakingImmature = "IMMATURE" - StakingExpired = "EXPIRED" - StakingRevoked = "REVOKED" - StakingVoted = "VOTED" -) - -func (wl *WalletLoad) StakingOverviewAllWallets() *dcrlibwallet.StakingOverview { - overview := new(dcrlibwallet.StakingOverview) - for _, w := range wl.MultiWallet.AllWallets() { - ov, _ := w.StakingOverview() - overview.All += ov.All - overview.Expired += ov.Expired - overview.Immature += ov.Immature - overview.Live += ov.Live - overview.Revoked += ov.Revoked - overview.Voted += ov.Voted - } - - wl.MultiWallet.GetLowestBlockTimestamp() - return overview -} - -func calculateDaysBehind(lastHeaderTime int64) string { - diff := time.Since(time.Unix(lastHeaderTime, 0)) - daysBehind := int(math.Round(diff.Hours() / 24)) - if daysBehind < 1 { - return "<1 day" - } else if daysBehind == 1 { - return "1 day" - } else { - return fmt.Sprintf("%d days", daysBehind) - } -} - -func filterToStatus(txFilter int32) string { - switch txFilter { - case dcrlibwallet.TxFilterImmature: - return StakingImmature - case dcrlibwallet.TxFilterLive: - return StakingLive - case dcrlibwallet.TxFilterExpired: - return StakingExpired - case dcrlibwallet.TxFilterRevoked: - return StakingRevoked - case dcrlibwallet.TxFilterVoted: - return StakingVoted - } - - return "" -} - -func transactionToTicket(tx dcrlibwallet.Transaction, status, walletName string) Ticket { - return Ticket{ - Status: status, - Amount: dcrutil.Amount(tx.Amount).String(), - DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), - MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), - DaysBehind: calculateDaysBehind(tx.Timestamp), - Fee: dcrutil.Amount(tx.Fee).String(), - WalletName: walletName, - timestamp: tx.Timestamp, - } -} - -func transactionsToTickets(txs []dcrlibwallet.Transaction, status, walletName string) []Ticket { - var tickets []Ticket - for _, tx := range txs { - tickets = append(tickets, transactionToTicket(tx, status, walletName)) - } - - return tickets -} - -func (wl *WalletLoad) getAllTickets(walletID int, newestFirst bool) ([]Ticket, error) { - w := wl.MultiWallet.WalletWithID(walletID) - var tickets []Ticket - - addTickets := func(txFilter int32, newestFirst bool) error { - txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) - if err != nil { - return err - } - - tickets = append(tickets, transactionsToTickets(txs, filterToStatus(txFilter), w.Name)...) - return nil - } - - filters := []int32{ - dcrlibwallet.TxFilterImmature, - dcrlibwallet.TxFilterLive, - dcrlibwallet.TxFilterVoted, - dcrlibwallet.TxFilterExpired, - dcrlibwallet.TxFilterRevoked, - } - - for _, filter := range filters { - err := addTickets(filter, newestFirst) - if err != nil { - return nil, err - } - } - - sort.SliceStable(tickets, func(i, j int) bool { - if newestFirst { - return tickets[i].timestamp > tickets[j].timestamp - } - return tickets[i].timestamp < tickets[j].timestamp - }) - - return tickets, nil -} - -func (wl *WalletLoad) GetTickets(walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { - if txFilter == dcrlibwallet.TxFilterStaking { - return wl.getAllTickets(walletID, newestFirst) - } - - w := wl.MultiWallet.WalletWithID(walletID) - txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) - if err != nil { - return nil, err - } - - return transactionsToTickets(txs, filterToStatus(txFilter), w.Name), nil -} - -func (wl *WalletLoad) AllLiveTickets() ([]Ticket, error) { - var tickets []Ticket - wallets := wl.MultiWallet.AllWallets() - - liveTicketFilters := []int32{dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} - for _, w := range wallets { - for _, filter := range liveTicketFilters { - tx, err := w.GetTransactionsRaw(0, 0, filter, true) - if err != nil { - return tickets, err - } - tickets = append(tickets, transactionsToTickets(tx, filterToStatus(filter), w.Name)...) - } - } - - return tickets, nil -} +// +//import ( +// "fmt" +// "math" +// "sort" +// "time" +// +// "github.com/decred/dcrd/dcrutil" +// "github.com/planetdecred/dcrlibwallet" +//) +// +//type Ticket struct { +// Status string +// Fee string +// Amount string +// DateTime string +// MonthDay string +// DaysBehind string +// WalletName string +// +// timestamp int64 +//} +// +//const ( +// StakingLive = "LIVE" +// StakingImmature = "IMMATURE" +// StakingExpired = "EXPIRED" +// StakingRevoked = "REVOKED" +// StakingVoted = "VOTED" +//) +// +//func (wl *WalletLoad) StakingOverviewAllWallets() *dcrlibwallet.StakingOverview { +// overview := new(dcrlibwallet.StakingOverview) +// for _, w := range wl.MultiWallet.AllWallets() { +// ov, _ := w.StakingOverview() +// overview.All += ov.All +// overview.Expired += ov.Expired +// overview.Immature += ov.Immature +// overview.Live += ov.Live +// overview.Revoked += ov.Revoked +// overview.Voted += ov.Voted +// } +// +// wl.MultiWallet.GetLowestBlockTimestamp() +// return overview +//} +// +//func calculateDaysBehind(lastHeaderTime int64) string { +// diff := time.Since(time.Unix(lastHeaderTime, 0)) +// daysBehind := int(math.Round(diff.Hours() / 24)) +// if daysBehind < 1 { +// return "<1 day" +// } else if daysBehind == 1 { +// return "1 day" +// } else { +// return fmt.Sprintf("%d days", daysBehind) +// } +//} +// +//func filterToStatus(txFilter int32) string { +// switch txFilter { +// case dcrlibwallet.TxFilterImmature: +// return StakingImmature +// case dcrlibwallet.TxFilterLive: +// return StakingLive +// case dcrlibwallet.TxFilterExpired: +// return StakingExpired +// case dcrlibwallet.TxFilterRevoked: +// return StakingRevoked +// case dcrlibwallet.TxFilterVoted: +// return StakingVoted +// } +// +// return "" +//} +// +//func transactionToTicket(tx dcrlibwallet.Transaction, status, walletName string) Ticket { +// return Ticket{ +// Status: status, +// Amount: dcrutil.Amount(tx.Amount).String(), +// DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), +// MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), +// DaysBehind: calculateDaysBehind(tx.Timestamp), +// Fee: dcrutil.Amount(tx.Fee).String(), +// WalletName: walletName, +// timestamp: tx.Timestamp, +// } +//} +// +//func transactionsToTickets(txs []dcrlibwallet.Transaction, status, walletName string) []Ticket { +// var tickets []Ticket +// for _, tx := range txs { +// tickets = append(tickets, transactionToTicket(tx, status, walletName)) +// } +// +// return tickets +//} +// +//func (wl *WalletLoad) getAllTickets(walletID int, newestFirst bool) ([]Ticket, error) { +// w := wl.MultiWallet.WalletWithID(walletID) +// var tickets []Ticket +// +// addTickets := func(txFilter int32, newestFirst bool) error { +// txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) +// if err != nil { +// return err +// } +// +// tickets = append(tickets, transactionsToTickets(txs, filterToStatus(txFilter), w.Name)...) +// return nil +// } +// +// filters := []int32{ +// dcrlibwallet.TxFilterImmature, +// dcrlibwallet.TxFilterLive, +// dcrlibwallet.TxFilterVoted, +// dcrlibwallet.TxFilterExpired, +// dcrlibwallet.TxFilterRevoked, +// } +// +// for _, filter := range filters { +// err := addTickets(filter, newestFirst) +// if err != nil { +// return nil, err +// } +// } +// +// sort.SliceStable(tickets, func(i, j int) bool { +// if newestFirst { +// return tickets[i].timestamp > tickets[j].timestamp +// } +// return tickets[i].timestamp < tickets[j].timestamp +// }) +// +// return tickets, nil +//} +// +//func (wl *WalletLoad) GetTickets(walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { +// if txFilter == dcrlibwallet.TxFilterStaking { +// return wl.getAllTickets(walletID, newestFirst) +// } +// +// w := wl.MultiWallet.WalletWithID(walletID) +// txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) +// if err != nil { +// return nil, err +// } +// +// return transactionsToTickets(txs, filterToStatus(txFilter), w.Name), nil +//} +// +//func (wl *WalletLoad) AllLiveTickets() ([]Ticket, error) { +// var tickets []Ticket +// wallets := wl.MultiWallet.AllWallets() +// +// liveTicketFilters := []int32{dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} +// for _, w := range wallets { +// for _, filter := range liveTicketFilters { +// tx, err := w.GetTransactionsRaw(0, 0, filter, true) +// if err != nil { +// return tickets, err +// } +// tickets = append(tickets, transactionsToTickets(tx, filterToStatus(filter), w.Name)...) +// } +// } +// +// return tickets, nil +//} diff --git a/ui/page/tickets/activity_page.go b/ui/page/tickets/activity_page.go index 8d5ca3a87..ad3eb0395 100644 --- a/ui/page/tickets/activity_page.go +++ b/ui/page/tickets/activity_page.go @@ -16,7 +16,7 @@ const ActivityPageID = "TicketsActivity" type ActivityPage struct { *load.Load - tickets []load.Ticket + tickets []Ticket ticketsList layout.List filterSorter int diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 4bf6e89eb..f50de7cf2 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -2,6 +2,7 @@ package tickets import ( "context" + "fmt" "image/color" "gioui.org/layout" @@ -23,7 +24,7 @@ type ListPage struct { ctx context.Context // page context ctxCancel context.CancelFunc - tickets []load.Ticket + tickets []Ticket ticketsList layout.List filterSorter int isGridView bool @@ -55,7 +56,6 @@ func newListPage(l *load.Load) *ListPage { {Text: "Immature"}, {Text: "Live"}, {Text: "Voted"}, - {Text: "Expired"}, {Text: "Revoked"}, }, 1) @@ -99,18 +99,18 @@ func (pg *ListPage) listenForTxNotifications() { func (pg *ListPage) fetchTickets() { var txFilter int32 - switch pg.ticketTypeDropDown.Selected() { - case "All": + switch pg.ticketTypeDropDown.SelectedIndex() { + case 0: txFilter = dcrlibwallet.TxFilterStaking - case "Immature": + case 1: + txFilter = dcrlibwallet.TxFilterUnmined + case 2: txFilter = dcrlibwallet.TxFilterImmature - case "Live": + case 3: txFilter = dcrlibwallet.TxFilterLive - case "Voted": + case 4: txFilter = dcrlibwallet.TxFilterVoted - case "Expired": - txFilter = dcrlibwallet.TxFilterExpired - case "Revoked": + case 5: txFilter = dcrlibwallet.TxFilterRevoked default: return @@ -118,10 +118,11 @@ func (pg *ListPage) fetchTickets() { newestFirst := pg.orderDropDown.SelectedIndex() == 0 selectedWalletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID - tickets, err := pg.WL.GetTickets(selectedWalletID, txFilter, newestFirst) + tickets, err := getTickets(pg.WL.MultiWallet, selectedWalletID, txFilter, newestFirst) if err != nil { pg.Toast.NotifyError(err.Error()) } else { + fmt.Printf("ticket length %v filter %v\n", len(tickets), txFilter) pg.tickets = tickets } } diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 8ea143916..fc6c43364 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -2,13 +2,13 @@ package tickets import ( "fmt" + "github.com/decred/dcrd/dcrutil" "strings" "gioui.org/layout" "gioui.org/unit" "gioui.org/widget" - "github.com/decred/dcrd/dcrutil" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" @@ -40,7 +40,7 @@ type Page struct { ticketTooltips []tooltips stakingOverview *dcrlibwallet.StakingOverview - liveTickets []load.Ticket + liveTickets []Ticket } func NewTicketPage(l *load.Load) *Page { @@ -76,13 +76,17 @@ func (pg *Page) ID() string { func (pg *Page) OnResume() { pg.ticketPrice = dcrutil.Amount(pg.WL.TicketPrice()).String() - pg.stakingOverview = pg.WL.StakingOverviewAllWallets() - - lt, err := pg.WL.AllLiveTickets() - if err != nil { - pg.Toast.NotifyError(err.Error()) - } - pg.liveTickets = lt + go func() { + overview, err := pg.WL.MultiWallet.StakingOverview() + if err != nil { + pg.Toast.NotifyError(err.Error()) + } + pg.stakingOverview = overview + }() + go func() { + mw := pg.WL.MultiWallet + pg.liveTickets = allLiveTickets(mw.AllWallets(), mw.TicketMaturity(), mw.GetBestBlock().Height) + }() go pg.WL.GetVSPList() // TODO: automatic ticket purchase functionality pg.autoPurchaseEnabled.Disabled() @@ -196,7 +200,6 @@ func (pg *Page) stakingCounts() []struct { {"IMMATURE", pg.stakingOverview.Immature}, {"LIVE", pg.stakingOverview.Live}, {"VOTED", pg.stakingOverview.Voted}, - {"EXPIRED", pg.stakingOverview.Expired}, {"REVOKED", pg.stakingOverview.Revoked}, } } @@ -212,7 +215,7 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { var elements []layout.FlexChild for i := 0; i < len(pg.stakingCounts()); i++ { item := pg.stakingCounts()[i] - if item.Status == load.StakingLive || item.Status == load.StakingImmature { + if item.Status == StakingLive || item.Status == StakingImmature { elements = append(elements, layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding14}.Layout(gtx, func(gtx C) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index b19e43dac..e94a59fd3 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -4,12 +4,16 @@ import ( "fmt" "image" "image/color" + "math" "strings" + "time" "gioui.org/gesture" "gioui.org/layout" "gioui.org/unit" + "github.com/decred/dcrd/dcrutil" + "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" @@ -42,6 +46,111 @@ var ( durationDesc = "" ) +type Ticket struct { + Status string + Fee string + Amount string + DateTime string + MonthDay string + DaysBehind string + WalletName string + + timestamp int64 +} + +const ( + StakingLive = "LIVE" + StakingUnmined = "UNMINED" + StakingImmature = "IMMATURE" + StakingRevoked = "REVOKED" + StakingVoted = "VOTED" +) + +func calculateDaysBehind(lastHeaderTime int64) string { + diff := time.Since(time.Unix(lastHeaderTime, 0)) + daysBehind := int(math.Round(diff.Hours() / 24)) + if daysBehind < 1 { + return "<1 day" + } else if daysBehind == 1 { + return "1 day" + } else { + return fmt.Sprintf("%d days", daysBehind) + } +} + +func transactionToTicket(tx dcrlibwallet.Transaction, walletName string, maturity, bestBlock int32) Ticket { + return Ticket{ + Status: getTicketStatus(tx, maturity, bestBlock), + Amount: dcrutil.Amount(tx.Amount).String(), + DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), + MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), + DaysBehind: calculateDaysBehind(tx.Timestamp), + Fee: dcrutil.Amount(tx.Fee).String(), + WalletName: walletName, + timestamp: tx.Timestamp, + } +} + +func transactionsToTickets(txs []dcrlibwallet.Transaction, walletName string, maturity, bestBlock int32) []Ticket { + var tickets []Ticket + for _, tx := range txs { + tickets = append(tickets, transactionToTicket(tx, walletName, maturity, bestBlock)) + } + + return tickets +} + +func getTicketStatus(txn dcrlibwallet.Transaction, ticketMaturity, bestBlock int32) string { + s := txn.TicketStatus(ticketMaturity, bestBlock) + if s == "" { + fmt.Printf("ticket status %v type %v\n \n", s == "", txn.Type) + } + + switch s { + case dcrlibwallet.TicketStatusUnmined: + return StakingUnmined + case dcrlibwallet.TicketStatusImmature: + return StakingImmature + case dcrlibwallet.TicketStatusLive: + return StakingLive + case dcrlibwallet.TicketStatusVotedOrRevoked: + if txn.Type == dcrlibwallet.TxTypeVote { + return StakingVoted + } else if txn.Type == dcrlibwallet.TxTypeRevocation { + return StakingRevoked + } + } + + return "" +} + +func getTickets(mw *dcrlibwallet.MultiWallet, walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { + w := mw.WalletWithID(walletID) + txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) + if err != nil { + return nil, err + } + + fmt.Printf("best block %v \n", mw.GetBestBlock().Height) + return transactionsToTickets(txs, w.Name, mw.TicketMaturity(), mw.GetBestBlock().Height), nil +} + +func allLiveTickets(wallets []*dcrlibwallet.Wallet, ticketMaturity, bestBlock int32) []Ticket { + var tickets []Ticket + liveTicketFilters := []int32{dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} + for _, w := range wallets { + for _, filter := range liveTicketFilters { + tx, err := w.GetTransactionsRaw(0, 0, filter, true) + if err != nil { + return tickets + } + tickets = append(tickets, transactionsToTickets(tx, w.Name, ticketMaturity, bestBlock)...) + } + } + + return tickets +} + func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { icon *decredmaterial.Image color color.NRGBA @@ -52,22 +161,22 @@ func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { color color.NRGBA background color.NRGBA }{ - "UNMINED": { + StakingUnmined: { l.Icons.TicketUnminedIcon, l.Theme.Color.DeepBlue, l.Theme.Color.LightBlue, }, - "IMMATURE": { + StakingImmature: { l.Icons.TicketImmatureIcon, l.Theme.Color.DeepBlue, l.Theme.Color.LightBlue, }, - "LIVE": { + StakingLive: { l.Icons.TicketLiveIcon, l.Theme.Color.Primary, l.Theme.Color.LightBlue, }, - "VOTED": { + StakingVoted: { l.Icons.TicketVotedIcon, l.Theme.Color.Success, l.Theme.Color.Success2, @@ -82,7 +191,7 @@ func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { l.Theme.Color.Gray, l.Theme.Color.LightGray, }, - "REVOKED": { + StakingRevoked: { l.Icons.TicketRevokedIcon, l.Theme.Color.Orange, l.Theme.Color.Orange2, @@ -188,12 +297,13 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { } // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid -func ticketCard(gtx layout.Context, l *load.Load, t load.Ticket, tooltip *decredmaterial.Tooltip) layout.Dimensions { +func ticketCard(gtx layout.Context, l *load.Load, t Ticket, tooltip *decredmaterial.Tooltip) layout.Dimensions { var itemWidth int st := ticketStatusProfile(l, t.Status) if st == nil { return layout.Dimensions{} } + return l.Theme.Shadow().Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -359,7 +469,7 @@ func ticketCard(gtx layout.Context, l *load.Load, t load.Ticket, tooltip *decred } // ticketActivityRow layouts out ticket info, display ticket activities on the tickets_page and tickets_activity_page -func ticketActivityRow(gtx layout.Context, l *load.Load, t load.Ticket, index int) layout.Dimensions { +func ticketActivityRow(gtx layout.Context, l *load.Load, t Ticket, index int) layout.Dimensions { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding16}.Layout(gtx, func(gtx C) D { diff --git a/wallet/commands.go b/wallet/commands.go index 72d713ee2..c319f5619 100644 --- a/wallet/commands.go +++ b/wallet/commands.go @@ -7,7 +7,6 @@ import ( "strconv" "time" - "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/dcrutil/v3" "github.com/planetdecred/dcrlibwallet" ) From fad67229310d5f00a52856341a0adf0c42d05a63 Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Fri, 27 Aug 2021 02:48:49 +0100 Subject: [PATCH 08/27] cleanup fetch ticket code --- go.mod | 2 +- go.sum | 4 +-- ui/page/tickets/list_page.go | 39 +++++++++++++----------- ui/page/tickets/overview.go | 7 +++-- ui/page/tickets/utils.go | 58 ++++++++++++++---------------------- 5 files changed, 52 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index 09a4a56d9..adbcce602 100644 --- a/go.mod +++ b/go.mod @@ -31,5 +31,5 @@ require ( replace ( github.com/decred/dcrdata/txhelpers/v4 => github.com/decred/dcrdata/txhelpers/v4 v4.0.0-20200108145420-f82113e7e212 - github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210820141537-cc670554081e + github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e ) diff --git a/go.sum b/go.sum index a36aa2981..f413bd6e1 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIo github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210820141537-cc670554081e h1:/tWa+uP8Gt/4RRXNDgRUALgEWnevC3f91A7hpP1igLY= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210820141537-cc670554081e/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e h1:x6AYXRKp/C56x6lsUEUQ6+QhBM9xO07KXt2kBnePaDs= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.4.8 h1:Rpmta4xZ/MgZnriKNd24iZMhGpP5dvUcs/uqfBapKZY= github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index f50de7cf2..b8f5a3339 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -2,7 +2,6 @@ package tickets import ( "context" - "fmt" "image/color" "gioui.org/layout" @@ -24,7 +23,7 @@ type ListPage struct { ctx context.Context // page context ctxCancel context.CancelFunc - tickets []Ticket + tickets []dcrlibwallet.Transaction ticketsList layout.List filterSorter int isGridView bool @@ -56,6 +55,7 @@ func newListPage(l *load.Load) *ListPage { {Text: "Immature"}, {Text: "Live"}, {Text: "Voted"}, + {Text: "Expired"}, {Text: "Revoked"}, }, 1) @@ -101,7 +101,7 @@ func (pg *ListPage) fetchTickets() { var txFilter int32 switch pg.ticketTypeDropDown.SelectedIndex() { case 0: - txFilter = dcrlibwallet.TxFilterStaking + txFilter = dcrlibwallet.TxFilterTickets case 1: txFilter = dcrlibwallet.TxFilterUnmined case 2: @@ -111,6 +111,8 @@ func (pg *ListPage) fetchTickets() { case 4: txFilter = dcrlibwallet.TxFilterVoted case 5: + txFilter = dcrlibwallet.TxFilterExpired + case 6: txFilter = dcrlibwallet.TxFilterRevoked default: return @@ -118,18 +120,17 @@ func (pg *ListPage) fetchTickets() { newestFirst := pg.orderDropDown.SelectedIndex() == 0 selectedWalletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID - tickets, err := getTickets(pg.WL.MultiWallet, selectedWalletID, txFilter, newestFirst) + w := pg.WL.MultiWallet.WalletWithID(selectedWalletID) + txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) if err != nil { pg.Toast.NotifyError(err.Error()) } else { - fmt.Printf("ticket length %v filter %v\n", len(tickets), txFilter) - pg.tickets = tickets + pg.tickets = txs } } func (pg *ListPage) Layout(gtx C) D { - walletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID - tickets := (*pg.tickets).Confirmed[walletID] + tickets := pg.tickets body := func(gtx C) D { page := components.SubPage{ @@ -255,9 +256,11 @@ func (pg *ListPage) dropDowns(gtx layout.Context) layout.Dimensions { ) } -func (pg *ListPage) ticketListLayout(gtx C, tickets []load.Ticket) D { +func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []dcrlibwallet.Transaction) layout.Dimensions { return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { - st := ticketStatusProfile(pg.Load, tickets[index].Status) + w := pg.WL.MultiWallet.WalletWithID(tickets[index].WalletID) + t := transactionToTicket(tickets[index], w, pg.WL.MultiWallet.TicketMaturity(), pg.WL.MultiWallet.GetBestBlock().Height) + st := ticketStatusProfile(pg.Load, t.Status) if st == nil { return D{} } @@ -305,17 +308,17 @@ func (pg *ListPage) ticketListLayout(gtx C, tickets []load.Ticket) D { }.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - dtime := pg.Theme.Label(values.TextSize14, tickets[index].DateTime) + dtime := pg.Theme.Label(values.TextSize14, t.DateTime) dtime.Color = pg.Theme.Color.Gray2 return components.EndToEndRow(gtx, func(gtx C) D { - return components.LayoutBalance(gtx, pg.Load, tickets[index].Amount) + return components.LayoutBalance(gtx, pg.Load, t.Amount) }, dtime.Layout) }), layout.Rigid(func(gtx C) D { l := func(gtx C) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - txt := pg.Theme.Label(values.MarginPadding14, tickets[index].Status) + txt := pg.Theme.Label(values.MarginPadding14, t.Status) txt.Color = st.color return txt.Layout(gtx) }), @@ -329,11 +332,11 @@ func (pg *ListPage) ticketListLayout(gtx C, tickets []load.Ticket) D { return pg.Icons.ImageBrightness1.Layout(gtx, values.MarginPadding5) }) }), - layout.Rigid(pg.Theme.Label(values.MarginPadding14, tickets[index].WalletName).Layout), + layout.Rigid(pg.Theme.Label(values.MarginPadding14, t.WalletName).Layout), ) } - r := func(gtx C) D { - txt := pg.Theme.Label(values.TextSize14, tickets[index].DaysBehind) + r := func(gtx C) layout.Dimensions { + txt := pg.Theme.Label(values.TextSize14, t.DaysBehind) txt.Color = pg.Theme.Color.Gray2 return txt.Layout(gtx) } @@ -348,7 +351,7 @@ func (pg *ListPage) ticketListLayout(gtx C, tickets []load.Ticket) D { }) } -func (pg *ListPage) ticketListGridLayout(gtx C, tickets []load.Ticket) D { +func (pg *ListPage) ticketListGridLayout(gtx C, tickets []dcrlibwallet.Transaction) D { // TODO: GridWrap's items not able to scroll vertically, will update when it fixed return layout.Center.Layout(gtx, func(gtx C) D { return pg.ticketsList.Layout(gtx, 1, func(gtx C, index int) D { @@ -363,6 +366,8 @@ func (pg *ListPage) ticketListGridLayout(gtx C, tickets []load.Ticket) D { Right: values.MarginPadding4, Bottom: values.MarginPadding8, }.Layout(gtx, func(gtx C) D { + //selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] + // return ticketCard(gtx, pg.Load, selectedWallet, tickets[index], pg.statusTooltips[index]) return ticketCard(gtx, pg.Load, &tickets[index], pg.ticketTooltips[index]) }) }) diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index fc6c43364..4ff568ab6 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -40,7 +40,7 @@ type Page struct { ticketTooltips []tooltips stakingOverview *dcrlibwallet.StakingOverview - liveTickets []Ticket + liveTickets []dcrlibwallet.Transaction } func NewTicketPage(l *load.Load) *Page { @@ -85,7 +85,7 @@ func (pg *Page) OnResume() { }() go func() { mw := pg.WL.MultiWallet - pg.liveTickets = allLiveTickets(mw.AllWallets(), mw.TicketMaturity(), mw.GetBestBlock().Height) + pg.liveTickets = allLiveTickets(mw.AllWallets()) }() go pg.WL.GetVSPList() // TODO: automatic ticket purchase functionality @@ -257,7 +257,8 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { return pg.ticketsLive.Layout(gtx, len(pg.liveTickets), func(gtx C, index int) D { return layout.Inset{Right: values.MarginPadding8}.Layout(gtx, func(gtx C) D { - return ticketCard(gtx, pg.Load, pg.liveTickets[index], pg.ticketTooltips[index]) + w := pg.WL.MultiWallet.WalletWithID(pg.liveTickets[index].WalletID) + return ticketCard(gtx, pg.Load, w, pg.liveTickets[index], pg.ticketTooltips[index]) }) }) }), diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index e94a59fd3..c724b7bdf 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -54,8 +54,6 @@ type Ticket struct { MonthDay string DaysBehind string WalletName string - - timestamp int64 } const ( @@ -64,6 +62,7 @@ const ( StakingImmature = "IMMATURE" StakingRevoked = "REVOKED" StakingVoted = "VOTED" + StakingExpired = "EXPIRED" ) func calculateDaysBehind(lastHeaderTime int64) string { @@ -78,34 +77,28 @@ func calculateDaysBehind(lastHeaderTime int64) string { } } -func transactionToTicket(tx dcrlibwallet.Transaction, walletName string, maturity, bestBlock int32) Ticket { +func transactionToTicket(tx dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, maturity, bestBlock int32) Ticket { return Ticket{ - Status: getTicketStatus(tx, maturity, bestBlock), + Status: getTicketStatus(tx, w, maturity, bestBlock), Amount: dcrutil.Amount(tx.Amount).String(), DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), DaysBehind: calculateDaysBehind(tx.Timestamp), Fee: dcrutil.Amount(tx.Fee).String(), - WalletName: walletName, - timestamp: tx.Timestamp, + WalletName: w.Name, } } -func transactionsToTickets(txs []dcrlibwallet.Transaction, walletName string, maturity, bestBlock int32) []Ticket { - var tickets []Ticket - for _, tx := range txs { - tickets = append(tickets, transactionToTicket(tx, walletName, maturity, bestBlock)) +func getTicketStatus(txn dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, ticketMaturity, bestBlock int32) string { + if txn.Type == dcrlibwallet.TxTypeVote { + return StakingVoted } - return tickets -} - -func getTicketStatus(txn dcrlibwallet.Transaction, ticketMaturity, bestBlock int32) string { - s := txn.TicketStatus(ticketMaturity, bestBlock) - if s == "" { - fmt.Printf("ticket status %v type %v\n \n", s == "", txn.Type) + if txn.Type == dcrlibwallet.TxTypeRevocation { + return StakingRevoked } + s := txn.TicketStatus(ticketMaturity, bestBlock) switch s { case dcrlibwallet.TicketStatusUnmined: return StakingUnmined @@ -114,9 +107,13 @@ func getTicketStatus(txn dcrlibwallet.Transaction, ticketMaturity, bestBlock int case dcrlibwallet.TicketStatusLive: return StakingLive case dcrlibwallet.TicketStatusVotedOrRevoked: - if txn.Type == dcrlibwallet.TxTypeVote { + // handle revocation and voted tickets that have the type "TicketPurchase" + tx, _ := w.TicketSpender(txn.Hash) + if tx.Type == dcrlibwallet.TxTypeVote { return StakingVoted - } else if txn.Type == dcrlibwallet.TxTypeRevocation { + } + + if tx.Type == dcrlibwallet.TxTypeRevocation { return StakingRevoked } } @@ -124,19 +121,8 @@ func getTicketStatus(txn dcrlibwallet.Transaction, ticketMaturity, bestBlock int return "" } -func getTickets(mw *dcrlibwallet.MultiWallet, walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { - w := mw.WalletWithID(walletID) - txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) - if err != nil { - return nil, err - } - - fmt.Printf("best block %v \n", mw.GetBestBlock().Height) - return transactionsToTickets(txs, w.Name, mw.TicketMaturity(), mw.GetBestBlock().Height), nil -} - -func allLiveTickets(wallets []*dcrlibwallet.Wallet, ticketMaturity, bestBlock int32) []Ticket { - var tickets []Ticket +func allLiveTickets(wallets []*dcrlibwallet.Wallet) []dcrlibwallet.Transaction { + var tickets []dcrlibwallet.Transaction liveTicketFilters := []int32{dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} for _, w := range wallets { for _, filter := range liveTicketFilters { @@ -144,7 +130,8 @@ func allLiveTickets(wallets []*dcrlibwallet.Wallet, ticketMaturity, bestBlock in if err != nil { return tickets } - tickets = append(tickets, transactionsToTickets(tx, w.Name, ticketMaturity, bestBlock)...) + + tickets = append(tickets, tx...) } } @@ -186,7 +173,7 @@ func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { l.Theme.Color.Gray, l.Theme.Color.LightGray, }, - "EXPIRED": { + StakingExpired: { l.Icons.TicketExpiredIcon, l.Theme.Color.Gray, l.Theme.Color.LightGray, @@ -297,7 +284,8 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { } // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid -func ticketCard(gtx layout.Context, l *load.Load, t Ticket, tooltip *decredmaterial.Tooltip) layout.Dimensions { +func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.Wallet, tx dcrlibwallet.Transaction, tooltip *decredmaterial.Tooltip) layout.Dimensions { + t := transactionToTicket(tx, selectedWallet, l.WL.MultiWallet.TicketMaturity(), l.WL.MultiWallet.GetBestBlock().Height) var itemWidth int st := ticketStatusProfile(l, t.Status) if st == nil { From cab492278cac3abf6e148b1780140adda7ba5a3f Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Fri, 27 Aug 2021 16:02:14 +0100 Subject: [PATCH 09/27] delete ticket file from load package --- ui/load/ticket.go | 169 ----------------------- ui/page/tickets/list_page.go | 1 + ui/page/tickets/overview.go | 4 +- ui/page/tickets/purchase_review_modal.go | 6 +- 4 files changed, 6 insertions(+), 174 deletions(-) delete mode 100644 ui/load/ticket.go diff --git a/ui/load/ticket.go b/ui/load/ticket.go deleted file mode 100644 index 008630058..000000000 --- a/ui/load/ticket.go +++ /dev/null @@ -1,169 +0,0 @@ -package load -// -//import ( -// "fmt" -// "math" -// "sort" -// "time" -// -// "github.com/decred/dcrd/dcrutil" -// "github.com/planetdecred/dcrlibwallet" -//) -// -//type Ticket struct { -// Status string -// Fee string -// Amount string -// DateTime string -// MonthDay string -// DaysBehind string -// WalletName string -// -// timestamp int64 -//} -// -//const ( -// StakingLive = "LIVE" -// StakingImmature = "IMMATURE" -// StakingExpired = "EXPIRED" -// StakingRevoked = "REVOKED" -// StakingVoted = "VOTED" -//) -// -//func (wl *WalletLoad) StakingOverviewAllWallets() *dcrlibwallet.StakingOverview { -// overview := new(dcrlibwallet.StakingOverview) -// for _, w := range wl.MultiWallet.AllWallets() { -// ov, _ := w.StakingOverview() -// overview.All += ov.All -// overview.Expired += ov.Expired -// overview.Immature += ov.Immature -// overview.Live += ov.Live -// overview.Revoked += ov.Revoked -// overview.Voted += ov.Voted -// } -// -// wl.MultiWallet.GetLowestBlockTimestamp() -// return overview -//} -// -//func calculateDaysBehind(lastHeaderTime int64) string { -// diff := time.Since(time.Unix(lastHeaderTime, 0)) -// daysBehind := int(math.Round(diff.Hours() / 24)) -// if daysBehind < 1 { -// return "<1 day" -// } else if daysBehind == 1 { -// return "1 day" -// } else { -// return fmt.Sprintf("%d days", daysBehind) -// } -//} -// -//func filterToStatus(txFilter int32) string { -// switch txFilter { -// case dcrlibwallet.TxFilterImmature: -// return StakingImmature -// case dcrlibwallet.TxFilterLive: -// return StakingLive -// case dcrlibwallet.TxFilterExpired: -// return StakingExpired -// case dcrlibwallet.TxFilterRevoked: -// return StakingRevoked -// case dcrlibwallet.TxFilterVoted: -// return StakingVoted -// } -// -// return "" -//} -// -//func transactionToTicket(tx dcrlibwallet.Transaction, status, walletName string) Ticket { -// return Ticket{ -// Status: status, -// Amount: dcrutil.Amount(tx.Amount).String(), -// DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), -// MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), -// DaysBehind: calculateDaysBehind(tx.Timestamp), -// Fee: dcrutil.Amount(tx.Fee).String(), -// WalletName: walletName, -// timestamp: tx.Timestamp, -// } -//} -// -//func transactionsToTickets(txs []dcrlibwallet.Transaction, status, walletName string) []Ticket { -// var tickets []Ticket -// for _, tx := range txs { -// tickets = append(tickets, transactionToTicket(tx, status, walletName)) -// } -// -// return tickets -//} -// -//func (wl *WalletLoad) getAllTickets(walletID int, newestFirst bool) ([]Ticket, error) { -// w := wl.MultiWallet.WalletWithID(walletID) -// var tickets []Ticket -// -// addTickets := func(txFilter int32, newestFirst bool) error { -// txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) -// if err != nil { -// return err -// } -// -// tickets = append(tickets, transactionsToTickets(txs, filterToStatus(txFilter), w.Name)...) -// return nil -// } -// -// filters := []int32{ -// dcrlibwallet.TxFilterImmature, -// dcrlibwallet.TxFilterLive, -// dcrlibwallet.TxFilterVoted, -// dcrlibwallet.TxFilterExpired, -// dcrlibwallet.TxFilterRevoked, -// } -// -// for _, filter := range filters { -// err := addTickets(filter, newestFirst) -// if err != nil { -// return nil, err -// } -// } -// -// sort.SliceStable(tickets, func(i, j int) bool { -// if newestFirst { -// return tickets[i].timestamp > tickets[j].timestamp -// } -// return tickets[i].timestamp < tickets[j].timestamp -// }) -// -// return tickets, nil -//} -// -//func (wl *WalletLoad) GetTickets(walletID int, txFilter int32, newestFirst bool) ([]Ticket, error) { -// if txFilter == dcrlibwallet.TxFilterStaking { -// return wl.getAllTickets(walletID, newestFirst) -// } -// -// w := wl.MultiWallet.WalletWithID(walletID) -// txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) -// if err != nil { -// return nil, err -// } -// -// return transactionsToTickets(txs, filterToStatus(txFilter), w.Name), nil -//} -// -//func (wl *WalletLoad) AllLiveTickets() ([]Ticket, error) { -// var tickets []Ticket -// wallets := wl.MultiWallet.AllWallets() -// -// liveTicketFilters := []int32{dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} -// for _, w := range wallets { -// for _, filter := range liveTicketFilters { -// tx, err := w.GetTransactionsRaw(0, 0, filter, true) -// if err != nil { -// return tickets, err -// } -// tickets = append(tickets, transactionsToTickets(tx, filterToStatus(filter), w.Name)...) -// } -// } -// -// return tickets, nil -//} diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index b8f5a3339..8d1bbd49e 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -7,6 +7,7 @@ import ( "gioui.org/layout" "gioui.org/text" "gioui.org/widget" + "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 4ff568ab6..5abdbd8dd 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -2,13 +2,13 @@ package tickets import ( "fmt" - "github.com/decred/dcrd/dcrutil" "strings" "gioui.org/layout" "gioui.org/unit" "gioui.org/widget" + "github.com/decred/dcrd/dcrutil" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" @@ -45,7 +45,7 @@ type Page struct { func NewTicketPage(l *load.Load) *Page { pg := &Page{ - Load: l, + Load: l, ticketsLive: &layout.List{Axis: layout.Horizontal}, ticketsActivity: &layout.List{Axis: layout.Vertical}, diff --git a/ui/page/tickets/purchase_review_modal.go b/ui/page/tickets/purchase_review_modal.go index 75994190f..39371ae65 100644 --- a/ui/page/tickets/purchase_review_modal.go +++ b/ui/page/tickets/purchase_review_modal.go @@ -4,14 +4,14 @@ import ( "fmt" "image/color" - "github.com/planetdecred/godcr/ui/load" - "github.com/planetdecred/godcr/ui/page/components" - "gioui.org/layout" "gioui.org/widget" + "github.com/decred/dcrd/dcrutil" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" + "github.com/planetdecred/godcr/ui/load" + "github.com/planetdecred/godcr/ui/page/components" "github.com/planetdecred/godcr/ui/values" ) From 963179a13b64d2eb23d5113911cbe596b2dd3ac6 Mon Sep 17 00:00:00 2001 From: Osho Klinsmann Date: Mon, 30 Aug 2021 15:54:10 +0100 Subject: [PATCH 10/27] move tooltip initialization to onResume --- ui/page/tickets/list_page.go | 46 ++++++++++-------------------------- ui/page/tickets/overview.go | 22 ++++++++--------- ui/page/tickets/utils.go | 18 +++++++------- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 8d1bbd49e..801878b13 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -128,11 +128,19 @@ func (pg *ListPage) fetchTickets() { } else { pg.tickets = txs } + + for range pg.tickets { + pg.ticketTooltips = append(pg.ticketTooltips, tooltips{ + statusTooltip: pg.Load.Theme.Tooltip(), + walletNameTooltip: pg.Load.Theme.Tooltip(), + dateTooltip: pg.Load.Theme.Tooltip(), + daysBehindTooltip: pg.Load.Theme.Tooltip(), + durationTooltip: pg.Load.Theme.Tooltip(), + }) + } } func (pg *ListPage) Layout(gtx C) D { - tickets := pg.tickets - body := func(gtx C) D { page := components.SubPage{ Load: pg.Load, @@ -142,15 +150,6 @@ func (pg *ListPage) Layout(gtx C) D { pg.PopFragment() }, Body: func(gtx C) D { - for range tickets { - pg.ticketTooltips = append(pg.ticketTooltips, tooltips{ - statusTooltip: pg.Load.Theme.Tooltip(), - walletNameTooltip: pg.Load.Theme.Tooltip(), - dateTooltip: pg.Load.Theme.Tooltip(), - daysBehindTooltip: pg.Load.Theme.Tooltip(), - durationTooltip: pg.Load.Theme.Tooltip(), - }) - } return layout.Stack{Alignment: layout.N}.Layout(gtx, layout.Expanded(func(gtx C) D { return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { @@ -236,27 +235,6 @@ func (pg *ListPage) Layout(gtx C) D { return components.UniformPadding(gtx, body) } -func (pg *ListPage) dropDowns(gtx layout.Context) layout.Dimensions { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, - layout.Rigid(pg.walletDropDown.Layout), - layout.Rigid(func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding5, - }.Layout(gtx, pg.ticketTypeDropDown.Layout) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding5, - }.Layout(gtx, pg.orderDropDown.Layout) - }), - ) - }), - ) -} - func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []dcrlibwallet.Transaction) layout.Dimensions { return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { w := pg.WL.MultiWallet.WalletWithID(tickets[index].WalletID) @@ -367,9 +345,9 @@ func (pg *ListPage) ticketListGridLayout(gtx C, tickets []dcrlibwallet.Transacti Right: values.MarginPadding4, Bottom: values.MarginPadding8, }.Layout(gtx, func(gtx C) D { - //selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] + selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] // return ticketCard(gtx, pg.Load, selectedWallet, tickets[index], pg.statusTooltips[index]) - return ticketCard(gtx, pg.Load, &tickets[index], pg.ticketTooltips[index]) + return ticketCard(gtx, pg.Load, selectedWallet, tickets[index], pg.ticketTooltips[index]) }) }) }) diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 5abdbd8dd..23a524bcd 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -41,6 +41,7 @@ type Page struct { stakingOverview *dcrlibwallet.StakingOverview liveTickets []dcrlibwallet.Transaction + tickets []dcrlibwallet.Transaction } func NewTicketPage(l *load.Load) *Page { @@ -86,6 +87,16 @@ func (pg *Page) OnResume() { go func() { mw := pg.WL.MultiWallet pg.liveTickets = allLiveTickets(mw.AllWallets()) + + for range pg.liveTickets { + pg.ticketTooltips = append(pg.ticketTooltips, tooltips{ + statusTooltip: pg.Load.Theme.Tooltip(), + walletNameTooltip: pg.Load.Theme.Tooltip(), + dateTooltip: pg.Load.Theme.Tooltip(), + daysBehindTooltip: pg.Load.Theme.Tooltip(), + durationTooltip: pg.Load.Theme.Tooltip(), + }) + } }() go pg.WL.GetVSPList() // TODO: automatic ticket purchase functionality @@ -244,17 +255,6 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { }) }), layout.Rigid(func(gtx C) D { - tickets := (*pg.tickets).LiveRecent - for range tickets { - pg.ticketTooltips = append(pg.ticketTooltips, tooltips{ - statusTooltip: pg.Load.Theme.Tooltip(), - walletNameTooltip: pg.Load.Theme.Tooltip(), - dateTooltip: pg.Load.Theme.Tooltip(), - daysBehindTooltip: pg.Load.Theme.Tooltip(), - durationTooltip: pg.Load.Theme.Tooltip(), - }) - } - return pg.ticketsLive.Layout(gtx, len(pg.liveTickets), func(gtx C, index int) D { return layout.Inset{Right: values.MarginPadding8}.Layout(gtx, func(gtx C) D { w := pg.WL.MultiWallet.WalletWithID(pg.liveTickets[index].WalletID) diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index c724b7bdf..cf0545721 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -18,7 +18,6 @@ import ( "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" "github.com/planetdecred/godcr/ui/values" - "github.com/planetdecred/godcr/wallet" ) const ( @@ -224,10 +223,10 @@ func setText(t string) { } } -func ticketStatusTooltip(gtx C, l *load.Load, t *wallet.Ticket) layout.Dimensions { - setText(t.Info.Status) - st := ticketStatusIcon(l, t.Info.Status) - status := l.Theme.Body2(t.Info.Status) +func ticketStatusTooltip(gtx C, l *load.Load, t Ticket) layout.Dimensions { + setText(t.Status) + st := ticketStatusProfile(l, t.Status) + status := l.Theme.Body2(t.Status) status.Color = st.color titleLabel, mainMsgLabel, mainMsgLabel2 := l.Theme.Body2(title), l.Theme.Body2(mainMsg), l.Theme.Body2(mainMsgDesc) @@ -284,14 +283,13 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { } // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid -func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.Wallet, tx dcrlibwallet.Transaction, tooltip *decredmaterial.Tooltip) layout.Dimensions { +func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.Wallet, tx dcrlibwallet.Transaction, tooltip tooltips) layout.Dimensions { t := transactionToTicket(tx, selectedWallet, l.WL.MultiWallet.TicketMaturity(), l.WL.MultiWallet.GetBestBlock().Height) var itemWidth int st := ticketStatusProfile(l, t.Status) if st == nil { return layout.Dimensions{} } - return l.Theme.Shadow().Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -314,7 +312,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W txt := l.Theme.Label(values.TextSize14, "10h 47m") txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tooltip.durationTooltip, func(gtx C) D { - setText(t.Info.Status) + setText(t.Status) return walletNameDateTimeTooltip(gtx, l, durationTitle, toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(durationMsg).Layout)) }) @@ -376,7 +374,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W txt.Color = st.color txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tooltip.statusTooltip, func(gtx C) D { - setText(t.Info.Status) + setText(t.Status) return ticketStatusTooltip(gtx, l, t) }) return txtLayout @@ -439,7 +437,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W txt.Text = t.DaysBehind txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tooltip.daysBehindTooltip, func(gtx C) D { - setText(t.Info.Status) + setText(t.Status) return walletNameDateTimeTooltip(gtx, l, dayBehind, toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(t.DaysBehind).Layout)) }) From 5d44591514ea74bbbcd6de1751a0bf1703add459 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Fri, 10 Sep 2021 13:10:55 +0100 Subject: [PATCH 11/27] Cleanup tickets overview page - display correct tickets reward value --- ui/page/tickets/overview.go | 275 ++++++++++++++++-------------------- 1 file changed, 119 insertions(+), 156 deletions(-) diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 23a524bcd..fe7e9f2b1 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -2,10 +2,8 @@ package tickets import ( "fmt" - "strings" "gioui.org/layout" - "gioui.org/unit" "gioui.org/widget" "github.com/decred/dcrd/dcrutil" @@ -32,16 +30,15 @@ type Page struct { purchaseTicket decredmaterial.Button - ticketPrice string + ticketPrice string + totalRewards string autoPurchaseEnabled *decredmaterial.Switch toTickets decredmaterial.TextAndIconButton - toTicketsActivity decredmaterial.TextAndIconButton ticketTooltips []tooltips stakingOverview *dcrlibwallet.StakingOverview liveTickets []dcrlibwallet.Transaction - tickets []dcrlibwallet.Transaction } func NewTicketPage(l *load.Load) *Page { @@ -55,7 +52,6 @@ func NewTicketPage(l *load.Load) *Page { autoPurchaseEnabled: l.Theme.Switch(), toTickets: l.Theme.TextAndIconButton(new(widget.Clickable), "See All", l.Icons.NavigationArrowForward), - toTicketsActivity: l.Theme.TextAndIconButton(new(widget.Clickable), "See All", l.Icons.NavigationArrowForward), } pg.purchaseTicket.TextSize = values.TextSize12 @@ -64,9 +60,6 @@ func NewTicketPage(l *load.Load) *Page { pg.toTickets.Color = l.Theme.Color.Primary pg.toTickets.BackgroundColor = l.Theme.Color.Surface - pg.toTicketsActivity.Color = l.Theme.Color.Primary - pg.toTicketsActivity.BackgroundColor = l.Theme.Color.Surface - pg.stakingOverview = new(dcrlibwallet.StakingOverview) return pg } @@ -76,14 +69,34 @@ func (pg *Page) ID() string { } func (pg *Page) OnResume() { - pg.ticketPrice = dcrutil.Amount(pg.WL.TicketPrice()).String() + + go func() { + ticketPrice, err := pg.WL.MultiWallet.TicketPrice() + if err != nil { + pg.Toast.NotifyError(err.Error()) + } else { + pg.ticketPrice = dcrutil.Amount(ticketPrice.TicketPrice).String() + } + }() + + go func() { + totalRewards, err := pg.WL.MultiWallet.TotalStakingRewards() + if err != nil { + pg.Toast.NotifyError(err.Error()) + } else { + pg.totalRewards = dcrutil.Amount(totalRewards).String() + } + }() + go func() { overview, err := pg.WL.MultiWallet.StakingOverview() if err != nil { pg.Toast.NotifyError(err.Error()) + } else { + pg.stakingOverview = overview } - pg.stakingOverview = overview }() + go func() { mw := pg.WL.MultiWallet pg.liveTickets = allLiveTickets(mw.AllWallets()) @@ -98,6 +111,7 @@ func (pg *Page) OnResume() { }) } }() + go pg.WL.GetVSPList() // TODO: automatic ticket purchase functionality pg.autoPurchaseEnabled.Disabled() @@ -112,14 +126,10 @@ func (pg *Page) Layout(gtx layout.Context) layout.Dimensions { func(ctx layout.Context) layout.Dimensions { return pg.ticketsLiveSection(gtx) }, - //func(ctx layout.Context) layout.Dimensions { - // return pg.ticketsActivitySection(gtx) - //}, func(ctx layout.Context) layout.Dimensions { return pg.stakingRecordSection(gtx) }, } - return pg.ticketPageContainer.Layout(gtx, len(sections), func(gtx C, i int) D { return sections[i](gtx) }) @@ -200,57 +210,19 @@ func (pg *Page) ticketPriceSection(gtx layout.Context) layout.Dimensions { }) } -func (pg *Page) stakingCounts() []struct { - Status string - Count int -} { - return []struct { - Status string - Count int - }{ - {"IMMATURE", pg.stakingOverview.Immature}, - {"LIVE", pg.stakingOverview.Live}, - {"VOTED", pg.stakingOverview.Voted}, - {"REVOKED", pg.stakingOverview.Revoked}, - } -} - func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { return pg.pageSections(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Inset{Bottom: values.MarginPadding14}.Layout(gtx, func(gtx C) D { title := pg.Theme.Label(values.TextSize14, "Live Tickets") - title.Color = pg.Theme.Color.Gray2 + title.Color = pg.Theme.Color.Gray return pg.titleRow(gtx, title.Layout, func(gtx C) D { - var elements []layout.FlexChild - for i := 0; i < len(pg.stakingCounts()); i++ { - item := pg.stakingCounts()[i] - if item.Status == StakingLive || item.Status == StakingImmature { - elements = append(elements, layout.Rigid(func(gtx C) D { - return layout.Inset{Right: values.MarginPadding14}.Layout(gtx, func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - st := ticketStatusProfile(pg.Load, item.Status) - if st == nil { - return layout.Dimensions{} - } - return st.icon.Layout16dp(gtx) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{Left: values.MarginPadding4}.Layout(gtx, func(gtx C) D { - label := pg.Theme.Label(values.TextSize14, fmt.Sprintf("%d", item.Count)) - label.Color = pg.Theme.Color.DeepBlue - return label.Layout(gtx) - }) - }), - ) - }) - })) - } - } - elements = append(elements, layout.Rigid(pg.toTickets.Layout)) - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, elements...) + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + pg.stakingCountIcon(gtx, pg.Icons.TicketImmatureIcon, pg.stakingOverview.Immature), + pg.stakingCountIcon(gtx, pg.Icons.TicketLiveIcon, pg.stakingOverview.Live), + layout.Rigid(pg.toTickets.Layout), + ) }) }) }), @@ -266,33 +238,29 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { }) } -//func (pg *Page) ticketsActivitySection(gtx layout.Context) layout.Dimensions { -// //tickets := (*pg.tickets).RecentActivity -// if len(tickets) == 0 { -// return layout.Dimensions{} -// } -// -// return pg.pageSections(gtx, func(gtx C) D { -// return layout.Flex{Axis: layout.Vertical}.Layout(gtx, -// layout.Rigid(func(gtx C) D { -// return layout.Inset{ -// Bottom: values.MarginPadding14, -// }.Layout(gtx, func(gtx C) D { -// title := pg.Theme.Label(values.TextSize14, "Recent Activity") -// title.Color = pg.Theme.Color.Gray2 -// return pg.titleRow(gtx, title.Layout, pg.toTicketsActivity.Layout) -// }) -// }), -// layout.Rigid(func(gtx C) D { -// return pg.ticketsActivity.Layout(gtx, len(tickets), func(gtx C, index int) D { -// return ticketActivityRow(gtx, pg.Load, tickets[index], index) -// }) -// }), -// ) -// }) -//} - -func (pg *Page) stakingRecordSection(gtx layout.Context) layout.Dimensions { +func (pg *Page) stakingCountIcon(gtx C, icon *decredmaterial.Image, count int) layout.FlexChild { + return layout.Rigid(func(gtx C) D { + if count == 0 { + return D{} + } + return layout.Inset{Right: values.MarginPadding14}.Layout(gtx, func(gtx C) D { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return icon.Layout16dp(gtx) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{Left: values.MarginPadding4}.Layout(gtx, func(gtx C) D { + label := pg.Theme.Label(values.TextSize14, fmt.Sprintf("%d", count)) + label.Color = pg.Theme.Color.DeepBlue + return label.Layout(gtx) + }) + }), + ) + }) + }) +} + +func (pg *Page) stakingRecordSection(gtx C) D { return pg.pageSections(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -301,88 +269,87 @@ func (pg *Page) stakingRecordSection(gtx layout.Context) layout.Dimensions { }.Layout(gtx, func(gtx C) D { title := pg.Theme.Label(values.TextSize14, "Staking Record") title.Color = pg.Theme.Color.Gray2 - return pg.titleRow(gtx, title.Layout, func(gtx C) D { return layout.Dimensions{} }) + return pg.titleRow(gtx, title.Layout, func(gtx C) D { return D{} }) }) }), layout.Rigid(func(gtx C) D { - counts := pg.stakingCounts() + wdgs := []layout.Widget{ + pg.stakingRecordIconCount(pg.Icons.TicketImmatureIcon, pg.stakingOverview.Immature, "Immature"), + pg.stakingRecordIconCount(pg.Icons.TicketLiveIcon, pg.stakingOverview.Live, "Live"), + pg.stakingRecordIconCount(pg.Icons.TicketVotedIcon, pg.stakingOverview.Voted, "Voted"), + pg.stakingRecordIconCount(pg.Icons.TicketExpiredIcon, pg.stakingOverview.Expired, "Expired"), + pg.stakingRecordIconCount(pg.Icons.TicketRevokedIcon, pg.stakingOverview.Revoked, "Revoked"), + } + return decredmaterial.GridWrap{ Axis: layout.Horizontal, Alignment: layout.End, - }.Layout(gtx, len(counts), func(gtx layout.Context, i int) layout.Dimensions { - count := counts[i] - width := unit.Value{U: unit.UnitDp, V: 118} - gtx.Constraints.Min.X = gtx.Px(width) - - return layout.Inset{Bottom: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - return layout.Flex{}.Layout(gtx, + }.Layout(gtx, len(wdgs), func(gtx C, i int) D { + return wdgs[i](gtx) + }) + }), + layout.Rigid(func(gtx C) D { + return decredmaterial.LinearLayout{ + Width: decredmaterial.MatchParent, + Height: decredmaterial.WrapContent, + Background: pg.Theme.Color.Success2, + Padding: layout.Inset{Top: values.MarginPadding16, Bottom: values.MarginPadding16}, + Border: decredmaterial.Border{Radius: decredmaterial.Radius(8)}, + Direction: layout.Center, + Orientation: layout.Vertical, + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{Bottom: values.MarginPadding4}.Layout(gtx, func(gtx C) D { + txt := pg.Theme.Label(values.TextSize14, "Rewards Earned") + txt.Color = pg.Theme.Color.Success + return txt.Layout(gtx) + }) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - st := ticketStatusProfile(pg.Load, count.Status) - if st == nil { - return layout.Dimensions{} - } - return st.icon.Layout24dp(gtx) + ic := pg.Icons.StakeyIcon + return ic.Layout24dp(gtx) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Left: values.MarginPadding4}.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - label := pg.Theme.Label(values.TextSize16, fmt.Sprintf("%d", count.Count)) - label.Color = pg.Theme.Color.DeepBlue - return label.Layout(gtx) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{Right: values.MarginPadding40}.Layout(gtx, func(gtx C) D { - txt := pg.Theme.Label(values.TextSize12, strings.Title(strings.ToLower(count.Status))) - txt.Color = pg.Theme.Color.Gray2 - return txt.Layout(gtx) - }) - }), - ) - }) + return components.LayoutBalance(gtx, pg.Load, pg.totalRewards) }), ) - }) - }) - }), - layout.Rigid(func(gtx C) D { - wrapper := pg.Theme.Card() - wrapper.Color = pg.Theme.Color.Success2 - return wrapper.Layout(gtx, func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.Center.Layout(gtx, func(gtx C) D { - return layout.Inset{ - Top: values.MarginPadding16, - Bottom: values.MarginPadding16, - }.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{Bottom: values.MarginPadding4}.Layout(gtx, func(gtx C) D { - txt := pg.Theme.Label(values.TextSize14, "Rewards Earned") - txt.Color = pg.Theme.Color.Success - return txt.Layout(gtx) - }) - }), - layout.Rigid(func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - ic := pg.Icons.StakeyIcon - return ic.Layout24dp(gtx) - }), - layout.Rigid(func(gtx C) D { - return components.LayoutBalance(gtx, pg.Load, "16.5112316") - }), - ) - }), - ) - }) - }) - }) + }), + ) }), ) }) } +func (pg *Page) stakingRecordIconCount(icon *decredmaterial.Image, count int, status string) layout.Widget { + return func(gtx C) D { + return layout.Inset{Bottom: values.MarginPadding16, Right: values.MarginPadding40}.Layout(gtx, func(gtx C) D { + return layout.Flex{}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return icon.Layout24dp(gtx) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{Left: values.MarginPadding4}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx C) D { + label := pg.Theme.Label(values.TextSize16, fmt.Sprintf("%d", count)) + label.Color = pg.Theme.Color.DeepBlue + return label.Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + txt := pg.Theme.Label(values.TextSize12, status) + txt.Color = pg.Theme.Color.Gray + return txt.Layout(gtx) + }), + ) + }) + }), + ) + }) + } +} + func (pg *Page) Handle() { if pg.purchaseTicket.Button.Clicked() { newTicketPurchaseModal(pg.Load). @@ -392,10 +359,6 @@ func (pg *Page) Handle() { if pg.toTickets.Button.Clicked() { pg.ChangeFragment(newListPage(pg.Load)) } - - if pg.toTicketsActivity.Button.Clicked() { - pg.ChangeFragment(newTicketActivityPage(pg.Load)) - } } func (pg *Page) OnClose() {} From 8e0bec5f89405055189ba798b9a36249ed6a610a Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Fri, 10 Sep 2021 15:59:41 +0100 Subject: [PATCH 12/27] Fix only tickets purchase tx showing as 'All' in tickets list page - add transactionItem to wrap ticket transaction and tooltips --- go.mod | 2 +- go.sum | 2 ++ ui/page/tickets/list_page.go | 55 ++++++++++++++++++++---------------- ui/page/tickets/overview.go | 30 +++++++++++--------- ui/page/tickets/utils.go | 26 +++++++++-------- 5 files changed, 64 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index adbcce602..88d8c3902 100644 --- a/go.mod +++ b/go.mod @@ -31,5 +31,5 @@ require ( replace ( github.com/decred/dcrdata/txhelpers/v4 => github.com/decred/dcrdata/txhelpers/v4 v4.0.0-20200108145420-f82113e7e212 - github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e + github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2 ) diff --git a/go.sum b/go.sum index f413bd6e1..5cd9c7718 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e h1:x6AYXRKp/C56x6lsUEUQ6+QhBM9xO07KXt2kBnePaDs= github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2 h1:EHZLz82ivykxAkm6BI15FBrPL/PdUsUD2gtrb6pnFpY= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.4.8 h1:Rpmta4xZ/MgZnriKNd24iZMhGpP5dvUcs/uqfBapKZY= github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 801878b13..143e6c637 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -24,10 +24,9 @@ type ListPage struct { ctx context.Context // page context ctxCancel context.CancelFunc - tickets []dcrlibwallet.Transaction - ticketsList layout.List - filterSorter int - isGridView bool + tickets []*transactionItem + ticketsList layout.List + isGridView bool toggleViewType *widget.Clickable @@ -36,8 +35,7 @@ type ListPage struct { walletDropDown *decredmaterial.DropDown backButton decredmaterial.IconButton - ticketTooltips []tooltips - wallets []*dcrlibwallet.Wallet + wallets []*dcrlibwallet.Wallet } func newListPage(l *load.Load) *ListPage { @@ -101,8 +99,6 @@ func (pg *ListPage) listenForTxNotifications() { func (pg *ListPage) fetchTickets() { var txFilter int32 switch pg.ticketTypeDropDown.SelectedIndex() { - case 0: - txFilter = dcrlibwallet.TxFilterTickets case 1: txFilter = dcrlibwallet.TxFilterUnmined case 2: @@ -116,7 +112,7 @@ func (pg *ListPage) fetchTickets() { case 6: txFilter = dcrlibwallet.TxFilterRevoked default: - return + txFilter = dcrlibwallet.TxFilterStaking } newestFirst := pg.orderDropDown.SelectedIndex() == 0 @@ -125,19 +121,23 @@ func (pg *ListPage) fetchTickets() { txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) if err != nil { pg.Toast.NotifyError(err.Error()) - } else { - pg.tickets = txs + return } - for range pg.tickets { - pg.ticketTooltips = append(pg.ticketTooltips, tooltips{ + tickets := make([]*transactionItem, len(txs)) + for i := range txs { + tickets[i] = &transactionItem{ + transaction: &txs[i], + statusTooltip: pg.Load.Theme.Tooltip(), walletNameTooltip: pg.Load.Theme.Tooltip(), dateTooltip: pg.Load.Theme.Tooltip(), daysBehindTooltip: pg.Load.Theme.Tooltip(), durationTooltip: pg.Load.Theme.Tooltip(), - }) + } } + + pg.tickets = tickets } func (pg *ListPage) Layout(gtx C) D { @@ -152,20 +152,25 @@ func (pg *ListPage) Layout(gtx C) D { Body: func(gtx C) D { return layout.Stack{Alignment: layout.N}.Layout(gtx, layout.Expanded(func(gtx C) D { + return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { return pg.Theme.Card().Layout(gtx, func(gtx C) D { - gtx.Constraints.Min = gtx.Constraints.Max - if len(pg.tickets) == 0 { + tickets := pg.tickets + + if len(tickets) == 0 { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + txt := pg.Theme.Body1("No tickets yet") txt.Color = pg.Theme.Color.Gray2 txt.Alignment = text.Middle - return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, txt.Layout) + return layout.Inset{Top: values.MarginPadding15, Bottom: values.MarginPadding16}.Layout(gtx, txt.Layout) } + return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { if pg.isGridView { - return pg.ticketListGridLayout(gtx, pg.tickets) + return pg.ticketListGridLayout(gtx, tickets) } - return pg.ticketListLayout(gtx, pg.tickets) + return pg.ticketListLayout(gtx, tickets) }) }) }) @@ -235,10 +240,11 @@ func (pg *ListPage) Layout(gtx C) D { return components.UniformPadding(gtx, body) } -func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []dcrlibwallet.Transaction) layout.Dimensions { +func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []*transactionItem) layout.Dimensions { + gtx.Constraints.Min = gtx.Constraints.Max return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { - w := pg.WL.MultiWallet.WalletWithID(tickets[index].WalletID) - t := transactionToTicket(tickets[index], w, pg.WL.MultiWallet.TicketMaturity(), pg.WL.MultiWallet.GetBestBlock().Height) + w := pg.WL.MultiWallet.WalletWithID(tickets[index].transaction.WalletID) + t := transactionToTicket(*tickets[index].transaction, w, pg.WL.MultiWallet.TicketMaturity(), pg.WL.MultiWallet.TicketExpiry(), pg.WL.MultiWallet.GetBestBlock().Height) st := ticketStatusProfile(pg.Load, t.Status) if st == nil { return D{} @@ -330,7 +336,7 @@ func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []dcrlibwallet. }) } -func (pg *ListPage) ticketListGridLayout(gtx C, tickets []dcrlibwallet.Transaction) D { +func (pg *ListPage) ticketListGridLayout(gtx layout.Context, tickets []*transactionItem) layout.Dimensions { // TODO: GridWrap's items not able to scroll vertically, will update when it fixed return layout.Center.Layout(gtx, func(gtx C) D { return pg.ticketsList.Layout(gtx, 1, func(gtx C, index int) D { @@ -346,8 +352,7 @@ func (pg *ListPage) ticketListGridLayout(gtx C, tickets []dcrlibwallet.Transacti Bottom: values.MarginPadding8, }.Layout(gtx, func(gtx C) D { selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] - // return ticketCard(gtx, pg.Load, selectedWallet, tickets[index], pg.statusTooltips[index]) - return ticketCard(gtx, pg.Load, selectedWallet, tickets[index], pg.ticketTooltips[index]) + return ticketCard(gtx, pg.Load, selectedWallet, tickets[index]) }) }) }) diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index fe7e9f2b1..0c640962a 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -26,7 +26,6 @@ type Page struct { ticketPageContainer *layout.List ticketsLive *layout.List - ticketsActivity *layout.List purchaseTicket decredmaterial.Button @@ -35,10 +34,9 @@ type Page struct { autoPurchaseEnabled *decredmaterial.Switch toTickets decredmaterial.TextAndIconButton - ticketTooltips []tooltips stakingOverview *dcrlibwallet.StakingOverview - liveTickets []dcrlibwallet.Transaction + liveTickets []*transactionItem } func NewTicketPage(l *load.Load) *Page { @@ -46,7 +44,6 @@ func NewTicketPage(l *load.Load) *Page { Load: l, ticketsLive: &layout.List{Axis: layout.Horizontal}, - ticketsActivity: &layout.List{Axis: layout.Vertical}, ticketPageContainer: &layout.List{Axis: layout.Vertical}, purchaseTicket: l.Theme.Button(new(widget.Clickable), "Purchase"), @@ -99,17 +96,22 @@ func (pg *Page) OnResume() { go func() { mw := pg.WL.MultiWallet - pg.liveTickets = allLiveTickets(mw.AllWallets()) + tickets := allLiveTickets(mw.AllWallets()) + + txItems := make([]*transactionItem, len(tickets)) + for i := range tickets { + txItems[i] = &transactionItem{ + transaction: &tickets[i], - for range pg.liveTickets { - pg.ticketTooltips = append(pg.ticketTooltips, tooltips{ statusTooltip: pg.Load.Theme.Tooltip(), walletNameTooltip: pg.Load.Theme.Tooltip(), dateTooltip: pg.Load.Theme.Tooltip(), daysBehindTooltip: pg.Load.Theme.Tooltip(), durationTooltip: pg.Load.Theme.Tooltip(), - }) + } } + + pg.liveTickets = txItems }() go pg.WL.GetVSPList() @@ -219,8 +221,9 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { title.Color = pg.Theme.Color.Gray return pg.titleRow(gtx, title.Layout, func(gtx C) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - pg.stakingCountIcon(gtx, pg.Icons.TicketImmatureIcon, pg.stakingOverview.Immature), - pg.stakingCountIcon(gtx, pg.Icons.TicketLiveIcon, pg.stakingOverview.Live), + pg.stakingCountIcon(pg.Icons.TicketUnminedIcon, pg.stakingOverview.Unmined), + pg.stakingCountIcon(pg.Icons.TicketImmatureIcon, pg.stakingOverview.Immature), + pg.stakingCountIcon(pg.Icons.TicketLiveIcon, pg.stakingOverview.Live), layout.Rigid(pg.toTickets.Layout), ) }) @@ -229,8 +232,8 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx C) D { return pg.ticketsLive.Layout(gtx, len(pg.liveTickets), func(gtx C, index int) D { return layout.Inset{Right: values.MarginPadding8}.Layout(gtx, func(gtx C) D { - w := pg.WL.MultiWallet.WalletWithID(pg.liveTickets[index].WalletID) - return ticketCard(gtx, pg.Load, w, pg.liveTickets[index], pg.ticketTooltips[index]) + w := pg.WL.MultiWallet.WalletWithID(pg.liveTickets[index].transaction.WalletID) + return ticketCard(gtx, pg.Load, w, pg.liveTickets[index]) }) }) }), @@ -238,7 +241,7 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { }) } -func (pg *Page) stakingCountIcon(gtx C, icon *decredmaterial.Image, count int) layout.FlexChild { +func (pg *Page) stakingCountIcon(icon *decredmaterial.Image, count int) layout.FlexChild { return layout.Rigid(func(gtx C) D { if count == 0 { return D{} @@ -274,6 +277,7 @@ func (pg *Page) stakingRecordSection(gtx C) D { }), layout.Rigid(func(gtx C) D { wdgs := []layout.Widget{ + pg.stakingRecordIconCount(pg.Icons.TicketUnminedIcon, pg.stakingOverview.Unmined, "Unmined"), pg.stakingRecordIconCount(pg.Icons.TicketImmatureIcon, pg.stakingOverview.Immature, "Immature"), pg.stakingRecordIconCount(pg.Icons.TicketLiveIcon, pg.stakingOverview.Live, "Live"), pg.stakingRecordIconCount(pg.Icons.TicketVotedIcon, pg.stakingOverview.Voted, "Voted"), diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index cf0545721..81a435c66 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -28,7 +28,9 @@ const ( durationMsg = "10 hrs 47 mins (118/256 blocks)" ) -type tooltips struct { +type transactionItem struct { + transaction *dcrlibwallet.Transaction + statusTooltip *decredmaterial.Tooltip walletNameTooltip *decredmaterial.Tooltip dateTooltip *decredmaterial.Tooltip @@ -76,9 +78,9 @@ func calculateDaysBehind(lastHeaderTime int64) string { } } -func transactionToTicket(tx dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, maturity, bestBlock int32) Ticket { +func transactionToTicket(tx dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, maturity, expiry, bestBlock int32) Ticket { return Ticket{ - Status: getTicketStatus(tx, w, maturity, bestBlock), + Status: getTicketStatus(tx, w, maturity, expiry, bestBlock), Amount: dcrutil.Amount(tx.Amount).String(), DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), @@ -88,7 +90,7 @@ func transactionToTicket(tx dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, ma } } -func getTicketStatus(txn dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, ticketMaturity, bestBlock int32) string { +func getTicketStatus(txn dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, ticketMaturity, ticketExpiry, bestBlock int32) string { if txn.Type == dcrlibwallet.TxTypeVote { return StakingVoted } @@ -97,7 +99,7 @@ func getTicketStatus(txn dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, ticke return StakingRevoked } - s := txn.TicketStatus(ticketMaturity, bestBlock) + s := txn.TicketStatus(ticketMaturity, ticketExpiry, bestBlock) switch s { case dcrlibwallet.TicketStatusUnmined: return StakingUnmined @@ -283,8 +285,8 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { } // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid -func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.Wallet, tx dcrlibwallet.Transaction, tooltip tooltips) layout.Dimensions { - t := transactionToTicket(tx, selectedWallet, l.WL.MultiWallet.TicketMaturity(), l.WL.MultiWallet.GetBestBlock().Height) +func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.Wallet, tx *transactionItem) layout.Dimensions { + t := transactionToTicket(*tx.transaction, selectedWallet, l.WL.MultiWallet.TicketMaturity(), l.WL.MultiWallet.TicketExpiry(), l.WL.MultiWallet.GetBestBlock().Height) var itemWidth int st := ticketStatusProfile(l, t.Status) if st == nil { @@ -311,7 +313,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W }.Layout(gtx, func(gtx C) D { txt := l.Theme.Label(values.TextSize14, "10h 47m") txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tooltip.durationTooltip, func(gtx C) D { + ticketCardTooltip(gtx, txtLayout, tx.durationTooltip, func(gtx C) D { setText(t.Status) return walletNameDateTimeTooltip(gtx, l, durationTitle, toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(durationMsg).Layout)) @@ -373,7 +375,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W txt := l.Theme.Label(values.MarginPadding14, t.Status) txt.Color = st.color txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tooltip.statusTooltip, func(gtx C) D { + ticketCardTooltip(gtx, txtLayout, tx.statusTooltip, func(gtx C) D { setText(t.Status) return ticketStatusTooltip(gtx, l, t) }) @@ -393,7 +395,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W txt := l.Theme.Label(values.MarginPadding14, t.WalletName) txt.Color = l.Theme.Color.Gray txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tooltip.walletNameTooltip, func(gtx C) D { + ticketCardTooltip(gtx, txtLayout, tx.walletNameTooltip, func(gtx C) D { return walletNameDateTimeTooltip(gtx, l, "Wallet name", toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(t.WalletName).Layout)) }) @@ -411,7 +413,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tooltip.dateTooltip, func(gtx C) D { + ticketCardTooltip(gtx, txtLayout, tx.dateTooltip, func(gtx C) D { dt := strings.Split(t.DateTime, " ") s1 := []string{dt[0], dt[1], dt[2]} date := strings.Join(s1, " ") @@ -436,7 +438,7 @@ func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.W layout.Rigid(func(gtx C) D { txt.Text = t.DaysBehind txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tooltip.daysBehindTooltip, func(gtx C) D { + ticketCardTooltip(gtx, txtLayout, tx.daysBehindTooltip, func(gtx C) D { setText(t.Status) return walletNameDateTimeTooltip(gtx, l, dayBehind, toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(t.DaysBehind).Layout)) From 93ba65bc6ba4a6ec2cdb4d038a08cba249901b76 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Fri, 10 Sep 2021 16:25:40 +0100 Subject: [PATCH 13/27] Add shadow to LinearLayout - Apply shadow to more list items and back up seed bottom pane --- ui/decredmaterial/linearlayout.go | 66 +++++++++++++---------- ui/decredmaterial/shadow.go | 2 +- ui/page/more_page.go | 3 +- ui/page/seedbackup/backup_instructions.go | 1 + 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/ui/decredmaterial/linearlayout.go b/ui/decredmaterial/linearlayout.go index 5bfcfa09f..94e4768c6 100644 --- a/ui/decredmaterial/linearlayout.go +++ b/ui/decredmaterial/linearlayout.go @@ -19,6 +19,7 @@ type LinearLayout struct { Height int Orientation layout.Axis Background color.NRGBA + Shadow *Shadow Border Border Margin layout.Inset Padding layout.Inset @@ -38,36 +39,45 @@ func (ll LinearLayout) Layout(gtx C, children ...layout.FlexChild) D { return ll.Direction.Layout(gtx, func(gtx C) D { // draw margin return ll.Margin.Layout(gtx, func(gtx C) D { - return layout.Stack{}.Layout(gtx, - layout.Expanded(func(gtx C) D { - ll.applyDimension(>x) - // draw background and clip the background to border radius - tr := float32(gtx.Px(unit.Dp(ll.Border.Radius.TopRight))) - tl := float32(gtx.Px(unit.Dp(ll.Border.Radius.TopLeft))) - br := float32(gtx.Px(unit.Dp(ll.Border.Radius.BottomRight))) - bl := float32(gtx.Px(unit.Dp(ll.Border.Radius.BottomLeft))) - clip.RRect{ - Rect: f32.Rectangle{Max: f32.Point{ - X: float32(gtx.Constraints.Min.X), - Y: float32(gtx.Constraints.Min.Y), - }}, - NW: tl, NE: tr, SE: br, SW: bl, - }.Add(gtx.Ops) - return fill(gtx, ll.Background) - }), - layout.Stacked(func(gtx C) D { - ll.applyDimension(>x) - return ll.Border.Layout(gtx, func(gtx C) D { - // draw padding - return ll.Padding.Layout(gtx, func(gtx C) D { - // draw layout direction - return ll.Direction.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: ll.Orientation, Alignment: ll.Alignment, Spacing: ll.Spacing}.Layout(gtx, children...) + + wdg := func(gtx C) D { + return layout.Stack{}.Layout(gtx, + layout.Expanded(func(gtx C) D { + ll.applyDimension(>x) + // draw background and clip the background to border radius + tr := float32(gtx.Px(unit.Dp(ll.Border.Radius.TopRight))) + tl := float32(gtx.Px(unit.Dp(ll.Border.Radius.TopLeft))) + br := float32(gtx.Px(unit.Dp(ll.Border.Radius.BottomRight))) + bl := float32(gtx.Px(unit.Dp(ll.Border.Radius.BottomLeft))) + clip.RRect{ + Rect: f32.Rectangle{Max: f32.Point{ + X: float32(gtx.Constraints.Min.X), + Y: float32(gtx.Constraints.Min.Y), + }}, + NW: tl, NE: tr, SE: br, SW: bl, + }.Add(gtx.Ops) + return fill(gtx, ll.Background) + }), + layout.Stacked(func(gtx C) D { + ll.applyDimension(>x) + return ll.Border.Layout(gtx, func(gtx C) D { + // draw padding + return ll.Padding.Layout(gtx, func(gtx C) D { + // draw layout direction + return ll.Direction.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: ll.Orientation, Alignment: ll.Alignment, Spacing: ll.Spacing}.Layout(gtx, children...) + }) }) }) - }) - }), - ) + }), + ) + } + + if ll.Shadow != nil { + return ll.Shadow.Layout(gtx, wdg) + } + + return wdg(gtx) }) }) } diff --git a/ui/decredmaterial/shadow.go b/ui/decredmaterial/shadow.go index 2d19385c4..9981deba4 100644 --- a/ui/decredmaterial/shadow.go +++ b/ui/decredmaterial/shadow.go @@ -40,7 +40,7 @@ func (s *Shadow) Layout(gtx C, w layout.Widget) D { return layout.Stack{}.Layout(gtx, layout.Expanded(func(gtx C) D { s.layout(gtx) - surface := clip.UniformRRect(f32.Rectangle{Max: layout.FPt(gtx.Constraints.Min)}, float32(gtx.Px(values.MarginPadding5))) + surface := clip.UniformRRect(f32.Rectangle{Max: layout.FPt(gtx.Constraints.Min)}, float32(gtx.Px(values.MarginPadding4))) paint.FillShape(gtx.Ops, s.surface, surface.Op(gtx.Ops)) return D{Size: gtx.Constraints.Min} }), diff --git a/ui/page/more_page.go b/ui/page/more_page.go index c7e93cfbf..bdd4f1e35 100644 --- a/ui/page/more_page.go +++ b/ui/page/more_page.go @@ -107,12 +107,13 @@ func (pg *MorePage) layoutMoreItems(gtx layout.Context) layout.Dimensions { list := layout.List{Axis: layout.Vertical} return list.Layout(gtx, len(pg.morePageListItems), func(gtx C, i int) D { - return layout.Inset{Bottom: values.MarginPadding5}.Layout(gtx, func(gtx C) D { + return layout.Inset{Bottom: values.MarginPadding8}.Layout(gtx, func(gtx C) D { return decredmaterial.Clickable(gtx, pg.morePageListItems[i].clickable, func(gtx C) D { return decredmaterial.LinearLayout{Orientation: layout.Horizontal, Width: decredmaterial.MatchParent, Height: decredmaterial.WrapContent, Background: pg.Theme.Color.Surface, + Shadow: pg.Theme.Shadow(), Border: decredmaterial.Border{Radius: decredmaterial.Radius(14)}, Padding: layout.UniformInset(values.MarginPadding15)}.Layout(gtx, layout.Rigid(func(gtx C) D { diff --git a/ui/page/seedbackup/backup_instructions.go b/ui/page/seedbackup/backup_instructions.go index 7c531a2a1..e49669f88 100644 --- a/ui/page/seedbackup/backup_instructions.go +++ b/ui/page/seedbackup/backup_instructions.go @@ -137,6 +137,7 @@ func container(gtx C, theme decredmaterial.Theme, body layout.Widget, infoText s Direction: layout.S, Alignment: layout.Baseline, Background: theme.Color.Surface, + Shadow: theme.Shadow(), Padding: layout.UniformInset(values.MarginPadding16), Margin: layout.Inset{Left: values.Size0_5}, Border: decredmaterial.Border{Radius: decredmaterial.Radius(4)}, From abcf5c647396ceff8cace5741dd4d51b6b045126 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Fri, 10 Sep 2021 18:55:44 +0100 Subject: [PATCH 14/27] Add GridLayout - use GridLayout for tickets grid view to fix scrolling lag issues --- ui/decredmaterial/gridlayout.go | 42 +++++++++++++++++++++++++++++++++ ui/page/tickets/list_page.go | 29 +++++++++++------------ 2 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 ui/decredmaterial/gridlayout.go diff --git a/ui/decredmaterial/gridlayout.go b/ui/decredmaterial/gridlayout.go new file mode 100644 index 000000000..cb778155a --- /dev/null +++ b/ui/decredmaterial/gridlayout.go @@ -0,0 +1,42 @@ +package decredmaterial + +import ( + "gioui.org/layout" +) + +type GridLayout struct { + List *layout.List + Alignment layout.Alignment + Direction layout.Direction + RowCount int +} + +func (g GridLayout) Layout(gtx layout.Context, num int, el GridElement) layout.Dimensions { + rows := make([]layout.Widget, 0) + + currentRow := make([]layout.FlexChild, 0) + for i := 0; i < num; i++ { + index := i + currentRow = append(currentRow, layout.Rigid(func(gtx C) D { return el(gtx, index) })) + + if len(currentRow) >= g.RowCount { + rowCopy := currentRow + rows = append(rows, func(gtx C) D { + return layout.Flex{Alignment: g.Alignment}.Layout(gtx, rowCopy...) + }) + currentRow = make([]layout.FlexChild, 0) + } + } + + if len(currentRow) > 0 { + rows = append(rows, func(gtx C) D { + return layout.Flex{}.Layout(gtx, currentRow...) + }) + } + + return g.List.Layout(gtx, len(rows), func(gtx C, index int) D { + return g.Direction.Layout(gtx, func(gtx C) D { + return rows[index](gtx) + }) + }) +} diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 143e6c637..d7824c131 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -339,21 +339,20 @@ func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []*transactionI func (pg *ListPage) ticketListGridLayout(gtx layout.Context, tickets []*transactionItem) layout.Dimensions { // TODO: GridWrap's items not able to scroll vertically, will update when it fixed return layout.Center.Layout(gtx, func(gtx C) D { - return pg.ticketsList.Layout(gtx, 1, func(gtx C, index int) D { - return pg.Theme.Card().Layout(gtx, func(gtx C) D { - gtx.Constraints.Min = gtx.Constraints.Max - return decredmaterial.GridWrap{ - Axis: layout.Horizontal, - Alignment: layout.End, - }.Layout(gtx, len(tickets), func(gtx C, index int) D { - return layout.Inset{ - Left: values.MarginPadding4, - Right: values.MarginPadding4, - Bottom: values.MarginPadding8, - }.Layout(gtx, func(gtx C) D { - selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] - return ticketCard(gtx, pg.Load, selectedWallet, tickets[index]) - }) + return pg.Theme.Card().Layout(gtx, func(gtx C) D { + gtx.Constraints.Min = gtx.Constraints.Max + return decredmaterial.GridLayout{ + List: &pg.ticketsList, + Direction: layout.Center, + RowCount: 3, + }.Layout(gtx, len(tickets), func(gtx C, index int) D { + return layout.Inset{ + Left: values.MarginPadding4, + Right: values.MarginPadding4, + Bottom: values.MarginPadding8, + }.Layout(gtx, func(gtx C) D { + selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] + return ticketCard(gtx, pg.Load, selectedWallet, tickets[index]) }) }) }) From 61f6336b6bec49b177efaaf92b7bfee8b95b2331 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Sun, 12 Sep 2021 14:44:07 +0100 Subject: [PATCH 15/27] tickets: fix ticket cards layouts - show ticket maturity progress and display correct value on time label - use correct progress bar and track colors - add TxStatus struct - sort ticket transactions and prioritize spent tickets --- ui/decredmaterial/card.go | 14 + ui/decredmaterial/gridlayout.go | 27 +- ui/decredmaterial/theme.go | 18 ++ ui/page/components/components.go | 113 +++++-- ui/page/tickets/list_page.go | 102 ++++-- ui/page/tickets/overview.go | 22 +- ui/page/tickets/utils.go | 483 ++++++++++++++++------------ ui/page/transaction_details_page.go | 4 +- ui/page/transactions_page.go | 8 +- ui/values/dimensions.go | 1 + 10 files changed, 512 insertions(+), 280 deletions(-) diff --git a/ui/decredmaterial/card.go b/ui/decredmaterial/card.go index 7ab42ed94..f6ddb5fde 100644 --- a/ui/decredmaterial/card.go +++ b/ui/decredmaterial/card.go @@ -33,6 +33,20 @@ func Radius(radius float32) CornerRadius { } } +func TopRadius(radius float32) CornerRadius { + return CornerRadius{ + TopLeft: radius, + TopRight: radius, + } +} + +func BottomRadius(radius float32) CornerRadius { + return CornerRadius{ + BottomRight: radius, + BottomLeft: radius, + } +} + const ( defaultRadius = 14 ) diff --git a/ui/decredmaterial/gridlayout.go b/ui/decredmaterial/gridlayout.go index cb778155a..1b7950d76 100644 --- a/ui/decredmaterial/gridlayout.go +++ b/ui/decredmaterial/gridlayout.go @@ -5,33 +5,36 @@ import ( ) type GridLayout struct { - List *layout.List - Alignment layout.Alignment - Direction layout.Direction - RowCount int + List *layout.List + HorizontalSpacing layout.Spacing + Alignment layout.Alignment + Direction layout.Direction + RowCount int } func (g GridLayout) Layout(gtx layout.Context, num int, el GridElement) layout.Dimensions { rows := make([]layout.Widget, 0) currentRow := make([]layout.FlexChild, 0) + + appendRow := func(row []layout.FlexChild) { + rows = append(rows, func(gtx C) D { + return layout.Flex{Alignment: g.Alignment, Spacing: g.HorizontalSpacing}.Layout(gtx, row...) + }) + currentRow = make([]layout.FlexChild, 0) + } + for i := 0; i < num; i++ { index := i currentRow = append(currentRow, layout.Rigid(func(gtx C) D { return el(gtx, index) })) if len(currentRow) >= g.RowCount { - rowCopy := currentRow - rows = append(rows, func(gtx C) D { - return layout.Flex{Alignment: g.Alignment}.Layout(gtx, rowCopy...) - }) - currentRow = make([]layout.FlexChild, 0) + appendRow(currentRow) } } if len(currentRow) > 0 { - rows = append(rows, func(gtx C) D { - return layout.Flex{}.Layout(gtx, currentRow...) - }) + appendRow(currentRow) } return g.List.Layout(gtx, len(rows), func(gtx C, index int) D { diff --git a/ui/decredmaterial/theme.go b/ui/decredmaterial/theme.go index d19413739..88cbdaded 100644 --- a/ui/decredmaterial/theme.go +++ b/ui/decredmaterial/theme.go @@ -56,6 +56,9 @@ type Theme struct { DeepBlue color.NRGBA LightBlue color.NRGBA LightBlue2 color.NRGBA + LightBlue3 color.NRGBA + LightBlue4 color.NRGBA + LightBlue5 color.NRGBA BlueProgressTint color.NRGBA LightGray color.NRGBA InactiveGray color.NRGBA @@ -65,11 +68,14 @@ type Theme struct { Gray3 color.NRGBA Orange color.NRGBA Orange2 color.NRGBA + Orange3 color.NRGBA Gray4 color.NRGBA Gray5 color.NRGBA Gray6 color.NRGBA Green50 color.NRGBA Green500 color.NRGBA + Turquoise100 color.NRGBA + Turquoise300 color.NRGBA Turquoise800 color.NRGBA Yellow color.NRGBA } @@ -124,9 +130,15 @@ func (t *Theme) setColorMode(darkMode bool) { t.Color.Background = argb(0x22444444) t.Color.LightBlue = rgb(0xe4f6ff) t.Color.LightBlue2 = rgb(0x75D8FF) + t.Color.LightBlue3 = rgb(0xBCE8FF) + t.Color.LightBlue4 = rgb(0xBBDEFF) + t.Color.LightBlue5 = rgb(0x70CBFF) t.Color.BlueProgressTint = rgb(0x73d7ff) t.Color.Orange = rgb(0xD34A21) t.Color.Orange2 = rgb(0xF8E8E7) + t.Color.Orange3 = rgb(0xF8CABC) + t.Color.Turquoise100 = rgb(0xB6EED7) + t.Color.Turquoise300 = rgb(0x2DD8A3) t.Color.Turquoise800 = rgb(0x008F52) t.Color.Yellow = rgb(0xffc84e) t.TextSize = unit.Sp(16) @@ -158,9 +170,15 @@ func (t *Theme) setColorMode(darkMode bool) { t.Color.Background = argb(0x22444444) t.Color.LightBlue = rgb(0xe4f6ff) t.Color.LightBlue2 = rgb(0x75D8FF) + t.Color.LightBlue3 = rgb(0xBCE8FF) + t.Color.LightBlue4 = rgb(0xBBDEFF) + t.Color.LightBlue5 = rgb(0x70CBFF) t.Color.BlueProgressTint = rgb(0x73d7ff) t.Color.Orange = rgb(0xD34A21) t.Color.Orange2 = rgb(0xF8E8E7) + t.Color.Orange3 = rgb(0xF8CABC) + t.Color.Turquoise100 = rgb(0xB6EED7) + t.Color.Turquoise300 = rgb(0x2DD8A3) t.Color.Turquoise800 = rgb(0x008F52) t.Color.Yellow = rgb(0xffc84e) t.TextSize = unit.Sp(16) diff --git a/ui/page/components/components.go b/ui/page/components/components.go index f73894c64..208a0beaf 100644 --- a/ui/page/components/components.go +++ b/ui/page/components/components.go @@ -5,6 +5,7 @@ package components import ( "fmt" + "image/color" "os/exec" "runtime" "strings" @@ -38,6 +39,18 @@ type ( Index int ShowBadge bool } + + TxStatus struct { + Title string + Icon *decredmaterial.Image + + // tx purchase only + TicketStatus string + Color color.NRGBA + ProgressBarColor color.NRGBA + ProgressTrackColor color.NRGBA + Background color.NRGBA + } ) // Container is simply a wrapper for the Inset type. Its purpose is to differentiate the use of an inset as a padding or @@ -68,47 +81,88 @@ func UniformPadding(gtx layout.Context, body layout.Widget) layout.Dimensions { }.Layout(gtx, body) } -func TransactionTitleIcon(l *load.Load, wal *dcrlibwallet.Wallet, tx *dcrlibwallet.Transaction) (string, *decredmaterial.Image) { - var title string - var icon *decredmaterial.Image - icon = l.Icons.TicketLiveIcon +func TransactionTitleIcon(l *load.Load, wal *dcrlibwallet.Wallet, tx *dcrlibwallet.Transaction, ticketSpender *dcrlibwallet.Transaction) *TxStatus { + var txStatus TxStatus + if tx.Type == dcrlibwallet.TxTypeRegular { if tx.Direction == dcrlibwallet.TxDirectionSent { - title = "Sent" - icon = l.Icons.SendIcon + txStatus.Title = "Sent" + txStatus.Icon = l.Icons.SendIcon } else if tx.Direction == dcrlibwallet.TxDirectionReceived { - title = "Received" - icon = l.Icons.ReceiveIcon + txStatus.Title = "Received" + txStatus.Icon = l.Icons.ReceiveIcon } else if tx.Direction == dcrlibwallet.TxDirectionTransferred { - title = "Yourself" - icon = l.Icons.Transferred + txStatus.Title = "Yourself" + txStatus.Icon = l.Icons.Transferred } } else if tx.Type == dcrlibwallet.TxTypeMixed { - title = "Mixed" - icon = l.Icons.MixedTx + txStatus.Title = "Mixed" + txStatus.Icon = l.Icons.MixedTx } else if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterStaking) { if tx.Type == dcrlibwallet.TxTypeTicketPurchase { - if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterImmature) { - title = "Immature" - icon = l.Icons.TicketImmatureIcon + if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterUnmined) { + txStatus.Title = "Unmined" + txStatus.Icon = l.Icons.TicketUnminedIcon + txStatus.TicketStatus = dcrlibwallet.TicketStatusUnmined + txStatus.Color = l.Theme.Color.DeepBlue + txStatus.Background = l.Theme.Color.LightBlue + } else if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterImmature) { + txStatus.Title = "Immature" + txStatus.Icon = l.Icons.TicketImmatureIcon + txStatus.Color = l.Theme.Color.DeepBlue + txStatus.TicketStatus = dcrlibwallet.TicketStatusImmature + txStatus.ProgressBarColor = l.Theme.Color.LightBlue5 + txStatus.ProgressTrackColor = l.Theme.Color.LightBlue3 + txStatus.Background = l.Theme.Color.LightBlue + } else if ticketSpender != nil { + if ticketSpender.Type == dcrlibwallet.TxTypeVote { + txStatus.Title = "Voted" + txStatus.Icon = l.Icons.TicketVotedIcon + txStatus.Color = l.Theme.Color.Success + txStatus.TicketStatus = dcrlibwallet.TicketStatusVotedOrRevoked + txStatus.ProgressBarColor = l.Theme.Color.Turquoise300 + txStatus.ProgressTrackColor = l.Theme.Color.Turquoise100 + txStatus.Background = l.Theme.Color.Success2 + } else { + txStatus.Title = "Revoked" + txStatus.Icon = l.Icons.TicketRevokedIcon + txStatus.Color = l.Theme.Color.Orange + txStatus.TicketStatus = dcrlibwallet.TicketStatusVotedOrRevoked + txStatus.ProgressBarColor = l.Theme.Color.Danger + txStatus.ProgressTrackColor = l.Theme.Color.Orange3 + txStatus.Background = l.Theme.Color.Orange2 + } } else if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterLive) { - title = "Live" - icon = l.Icons.TicketLiveIcon + txStatus.Title = "Live" + txStatus.Icon = l.Icons.TicketLiveIcon + txStatus.Color = l.Theme.Color.Primary + txStatus.TicketStatus = dcrlibwallet.TicketStatusLive + txStatus.ProgressBarColor = l.Theme.Color.Primary + txStatus.ProgressTrackColor = l.Theme.Color.LightBlue4 + txStatus.Background = l.Theme.Color.LightBlue + } else if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterExpired) { + txStatus.Title = "Expired" + txStatus.Icon = l.Icons.TicketExpiredIcon + txStatus.Color = l.Theme.Color.Gray + txStatus.TicketStatus = dcrlibwallet.TicketStatusExpired + txStatus.Background = l.Theme.Color.LightGray } else { - title = "Purchased" - icon = l.Icons.TicketPurchasedIcon + txStatus.Title = "Purchased" + txStatus.Icon = l.Icons.TicketPurchasedIcon + txStatus.Color = l.Theme.Color.DeepBlue + txStatus.Background = l.Theme.Color.LightBlue } } else if tx.Type == dcrlibwallet.TxTypeVote { - title = "Vote" - icon = l.Icons.TicketVotedIcon + txStatus.Title = "Vote" + txStatus.Icon = l.Icons.TicketVotedIcon } else if tx.Type == dcrlibwallet.TxTypeRevocation { - title = "Revocation" - icon = l.Icons.TicketRevokedIcon + txStatus.Title = "Revocation" + txStatus.Icon = l.Icons.TicketRevokedIcon } } - return title, icon + return &txStatus } // transactionRow is a single transaction row on the transactions and overview page. It lays out a transaction's @@ -117,8 +171,11 @@ func LayoutTransactionRow(gtx layout.Context, l *load.Load, row TransactionRow) gtx.Constraints.Min.X = gtx.Constraints.Max.X wal := l.WL.MultiWallet.WalletWithID(row.Transaction.WalletID) - - title, icon := TransactionTitleIcon(l, wal, &row.Transaction) + var ticketSpender *dcrlibwallet.Transaction + if wal.TxMatchesFilter(&row.Transaction, dcrlibwallet.TxFilterStaking) { + ticketSpender, _ = wal.TicketSpender(row.Transaction.Hash) + } + txStatus := TransactionTitleIcon(l, wal, &row.Transaction, ticketSpender) return decredmaterial.LinearLayout{ Orientation: layout.Horizontal, @@ -129,7 +186,7 @@ func LayoutTransactionRow(gtx layout.Context, l *load.Load, row TransactionRow) }.Layout(gtx, layout.Rigid(func(gtx C) D { gtx.Constraints.Min.Y = gtx.Constraints.Max.Y - return layout.W.Layout(gtx, icon.Layout24dp) + return layout.W.Layout(gtx, txStatus.Icon.Layout24dp) }), layout.Rigid(func(gtx C) D { return decredmaterial.LinearLayout{ @@ -150,7 +207,7 @@ func LayoutTransactionRow(gtx layout.Context, l *load.Load, row TransactionRow) return LayoutBalance(gtx, l, amount) } - label := l.Theme.Label(values.TextSize18, title) + label := l.Theme.Label(values.TextSize18, txStatus.Title) label.Color = l.Theme.Color.DeepBlue return label.Layout(gtx) }), diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index d7824c131..99bb28f20 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -3,6 +3,7 @@ package tickets import ( "context" "image/color" + "sort" "gioui.org/layout" "gioui.org/text" @@ -18,6 +19,18 @@ import ( const listPageID = "TicketsList" +type txType int + +const ( + All txType = iota + Unmined + Immature + Live + Voted + Expired + Revoked +) + type ListPage struct { *load.Load @@ -98,21 +111,22 @@ func (pg *ListPage) listenForTxNotifications() { func (pg *ListPage) fetchTickets() { var txFilter int32 - switch pg.ticketTypeDropDown.SelectedIndex() { - case 1: + var ticketTypeDropdown = txType(pg.ticketTypeDropDown.SelectedIndex()) + switch ticketTypeDropdown { + case Unmined: txFilter = dcrlibwallet.TxFilterUnmined - case 2: + case Immature: txFilter = dcrlibwallet.TxFilterImmature - case 3: + case Live: txFilter = dcrlibwallet.TxFilterLive - case 4: - txFilter = dcrlibwallet.TxFilterVoted - case 5: + case Voted: + txFilter = dcrlibwallet.TxFilterTickets + case Expired: txFilter = dcrlibwallet.TxFilterExpired - case 6: - txFilter = dcrlibwallet.TxFilterRevoked + case Revoked: + txFilter = dcrlibwallet.TxFilterTickets default: - txFilter = dcrlibwallet.TxFilterStaking + txFilter = dcrlibwallet.TxFilterTickets } newestFirst := pg.orderDropDown.SelectedIndex() == 0 @@ -124,19 +138,65 @@ func (pg *ListPage) fetchTickets() { return } - tickets := make([]*transactionItem, len(txs)) - for i := range txs { - tickets[i] = &transactionItem{ - transaction: &txs[i], + tickets := make([]*transactionItem, 0) + for _, tx := range txs { + ticketSpender, err := w.TicketSpender(tx.Hash) + if err != nil { + pg.Toast.NotifyError(err.Error()) + return + } + + // Apply voted and revoked tx filter + if (ticketTypeDropdown == Voted || ticketTypeDropdown == Revoked) && ticketSpender == nil { + continue + } + + if ticketTypeDropdown == Voted && ticketSpender.Type != dcrlibwallet.TxTypeVote { + continue + } + + if ticketTypeDropdown == Revoked && ticketSpender.Type != dcrlibwallet.TxTypeRevocation { + continue + } + + // This fixes a dcrlibwallet bug where live tickets transactions + // do not have updated data of ticket spender. + if txFilter == dcrlibwallet.TxFilterLive && ticketSpender != nil { + continue + } + + ticketCopy := tx + + tickets = append(tickets, &transactionItem{ + transaction: &ticketCopy, + ticketSpender: ticketSpender, statusTooltip: pg.Load.Theme.Tooltip(), - walletNameTooltip: pg.Load.Theme.Tooltip(), dateTooltip: pg.Load.Theme.Tooltip(), daysBehindTooltip: pg.Load.Theme.Tooltip(), durationTooltip: pg.Load.Theme.Tooltip(), - } + }) } + // bring vote and revoke tx forward + sort.Slice(tickets[:], func(i, j int) bool { + var timeStampI = tickets[i].transaction.Timestamp + var timeStampJ = tickets[j].transaction.Timestamp + + if tickets[i].ticketSpender != nil { + timeStampI = tickets[i].ticketSpender.Timestamp + } + + if tickets[j].ticketSpender != nil { + timeStampJ = tickets[j].ticketSpender.Timestamp + } + + if newestFirst { + return timeStampI > timeStampJ + } + return timeStampI < timeStampJ + }) + pg.tickets = tickets } @@ -342,17 +402,17 @@ func (pg *ListPage) ticketListGridLayout(gtx layout.Context, tickets []*transact return pg.Theme.Card().Layout(gtx, func(gtx C) D { gtx.Constraints.Min = gtx.Constraints.Max return decredmaterial.GridLayout{ - List: &pg.ticketsList, - Direction: layout.Center, - RowCount: 3, + List: &pg.ticketsList, + HorizontalSpacing: layout.SpaceBetween, + Direction: layout.Center, + RowCount: 3, }.Layout(gtx, len(tickets), func(gtx C, index int) D { return layout.Inset{ Left: values.MarginPadding4, Right: values.MarginPadding4, Bottom: values.MarginPadding8, }.Layout(gtx, func(gtx C) D { - selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] - return ticketCard(gtx, pg.Load, selectedWallet, tickets[index]) + return ticketCard(gtx, pg.Load, tickets[index], false) }) }) }) diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 0c640962a..741e9a4cf 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -96,15 +96,26 @@ func (pg *Page) OnResume() { go func() { mw := pg.WL.MultiWallet - tickets := allLiveTickets(mw.AllWallets()) + tickets, err := allLiveTickets(mw) + if err != nil { + pg.Toast.NotifyError(err.Error()) + return + } txItems := make([]*transactionItem, len(tickets)) - for i := range tickets { + for i, ticket := range tickets { + + ticketSpender, err := mw.WalletWithID(ticket.WalletID).TicketSpender(ticket.Hash) + if err != nil { + pg.Toast.NotifyError(err.Error()) + return + } + txItems[i] = &transactionItem{ - transaction: &tickets[i], + transaction: &ticket, + ticketSpender: ticketSpender, statusTooltip: pg.Load.Theme.Tooltip(), - walletNameTooltip: pg.Load.Theme.Tooltip(), dateTooltip: pg.Load.Theme.Tooltip(), daysBehindTooltip: pg.Load.Theme.Tooltip(), durationTooltip: pg.Load.Theme.Tooltip(), @@ -232,8 +243,7 @@ func (pg *Page) ticketsLiveSection(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx C) D { return pg.ticketsLive.Layout(gtx, len(pg.liveTickets), func(gtx C, index int) D { return layout.Inset{Right: values.MarginPadding8}.Layout(gtx, func(gtx C) D { - w := pg.WL.MultiWallet.WalletWithID(pg.liveTickets[index].transaction.WalletID) - return ticketCard(gtx, pg.Load, w, pg.liveTickets[index]) + return ticketCard(gtx, pg.Load, pg.liveTickets[index], true) }) }) }), diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 81a435c66..03e166af2 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -10,6 +10,7 @@ import ( "gioui.org/gesture" "gioui.org/layout" + "gioui.org/text" "gioui.org/unit" "github.com/decred/dcrd/dcrutil" @@ -24,29 +25,22 @@ const ( uint32Size = 32 << (^uint32(0) >> 32 & 1) // 32 or 64 maxInt32 = 1<<(uint32Size-1) - 1 - ticketAge = "Ticket age" + // ticketAge = "Ticket age" durationMsg = "10 hrs 47 mins (118/256 blocks)" + + ansicDateFormat = "2006-01-02 15:04:05" ) type transactionItem struct { - transaction *dcrlibwallet.Transaction + transaction *dcrlibwallet.Transaction + ticketSpender *dcrlibwallet.Transaction statusTooltip *decredmaterial.Tooltip - walletNameTooltip *decredmaterial.Tooltip dateTooltip *decredmaterial.Tooltip daysBehindTooltip *decredmaterial.Tooltip durationTooltip *decredmaterial.Tooltip } -var ( - title = "" - mainMsg = "" - mainMsgDesc = "" - dayBehind = "" - durationTitle = "" - durationDesc = "" -) - type Ticket struct { Status string Fee string @@ -122,21 +116,19 @@ func getTicketStatus(txn dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, ticke return "" } -func allLiveTickets(wallets []*dcrlibwallet.Wallet) []dcrlibwallet.Transaction { +func allLiveTickets(mw *dcrlibwallet.MultiWallet) ([]dcrlibwallet.Transaction, error) { var tickets []dcrlibwallet.Transaction - liveTicketFilters := []int32{dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} - for _, w := range wallets { - for _, filter := range liveTicketFilters { - tx, err := w.GetTransactionsRaw(0, 0, filter, true) - if err != nil { - return tickets - } - - tickets = append(tickets, tx...) + liveTicketFilters := []int32{dcrlibwallet.TxFilterUnmined, dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} + for _, filter := range liveTicketFilters { + tx, err := mw.GetTransactionsRaw(0, 0, filter, true) + if err != nil { + return nil, err } + + tickets = append(tickets, tx...) } - return tickets + return tickets, nil } func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { @@ -193,57 +185,64 @@ func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { } func setText(t string) { - switch t { - case "UNMINED": - title = "This ticket is waiting in mempool to be included in a block." - mainMsg, mainMsgDesc, dayBehind, durationTitle, durationDesc = "", "", ticketAge, "Live in", durationMsg - case "IMMATURE": - title = "This ticket will enter the ticket pool and become a live ticket after 256 blocks (~20 hrs)." - mainMsg, mainMsgDesc, dayBehind, durationTitle, durationDesc = "", "", ticketAge, "Live in", durationMsg - case "LIVE": - title = "Waiting to be chosen to vote." - mainMsg = "The average vote time is 28 days, but can take up to 142 days." - mainMsgDesc = "There is a 0.5% chance of expiring before being chosen to vote (this expiration returns the original ticket price without a reward)." - dayBehind, durationTitle, durationDesc = ticketAge, "Live in", durationMsg - case "VOTED": - title = "Congratulations! This ticket has voted." - mainMsg = "The ticket price + reward will become spendable after 256 blocks (~20 hrs)." - dayBehind, durationTitle, durationDesc = "Days to vote", "Spendable in", durationMsg - case "MISSED": - title = "This ticket was chosen to vote, but missed the voting window." - mainMsg = "Missed tickets will be revoked to return the original ticket price to you." - mainMsgDesc = "If a ticket is not revoked automatically, use the revoke button." - dayBehind, durationTitle, durationDesc = "Days to miss", "Miss in", durationMsg - case "EXPIRED": - title = "This ticket has not been chosen to vote within 40960 blocks, and thus expired. " - mainMsg = "Expired tickets will be revoked to return the original ticket price to you." - mainMsgDesc = "If a ticket is not revoked automatically, use the revoke button." - dayBehind, durationTitle, durationDesc = "Days to expire", "Expired in", durationMsg - case "REVOKED": - title = "This ticket has been revoked." - dayBehind, durationTitle, durationDesc = ticketAge, "Spendable in", durationMsg - } + // var ( + // title = "" + // mainMsg = "" + // mainMsgDesc = "" + // dayBehind = "" + // durationTitle = "" + // durationDesc = "" + // ) + // switch t { + // case "UNMINED": + // title = "This ticket is waiting in mempool to be included in a block." + // mainMsg, mainMsgDesc, dayBehind, durationTitle, durationDesc = "", "", ticketAge, "Live in", durationMsg + // case "IMMATURE": + // title = "This ticket will enter the ticket pool and become a live ticket after 256 blocks (~20 hrs)." + // mainMsg, mainMsgDesc, dayBehind, durationTitle, durationDesc = "", "", ticketAge, "Live in", durationMsg + // case "LIVE": + // title = "Waiting to be chosen to vote." + // mainMsg = "The average vote time is 28 days, but can take up to 142 days." + // mainMsgDesc = "There is a 0.5% chance of expiring before being chosen to vote (this expiration returns the original ticket price without a reward)." + // dayBehind, durationTitle, durationDesc = ticketAge, "Live in", durationMsg + // case "VOTED": + // title = "Congratulations! This ticket has voted." + // mainMsg = "The ticket price + reward will become spendable after 256 blocks (~20 hrs)." + // dayBehind, durationTitle, durationDesc = "Days to vote", "Spendable in", durationMsg + // case "MISSED": + // title = "This ticket was chosen to vote, but missed the voting window." + // mainMsg = "Missed tickets will be revoked to return the original ticket price to you." + // mainMsgDesc = "If a ticket is not revoked automatically, use the revoke button." + // dayBehind, durationTitle, durationDesc = "Days to miss", "Miss in", durationMsg + // case "EXPIRED": + // title = "This ticket has not been chosen to vote within 40960 blocks, and thus expired. " + // mainMsg = "Expired tickets will be revoked to return the original ticket price to you." + // mainMsgDesc = "If a ticket is not revoked automatically, use the revoke button." + // dayBehind, durationTitle, durationDesc = "Days to expire", "Expired in", durationMsg + // case "REVOKED": + // title = "This ticket has been revoked." + // dayBehind, durationTitle, durationDesc = ticketAge, "Spendable in", durationMsg + // } } -func ticketStatusTooltip(gtx C, l *load.Load, t Ticket) layout.Dimensions { - setText(t.Status) - st := ticketStatusProfile(l, t.Status) - status := l.Theme.Body2(t.Status) - status.Color = st.color +func ticketStatusTooltip(gtx C, l *load.Load, txStatus *components.TxStatus) layout.Dimensions { + // setText(text) + status := l.Theme.Body2(txStatus.Title) + status.Color = txStatus.Color - titleLabel, mainMsgLabel, mainMsgLabel2 := l.Theme.Body2(title), l.Theme.Body2(mainMsg), l.Theme.Body2(mainMsgDesc) + titleLabel, mainMsgLabel, mainMsgLabel2 := l.Theme.Body2("title"), l.Theme.Body2("mainMsg"), l.Theme.Body2("mainMsgDesc") mainMsgLabel.Color, mainMsgLabel2.Color = l.Theme.Color.Gray, l.Theme.Color.Gray return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(st.icon.Layout24dp), + layout.Rigid(txStatus.Icon.Layout24dp), layout.Rigid(toolTipContent(layout.Inset{Left: values.MarginPadding4}, status.Layout)), ) }), layout.Rigid(toolTipContent(layout.Inset{Top: values.MarginPadding8}, titleLabel.Layout)), layout.Rigid(toolTipContent(layout.Inset{Top: values.MarginPadding8}, mainMsgLabel.Layout)), layout.Rigid(func(gtx C) D { - if mainMsgDesc != "" { + if "mainMsgDesc" != "" { toolTipContent(layout.Inset{Top: values.MarginPadding8}, mainMsgLabel2.Layout) } return layout.Dimensions{} @@ -285,175 +284,221 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { } // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid -func ticketCard(gtx layout.Context, l *load.Load, selectedWallet *dcrlibwallet.Wallet, tx *transactionItem) layout.Dimensions { - t := transactionToTicket(*tx.transaction, selectedWallet, l.WL.MultiWallet.TicketMaturity(), l.WL.MultiWallet.TicketExpiry(), l.WL.MultiWallet.GetBestBlock().Height) - var itemWidth int - st := ticketStatusProfile(l, t.Status) - if st == nil { - return layout.Dimensions{} +func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalletName bool) layout.Dimensions { + wal := l.WL.MultiWallet.WalletWithID(tx.transaction.WalletID) + txStatus := components.TransactionTitleIcon(l, wal, tx.transaction, tx.ticketSpender) + bestBlock := wal.GetBestBlock() + confs := tx.transaction.Confirmations(bestBlock) + maturity := l.WL.MultiWallet.TicketMaturity() + expiry := l.WL.MultiWallet.TicketExpiry() + + showProgress := txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || txStatus.TicketStatus == dcrlibwallet.TicketStatusLive + if tx.ticketSpender != nil { /// voted or revoked + showProgress = tx.ticketSpender.Confirmations(wal.GetBestBlock()) <= maturity } - return l.Theme.Shadow().Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - wrap := l.Theme.Card() - wrap.Radius = decredmaterial.CornerRadius{TopRight: 8, TopLeft: 8, BottomRight: 0, BottomLeft: 0} - wrap.Color = st.background - return wrap.Layout(gtx, func(gtx C) D { - return layout.Stack{Alignment: layout.S}.Layout(gtx, - layout.Expanded(func(gtx C) D { - return layout.NE.Layout(gtx, func(gtx C) D { - wTimeLabel := l.Theme.Card() - wTimeLabel.Radius = decredmaterial.CornerRadius{TopRight: 8, TopLeft: 0, BottomRight: 0, BottomLeft: 8} - return wTimeLabel.Layout(gtx, func(gtx C) D { - return layout.Inset{ - Top: values.MarginPadding4, - Bottom: values.MarginPadding4, - Right: values.MarginPadding8, - Left: values.MarginPadding8, - }.Layout(gtx, func(gtx C) D { - txt := l.Theme.Label(values.TextSize14, "10h 47m") - txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tx.durationTooltip, func(gtx C) D { - setText(t.Status) - return walletNameDateTimeTooltip(gtx, l, durationTitle, - toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(durationMsg).Layout)) - }) - return txtLayout - }) - }) - }) - }), - layout.Stacked(func(gtx C) D { - content := layout.Inset{ + showTime := showProgress && txStatus.TicketStatus != dcrlibwallet.TicketStatusLive + + return decredmaterial.LinearLayout{ + Width: gtx.Px(values.MarginPadding168), + Height: decredmaterial.WrapContent, + Orientation: layout.Vertical, + Shadow: l.Theme.Shadow(), + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return decredmaterial.LinearLayout{ + Width: decredmaterial.MatchParent, + Height: decredmaterial.WrapContent, + Background: txStatus.Background, + Border: decredmaterial.Border{Radius: decredmaterial.TopRadius(8)}, + }.Layout2(gtx, func(gtx C) D { + + confirmations := confs + if tx.ticketSpender != nil { + confirmations = tx.ticketSpender.Confirmations(wal.GetBestBlock()) + } + + return layout.Stack{}.Layout(gtx, + layout.Stacked(func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Center.Layout(gtx, func(gtx C) D { + return layout.Inset{ Top: values.MarginPadding24, - Right: values.MarginPadding62, - Left: values.MarginPadding62, Bottom: values.MarginPadding24, - }.Layout(gtx, func(gtx C) D { - return st.icon.Layout36dp(gtx) - }) - itemWidth = content.Size.X - return content - }), + }.Layout(gtx, txStatus.Icon.Layout36dp) + }) + }), + layout.Expanded(func(gtx C) D { + if !showTime { + return D{} + } + + return layout.NE.Layout(gtx, func(gtx C) D { + timeWrapper := l.Theme.Card() + timeWrapper.Radius = decredmaterial.CornerRadius{TopRight: 8, TopLeft: 0, BottomRight: 0, BottomLeft: 8} + return timeWrapper.Layout(gtx, func(gtx C) D { + return layout.Inset{ + Top: values.MarginPadding4, + Bottom: values.MarginPadding4, + Right: values.MarginPadding8, + Left: values.MarginPadding8, + }.Layout(gtx, func(gtx C) D { + + timeRemaining := time.Duration(float64(maturity-confirmations)*l.WL.MultiWallet.TargetTimePerBlockMinutes()) * time.Minute + txt := l.Theme.Label(values.TextSize14, timeRemaining.Truncate(time.Minute).String()) + + durationLayout := layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{Right: values.MarginPadding4}.Layout(gtx, l.Icons.TimerIcon.Layout) + }), + layout.Rigid(txt.Layout), + ) - layout.Stacked(func(gtx C) D { - return layout.Center.Layout(gtx, func(gtx C) D { - return layout.Inset{Top: values.MarginPadding20}.Layout(gtx, func(gtx C) D { - gtx.Constraints.Max.X = itemWidth - p := l.Theme.ProgressBar(20) - p.Height, p.Radius = values.MarginPadding4, values.MarginPadding1 - p.Color = st.color - return p.Layout(gtx) + ticketCardTooltip(gtx, durationLayout, tx.durationTooltip, func(gtx C) D { + setText(txStatus.Title) + return walletNameDateTimeTooltip(gtx, l, "durationTitle", + toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(durationMsg).Layout)) + }) + + return durationLayout }) }) - }), - ) - }) - }), - layout.Rigid(func(gtx C) D { - wrap := l.Theme.Card() - wrap.Radius = decredmaterial.CornerRadius{TopRight: 0, TopLeft: 0, BottomRight: 8, BottomLeft: 8} - return wrap.Layout(gtx, func(gtx C) D { - gtx.Constraints.Min.X, gtx.Constraints.Max.X = itemWidth, itemWidth + }) + }), + layout.Expanded(func(gtx C) D { + return layout.S.Layout(gtx, func(gtx C) D { + + if !showProgress { + return D{} + } + + progressMax := maturity + if txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { + progressMax = expiry + } + + progress := (float32(confirmations) / float32(progressMax)) * 100 + p := l.Theme.ProgressBar(int(progress)) + p.Height, p.Radius = values.MarginPadding4, values.MarginPadding1 + p.Color = txStatus.ProgressBarColor + p.TrackColor = txStatus.ProgressTrackColor + return p.Layout(gtx) + }) + }), + ) + }) + }), + layout.Rigid(func(gtx C) D { + return decredmaterial.LinearLayout{ + Width: decredmaterial.MatchParent, + Height: decredmaterial.WrapContent, + Orientation: layout.Vertical, + Padding: layout.UniformInset(values.MarginPadding16), + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return components.LayoutBalance(gtx, l, dcrutil.Amount(tx.transaction.Amount).String()) + }), + layout.Rigid(func(gtx C) D { return layout.Inset{ - Left: values.MarginPadding12, - Right: values.MarginPadding12, - Bottom: values.MarginPadding8, + Top: values.MarginPadding4, + Bottom: values.MarginPadding16, }.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{ - Top: values.MarginPadding16, - }.Layout(gtx, func(gtx C) D { - return components.LayoutBalance(gtx, l, t.Amount) + txt := l.Theme.Label(values.MarginPadding14, txStatus.Title) + txt.Color = txStatus.Color + txt.Font.Weight = text.Medium + txtLayout := txt.Layout(gtx) + ticketCardTooltip(gtx, txtLayout, tx.statusTooltip, func(gtx C) D { + setText(txStatus.Title) + return ticketStatusTooltip(gtx, l, txStatus) }) + return txtLayout }), layout.Rigid(func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - txt := l.Theme.Label(values.MarginPadding14, t.Status) - txt.Color = st.color - txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tx.statusTooltip, func(gtx C) D { - setText(t.Status) - return ticketStatusTooltip(gtx, l, t) - }) - return txtLayout - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding4, - Right: values.MarginPadding4, - }.Layout(gtx, func(gtx C) D { - ic := l.Icons.ImageBrightness1 - ic.Color = l.Theme.Color.Gray2 - return l.Icons.ImageBrightness1.Layout(gtx, values.MarginPadding5) - }) - }), - layout.Rigid(func(gtx C) D { - txt := l.Theme.Label(values.MarginPadding14, t.WalletName) - txt.Color = l.Theme.Color.Gray - txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tx.walletNameTooltip, func(gtx C) D { - return walletNameDateTimeTooltip(gtx, l, "Wallet name", - toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(t.WalletName).Layout)) - }) - return txtLayout - }), - ) - }), - layout.Rigid(func(gtx C) D { + if !showWalletName { + return D{} + } + return layout.Inset{ - Top: values.MarginPadding16, - Bottom: values.MarginPadding16, + Left: values.MarginPadding4, + Right: values.MarginPadding4, }.Layout(gtx, func(gtx C) D { - txt := l.Theme.Label(values.TextSize14, t.MonthDay) - txt.Color = l.Theme.Color.Gray2 - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tx.dateTooltip, func(gtx C) D { - dt := strings.Split(t.DateTime, " ") - s1 := []string{dt[0], dt[1], dt[2]} - date := strings.Join(s1, " ") - s2 := []string{dt[3], dt[4]} - time := strings.Join(s2, " ") - dateTime := fmt.Sprintf("%s at %s", date, time) - return walletNameDateTimeTooltip(gtx, l, "Purchased", - toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(dateTime).Layout)) - }) - return txtLayout - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding4, - Right: values.MarginPadding4, - }.Layout(gtx, func(gtx C) D { - ic := l.Icons.ImageBrightness1 - ic.Color = l.Theme.Color.Gray2 - return l.Icons.ImageBrightness1.Layout(gtx, values.MarginPadding5) - }) - }), - layout.Rigid(func(gtx C) D { - txt.Text = t.DaysBehind - txtLayout := txt.Layout(gtx) - ticketCardTooltip(gtx, txtLayout, tx.daysBehindTooltip, func(gtx C) D { - setText(t.Status) - return walletNameDateTimeTooltip(gtx, l, dayBehind, - toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(t.DaysBehind).Layout)) - }) - return txtLayout - }), - ) + txt := l.Theme.Label(values.MarginPadding14, "•") + txt.Color = l.Theme.Color.Gray + + return txt.Layout(gtx) }) }), + layout.Rigid(func(gtx C) D { + if !showWalletName { + return D{} + } + + txt := l.Theme.Label(values.MarginPadding14, wal.Name) + txt.Color = l.Theme.Color.Gray + return txt.Layout(gtx) + }), ) }) - }) - }), - ) - }) + }), + layout.Rigid(func(gtx C) D { + txt := l.Theme.Label(values.TextSize14, time.Unix(tx.transaction.Timestamp, 0).Format("Jan 2")) + txt.Color = l.Theme.Color.Gray2 + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + txtLayout := txt.Layout(gtx) + ticketCardTooltip(gtx, txtLayout, tx.dateTooltip, func(gtx C) D { + dateTime := time.Unix(tx.transaction.Timestamp, 0).Format("Jan 2, 2006 at 03:04:05 PM") + return walletNameDateTimeTooltip(gtx, l, "Purchased", + toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(dateTime).Layout)) + }) + return txtLayout + }), + layout.Rigid(func(gtx C) D { + + var age string + if tx.ticketSpender != nil { // voted or revoked + age = fmt.Sprintf("%d days", tx.ticketSpender.DaysToVoteOrRevoke) + } else if txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || + txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { + + ticketAgeDuration := time.Since(time.Unix(tx.transaction.Timestamp, 0)).Seconds() + + age = ticketAge(int(ticketAgeDuration)) + } else { + return D{} + } + + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding4, + Right: values.MarginPadding4, + }.Layout(gtx, func(gtx C) D { + ic := l.Icons.ImageBrightness1 + ic.Color = l.Theme.Color.Gray2 + return l.Icons.ImageBrightness1.Layout(gtx, values.MarginPadding5) + }) + }), + layout.Rigid(func(gtx C) D { + + txt.Text = age + txtLayout := txt.Layout(gtx) + ticketCardTooltip(gtx, txtLayout, tx.daysBehindTooltip, func(gtx C) D { + setText(txStatus.Title) + return walletNameDateTimeTooltip(gtx, l, "Ticket age", + toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(age).Layout)) + }) + return txtLayout + }), + ) + }), + ) + }), + ) + }), + ) } // ticketActivityRow layouts out ticket info, display ticket activities on the tickets_page and tickets_activity_page @@ -556,6 +601,22 @@ func createClickGestures(count int) []*gesture.Click { return gestures } +func ticketAge(secs int) string { + if secs > 86399 { + days := secs / 86400 + return fmt.Sprintf("%dd", days) + } else if secs > 3599 { + hours := secs / 3600 + return fmt.Sprintf("%dh", hours) + } else if secs > 59 { + mins := secs / 60 + return fmt.Sprintf("%dm", mins) + } + + return fmt.Sprintf("%ds", secs) + +} + func nextTicketRemaining(allsecs int) string { if allsecs == 0 { return "imminent" diff --git a/ui/page/transaction_details_page.go b/ui/page/transaction_details_page.go index 200bf9c73..8aa4ae319 100644 --- a/ui/page/transaction_details_page.go +++ b/ui/page/transaction_details_page.go @@ -345,7 +345,7 @@ func (pg *TransactionDetailsPage) ticketDetails(gtx C) D { var status string if pg.ticketSpender != nil { if pg.ticketSpender.Type == dcrlibwallet.TxTypeVote { - status = "Vote" + status = "Voted" } else { status = "Revoked" } @@ -355,6 +355,8 @@ func (pg *TransactionDetailsPage) ticketDetails(gtx C) D { status = "Immature" } else if pg.wallet.TxMatchesFilter(pg.transaction, dcrlibwallet.TxFilterUnmined) { status = "Unmined" + } else if pg.wallet.TxMatchesFilter(pg.transaction, dcrlibwallet.TxFilterExpired) { + status = "Expired" } else { status = "Unknown" } diff --git a/ui/page/transactions_page.go b/ui/page/transactions_page.go index b2c3cb29d..2c1aa639c 100644 --- a/ui/page/transactions_page.go +++ b/ui/page/transactions_page.go @@ -240,7 +240,13 @@ func initTxnWidgets(l *load.Load, transaction *dcrlibwallet.Transaction) transac txn.confirmationIcons = l.Icons.PendingIcon } - txn.title, txn.icon = components.TransactionTitleIcon(l, wal, transaction) + var ticketSpender *dcrlibwallet.Transaction + if wal.TxMatchesFilter(transaction, dcrlibwallet.TxFilterStaking) { + ticketSpender, _ = wal.TicketSpender(transaction.Hash) + } + txStatus := components.TransactionTitleIcon(l, wal, transaction, ticketSpender) + txn.title = txStatus.Title + txn.icon = txStatus.Icon return txn } diff --git a/ui/values/dimensions.go b/ui/values/dimensions.go index 71ee7acab..40a0ee64c 100644 --- a/ui/values/dimensions.go +++ b/ui/values/dimensions.go @@ -57,6 +57,7 @@ var ( MarginPadding130 = unit.Dp(130) MarginPadding140 = unit.Dp(140) MarginPadding150 = unit.Dp(150) + MarginPadding168 = unit.Dp(168) MarginPadding195 = unit.Dp(195) MarginPaddingMinus195 = unit.Dp(-195) MarginPadding200 = unit.Dp(200) From 06c6bcf363a7b01be41843f8e4633321963ff6d0 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Sun, 12 Sep 2021 16:20:28 +0100 Subject: [PATCH 16/27] Fix ticket status tooltip - add tooltip messages for all ticket statuses and format block time for voted and revoked tickets - fix bug where overview page shows the same details for all tickets - add ticket status and confirmations property to transactionItem struct - add shadow to tooltip and remove border stroke --- ui/decredmaterial/theme.go | 6 ++ ui/decredmaterial/tooltip.go | 34 ++++----- ui/page/components/components.go | 6 +- ui/page/tickets/list_page.go | 3 + ui/page/tickets/overview.go | 11 ++- ui/page/tickets/utils.go | 119 +++++++++++++++---------------- 6 files changed, 96 insertions(+), 83 deletions(-) diff --git a/ui/decredmaterial/theme.go b/ui/decredmaterial/theme.go index 88cbdaded..f82704f6d 100644 --- a/ui/decredmaterial/theme.go +++ b/ui/decredmaterial/theme.go @@ -41,6 +41,7 @@ type Theme struct { Base *material.Theme Color struct { Primary color.NRGBA + Primary50 color.NRGBA Secondary color.NRGBA Text color.NRGBA Hint color.NRGBA @@ -59,6 +60,7 @@ type Theme struct { LightBlue3 color.NRGBA LightBlue4 color.NRGBA LightBlue5 color.NRGBA + LightBlue6 color.NRGBA BlueProgressTint color.NRGBA LightGray color.NRGBA InactiveGray color.NRGBA @@ -105,6 +107,7 @@ func (t *Theme) setColorMode(darkMode bool) { if darkMode { t.DarkMode = true t.Color.Primary = rgb(0x57B6FF) + t.Color.Primary50 = rgb(0xE3F2FF) t.Color.Text = argb(0x99FFFFFF) t.Color.Hint = rgb(0x8997A5) t.Color.InvText = rgb(0xffffff) @@ -133,6 +136,7 @@ func (t *Theme) setColorMode(darkMode bool) { t.Color.LightBlue3 = rgb(0xBCE8FF) t.Color.LightBlue4 = rgb(0xBBDEFF) t.Color.LightBlue5 = rgb(0x70CBFF) + t.Color.LightBlue6 = rgb(0x4B91D8) t.Color.BlueProgressTint = rgb(0x73d7ff) t.Color.Orange = rgb(0xD34A21) t.Color.Orange2 = rgb(0xF8E8E7) @@ -145,6 +149,7 @@ func (t *Theme) setColorMode(darkMode bool) { } else { t.DarkMode = false t.Color.Primary = keyblue + t.Color.Primary50 = rgb(0xE3F2FF) t.Color.Text = darkblue t.Color.Hint = rgb(0x8997A5) t.Color.InvText = rgb(0xffffff) @@ -173,6 +178,7 @@ func (t *Theme) setColorMode(darkMode bool) { t.Color.LightBlue3 = rgb(0xBCE8FF) t.Color.LightBlue4 = rgb(0xBBDEFF) t.Color.LightBlue5 = rgb(0x70CBFF) + t.Color.LightBlue6 = rgb(0x4B91D8) t.Color.BlueProgressTint = rgb(0x73d7ff) t.Color.Orange = rgb(0xD34A21) t.Color.Orange2 = rgb(0xF8E8E7) diff --git a/ui/decredmaterial/tooltip.go b/ui/decredmaterial/tooltip.go index 51105e564..411ea0164 100644 --- a/ui/decredmaterial/tooltip.go +++ b/ui/decredmaterial/tooltip.go @@ -6,39 +6,39 @@ import ( "gioui.org/layout" "gioui.org/op" - "gioui.org/unit" - "gioui.org/widget" + "github.com/planetdecred/godcr/ui/values" ) type Tooltip struct { - hoverable *Hoverable - card Card - borderColor color.NRGBA + hoverable *Hoverable + background color.NRGBA + shadow *Shadow } func (t *Theme) Tooltip() *Tooltip { return &Tooltip{ - hoverable: t.Hoverable(), - card: t.Card(), - borderColor: t.Color.Gray1, + hoverable: t.Hoverable(), + background: t.Color.Surface, + shadow: t.Shadow(), } } func (t *Tooltip) layout(gtx C, pos layout.Inset, wdgt layout.Widget) D { - border := widget.Border{ - Color: t.borderColor, - CornerRadius: unit.Dp(5), - Width: unit.Dp(1), + border := Border{ + Radius: Radius(7), } return pos.Layout(gtx, func(gtx C) D { return layout.Stack{}.Layout(gtx, layout.Stacked(func(gtx C) D { - return border.Layout(gtx, func(gtx C) D { - return t.card.Layout(gtx, func(gtx C) D { - return layout.UniformInset(unit.Dp(10)).Layout(gtx, wdgt) - }) - }) + return LinearLayout{ + Width: WrapContent, + Height: WrapContent, + Padding: layout.UniformInset(values.MarginPadding12), + Background: t.background, + Border: border, + Shadow: t.shadow, + }.Layout2(gtx, wdgt) }), ) }) diff --git a/ui/page/components/components.go b/ui/page/components/components.go index 208a0beaf..1f469571a 100644 --- a/ui/page/components/components.go +++ b/ui/page/components/components.go @@ -105,12 +105,12 @@ func TransactionTitleIcon(l *load.Load, wal *dcrlibwallet.Wallet, tx *dcrlibwall txStatus.Title = "Unmined" txStatus.Icon = l.Icons.TicketUnminedIcon txStatus.TicketStatus = dcrlibwallet.TicketStatusUnmined - txStatus.Color = l.Theme.Color.DeepBlue + txStatus.Color = l.Theme.Color.LightBlue6 txStatus.Background = l.Theme.Color.LightBlue } else if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterImmature) { txStatus.Title = "Immature" txStatus.Icon = l.Icons.TicketImmatureIcon - txStatus.Color = l.Theme.Color.DeepBlue + txStatus.Color = l.Theme.Color.LightBlue6 txStatus.TicketStatus = dcrlibwallet.TicketStatusImmature txStatus.ProgressBarColor = l.Theme.Color.LightBlue5 txStatus.ProgressTrackColor = l.Theme.Color.LightBlue3 @@ -140,7 +140,7 @@ func TransactionTitleIcon(l *load.Load, wal *dcrlibwallet.Wallet, tx *dcrlibwall txStatus.TicketStatus = dcrlibwallet.TicketStatusLive txStatus.ProgressBarColor = l.Theme.Color.Primary txStatus.ProgressTrackColor = l.Theme.Color.LightBlue4 - txStatus.Background = l.Theme.Color.LightBlue + txStatus.Background = l.Theme.Color.Primary50 } else if wal.TxMatchesFilter(tx, dcrlibwallet.TxFilterExpired) { txStatus.Title = "Expired" txStatus.Icon = l.Icons.TicketExpiredIcon diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 99bb28f20..908bd025e 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -166,10 +166,13 @@ func (pg *ListPage) fetchTickets() { } ticketCopy := tx + txStatus := components.TransactionTitleIcon(pg.Load, w, &tx, ticketSpender) tickets = append(tickets, &transactionItem{ transaction: &ticketCopy, ticketSpender: ticketSpender, + status: txStatus, + confirmations: tx.Confirmations(w.GetBestBlock()), statusTooltip: pg.Load.Theme.Tooltip(), dateTooltip: pg.Load.Theme.Tooltip(), diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 741e9a4cf..213c1c0a6 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -105,15 +105,22 @@ func (pg *Page) OnResume() { txItems := make([]*transactionItem, len(tickets)) for i, ticket := range tickets { - ticketSpender, err := mw.WalletWithID(ticket.WalletID).TicketSpender(ticket.Hash) + wal := mw.WalletWithID(ticket.WalletID) + + ticketSpender, err := wal.TicketSpender(ticket.Hash) if err != nil { pg.Toast.NotifyError(err.Error()) return } + ticketCopy := ticket + txStatus := components.TransactionTitleIcon(pg.Load, wal, &ticket, ticketSpender) + txItems[i] = &transactionItem{ - transaction: &ticket, + transaction: &ticketCopy, ticketSpender: ticketSpender, + status: txStatus, + confirmations: ticket.Confirmations(wal.GetBestBlock()), statusTooltip: pg.Load.Theme.Tooltip(), dateTooltip: pg.Load.Theme.Tooltip(), diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 03e166af2..5ea39ce2b 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -27,13 +27,13 @@ const ( // ticketAge = "Ticket age" durationMsg = "10 hrs 47 mins (118/256 blocks)" - - ansicDateFormat = "2006-01-02 15:04:05" ) type transactionItem struct { transaction *dcrlibwallet.Transaction ticketSpender *dcrlibwallet.Transaction + status *components.TxStatus + confirmations int32 statusTooltip *decredmaterial.Tooltip dateTooltip *decredmaterial.Tooltip @@ -184,66 +184,68 @@ func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { return &st } -func setText(t string) { - // var ( - // title = "" - // mainMsg = "" - // mainMsgDesc = "" - // dayBehind = "" - // durationTitle = "" - // durationDesc = "" - // ) - // switch t { - // case "UNMINED": - // title = "This ticket is waiting in mempool to be included in a block." - // mainMsg, mainMsgDesc, dayBehind, durationTitle, durationDesc = "", "", ticketAge, "Live in", durationMsg - // case "IMMATURE": - // title = "This ticket will enter the ticket pool and become a live ticket after 256 blocks (~20 hrs)." - // mainMsg, mainMsgDesc, dayBehind, durationTitle, durationDesc = "", "", ticketAge, "Live in", durationMsg - // case "LIVE": - // title = "Waiting to be chosen to vote." - // mainMsg = "The average vote time is 28 days, but can take up to 142 days." - // mainMsgDesc = "There is a 0.5% chance of expiring before being chosen to vote (this expiration returns the original ticket price without a reward)." - // dayBehind, durationTitle, durationDesc = ticketAge, "Live in", durationMsg - // case "VOTED": - // title = "Congratulations! This ticket has voted." - // mainMsg = "The ticket price + reward will become spendable after 256 blocks (~20 hrs)." - // dayBehind, durationTitle, durationDesc = "Days to vote", "Spendable in", durationMsg - // case "MISSED": - // title = "This ticket was chosen to vote, but missed the voting window." - // mainMsg = "Missed tickets will be revoked to return the original ticket price to you." - // mainMsgDesc = "If a ticket is not revoked automatically, use the revoke button." - // dayBehind, durationTitle, durationDesc = "Days to miss", "Miss in", durationMsg - // case "EXPIRED": - // title = "This ticket has not been chosen to vote within 40960 blocks, and thus expired. " - // mainMsg = "Expired tickets will be revoked to return the original ticket price to you." - // mainMsgDesc = "If a ticket is not revoked automatically, use the revoke button." - // dayBehind, durationTitle, durationDesc = "Days to expire", "Expired in", durationMsg - // case "REVOKED": - // title = "This ticket has been revoked." - // dayBehind, durationTitle, durationDesc = ticketAge, "Spendable in", durationMsg - // } -} +func ticketStatusTooltip(gtx C, l *load.Load, tx *transactionItem) layout.Dimensions { + status := l.Theme.Label(values.MarginPadding14, strings.ToUpper(tx.status.Title)) + status.Font.Weight = text.Medium + status.Color = tx.status.Color + + var title, mainDesc, subDesc string + switch tx.status.TicketStatus { + case dcrlibwallet.TicketStatusImmature: + title = "This ticket will enter the ticket pool and become a live ticket after 256 blocks (~20 hrs)." + case dcrlibwallet.TicketStatusLive: + title = "Waiting to be chosen to vote." + mainDesc = "The average vote time is 28 days, but can take up to 142 days." + subDesc = "There is a 0.5% chance of expiring before being chosen to vote (this expiration returns the original ticket price without a reward)." + case dcrlibwallet.TicketStatusVotedOrRevoked: + if tx.ticketSpender.Type == dcrlibwallet.TxTypeVote { + title = "Congratulations! This ticket has voted." + mainDesc = "The ticket price + reward will become spendable after %d blocks (~%s)." + } else { + title = "This ticket has been revoked." + mainDesc = "The ticket price will become spendable after %d blocks (~%s)." + } -func ticketStatusTooltip(gtx C, l *load.Load, txStatus *components.TxStatus) layout.Dimensions { - // setText(text) - status := l.Theme.Body2(txStatus.Title) - status.Color = txStatus.Color + maturity := l.WL.MultiWallet.TicketMaturity() - titleLabel, mainMsgLabel, mainMsgLabel2 := l.Theme.Body2("title"), l.Theme.Body2("mainMsg"), l.Theme.Body2("mainMsgDesc") - mainMsgLabel.Color, mainMsgLabel2.Color = l.Theme.Color.Gray, l.Theme.Color.Gray + if tx.confirmations > maturity { + mainDesc = "" + } else { + blockTime := l.WL.MultiWallet.TargetTimePerBlockMinutes() + maturityDuration := time.Duration(maturity*int32(blockTime)) * time.Minute + mainDesc = fmt.Sprintf(mainDesc, maturity, ticketAge(int(maturityDuration.Seconds()))) + } + case dcrlibwallet.TicketStatusExpired: + title = fmt.Sprintf("This ticket has not been chosen to vote within %d blocks, and thus expired.", l.WL.MultiWallet.TicketMaturity()) + mainDesc = "Expired tickets will be revoked to return the original ticket price to you." + subDesc = "If a ticket is not revoked automatically, use the revoke button." + } + + titleLabel := l.Theme.Label(values.MarginPadding14, title) + titleLabel.Color = l.Theme.Color.DeepBlue + + mainDescLabel := l.Theme.Label(values.MarginPadding14, mainDesc) + mainDescLabel.Color = l.Theme.Color.Gray + subDescLabel := l.Theme.Label(values.MarginPadding14, subDesc) + subDescLabel.Color = l.Theme.Color.Gray return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(txStatus.Icon.Layout24dp), + return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(tx.status.Icon.Layout16dp), layout.Rigid(toolTipContent(layout.Inset{Left: values.MarginPadding4}, status.Layout)), ) }), layout.Rigid(toolTipContent(layout.Inset{Top: values.MarginPadding8}, titleLabel.Layout)), - layout.Rigid(toolTipContent(layout.Inset{Top: values.MarginPadding8}, mainMsgLabel.Layout)), layout.Rigid(func(gtx C) D { - if "mainMsgDesc" != "" { - toolTipContent(layout.Inset{Top: values.MarginPadding8}, mainMsgLabel2.Layout) + if mainDesc != "" { + return toolTipContent(layout.Inset{Top: values.MarginPadding8}, mainDescLabel.Layout)(gtx) + } + + return D{} + }), + layout.Rigid(func(gtx C) D { + if subDesc != "" { + return toolTipContent(layout.Inset{Top: values.MarginPadding8}, subDescLabel.Layout)(gtx) } return layout.Dimensions{} }), @@ -286,9 +288,7 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { // ticketCard layouts out ticket info with the shadow box, use for list horizontal or list grid func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalletName bool) layout.Dimensions { wal := l.WL.MultiWallet.WalletWithID(tx.transaction.WalletID) - txStatus := components.TransactionTitleIcon(l, wal, tx.transaction, tx.ticketSpender) - bestBlock := wal.GetBestBlock() - confs := tx.transaction.Confirmations(bestBlock) + txStatus := tx.status maturity := l.WL.MultiWallet.TicketMaturity() expiry := l.WL.MultiWallet.TicketExpiry() @@ -313,7 +313,7 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle Border: decredmaterial.Border{Radius: decredmaterial.TopRadius(8)}, }.Layout2(gtx, func(gtx C) D { - confirmations := confs + confirmations := tx.confirmations if tx.ticketSpender != nil { confirmations = tx.ticketSpender.Confirmations(wal.GetBestBlock()) } @@ -355,7 +355,6 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle ) ticketCardTooltip(gtx, durationLayout, tx.durationTooltip, func(gtx C) D { - setText(txStatus.Title) return walletNameDateTimeTooltip(gtx, l, "durationTitle", toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(durationMsg).Layout)) }) @@ -410,8 +409,7 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle txt.Font.Weight = text.Medium txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tx.statusTooltip, func(gtx C) D { - setText(txStatus.Title) - return ticketStatusTooltip(gtx, l, txStatus) + return ticketStatusTooltip(gtx, l, tx) }) return txtLayout }), @@ -486,7 +484,6 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle txt.Text = age txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tx.daysBehindTooltip, func(gtx C) D { - setText(txStatus.Title) return walletNameDateTimeTooltip(gtx, l, "Ticket age", toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(age).Layout)) }) From b9c79232de53d0671651a9cb7131e1bb5f8575ad Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Sun, 12 Sep 2021 18:25:39 +0100 Subject: [PATCH 17/27] Fix content displayed in ticket age, days to vote and maturity duration tooltips --- ui/page/tickets/utils.go | 43 ++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 5ea39ce2b..a4b5cdd66 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -24,9 +24,6 @@ import ( const ( uint32Size = 32 << (^uint32(0) >> 32 & 1) // 32 or 64 maxInt32 = 1<<(uint32Size-1) - 1 - - // ticketAge = "Ticket age" - durationMsg = "10 hrs 47 mins (118/256 blocks)" ) type transactionItem struct { @@ -191,6 +188,8 @@ func ticketStatusTooltip(gtx C, l *load.Load, tx *transactionItem) layout.Dimens var title, mainDesc, subDesc string switch tx.status.TicketStatus { + case dcrlibwallet.TicketStatusUnmined: + title = "This ticket is waiting in mempool to be included in a block." case dcrlibwallet.TicketStatusImmature: title = "This ticket will enter the ticket pool and become a live ticket after 256 blocks (~20 hrs)." case dcrlibwallet.TicketStatusLive: @@ -269,13 +268,16 @@ func ticketCardTooltip(gtx C, rectLayout layout.Dimensions, tooltip *decredmater tooltip.Layout(gtx, rect, inset, body) } -func walletNameDateTimeTooltip(gtx C, l *load.Load, title string, body layout.Widget) layout.Dimensions { - walletNameLabel := l.Theme.Body2(title) - walletNameLabel.Color = l.Theme.Color.Gray +func titleDescTooltip(gtx C, l *load.Load, title string, desc string) layout.Dimensions { + titleLabel := l.Theme.Label(values.MarginPadding14, title) + titleLabel.Color = l.Theme.Color.Gray + + descLabel := l.Theme.Label(values.MarginPadding14, desc) + descLabel.Color = l.Theme.Color.DeepBlue return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(walletNameLabel.Layout), - layout.Rigid(body), + layout.Rigid(titleLabel.Layout), + layout.Rigid(toolTipContent(layout.Inset{Top: values.MarginPadding4}, descLabel.Layout)), ) } @@ -345,7 +347,8 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle }.Layout(gtx, func(gtx C) D { timeRemaining := time.Duration(float64(maturity-confirmations)*l.WL.MultiWallet.TargetTimePerBlockMinutes()) * time.Minute - txt := l.Theme.Label(values.TextSize14, timeRemaining.Truncate(time.Minute).String()) + maturityDuration := timeRemaining.Truncate(time.Minute).String() + txt := l.Theme.Label(values.TextSize14, maturityDuration) durationLayout := layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -354,9 +357,14 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle layout.Rigid(txt.Layout), ) + var durationTitle = "Live in" // immature + if txStatus.TicketStatus != dcrlibwallet.TicketStatusImmature { + // voted or revoked + durationTitle = "Spendable in" + } + ticketCardTooltip(gtx, durationLayout, tx.durationTooltip, func(gtx C) D { - return walletNameDateTimeTooltip(gtx, l, "durationTitle", - toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(durationMsg).Layout)) + return titleDescTooltip(gtx, l, durationTitle, fmt.Sprintf("%s (%d/%d blocks)", maturityDuration, confirmations, maturity)) }) return durationLayout @@ -448,22 +456,28 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tx.dateTooltip, func(gtx C) D { dateTime := time.Unix(tx.transaction.Timestamp, 0).Format("Jan 2, 2006 at 03:04:05 PM") - return walletNameDateTimeTooltip(gtx, l, "Purchased", - toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(dateTime).Layout)) + return titleDescTooltip(gtx, l, "Purchased", dateTime) }) return txtLayout }), layout.Rigid(func(gtx C) D { var age string + var tooltipTitle string if tx.ticketSpender != nil { // voted or revoked age = fmt.Sprintf("%d days", tx.ticketSpender.DaysToVoteOrRevoke) + if tx.ticketSpender.Type == dcrlibwallet.TxTypeVote { + tooltipTitle = "Days to vote" + } else { + tooltipTitle = "Days to miss" + } } else if txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { ticketAgeDuration := time.Since(time.Unix(tx.transaction.Timestamp, 0)).Seconds() age = ticketAge(int(ticketAgeDuration)) + tooltipTitle = "Ticket age" } else { return D{} } @@ -484,8 +498,7 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle txt.Text = age txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tx.daysBehindTooltip, func(gtx C) D { - return walletNameDateTimeTooltip(gtx, l, "Ticket age", - toolTipContent(layout.Inset{Top: values.MarginPadding8}, l.Theme.Body2(age).Layout)) + return titleDescTooltip(gtx, l, tooltipTitle, age) }) return txtLayout }), From aea8c27caa6e9729b3708d7ec4e82acd3ceace81 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Mon, 13 Sep 2021 06:26:42 +0100 Subject: [PATCH 18/27] Use transactionItem to populate tickets list values - Add second progress bar layout that fixes a border radius bug - Add shared data to transactionItem - Refresh window after loading data to remove lag in ui update - Use correct format for ticket maturity time --- ui/decredmaterial/progressbar.go | 49 +++++++++--- ui/decredmaterial/theme.go | 3 + ui/page/components/components.go | 2 +- ui/page/overview_page.go | 2 +- ui/page/tickets/list_page.go | 114 ++++++++++++++++++---------- ui/page/tickets/overview.go | 4 + ui/page/tickets/utils.go | 78 +++++++++---------- ui/page/transaction_details_page.go | 2 +- 8 files changed, 159 insertions(+), 95 deletions(-) diff --git a/ui/decredmaterial/progressbar.go b/ui/decredmaterial/progressbar.go index 2e089a03b..0ac6ff01a 100644 --- a/ui/decredmaterial/progressbar.go +++ b/ui/decredmaterial/progressbar.go @@ -15,9 +15,10 @@ import ( ) type ProgressBarStyle struct { - Radius unit.Value - Height unit.Value - Width unit.Value + Radius CornerRadius + Height unit.Value + Width unit.Value + Direction layout.Direction material.ProgressBarStyle } @@ -25,6 +26,30 @@ func (t *Theme) ProgressBar(progress int) ProgressBarStyle { return ProgressBarStyle{ProgressBarStyle: material.ProgressBar(t.Base, float32(progress)/100)} } +// This achieves a progress bar using linear layouts. +func (p ProgressBarStyle) Layout2(gtx C) D { + if p.Width.V <= 0 { + p.Width = unit.Px(float32(gtx.Constraints.Max.X)) + } + + return p.Direction.Layout(gtx, func(gtx C) D { + return LinearLayout{ + Width: gtx.Px(p.Width), + Height: gtx.Px(p.Height), + Background: p.TrackColor, + Border: Border{Radius: p.Radius}, + }.Layout2(gtx, func(gtx C) D { + + return LinearLayout{ + Width: int(p.Width.V * clamp1(p.Progress)), + Height: gtx.Px(p.Height), + Background: p.Color, + Border: Border{Radius: p.Radius}, + }.Layout(gtx) + }) + }) +} + func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions { shader := func(width float32, color color.NRGBA) layout.Dimensions { maxHeight := p.Height @@ -32,15 +57,19 @@ func (p ProgressBarStyle) Layout(gtx layout.Context) layout.Dimensions { maxHeight = unit.Dp(4) } - rr := float32(gtx.Px(p.Radius)) - if p.Radius.V <= 0 { - rr = float32(gtx.Px(unit.Dp(2))) - } - d := image.Point{X: int(width), Y: gtx.Px(maxHeight)} - height := float32(gtx.Px(maxHeight)) - clip.UniformRRect(f32.Rectangle{Max: f32.Pt(width, height)}, rr).Add(gtx.Ops) + + tr := float32(gtx.Px(unit.Dp(p.Radius.TopRight))) + tl := float32(gtx.Px(unit.Dp(p.Radius.TopLeft))) + br := float32(gtx.Px(unit.Dp(p.Radius.BottomRight))) + bl := float32(gtx.Px(unit.Dp(p.Radius.BottomLeft))) + + clip.RRect{ + Rect: f32.Rectangle{Max: f32.Pt(width, height)}, + NW: tl, NE: tr, SE: br, SW: bl, + }.Add(gtx.Ops) + paint.ColorOp{Color: color}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) diff --git a/ui/decredmaterial/theme.go b/ui/decredmaterial/theme.go index f82704f6d..a226e7fec 100644 --- a/ui/decredmaterial/theme.go +++ b/ui/decredmaterial/theme.go @@ -78,6 +78,7 @@ type Theme struct { Green500 color.NRGBA Turquoise100 color.NRGBA Turquoise300 color.NRGBA + Turquoise700 color.NRGBA Turquoise800 color.NRGBA Yellow color.NRGBA } @@ -143,6 +144,7 @@ func (t *Theme) setColorMode(darkMode bool) { t.Color.Orange3 = rgb(0xF8CABC) t.Color.Turquoise100 = rgb(0xB6EED7) t.Color.Turquoise300 = rgb(0x2DD8A3) + t.Color.Turquoise700 = rgb(0x00A05F) t.Color.Turquoise800 = rgb(0x008F52) t.Color.Yellow = rgb(0xffc84e) t.TextSize = unit.Sp(16) @@ -185,6 +187,7 @@ func (t *Theme) setColorMode(darkMode bool) { t.Color.Orange3 = rgb(0xF8CABC) t.Color.Turquoise100 = rgb(0xB6EED7) t.Color.Turquoise300 = rgb(0x2DD8A3) + t.Color.Turquoise700 = rgb(0x00A05F) t.Color.Turquoise800 = rgb(0x008F52) t.Color.Yellow = rgb(0xffc84e) t.TextSize = unit.Sp(16) diff --git a/ui/page/components/components.go b/ui/page/components/components.go index 1f469571a..d5e19ca05 100644 --- a/ui/page/components/components.go +++ b/ui/page/components/components.go @@ -119,7 +119,7 @@ func TransactionTitleIcon(l *load.Load, wal *dcrlibwallet.Wallet, tx *dcrlibwall if ticketSpender.Type == dcrlibwallet.TxTypeVote { txStatus.Title = "Voted" txStatus.Icon = l.Icons.TicketVotedIcon - txStatus.Color = l.Theme.Color.Success + txStatus.Color = l.Theme.Color.Turquoise700 txStatus.TicketStatus = dcrlibwallet.TicketStatusVotedOrRevoked txStatus.ProgressBarColor = l.Theme.Color.Turquoise300 txStatus.ProgressTrackColor = l.Theme.Color.Turquoise100 diff --git a/ui/page/overview_page.go b/ui/page/overview_page.go index 7d51f44e3..916020084 100644 --- a/ui/page/overview_page.go +++ b/ui/page/overview_page.go @@ -514,7 +514,7 @@ func (pg *OverviewPage) progressBarRow(gtx layout.Context, inset layout.Inset) l } p := pg.Theme.ProgressBar(progress) p.Height = values.MarginPadding8 - p.Radius = values.MarginPadding4 + p.Radius = decredmaterial.Radius(values.MarginPadding4.V) p.Color = pg.Theme.Color.Success p.TrackColor = pg.Theme.Color.Gray1 return p.Layout(gtx) diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 908bd025e..3dd4eebb2 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -2,13 +2,16 @@ package tickets import ( "context" + "fmt" "image/color" "sort" + "time" "gioui.org/layout" "gioui.org/text" "gioui.org/widget" + "github.com/decred/dcrd/dcrutil" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" @@ -98,6 +101,12 @@ func (pg *ListPage) listenForTxNotifications() { } switch n := notification.(type) { + case wallet.NewBlock: + selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] + if selectedWallet.ID == n.WalletID { + pg.fetchTickets() + pg.RefreshWindow() + } case wallet.NewTransaction: selectedWallet := pg.wallets[pg.walletDropDown.SelectedIndex()] if selectedWallet.ID == n.Transaction.WalletID { @@ -131,7 +140,8 @@ func (pg *ListPage) fetchTickets() { newestFirst := pg.orderDropDown.SelectedIndex() == 0 selectedWalletID := pg.wallets[pg.walletDropDown.SelectedIndex()].ID - w := pg.WL.MultiWallet.WalletWithID(selectedWalletID) + multiWallet := pg.WL.MultiWallet + w := multiWallet.WalletWithID(selectedWalletID) txs, err := w.GetTransactionsRaw(0, 0, txFilter, newestFirst) if err != nil { pg.Toast.NotifyError(err.Error()) @@ -167,12 +177,48 @@ func (pg *ListPage) fetchTickets() { ticketCopy := tx txStatus := components.TransactionTitleIcon(pg.Load, w, &tx, ticketSpender) + confirmations := tx.Confirmations(w.GetBestBlock()) + var ticketAge string + + showProgress := txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || txStatus.TicketStatus == dcrlibwallet.TicketStatusLive + if ticketSpender != nil { /// voted or revoked + showProgress = ticketSpender.Confirmations(w.GetBestBlock()) <= multiWallet.TicketMaturity() + ticketAge = fmt.Sprintf("%d days", ticketSpender.DaysToVoteOrRevoke) + } else if txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || + txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { + + ticketAgeDuration := time.Since(time.Unix(tx.Timestamp, 0)).Seconds() + ticketAge = ticketAgeTimeFormat(int(ticketAgeDuration)) + + } + + showTime := showProgress && txStatus.TicketStatus != dcrlibwallet.TicketStatusLive + + var progress float32 + if showProgress { + progressMax := multiWallet.TicketMaturity() + if txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { + progressMax = multiWallet.TicketExpiry() + } + + confs := confirmations + if ticketSpender != nil { + confs = ticketSpender.Confirmations(w.GetBestBlock()) + } + + progress = (float32(confs) / float32(progressMax)) * 100 + } tickets = append(tickets, &transactionItem{ transaction: &ticketCopy, ticketSpender: ticketSpender, status: txStatus, confirmations: tx.Confirmations(w.GetBestBlock()), + progress: progress, + showProgress: showProgress, + showTime: showTime, + purchaseTime: time.Unix(tx.Timestamp, 0).Format("Jan 2"), + ticketAge: ticketAge, statusTooltip: pg.Load.Theme.Tooltip(), dateTooltip: pg.Load.Theme.Tooltip(), @@ -306,34 +352,33 @@ func (pg *ListPage) Layout(gtx C) D { func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []*transactionItem) layout.Dimensions { gtx.Constraints.Min = gtx.Constraints.Max return pg.ticketsList.Layout(gtx, len(tickets), func(gtx C, index int) D { - w := pg.WL.MultiWallet.WalletWithID(tickets[index].transaction.WalletID) - t := transactionToTicket(*tickets[index].transaction, w, pg.WL.MultiWallet.TicketMaturity(), pg.WL.MultiWallet.TicketExpiry(), pg.WL.MultiWallet.GetBestBlock().Height) - st := ticketStatusProfile(pg.Load, t.Status) - if st == nil { - return D{} - } + var ticket = tickets[index] return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - var progressBarWidth int return layout.Stack{Alignment: layout.S}.Layout(gtx, layout.Stacked(func(gtx C) D { wrapIcon := pg.Theme.Card() - wrapIcon.Color = st.background + wrapIcon.Color = ticket.status.Background wrapIcon.Radius = decredmaterial.Radius(8) dims := wrapIcon.Layout(gtx, func(gtx C) D { - return layout.UniformInset(values.MarginPadding10).Layout(gtx, st.icon.Layout24dp) + return layout.UniformInset(values.MarginPadding10).Layout(gtx, ticket.status.Icon.Layout24dp) }) - progressBarWidth = dims.Size.X return dims }), - layout.Stacked(func(gtx C) D { - gtx.Constraints.Max.X = progressBarWidth - 4 - p := pg.Theme.ProgressBar(20) - p.Height, p.Radius = values.MarginPadding4, values.MarginPadding2 - p.Color = st.color - return p.Layout(gtx) + layout.Expanded(func(gtx C) D { + if !ticket.showProgress { + return D{} + } + p := pg.Theme.ProgressBar(int(ticket.progress)) + p.Width = values.MarginPadding44 + p.Height = values.MarginPadding4 + p.Direction = layout.SW + p.Radius = decredmaterial.BottomRadius(8) + p.Color = ticket.status.ProgressBarColor + p.TrackColor = ticket.status.ProgressTrackColor + return p.Layout2(gtx) }), ) }) @@ -356,35 +401,26 @@ func (pg *ListPage) ticketListLayout(gtx layout.Context, tickets []*transactionI }.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - dtime := pg.Theme.Label(values.TextSize14, t.DateTime) + + dtime := pg.Theme.Label(values.TextSize14, ticket.purchaseTime) dtime.Color = pg.Theme.Color.Gray2 return components.EndToEndRow(gtx, func(gtx C) D { - return components.LayoutBalance(gtx, pg.Load, t.Amount) + return components.LayoutBalance(gtx, pg.Load, dcrutil.Amount(ticket.transaction.Amount).String()) }, dtime.Layout) }), layout.Rigid(func(gtx C) D { - l := func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - txt := pg.Theme.Label(values.MarginPadding14, t.Status) - txt.Color = st.color - return txt.Layout(gtx) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding4, - Right: values.MarginPadding4, - }.Layout(gtx, func(gtx C) D { - ic := pg.Icons.ImageBrightness1 - ic.Color = pg.Theme.Color.Gray2 - return pg.Icons.ImageBrightness1.Layout(gtx, values.MarginPadding5) - }) - }), - layout.Rigid(pg.Theme.Label(values.MarginPadding14, t.WalletName).Layout), - ) + l := func(gtx C) layout.Dimensions { + txt := pg.Theme.Label(values.MarginPadding14, ticket.status.Title) + txt.Color = ticket.status.Color + return txt.Layout(gtx) } r := func(gtx C) layout.Dimensions { - txt := pg.Theme.Label(values.TextSize14, t.DaysBehind) + + if ticket.ticketAge == "" { + return D{} + } + + txt := pg.Theme.Label(values.TextSize14, ticket.ticketAge) txt.Color = pg.Theme.Color.Gray2 return txt.Layout(gtx) } diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 213c1c0a6..c145f7fd1 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -73,6 +73,7 @@ func (pg *Page) OnResume() { pg.Toast.NotifyError(err.Error()) } else { pg.ticketPrice = dcrutil.Amount(ticketPrice.TicketPrice).String() + pg.RefreshWindow() } }() @@ -82,6 +83,7 @@ func (pg *Page) OnResume() { pg.Toast.NotifyError(err.Error()) } else { pg.totalRewards = dcrutil.Amount(totalRewards).String() + pg.RefreshWindow() } }() @@ -91,6 +93,7 @@ func (pg *Page) OnResume() { pg.Toast.NotifyError(err.Error()) } else { pg.stakingOverview = overview + pg.RefreshWindow() } }() @@ -130,6 +133,7 @@ func (pg *Page) OnResume() { } pg.liveTickets = txItems + pg.RefreshWindow() }() go pg.WL.GetVSPList() diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index a4b5cdd66..3df16eec5 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -31,6 +31,11 @@ type transactionItem struct { ticketSpender *dcrlibwallet.Transaction status *components.TxStatus confirmations int32 + progress float32 + showProgress bool + showTime bool + purchaseTime string + ticketAge string statusTooltip *decredmaterial.Tooltip dateTooltip *decredmaterial.Tooltip @@ -186,12 +191,16 @@ func ticketStatusTooltip(gtx C, l *load.Load, tx *transactionItem) layout.Dimens status.Font.Weight = text.Medium status.Color = tx.status.Color + maturity := l.WL.MultiWallet.TicketMaturity() + blockTime := l.WL.MultiWallet.TargetTimePerBlockMinutes() + maturityDuration := time.Duration(maturity*int32(blockTime)) * time.Minute + maturityTime := ticketAgeTimeFormat(int(maturityDuration.Seconds())) var title, mainDesc, subDesc string switch tx.status.TicketStatus { case dcrlibwallet.TicketStatusUnmined: title = "This ticket is waiting in mempool to be included in a block." case dcrlibwallet.TicketStatusImmature: - title = "This ticket will enter the ticket pool and become a live ticket after 256 blocks (~20 hrs)." + title = fmt.Sprintf("This ticket will enter the ticket pool and become a live ticket after %d blocks (~%s).", maturity, maturityTime) case dcrlibwallet.TicketStatusLive: title = "Waiting to be chosen to vote." mainDesc = "The average vote time is 28 days, but can take up to 142 days." @@ -205,14 +214,11 @@ func ticketStatusTooltip(gtx C, l *load.Load, tx *transactionItem) layout.Dimens mainDesc = "The ticket price will become spendable after %d blocks (~%s)." } - maturity := l.WL.MultiWallet.TicketMaturity() - - if tx.confirmations > maturity { + if tx.ticketSpender.Confirmations(l.WL.MultiWallet.GetBestBlock().Height) > maturity { mainDesc = "" } else { - blockTime := l.WL.MultiWallet.TargetTimePerBlockMinutes() - maturityDuration := time.Duration(maturity*int32(blockTime)) * time.Minute - mainDesc = fmt.Sprintf(mainDesc, maturity, ticketAge(int(maturityDuration.Seconds()))) + + mainDesc = fmt.Sprintf(mainDesc, maturity, maturityTime) } case dcrlibwallet.TicketStatusExpired: title = fmt.Sprintf("This ticket has not been chosen to vote within %d blocks, and thus expired.", l.WL.MultiWallet.TicketMaturity()) @@ -291,15 +297,9 @@ func toolTipContent(inset layout.Inset, body layout.Widget) layout.Widget { func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalletName bool) layout.Dimensions { wal := l.WL.MultiWallet.WalletWithID(tx.transaction.WalletID) txStatus := tx.status - maturity := l.WL.MultiWallet.TicketMaturity() - expiry := l.WL.MultiWallet.TicketExpiry() - showProgress := txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || txStatus.TicketStatus == dcrlibwallet.TicketStatusLive - if tx.ticketSpender != nil { /// voted or revoked - showProgress = tx.ticketSpender.Confirmations(wal.GetBestBlock()) <= maturity - } - - showTime := showProgress && txStatus.TicketStatus != dcrlibwallet.TicketStatusLive + // add this data to transactionItem so it can be shared with list + maturity := l.WL.MultiWallet.TicketMaturity() return decredmaterial.LinearLayout{ Width: gtx.Px(values.MarginPadding168), @@ -315,11 +315,6 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle Border: decredmaterial.Border{Radius: decredmaterial.TopRadius(8)}, }.Layout2(gtx, func(gtx C) D { - confirmations := tx.confirmations - if tx.ticketSpender != nil { - confirmations = tx.ticketSpender.Confirmations(wal.GetBestBlock()) - } - return layout.Stack{}.Layout(gtx, layout.Stacked(func(gtx C) D { gtx.Constraints.Min.X = gtx.Constraints.Max.X @@ -331,7 +326,7 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle }) }), layout.Expanded(func(gtx C) D { - if !showTime { + if !tx.showTime { return D{} } @@ -346,9 +341,14 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle Left: values.MarginPadding8, }.Layout(gtx, func(gtx C) D { + confirmations := tx.confirmations + if tx.ticketSpender != nil { + confirmations = tx.ticketSpender.Confirmations(wal.GetBestBlock()) + } + timeRemaining := time.Duration(float64(maturity-confirmations)*l.WL.MultiWallet.TargetTimePerBlockMinutes()) * time.Minute - maturityDuration := timeRemaining.Truncate(time.Minute).String() - txt := l.Theme.Label(values.TextSize14, maturityDuration) + maturityDuration := ticketAgeTimeFormat(int(timeRemaining.Seconds())) + txt := l.Theme.Label(values.TextSize14, maturityTimeFormat(int(timeRemaining.Minutes()))) durationLayout := layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -375,21 +375,15 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle layout.Expanded(func(gtx C) D { return layout.S.Layout(gtx, func(gtx C) D { - if !showProgress { + if !tx.showProgress { return D{} } - progressMax := maturity - if txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { - progressMax = expiry - } - - progress := (float32(confirmations) / float32(progressMax)) * 100 - p := l.Theme.ProgressBar(int(progress)) - p.Height, p.Radius = values.MarginPadding4, values.MarginPadding1 + p := l.Theme.ProgressBar(int(tx.progress)) + p.Height = values.MarginPadding4 p.Color = txStatus.ProgressBarColor p.TrackColor = txStatus.ProgressTrackColor - return p.Layout(gtx) + return p.Layout2(gtx) }) }), ) @@ -408,7 +402,7 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle layout.Rigid(func(gtx C) D { return layout.Inset{ Top: values.MarginPadding4, - Bottom: values.MarginPadding16, + Bottom: values.MarginPadding8, }.Layout(gtx, func(gtx C) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -462,10 +456,8 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle }), layout.Rigid(func(gtx C) D { - var age string var tooltipTitle string if tx.ticketSpender != nil { // voted or revoked - age = fmt.Sprintf("%d days", tx.ticketSpender.DaysToVoteOrRevoke) if tx.ticketSpender.Type == dcrlibwallet.TxTypeVote { tooltipTitle = "Days to vote" } else { @@ -473,10 +465,6 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle } } else if txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { - - ticketAgeDuration := time.Since(time.Unix(tx.transaction.Timestamp, 0)).Seconds() - - age = ticketAge(int(ticketAgeDuration)) tooltipTitle = "Ticket age" } else { return D{} @@ -495,10 +483,10 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle }), layout.Rigid(func(gtx C) D { - txt.Text = age + txt.Text = tx.ticketAge txtLayout := txt.Layout(gtx) ticketCardTooltip(gtx, txtLayout, tx.daysBehindTooltip, func(gtx C) D { - return titleDescTooltip(gtx, l, tooltipTitle, age) + return titleDescTooltip(gtx, l, tooltipTitle, tx.ticketAge) }) return txtLayout }), @@ -611,7 +599,7 @@ func createClickGestures(count int) []*gesture.Click { return gestures } -func ticketAge(secs int) string { +func ticketAgeTimeFormat(secs int) string { if secs > 86399 { days := secs / 86400 return fmt.Sprintf("%dd", days) @@ -627,6 +615,10 @@ func ticketAge(secs int) string { } +func maturityTimeFormat(maturityTimeMinutes int) string { + return fmt.Sprintf("%02d:%02d", maturityTimeMinutes/60, maturityTimeMinutes%60) +} + func nextTicketRemaining(allsecs int) string { if allsecs == 0 { return "imminent" diff --git a/ui/page/transaction_details_page.go b/ui/page/transaction_details_page.go index 8aa4ae319..42403dd17 100644 --- a/ui/page/transaction_details_page.go +++ b/ui/page/transaction_details_page.go @@ -304,7 +304,7 @@ func (pg *TransactionDetailsPage) maturityProgressBar(gtx C) D { progress.TrackColor = pg.theme.Color.BlueProgressTint progress.Height = values.MarginPadding8 progress.Width = values.MarginPadding80 - progress.Radius = values.MarginPadding8 + progress.Radius = decredmaterial.Radius(values.MarginPadding8.V) timeLeft := pg.theme.Label(values.TextSize16, "18 hours") timeLeft.Color = pg.Theme.Color.DeepBlue From 94e2e71f97c6f1d0ae85bdf557b8fc938c679a1d Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Mon, 13 Sep 2021 06:59:16 +0100 Subject: [PATCH 19/27] Fix popups and complete tx details not showing on tx overview page --- ui/page/tickets/list_page.go | 108 ++++------------------------------- ui/page/tickets/overview.go | 36 +++++------- ui/page/tickets/utils.go | 105 ++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 120 deletions(-) diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 3dd4eebb2..7014f19f5 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -2,10 +2,7 @@ package tickets import ( "context" - "fmt" "image/color" - "sort" - "time" "gioui.org/layout" "gioui.org/text" @@ -148,103 +145,20 @@ func (pg *ListPage) fetchTickets() { return } - tickets := make([]*transactionItem, 0) - for _, tx := range txs { - ticketSpender, err := w.TicketSpender(tx.Hash) - if err != nil { - pg.Toast.NotifyError(err.Error()) - return + tickets, err := ticketsToTransactionItems(pg.Load, txs, newestFirst, func(filter int32) bool { + switch filter { + case dcrlibwallet.TxFilterVoted: + return ticketTypeDropdown == Voted + case dcrlibwallet.TxFilterRevoked: + return ticketTypeDropdown == Revoked } - // Apply voted and revoked tx filter - if (ticketTypeDropdown == Voted || ticketTypeDropdown == Revoked) && ticketSpender == nil { - continue - } - - if ticketTypeDropdown == Voted && ticketSpender.Type != dcrlibwallet.TxTypeVote { - continue - } - - if ticketTypeDropdown == Revoked && ticketSpender.Type != dcrlibwallet.TxTypeRevocation { - continue - } - - // This fixes a dcrlibwallet bug where live tickets transactions - // do not have updated data of ticket spender. - if txFilter == dcrlibwallet.TxFilterLive && ticketSpender != nil { - continue - } - - ticketCopy := tx - txStatus := components.TransactionTitleIcon(pg.Load, w, &tx, ticketSpender) - confirmations := tx.Confirmations(w.GetBestBlock()) - var ticketAge string - - showProgress := txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || txStatus.TicketStatus == dcrlibwallet.TicketStatusLive - if ticketSpender != nil { /// voted or revoked - showProgress = ticketSpender.Confirmations(w.GetBestBlock()) <= multiWallet.TicketMaturity() - ticketAge = fmt.Sprintf("%d days", ticketSpender.DaysToVoteOrRevoke) - } else if txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || - txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { - - ticketAgeDuration := time.Since(time.Unix(tx.Timestamp, 0)).Seconds() - ticketAge = ticketAgeTimeFormat(int(ticketAgeDuration)) - - } - - showTime := showProgress && txStatus.TicketStatus != dcrlibwallet.TicketStatusLive - - var progress float32 - if showProgress { - progressMax := multiWallet.TicketMaturity() - if txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { - progressMax = multiWallet.TicketExpiry() - } - - confs := confirmations - if ticketSpender != nil { - confs = ticketSpender.Confirmations(w.GetBestBlock()) - } - - progress = (float32(confs) / float32(progressMax)) * 100 - } - - tickets = append(tickets, &transactionItem{ - transaction: &ticketCopy, - ticketSpender: ticketSpender, - status: txStatus, - confirmations: tx.Confirmations(w.GetBestBlock()), - progress: progress, - showProgress: showProgress, - showTime: showTime, - purchaseTime: time.Unix(tx.Timestamp, 0).Format("Jan 2"), - ticketAge: ticketAge, - - statusTooltip: pg.Load.Theme.Tooltip(), - dateTooltip: pg.Load.Theme.Tooltip(), - daysBehindTooltip: pg.Load.Theme.Tooltip(), - durationTooltip: pg.Load.Theme.Tooltip(), - }) - } - - // bring vote and revoke tx forward - sort.Slice(tickets[:], func(i, j int) bool { - var timeStampI = tickets[i].transaction.Timestamp - var timeStampJ = tickets[j].transaction.Timestamp - - if tickets[i].ticketSpender != nil { - timeStampI = tickets[i].ticketSpender.Timestamp - } - - if tickets[j].ticketSpender != nil { - timeStampJ = tickets[j].ticketSpender.Timestamp - } - - if newestFirst { - return timeStampI > timeStampJ - } - return timeStampI < timeStampJ + return filter == txFilter }) + if err != nil { + pg.Toast.NotifyError(err.Error()) + return + } pg.tickets = tickets } diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index c145f7fd1..66d806a3b 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -105,31 +105,21 @@ func (pg *Page) OnResume() { return } - txItems := make([]*transactionItem, len(tickets)) - for i, ticket := range tickets { - - wal := mw.WalletWithID(ticket.WalletID) - - ticketSpender, err := wal.TicketSpender(ticket.Hash) - if err != nil { - pg.Toast.NotifyError(err.Error()) - return + txItems, err := ticketsToTransactionItems(pg.Load, tickets, true, func(filter int32) bool { + switch filter { + case dcrlibwallet.TxFilterUnmined: + fallthrough + case dcrlibwallet.TxFilterImmature: + fallthrough + case dcrlibwallet.TxFilterLive: + return true } - ticketCopy := ticket - txStatus := components.TransactionTitleIcon(pg.Load, wal, &ticket, ticketSpender) - - txItems[i] = &transactionItem{ - transaction: &ticketCopy, - ticketSpender: ticketSpender, - status: txStatus, - confirmations: ticket.Confirmations(wal.GetBestBlock()), - - statusTooltip: pg.Load.Theme.Tooltip(), - dateTooltip: pg.Load.Theme.Tooltip(), - daysBehindTooltip: pg.Load.Theme.Tooltip(), - durationTooltip: pg.Load.Theme.Tooltip(), - } + return false + }) + if err != nil { + pg.Toast.NotifyError(err.Error()) + return } pg.liveTickets = txItems diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 3df16eec5..65196bd85 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "math" + "sort" "strings" "time" @@ -62,6 +63,110 @@ const ( StakingExpired = "EXPIRED" ) +func ticketsToTransactionItems(l *load.Load, txs []dcrlibwallet.Transaction, newestFirst bool, hasFilter func(int32) bool) ([]*transactionItem, error) { + tickets := make([]*transactionItem, 0) + multiWallet := l.WL.MultiWallet + for _, tx := range txs { + w := multiWallet.WalletWithID(tx.WalletID) + + ticketSpender, err := w.TicketSpender(tx.Hash) + if err != nil { + return nil, err + } + + // Apply voted and revoked tx filter + if (hasFilter(dcrlibwallet.TxFilterVoted) || hasFilter(dcrlibwallet.TxFilterRevoked)) && ticketSpender == nil { + continue + } + + if hasFilter(dcrlibwallet.TxFilterVoted) && ticketSpender.Type != dcrlibwallet.TxTypeVote { + continue + } + + if hasFilter(dcrlibwallet.TxFilterRevoked) && ticketSpender.Type != dcrlibwallet.TxTypeRevocation { + continue + } + + // This fixes a dcrlibwallet bug where live tickets transactions + // do not have updated data of ticket spender. + if hasFilter(dcrlibwallet.TxFilterLive) && ticketSpender != nil { + continue + } + + ticketCopy := tx + txStatus := components.TransactionTitleIcon(l, w, &tx, ticketSpender) + confirmations := tx.Confirmations(w.GetBestBlock()) + var ticketAge string + + showProgress := txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || txStatus.TicketStatus == dcrlibwallet.TicketStatusLive + if ticketSpender != nil { /// voted or revoked + showProgress = ticketSpender.Confirmations(w.GetBestBlock()) <= multiWallet.TicketMaturity() + ticketAge = fmt.Sprintf("%d days", ticketSpender.DaysToVoteOrRevoke) + } else if txStatus.TicketStatus == dcrlibwallet.TicketStatusImmature || + txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { + + ticketAgeDuration := time.Since(time.Unix(tx.Timestamp, 0)).Seconds() + ticketAge = ticketAgeTimeFormat(int(ticketAgeDuration)) + + } + + showTime := showProgress && txStatus.TicketStatus != dcrlibwallet.TicketStatusLive + + var progress float32 + if showProgress { + progressMax := multiWallet.TicketMaturity() + if txStatus.TicketStatus == dcrlibwallet.TicketStatusLive { + progressMax = multiWallet.TicketExpiry() + } + + confs := confirmations + if ticketSpender != nil { + confs = ticketSpender.Confirmations(w.GetBestBlock()) + } + + progress = (float32(confs) / float32(progressMax)) * 100 + } + + tickets = append(tickets, &transactionItem{ + transaction: &ticketCopy, + ticketSpender: ticketSpender, + status: txStatus, + confirmations: tx.Confirmations(w.GetBestBlock()), + progress: progress, + showProgress: showProgress, + showTime: showTime, + purchaseTime: time.Unix(tx.Timestamp, 0).Format("Jan 2"), + ticketAge: ticketAge, + + statusTooltip: l.Theme.Tooltip(), + dateTooltip: l.Theme.Tooltip(), + daysBehindTooltip: l.Theme.Tooltip(), + durationTooltip: l.Theme.Tooltip(), + }) + } + + // bring vote and revoke tx forward + sort.Slice(tickets[:], func(i, j int) bool { + var timeStampI = tickets[i].transaction.Timestamp + var timeStampJ = tickets[j].transaction.Timestamp + + if tickets[i].ticketSpender != nil { + timeStampI = tickets[i].ticketSpender.Timestamp + } + + if tickets[j].ticketSpender != nil { + timeStampJ = tickets[j].ticketSpender.Timestamp + } + + if newestFirst { + return timeStampI > timeStampJ + } + return timeStampI < timeStampJ + }) + + return tickets, nil +} + func calculateDaysBehind(lastHeaderTime int64) string { diff := time.Since(time.Unix(lastHeaderTime, 0)) daysBehind := int(math.Round(diff.Hours() / 24)) From 9d29e929a714612cfbf0b4d3b691aaec14fdacea Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Mon, 13 Sep 2021 07:03:02 +0100 Subject: [PATCH 20/27] Remove tickets activity page and unused util functions --- ui/page/tickets/activity_page.go | 136 -------------------- ui/page/tickets/purchase_modal.go | 2 +- ui/page/tickets/utils.go | 200 ------------------------------ 3 files changed, 1 insertion(+), 337 deletions(-) delete mode 100644 ui/page/tickets/activity_page.go diff --git a/ui/page/tickets/activity_page.go b/ui/page/tickets/activity_page.go deleted file mode 100644 index ad3eb0395..000000000 --- a/ui/page/tickets/activity_page.go +++ /dev/null @@ -1,136 +0,0 @@ -package tickets - -import ( - "gioui.org/layout" - "gioui.org/text" - - "github.com/planetdecred/dcrlibwallet" - "github.com/planetdecred/godcr/ui/decredmaterial" - "github.com/planetdecred/godcr/ui/load" - "github.com/planetdecred/godcr/ui/page/components" - "github.com/planetdecred/godcr/wallet" - "github.com/planetdecred/godcr/ui/values" -) - -const ActivityPageID = "TicketsActivity" - -type ActivityPage struct { - *load.Load - tickets []Ticket - ticketsList layout.List - filterSorter int - - orderDropDown *decredmaterial.DropDown - ticketTypeDropDown *decredmaterial.DropDown - walletDropDown *decredmaterial.DropDown - - wallets []*dcrlibwallet.Wallet - - backButton decredmaterial.IconButton -} - -func newTicketActivityPage(l *load.Load) *ActivityPage { - pg := &ActivityPage{ - Load: l, - ticketsList: layout.List{Axis: layout.Vertical}, - } - pg.orderDropDown = components.CreateOrderDropDown(l) - pg.ticketTypeDropDown = pg.Theme.DropDown([]decredmaterial.DropDownItem{ - {Text: "All"}, - {Text: "Unmined"}, - {Text: "Immature"}, - {Text: "Live"}, - {Text: "Voted"}, - {Text: "Missed"}, - {Text: "Expired"}, - {Text: "Revoked"}, - }, 1) - - pg.backButton, _ = components.SubpageHeaderButtons(pg.Load) - - return pg -} - -func (pg *ActivityPage) ID() string { - return ActivityPageID -} - -func (pg *ActivityPage) OnResume() { - pg.wallets = pg.WL.SortedWalletList() - components.CreateOrUpdateWalletDropDown(pg.Load, &pg.walletDropDown, pg.wallets) -} - -func (pg *ActivityPage) Layout(gtx layout.Context) layout.Dimensions { - components.CreateOrUpdateWalletDropDown(pg.Load, &pg.walletDropDown, pg.wallets) - body := func(gtx C) D { - page := components.SubPage{ - Load: pg.Load, - Title: "Ticket activity", - BackButton: pg.backButton, - Back: func() { - pg.PopFragment() - }, - Body: func(gtx C) D { - return layout.Stack{Alignment: layout.N}.Layout(gtx, - layout.Expanded(func(gtx C) D { - return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, func(gtx C) D { - return pg.Theme.Card().Layout(gtx, func(gtx C) D { - gtx.Constraints.Min = gtx.Constraints.Max - if len(pg.tickets) == 0 { - txt := pg.Theme.Body1("No tickets yet") - txt.Color = pg.Theme.Color.Gray2 - txt.Alignment = text.Middle - return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { return txt.Layout(gtx) }) - } - return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { - return pg.ticketsList.Layout(gtx, len(pg.tickets), func(gtx C, index int) D { - return ticketActivityRow(gtx, pg.Load, pg.tickets[index], index) - }) - }) - }) - }) - }), - layout.Stacked(func(gtx C) D { - return pg.dropDowns(gtx) - }), - ) - }, - } - return page.Layout(gtx) - } - - return components.UniformPadding(gtx, body) -} - -func (pg *ActivityPage) dropDowns(gtx layout.Context) layout.Dimensions { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return pg.walletDropDown.Layout(gtx) - }), - layout.Rigid(func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding5, - }.Layout(gtx, func(gtx C) D { - return pg.ticketTypeDropDown.Layout(gtx) - }) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding5, - }.Layout(gtx, func(gtx C) D { - return pg.orderDropDown.Layout(gtx) - }) - }), - ) - }), - ) -} - -func (pg *ActivityPage) Handle() { - -} - -func (pg *ActivityPage) OnClose() {} diff --git a/ui/page/tickets/purchase_modal.go b/ui/page/tickets/purchase_modal.go index 144848420..173bfde0d 100644 --- a/ui/page/tickets/purchase_modal.go +++ b/ui/page/tickets/purchase_modal.go @@ -202,7 +202,7 @@ func (tp *ticketPurchaseModal) initializeAccountSelector() { wal := tp.WL.MultiWallet.WalletWithID(account.WalletID) // Imported and watch only wallet accounts are invalid for sending - accountIsValid := account.Number != maxInt32 && !wal.IsWatchingOnlyWallet() + accountIsValid := account.Number != dcrlibwallet.ImportedAccountNumber && !wal.IsWatchingOnlyWallet() if wal.ReadBoolConfigValueForKey(dcrlibwallet.AccountMixerConfigSet, false) { // privacy is enabled for selected wallet diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 65196bd85..5ad7815d9 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -3,8 +3,6 @@ package tickets import ( "fmt" "image" - "image/color" - "math" "sort" "strings" "time" @@ -22,11 +20,6 @@ import ( "github.com/planetdecred/godcr/ui/values" ) -const ( - uint32Size = 32 << (^uint32(0) >> 32 & 1) // 32 or 64 - maxInt32 = 1<<(uint32Size-1) - 1 -) - type transactionItem struct { transaction *dcrlibwallet.Transaction ticketSpender *dcrlibwallet.Transaction @@ -167,62 +160,6 @@ func ticketsToTransactionItems(l *load.Load, txs []dcrlibwallet.Transaction, new return tickets, nil } -func calculateDaysBehind(lastHeaderTime int64) string { - diff := time.Since(time.Unix(lastHeaderTime, 0)) - daysBehind := int(math.Round(diff.Hours() / 24)) - if daysBehind < 1 { - return "<1 day" - } else if daysBehind == 1 { - return "1 day" - } else { - return fmt.Sprintf("%d days", daysBehind) - } -} - -func transactionToTicket(tx dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, maturity, expiry, bestBlock int32) Ticket { - return Ticket{ - Status: getTicketStatus(tx, w, maturity, expiry, bestBlock), - Amount: dcrutil.Amount(tx.Amount).String(), - DateTime: time.Unix(tx.Timestamp, 0).Format("Jan 2, 2006 03:04:05 PM"), - MonthDay: time.Unix(tx.Timestamp, 0).Format("Jan 2"), - DaysBehind: calculateDaysBehind(tx.Timestamp), - Fee: dcrutil.Amount(tx.Fee).String(), - WalletName: w.Name, - } -} - -func getTicketStatus(txn dcrlibwallet.Transaction, w *dcrlibwallet.Wallet, ticketMaturity, ticketExpiry, bestBlock int32) string { - if txn.Type == dcrlibwallet.TxTypeVote { - return StakingVoted - } - - if txn.Type == dcrlibwallet.TxTypeRevocation { - return StakingRevoked - } - - s := txn.TicketStatus(ticketMaturity, ticketExpiry, bestBlock) - switch s { - case dcrlibwallet.TicketStatusUnmined: - return StakingUnmined - case dcrlibwallet.TicketStatusImmature: - return StakingImmature - case dcrlibwallet.TicketStatusLive: - return StakingLive - case dcrlibwallet.TicketStatusVotedOrRevoked: - // handle revocation and voted tickets that have the type "TicketPurchase" - tx, _ := w.TicketSpender(txn.Hash) - if tx.Type == dcrlibwallet.TxTypeVote { - return StakingVoted - } - - if tx.Type == dcrlibwallet.TxTypeRevocation { - return StakingRevoked - } - } - - return "" -} - func allLiveTickets(mw *dcrlibwallet.MultiWallet) ([]dcrlibwallet.Transaction, error) { var tickets []dcrlibwallet.Transaction liveTicketFilters := []int32{dcrlibwallet.TxFilterUnmined, dcrlibwallet.TxFilterImmature, dcrlibwallet.TxFilterLive} @@ -238,59 +175,6 @@ func allLiveTickets(mw *dcrlibwallet.MultiWallet) ([]dcrlibwallet.Transaction, e return tickets, nil } -func ticketStatusProfile(l *load.Load, ticketStatus string) *struct { - icon *decredmaterial.Image - color color.NRGBA - background color.NRGBA -} { - m := map[string]struct { - icon *decredmaterial.Image - color color.NRGBA - background color.NRGBA - }{ - StakingUnmined: { - l.Icons.TicketUnminedIcon, - l.Theme.Color.DeepBlue, - l.Theme.Color.LightBlue, - }, - StakingImmature: { - l.Icons.TicketImmatureIcon, - l.Theme.Color.DeepBlue, - l.Theme.Color.LightBlue, - }, - StakingLive: { - l.Icons.TicketLiveIcon, - l.Theme.Color.Primary, - l.Theme.Color.LightBlue, - }, - StakingVoted: { - l.Icons.TicketVotedIcon, - l.Theme.Color.Success, - l.Theme.Color.Success2, - }, - "MISSED": { - l.Icons.TicketMissedIcon, - l.Theme.Color.Gray, - l.Theme.Color.LightGray, - }, - StakingExpired: { - l.Icons.TicketExpiredIcon, - l.Theme.Color.Gray, - l.Theme.Color.LightGray, - }, - StakingRevoked: { - l.Icons.TicketRevokedIcon, - l.Theme.Color.Orange, - l.Theme.Color.Orange2, - }, - } - st, ok := m[ticketStatus] - if !ok { - return nil - } - return &st -} - func ticketStatusTooltip(gtx C, l *load.Load, tx *transactionItem) layout.Dimensions { status := l.Theme.Label(values.MarginPadding14, strings.ToUpper(tx.status.Title)) status.Font.Weight = text.Medium @@ -604,90 +488,6 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle ) } -// ticketActivityRow layouts out ticket info, display ticket activities on the tickets_page and tickets_activity_page -func ticketActivityRow(gtx layout.Context, l *load.Load, t Ticket, index int) layout.Dimensions { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{Right: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - st := ticketStatusProfile(l, t.Status) - if st == nil { - return layout.Dimensions{} - } - return st.icon.Layout24dp(gtx) - }) - }), - layout.Flexed(1, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - if index == 0 { - return layout.Dimensions{} - } - gtx.Constraints.Min.X = gtx.Constraints.Max.X - separator := l.Theme.Separator() - separator.Width = gtx.Constraints.Max.X - return layout.E.Layout(gtx, func(gtx C) D { - return separator.Layout(gtx) - }) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Top: values.MarginPadding8, - Bottom: values.MarginPadding8, - }.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - labelStatus := l.Theme.Label(values.TextSize18, strings.Title(strings.ToLower(t.Status))) - labelStatus.Color = l.Theme.Color.DeepBlue - - labelDaysBehind := l.Theme.Label(values.TextSize14, t.DaysBehind) - labelDaysBehind.Color = l.Theme.Color.DeepBlue - - return components.EndToEndRow(gtx, - labelStatus.Layout, - labelDaysBehind.Layout) - }), - layout.Rigid(func(gtx C) D { - return layout.Flex{ - Alignment: layout.Middle, - }.Layout(gtx, - layout.Rigid(func(gtx C) D { - txt := l.Theme.Label(values.TextSize14, t.WalletName) - txt.Color = l.Theme.Color.Gray2 - return txt.Layout(gtx) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding4, - Right: values.MarginPadding4, - }.Layout(gtx, func(gtx C) D { - ic := l.Icons.ImageBrightness1 - ic.Color = l.Theme.Color.Gray2 - return l.Icons.ImageBrightness1.Layout(gtx, values.MarginPadding5) - }) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Right: values.MarginPadding4, - }.Layout(gtx, func(gtx C) D { - ic := l.Icons.TicketIconInactive - return ic.Layout12dp(gtx) - }) - }), - layout.Rigid(func(gtx C) D { - txt := l.Theme.Label(values.TextSize14, t.Amount) - txt.Color = l.Theme.Color.Gray2 - return txt.Layout(gtx) - }), - ) - }), - ) - }) - }), - ) - }), - ) -} - // todo: cleanup func createOrderDropDown(th *decredmaterial.Theme) *decredmaterial.DropDown { return th.DropDown([]decredmaterial.DropDownItem{{Text: values.String(values.StrNewest)}, From 26e5aeb59d7986bd9f8a6c169f6d8e3c29429135 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Mon, 13 Sep 2021 09:19:16 +0100 Subject: [PATCH 21/27] Cleanup ticket purchase modal --- go.mod | 2 +- go.sum | 2 + ui/load/vsp.go | 45 ++--------- ui/page/overview_page.go | 7 -- ui/page/tickets/purchase_modal.go | 125 +++++++++++++++--------------- ui/page/tickets/utils.go | 2 +- ui/page/tickets/vsp_selector.go | 32 ++++---- ui/state.go | 2 +- ui/values/dimensions.go | 1 + ui/window.go | 2 +- wallet/responses.go | 2 +- 11 files changed, 90 insertions(+), 132 deletions(-) diff --git a/go.mod b/go.mod index 88d8c3902..1148d5395 100644 --- a/go.mod +++ b/go.mod @@ -31,5 +31,5 @@ require ( replace ( github.com/decred/dcrdata/txhelpers/v4 => github.com/decred/dcrdata/txhelpers/v4 v4.0.0-20200108145420-f82113e7e212 - github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2 + github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210912175524-041481a23c8b ) diff --git a/go.sum b/go.sum index 5cd9c7718..fc5e033b9 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e h1:x6A github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2 h1:EHZLz82ivykxAkm6BI15FBrPL/PdUsUD2gtrb6pnFpY= github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210912175524-041481a23c8b h1:JvcNx/P2fSfgXpySx/HVbnRyYo6ZPgnpyVmnar6SuE4= +github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210912175524-041481a23c8b/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.4.8 h1:Rpmta4xZ/MgZnriKNd24iZMhGpP5dvUcs/uqfBapKZY= github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= diff --git a/ui/load/vsp.go b/ui/load/vsp.go index e872d37a5..ac2099d79 100644 --- a/ui/load/vsp.go +++ b/ui/load/vsp.go @@ -1,7 +1,6 @@ package load import ( - "context" "errors" "fmt" "strings" @@ -95,12 +94,12 @@ func (wl *WalletLoad) GetVSPList() { } wl.MultiWallet.ReadUserConfigValue(dcrlibwallet.VSPHostConfigKey, &valueOut) - var loadedVSP []wallet.VSPInfo + var loadedVSP []*wallet.VSPInfo for _, host := range valueOut.List { v, err := getVSPInfo(host) if err == nil { - loadedVSP = append(loadedVSP, wallet.VSPInfo{ + loadedVSP = append(loadedVSP, &wallet.VSPInfo{ Host: host, Info: v, }) @@ -110,14 +109,14 @@ func (wl *WalletLoad) GetVSPList() { l, _ := getInitVSPInfo("https://api.decred.org/?c=vsp") for h, v := range l { if strings.Contains(wl.Wallet.Net, v.Network) { - loadedVSP = append(loadedVSP, wallet.VSPInfo{ + loadedVSP = append(loadedVSP, &wallet.VSPInfo{ Host: fmt.Sprintf("https://%s", h), Info: v, }) } } - (*wl.VspInfo).List = loadedVSP + wl.VspInfo.List = loadedVSP } // TicketPrice get ticket price @@ -130,40 +129,6 @@ func (wl *WalletLoad) TicketPrice() int64 { return pr.TicketPrice } -func (wl *WalletLoad) NewVSPD(host string, walletID int, accountID int32) (*dcrlibwallet.VSP, error) { - if host == "" { - return nil, fmt.Errorf("Host is required") - } - wall := wl.MultiWallet.WalletWithID(walletID) - if wall == nil { - return nil, ErrIDNotExist - } - vspd, err := wl.MultiWallet.NewVSPClient(host, walletID, uint32(accountID)) - if err != nil { - return nil, fmt.Errorf("Something wrong when creating new VSPD: %v", err) - } - return vspd, nil -} - -func (wl *WalletLoad) PurchaseTicket(walletID int, tickets uint32, passphrase []byte, vspd *dcrlibwallet.VSP) (err error) { - wall := wl.MultiWallet.WalletWithID(walletID) - if wall == nil { - return fmt.Errorf("wallet ID does not exist") - } - - _, err = vspd.GetInfo(context.Background()) - if err != nil { - return err - } - - err = vspd.PurchaseTickets(int32(tickets), wl.MultiWallet.GetBestBlock().Height+256, passphrase) - if err != nil { - return - } - - return -} - func (wl *WalletLoad) AddVSP(host string) (err error) { var valueOut struct { Remember string @@ -190,7 +155,7 @@ func (wl *WalletLoad) AddVSP(host string) (err error) { valueOut.List = append(valueOut.List, host) wl.MultiWallet.SaveUserConfigValue(dcrlibwallet.VSPHostConfigKey, valueOut) - (*wl.VspInfo).List = append((*wl.VspInfo).List, wallet.VSPInfo{ + (*wl.VspInfo).List = append((*wl.VspInfo).List, &wallet.VSPInfo{ Host: host, Info: info, }) diff --git a/ui/page/overview_page.go b/ui/page/overview_page.go index 916020084..8bc9d5a02 100644 --- a/ui/page/overview_page.go +++ b/ui/page/overview_page.go @@ -146,13 +146,6 @@ func (pg *OverviewPage) loadTransactions() { // Layout lays out the entire content for overview pg. func (pg *OverviewPage) Layout(gtx layout.Context) layout.Dimensions { pg.queue = gtx - if pg.WL.Info.LoadedWallets == 0 { - return components.UniformPadding(gtx, func(gtx C) D { - return layout.Center.Layout(gtx, func(gtx C) D { - return pg.Theme.H3(values.String(values.StrNoWalletLoaded)).Layout(gtx) - }) - }) - } pageContent := []func(gtx C) D{ func(gtx C) D { diff --git a/ui/page/tickets/purchase_modal.go b/ui/page/tickets/purchase_modal.go index 173bfde0d..19f69db2a 100644 --- a/ui/page/tickets/purchase_modal.go +++ b/ui/page/tickets/purchase_modal.go @@ -5,11 +5,10 @@ import ( "image/color" "strconv" - "gioui.org/gesture" "gioui.org/layout" "gioui.org/widget" - "github.com/decred/dcrd/dcrutil" + "github.com/decred/dcrd/dcrutil/v3" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/load" @@ -22,23 +21,19 @@ const purchaseModalID = "ticket_purchase_modal" type ticketPurchaseModal struct { *load.Load - ticketPrice string - totalCost int64 - balanceLessCost int64 - vspIsFetched bool - isPurchaseLoading bool + ticketPrice dcrutil.Amount + totalCost int64 + balanceLessCost int64 + vspIsFetched bool modal decredmaterial.Modal tickets decredmaterial.Editor rememberVSP decredmaterial.CheckBoxStyle - selectVSP []*gesture.Click cancelPurchase decredmaterial.Button reviewPurchase decredmaterial.Button accountSelector *components.AccountSelector vspSelector *vspSelector - - vsp *dcrlibwallet.VSP } func newTicketPurchaseModal(l *load.Load) *ticketPurchaseModal { @@ -60,6 +55,22 @@ func newTicketPurchaseModal(l *load.Load) *ticketPurchaseModal { return tp } +func (tp *ticketPurchaseModal) OnResume() { + tp.initializeAccountSelector() + err := tp.accountSelector.SelectFirstWalletValidAccount() + if err != nil { + tp.Toast.NotifyError(err.Error()) + } + + tp.vspSelector = newVSPSelector(tp.Load).title("Select a vsp") + tp.ticketPrice = dcrutil.Amount(tp.WL.TicketPrice()) + + if tp.vspIsFetched && tp.WL.GetRememberVSP() != "" { + tp.vspSelector.selectVSP(tp.WL.GetRememberVSP()) + tp.rememberVSP.CheckBox.Value = true + } +} + func (tp *ticketPurchaseModal) Layout(gtx layout.Context) layout.Dimensions { l := []layout.Widget{ func(gtx C) D { @@ -73,28 +84,30 @@ func (tp *ticketPurchaseModal) Layout(gtx layout.Context) layout.Dimensions { }), layout.Rigid(func(gtx C) D { return layout.Inset{Top: values.MarginPadding8}.Layout(gtx, func(gtx C) D { - return components.LayoutBalance(gtx, tp.Load, tp.ticketPrice) + return components.LayoutBalanceSize(gtx, tp.Load, tp.ticketPrice.String(), values.Size28) }) }), ) }) }), layout.Rigid(func(gtx C) D { - return layout.Flex{}.Layout(gtx, - layout.Flexed(.5, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - tit := tp.Theme.Label(values.TextSize14, "Total") - tit.Color = tp.Theme.Color.Gray2 - return tit.Layout(gtx) - }), - layout.Rigid(func(gtx C) D { - return tp.Theme.Label(values.TextSize16, tp.ticketPrice).Layout(gtx) - }), - ) - }), - layout.Flexed(.5, tp.tickets.Layout), - ) + return layout.Inset{Top: values.MarginPadding8}.Layout(gtx, func(gtx C) D { + return layout.Flex{}.Layout(gtx, + layout.Flexed(.5, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx C) D { + tit := tp.Theme.Label(values.TextSize14, "Total") + tit.Color = tp.Theme.Color.Gray3 + return tit.Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + return tp.Theme.Label(values.TextSize16, dcrutil.Amount(int64(tp.ticketPrice)*tp.ticketCount()).String()).Layout(gtx) + }), + ) + }), + layout.Flexed(.5, tp.tickets.Layout), + ) + }) }), ) }, @@ -145,7 +158,7 @@ func (tp *ticketPurchaseModal) ticketCount() int64 { } func (tp *ticketPurchaseModal) canPurchase() bool { - if tp.vspSelector.selectedVSP.Info == nil { + if tp.vspSelector.selectedVSP == nil { return false } @@ -155,10 +168,6 @@ func (tp *ticketPurchaseModal) canPurchase() bool { return false } - if tp.vspSelector.selectedVSP.Host == "" { - return false - } - if tp.ticketCount() < 1 { return false } @@ -178,22 +187,6 @@ func (tp *ticketPurchaseModal) Dismiss() { tp.DismissModal(tp) } -func (tp *ticketPurchaseModal) OnResume() { - tp.initializeAccountSelector() - err := tp.accountSelector.SelectFirstWalletValidAccount() - if err != nil { - tp.Toast.NotifyError(err.Error()) - } - - tp.vspSelector = newVSPSelector(tp.Load).title("Select a vsp") - tp.ticketPrice = dcrutil.Amount(tp.WL.TicketPrice()).String() - - if tp.vspIsFetched && tp.WL.GetRememberVSP() != "" { - tp.vspSelector.selectVSP(tp.WL.GetRememberVSP()) - tp.rememberVSP.CheckBox.Value = true - } -} - func (tp *ticketPurchaseModal) initializeAccountSelector() { tp.accountSelector = components.NewAccountSelector(tp.Load). Title("Purchasing account"). @@ -216,35 +209,44 @@ func (tp *ticketPurchaseModal) initializeAccountSelector() { func (tp *ticketPurchaseModal) OnDismiss() {} func (tp *ticketPurchaseModal) calculateTotals() { - accountBalance := tp.accountSelector.SelectedAccount().Balance.Spendable - feePercentage := tp.vspSelector.selectedVSP.Info.FeePercentage - total := tp.WL.TicketPrice() * tp.ticketCount() - fee := int64((float64(total) / 100) * feePercentage) - tp.totalCost = total + fee - tp.balanceLessCost = accountBalance - tp.totalCost -} + account := tp.accountSelector.SelectedAccount() + wal := tp.WL.MultiWallet.WalletWithID(account.WalletID) -func (tp *ticketPurchaseModal) createNewVSPD() { - selectedAccount := tp.accountSelector.SelectedAccount() - selectedVSP := tp.vspSelector.SelectedVSP() - vspd, err := tp.WL.NewVSPD(selectedVSP.Host, selectedAccount.WalletID, selectedAccount.Number) + ticketPrice, err := wal.TicketPrice() if err != nil { tp.Toast.NotifyError(err.Error()) + return } - tp.vsp = vspd + + feePercentage := tp.vspSelector.selectedVSP.Info.FeePercentage + total := ticketPrice.TicketPrice * tp.ticketCount() + fee := int64((float64(total) / 100) * feePercentage) + + tp.totalCost = total + fee + tp.balanceLessCost = account.Balance.Spendable - tp.totalCost } func (tp *ticketPurchaseModal) purchaseTickets(password []byte) { tp.Dismiss() - tp.Toast.Notify(fmt.Sprintf("attempting to purchase %v ticket(s)", tp.ticketCount())) + tp.Toast.Notify(fmt.Sprintf("Attempting to purchase %v ticket(s)", tp.ticketCount())) go func() { + selectedVSP := tp.vspSelector.SelectedVSP() account := tp.accountSelector.SelectedAccount() - err := tp.WL.PurchaseTicket(account.WalletID, uint32(tp.ticketCount()), password, tp.vsp) + wal := tp.WL.MultiWallet.WalletWithID(account.WalletID) + + vsp, err := tp.WL.MultiWallet.NewVSPClient(selectedVSP.Host, account.WalletID, uint32(account.Number)) if err != nil { tp.Toast.NotifyError(err.Error()) return } + + err = vsp.PurchaseTickets(int32(tp.ticketCount()), wal.GetBestBlock()+256, password) + if err != nil { + tp.Toast.NotifyError(err.Error()) + return + } + tp.Toast.Notify(fmt.Sprintf("%v ticket(s) purchased successfully", tp.ticketCount())) }() } @@ -263,7 +265,6 @@ func (tp *ticketPurchaseModal) Handle() { } if tp.reviewPurchase.Button.Clicked() && tp.canPurchase() { - go tp.createNewVSPD() if tp.vspSelector.Changed() && tp.rememberVSP.CheckBox.Value { tp.WL.RememberVSP(tp.vspSelector.selectedVSP.Host) diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 5ad7815d9..c1a224cc2 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -341,7 +341,7 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle durationLayout := layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Right: values.MarginPadding4}.Layout(gtx, l.Icons.TimerIcon.Layout) + return layout.Inset{Right: values.MarginPadding4}.Layout(gtx, l.Icons.TimerIcon.Layout12dp) }), layout.Rigid(txt.Layout), ) diff --git a/ui/page/tickets/vsp_selector.go b/ui/page/tickets/vsp_selector.go index d63209b2f..aaacc84df 100644 --- a/ui/page/tickets/vsp_selector.go +++ b/ui/page/tickets/vsp_selector.go @@ -23,14 +23,12 @@ type vspSelector struct { changed bool showVSPModal *widget.Clickable - vspInfo *wallet.VSP - selectedVSP wallet.VSPInfo + selectedVSP *wallet.VSPInfo } func newVSPSelector(l *load.Load) *vspSelector { v := &vspSelector{ Load: l, - vspInfo: l.WL.VspInfo, showVSPModal: new(widget.Clickable), } return v @@ -48,7 +46,7 @@ func (v *vspSelector) Changed() bool { } func (v *vspSelector) selectVSP(vspHost string) { - for _, vsp := range (*v.vspInfo).List { + for _, vsp := range v.WL.VspInfo.List { if vsp.Host == vspHost { v.changed = true v.selectedVSP = vsp @@ -57,7 +55,7 @@ func (v *vspSelector) selectVSP(vspHost string) { } } -func (v *vspSelector) SelectedVSP() wallet.VSPInfo { +func (v *vspSelector) SelectedVSP() *wallet.VSPInfo { return v.selectedVSP } @@ -65,7 +63,7 @@ func (v *vspSelector) handle() { if v.showVSPModal.Clicked() { newVSPSelectorModal(v.Load). title("Voting service provider"). - vspSelected(func(info wallet.VSPInfo) { + vspSelected(func(info *wallet.VSPInfo) { v.selectVSP(info.Host) }). Show() @@ -86,7 +84,7 @@ func (v *vspSelector) Layout(gtx layout.Context) layout.Dimensions { return decredmaterial.Clickable(gtx, v.showVSPModal, func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(func(gtx C) D { - if v.selectedVSP.Host == "" { + if v.selectedVSP == nil { txt := v.Theme.Label(values.TextSize16, "Select VSP...") txt.Color = v.Theme.Color.Gray2 return txt.Layout(gtx) @@ -97,7 +95,7 @@ func (v *vspSelector) Layout(gtx layout.Context) layout.Dimensions { return layout.E.Layout(gtx, func(gtx C) D { return layout.Flex{}.Layout(gtx, layout.Rigid(func(gtx C) D { - if v.selectedVSP.Info == nil { + if v.selectedVSP == nil { return layout.Dimensions{} } txt := v.Theme.Label(values.TextSize16, fmt.Sprintf("%v%%", v.selectedVSP.Info.FeePercentage)) @@ -132,19 +130,17 @@ type vspSelectorModal struct { inputVSP decredmaterial.Editor addVSP decredmaterial.Button - vspInfo *wallet.VSP vspHosts *layout.List selectVSP []*gesture.Click - selectedVSP wallet.VSPInfo + selectedVSP *wallet.VSPInfo - vspSelectedCallback func(wallet.VSPInfo) + vspSelectedCallback func(*wallet.VSPInfo) } func newVSPSelectorModal(l *load.Load) *vspSelectorModal { v := &vspSelectorModal{ Load: l, - vspInfo: l.WL.VspInfo, inputVSP: l.Theme.Editor(new(widget.Editor), "Add a new VSP..."), addVSP: l.Theme.Button(new(widget.Clickable), "Save"), vspHosts: &layout.List{Axis: layout.Vertical}, @@ -182,7 +178,7 @@ func (v *vspSelectorModal) Handle() { }() } - vspList := (*v.vspInfo).List + vspList := v.WL.VspInfo.List if len(vspList) != len(v.selectVSP) { v.selectVSP = createClickGestures(len(vspList)) } @@ -193,7 +189,7 @@ func (v *vspSelectorModal) title(title string) *vspSelectorModal { return v } -func (v *vspSelectorModal) vspSelected(callback func(wallet.VSPInfo)) *vspSelectorModal { +func (v *vspSelectorModal) vspSelected(callback func(*wallet.VSPInfo)) *vspSelectorModal { v.vspSelectedCallback = callback v.Dismiss() return v @@ -218,8 +214,8 @@ func (v *vspSelectorModal) Layout(gtx layout.Context) layout.Dimensions { }) }), layout.Rigid(func(gtx C) D { - listVSP := (*v.vspInfo).List - return v.vspHosts.Layout(gtx, len(v.selectVSP), func(gtx C, i int) D { + listVSP := v.WL.VspInfo.List + return v.vspHosts.Layout(gtx, len(listVSP), func(gtx C, i int) D { click := v.selectVSP[i] pointer.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Add(gtx.Ops) click.Add(gtx.Ops) @@ -234,7 +230,7 @@ func (v *vspSelectorModal) Layout(gtx layout.Context) layout.Dimensions { }) }), layout.Rigid(func(gtx C) D { - if v.selectedVSP.Host != listVSP[i].Host { + if v.selectedVSP != nil || v.selectedVSP != listVSP[i] { return layout.Inset{Right: values.MarginPadding40}.Layout(gtx, func(gtx C) D { return layout.Dimensions{} }) @@ -257,7 +253,7 @@ func (v *vspSelectorModal) Layout(gtx layout.Context) layout.Dimensions { }, 900) } -func (v *vspSelectorModal) handlerSelectVSP(events []gesture.ClickEvent, info wallet.VSPInfo) { +func (v *vspSelectorModal) handlerSelectVSP(events []gesture.ClickEvent, info *wallet.VSPInfo) { for _, e := range events { if e.Type == gesture.TypeClick { v.selectedVSP = info diff --git a/ui/state.go b/ui/state.go index 465abcc12..6c2803ff8 100644 --- a/ui/state.go +++ b/ui/state.go @@ -51,7 +51,7 @@ func (win *Window) updateStates(update interface{}) { win.walletUnspentOutputs = e case *wallet.VSPInfo: win.states.loading = false - win.vspInfo.List = append(win.vspInfo.List, *e) + // win.vspInfo.List = append(win.vspInfo.List, *e) return case *wallet.VSP: win.vspInfo = e diff --git a/ui/values/dimensions.go b/ui/values/dimensions.go index 40a0ee64c..d02c4cbf1 100644 --- a/ui/values/dimensions.go +++ b/ui/values/dimensions.go @@ -4,6 +4,7 @@ import "gioui.org/unit" var ( Size0_5 = unit.Dp(0.5) + Size28 = unit.Dp(28) MarginPadding0 = unit.Dp(0) MarginPadding1 = unit.Dp(1) diff --git a/ui/window.go b/ui/window.go index 22e0032bb..3c0dd99c4 100644 --- a/ui/window.go +++ b/ui/window.go @@ -289,7 +289,7 @@ func (win *Window) Loop(w *app.Window, shutdown chan int) { break } - win.updateStates(e.Resp) + // win.updateStates(e.Resp) case update := <-win.wallet.Sync: switch update.Stage { diff --git a/wallet/responses.go b/wallet/responses.go index 42565655f..ed78a3d97 100644 --- a/wallet/responses.go +++ b/wallet/responses.go @@ -205,7 +205,7 @@ type VSPInfo struct { // VSP is sent when the Wallet is done getting all VSP info type VSP struct { - List []VSPInfo + List []*VSPInfo } // Proposals is sent when all proposals has been fetched From b3c43492806c08b3529813cb232c81aef4f585bd Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Tue, 14 Sep 2021 11:11:21 +0100 Subject: [PATCH 22/27] Resolve requested changes --- ui/page/tickets/purchase_modal.go | 2 +- ui/page/tickets/utils.go | 2 -- ui/state.go | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ui/page/tickets/purchase_modal.go b/ui/page/tickets/purchase_modal.go index 19f69db2a..297579daf 100644 --- a/ui/page/tickets/purchase_modal.go +++ b/ui/page/tickets/purchase_modal.go @@ -65,7 +65,7 @@ func (tp *ticketPurchaseModal) OnResume() { tp.vspSelector = newVSPSelector(tp.Load).title("Select a vsp") tp.ticketPrice = dcrutil.Amount(tp.WL.TicketPrice()) - if tp.vspIsFetched && tp.WL.GetRememberVSP() != "" { + if tp.vspIsFetched && components.StringNotEmpty(tp.WL.GetRememberVSP()) { tp.vspSelector.selectVSP(tp.WL.GetRememberVSP()) tp.rememberVSP.CheckBox.Value = true } diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index c1a224cc2..2f92426e4 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -100,7 +100,6 @@ func ticketsToTransactionItems(l *load.Load, txs []dcrlibwallet.Transaction, new ticketAgeDuration := time.Since(time.Unix(tx.Timestamp, 0)).Seconds() ticketAge = ticketAgeTimeFormat(int(ticketAgeDuration)) - } showTime := showProgress && txStatus.TicketStatus != dcrlibwallet.TicketStatusLive @@ -517,7 +516,6 @@ func ticketAgeTimeFormat(secs int) string { } return fmt.Sprintf("%ds", secs) - } func maturityTimeFormat(maturityTimeMinutes int) string { diff --git a/ui/state.go b/ui/state.go index 6c2803ff8..d6c187882 100644 --- a/ui/state.go +++ b/ui/state.go @@ -51,7 +51,6 @@ func (win *Window) updateStates(update interface{}) { win.walletUnspentOutputs = e case *wallet.VSPInfo: win.states.loading = false - // win.vspInfo.List = append(win.vspInfo.List, *e) return case *wallet.VSP: win.vspInfo = e From 4cc99e7f52bb01e949a9f2f5f7cb7d889d12f07c Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Wed, 15 Sep 2021 04:35:04 +0100 Subject: [PATCH 23/27] Show error if selected ticket purchase account has insufficient balance --- ui/page/tickets/purchase_modal.go | 47 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/ui/page/tickets/purchase_modal.go b/ui/page/tickets/purchase_modal.go index 297579daf..606dc9880 100644 --- a/ui/page/tickets/purchase_modal.go +++ b/ui/page/tickets/purchase_modal.go @@ -21,17 +21,17 @@ const purchaseModalID = "ticket_purchase_modal" type ticketPurchaseModal struct { *load.Load + balanceError string ticketPrice dcrutil.Amount totalCost int64 balanceLessCost int64 vspIsFetched bool - modal decredmaterial.Modal - tickets decredmaterial.Editor - rememberVSP decredmaterial.CheckBoxStyle - cancelPurchase decredmaterial.Button - reviewPurchase decredmaterial.Button - + modal decredmaterial.Modal + tickets decredmaterial.Editor + rememberVSP decredmaterial.CheckBoxStyle + cancelPurchase decredmaterial.Button + reviewPurchase decredmaterial.Button accountSelector *components.AccountSelector vspSelector *vspSelector } @@ -116,6 +116,15 @@ func (tp *ticketPurchaseModal) Layout(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx C) D { return tp.accountSelector.Layout(gtx) }), + layout.Rigid(func(gtx C) D { + if tp.balanceError == "" { + return D{} + } + + label := tp.Theme.Body1(tp.balanceError) + label.Color = tp.Theme.Color.Orange + return label.Layout(gtx) + }), layout.Rigid(func(gtx C) D { return layout.Inset{Top: values.MarginPadding16}.Layout(gtx, func(gtx C) D { return tp.vspSelector.Layout(gtx) @@ -158,17 +167,20 @@ func (tp *ticketPurchaseModal) ticketCount() int64 { } func (tp *ticketPurchaseModal) canPurchase() bool { + tp.balanceError = "" + if tp.ticketCount() < 1 { + return false + } + if tp.vspSelector.selectedVSP == nil { return false } tp.calculateTotals() + accountBalance := tp.accountSelector.SelectedAccount().Balance.Spendable if accountBalance < tp.totalCost || tp.balanceLessCost < 0 { - return false - } - - if tp.ticketCount() < 1 { + tp.balanceError = "Insufficient funds" return false } @@ -264,7 +276,7 @@ func (tp *ticketPurchaseModal) Handle() { tp.Dismiss() } - if tp.reviewPurchase.Button.Clicked() && tp.canPurchase() { + if tp.canPurchase() && tp.reviewPurchase.Button.Clicked() { if tp.vspSelector.Changed() && tp.rememberVSP.CheckBox.Value { tp.WL.RememberVSP(tp.vspSelector.selectedVSP.Host) @@ -282,16 +294,3 @@ func (tp *ticketPurchaseModal) Handle() { Show() } } - -func (tp *ticketPurchaseModal) editorsNotEmpty(btn *decredmaterial.Button, editors ...*widget.Editor) bool { - btn.Color = tp.Theme.Color.Surface - for _, e := range editors { - if e.Text() == "" { - btn.Background = tp.Theme.Color.Hint - return false - } - } - - btn.Background = tp.Theme.Color.Primary - return true -} From 541b3bcc7b14e28265052327c84e247565e6b7de Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Wed, 15 Sep 2021 14:17:51 +0100 Subject: [PATCH 24/27] Process ticket purchase in review modal --- ui/page/tickets/list_page.go | 1 - ui/page/tickets/purchase_modal.go | 12 ++-- ui/page/tickets/purchase_review_modal.go | 79 ++++++++++++++++++------ 3 files changed, 67 insertions(+), 25 deletions(-) diff --git a/ui/page/tickets/list_page.go b/ui/page/tickets/list_page.go index 7014f19f5..0d6114b3a 100644 --- a/ui/page/tickets/list_page.go +++ b/ui/page/tickets/list_page.go @@ -357,7 +357,6 @@ func (pg *ListPage) ticketListGridLayout(gtx layout.Context, tickets []*transact return decredmaterial.GridLayout{ List: &pg.ticketsList, HorizontalSpacing: layout.SpaceBetween, - Direction: layout.Center, RowCount: 3, }.Layout(gtx, len(tickets), func(gtx C, index int) D { return layout.Inset{ diff --git a/ui/page/tickets/purchase_modal.go b/ui/page/tickets/purchase_modal.go index 606dc9880..b1d13b65d 100644 --- a/ui/page/tickets/purchase_modal.go +++ b/ui/page/tickets/purchase_modal.go @@ -244,6 +244,7 @@ func (tp *ticketPurchaseModal) purchaseTickets(password []byte) { go func() { selectedVSP := tp.vspSelector.SelectedVSP() + account := tp.accountSelector.SelectedAccount() wal := tp.WL.MultiWallet.WalletWithID(account.WalletID) @@ -284,13 +285,16 @@ func (tp *ticketPurchaseModal) Handle() { tp.WL.RememberVSP("") } - newTicketReviewModal(tp.Load). - Account(tp.accountSelector.SelectedAccount()). - VSPHost(tp.vspSelector.selectedVSP.Host). + selectedVSP := tp.vspSelector.SelectedVSP() + account := tp.accountSelector.SelectedAccount() + + newTicketReviewModal(tp.Load, account, selectedVSP). TicketCount(tp.ticketCount()). TotalCost(tp.totalCost). BalanceLessCost(tp.balanceLessCost). - TicketPurchase(tp.purchaseTickets). + TicketPurchased(func() { + tp.Dismiss() + }). Show() } } diff --git a/ui/page/tickets/purchase_review_modal.go b/ui/page/tickets/purchase_review_modal.go index 39371ae65..d69a82a02 100644 --- a/ui/page/tickets/purchase_review_modal.go +++ b/ui/page/tickets/purchase_review_modal.go @@ -4,8 +4,10 @@ import ( "fmt" "image/color" + "gioui.org/font/gofont" "gioui.org/layout" "gioui.org/widget" + "gioui.org/widget/material" "github.com/decred/dcrd/dcrutil" "github.com/planetdecred/dcrlibwallet" @@ -13,36 +15,44 @@ import ( "github.com/planetdecred/godcr/ui/load" "github.com/planetdecred/godcr/ui/page/components" "github.com/planetdecred/godcr/ui/values" + "github.com/planetdecred/godcr/wallet" ) const reviewModalID = "ticket_review_modal" type ticketReviewModal struct { *load.Load + account *dcrlibwallet.Account + selectedVSP *wallet.VSPInfo totalCost int64 - vspHost string ticketCount int64 balanceLessCost int64 + isLoading bool + materialLoader material.LoaderStyle modal decredmaterial.Modal spendingPassword decredmaterial.Editor purchase decredmaterial.Button cancelPurchase decredmaterial.Button - purchaseTickets func(password []byte) - - account *dcrlibwallet.Account + ticketsPurchased func() } -func newTicketReviewModal(l *load.Load) *ticketReviewModal { +func newTicketReviewModal(l *load.Load, account *dcrlibwallet.Account, selectedVSP *wallet.VSPInfo) *ticketReviewModal { m := &ticketReviewModal{ Load: l, + account: account, + selectedVSP: selectedVSP, modal: *l.Theme.ModalFloatTitle(), spendingPassword: l.Theme.EditorPassword(new(widget.Editor), "Spending password"), purchase: l.Theme.Button(new(widget.Clickable), "Purchase ticket"), cancelPurchase: l.Theme.Button(new(widget.Clickable), "Cancel"), } + th := material.NewTheme(gofont.Collection()) + m.materialLoader = material.Loader(th) + + m.purchase.Background = m.Theme.Color.Primary m.cancelPurchase.Background, m.cancelPurchase.Color = color.NRGBA{}, l.Theme.Color.Primary return m } @@ -94,7 +104,7 @@ func (t *ticketReviewModal) Layout(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx C) D { tleft := t.Theme.Label(values.TextSize14, "VSP") tleft.Color = t.Theme.Color.Gray2 - tright := t.Theme.Label(values.TextSize14, t.vspHost) + tright := t.Theme.Label(values.TextSize14, t.selectedVSP.Host) return components.EndToEndRow(gtx, tleft.Layout, tright.Layout) }), ) @@ -111,9 +121,10 @@ func (t *ticketReviewModal) Layout(gtx layout.Context) layout.Dimensions { return layout.Inset{Right: values.MarginPadding4}.Layout(gtx, t.cancelPurchase.Layout) }), layout.Rigid(func(gtx C) D { - if t.ticketCount > 1 { - t.purchase.Text = fmt.Sprintf("Purchase %d tickets", t.ticketCount) + if t.isLoading { + return t.materialLoader.Layout(gtx) } + return t.purchase.Layout(gtx) }), ) @@ -141,27 +152,55 @@ func (t *ticketReviewModal) OnResume() {} func (t *ticketReviewModal) Handle() { for t.cancelPurchase.Button.Clicked() { - t.Dismiss() + if !t.isLoading { + t.Dismiss() + } } for t.purchase.Button.Clicked() { - t.purchaseTickets([]byte(t.spendingPassword.Editor.Text())) - t.Dismiss() + t.purchaseTickets() + } } -func (t *ticketReviewModal) VSPHost(host string) *ticketReviewModal { - t.vspHost = host - return t -} +func (t *ticketReviewModal) purchaseTickets() { + if t.isLoading { + return + } -func (t *ticketReviewModal) Account(account *dcrlibwallet.Account) *ticketReviewModal { - t.account = account - return t + t.isLoading = true + go func() { + password := []byte(t.spendingPassword.Editor.Text()) + + wal := t.WL.MultiWallet.WalletWithID(t.account.WalletID) + + defer func() { + t.isLoading = false + }() + + vsp, err := t.WL.MultiWallet.NewVSPClient(t.selectedVSP.Host, t.account.WalletID, uint32(t.account.Number)) + if err != nil { + t.Toast.NotifyError(err.Error()) + return + } + + err = vsp.PurchaseTickets(int32(t.ticketCount), wal.GetBestBlock()+256, password) + if err != nil { + t.Toast.NotifyError(err.Error()) + return + } + + t.ticketsPurchased() + t.Dismiss() + t.Toast.Notify(fmt.Sprintf("%v ticket(s) purchased successfully", t.ticketCount)) + }() } func (t *ticketReviewModal) TicketCount(tickets int64) *ticketReviewModal { t.ticketCount = tickets + if t.ticketCount > 1 { + t.purchase.Text = fmt.Sprintf("Purchase %d tickets", t.ticketCount) + } return t } @@ -175,7 +214,7 @@ func (t *ticketReviewModal) BalanceLessCost(remaining int64) *ticketReviewModal return t } -func (t *ticketReviewModal) TicketPurchase(purchaseTickets func([]byte)) *ticketReviewModal { - t.purchaseTickets = purchaseTickets +func (t *ticketReviewModal) TicketPurchased(ticketsPurchased func()) *ticketReviewModal { + t.ticketsPurchased = ticketsPurchased return t } From 3bb1845a320aed9c40d3c6ced5f4af5ef0486538 Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Wed, 15 Sep 2021 15:30:55 +0100 Subject: [PATCH 25/27] Refresh tickets overview data after successful ticket purchase --- ui/page/tickets/overview.go | 17 ++++++++---- ui/page/tickets/purchase_modal.go | 44 +++++++++---------------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/ui/page/tickets/overview.go b/ui/page/tickets/overview.go index 66d806a3b..d3b591ebb 100644 --- a/ui/page/tickets/overview.go +++ b/ui/page/tickets/overview.go @@ -67,6 +67,14 @@ func (pg *Page) ID() string { func (pg *Page) OnResume() { + pg.loadPageData() + + go pg.WL.GetVSPList() + // TODO: automatic ticket purchase functionality + pg.autoPurchaseEnabled.Disabled() +} + +func (pg *Page) loadPageData() { go func() { ticketPrice, err := pg.WL.MultiWallet.TicketPrice() if err != nil { @@ -125,10 +133,6 @@ func (pg *Page) OnResume() { pg.liveTickets = txItems pg.RefreshWindow() }() - - go pg.WL.GetVSPList() - // TODO: automatic ticket purchase functionality - pg.autoPurchaseEnabled.Disabled() } func (pg *Page) Layout(gtx layout.Context) layout.Dimensions { @@ -368,7 +372,10 @@ func (pg *Page) stakingRecordIconCount(icon *decredmaterial.Image, count int, st func (pg *Page) Handle() { if pg.purchaseTicket.Button.Clicked() { newTicketPurchaseModal(pg.Load). - Show() + TicketPurchased(func() { + fmt.Println("Overview ticket pruchsased") + pg.loadPageData() + }).Show() } if pg.toTickets.Button.Clicked() { diff --git a/ui/page/tickets/purchase_modal.go b/ui/page/tickets/purchase_modal.go index b1d13b65d..2cb956a2b 100644 --- a/ui/page/tickets/purchase_modal.go +++ b/ui/page/tickets/purchase_modal.go @@ -1,7 +1,6 @@ package tickets import ( - "fmt" "image/color" "strconv" @@ -21,11 +20,12 @@ const purchaseModalID = "ticket_purchase_modal" type ticketPurchaseModal struct { *load.Load - balanceError string - ticketPrice dcrutil.Amount - totalCost int64 - balanceLessCost int64 - vspIsFetched bool + balanceError string + ticketPrice dcrutil.Amount + totalCost int64 + balanceLessCost int64 + vspIsFetched bool + ticketsPurchased func() modal decredmaterial.Modal tickets decredmaterial.Editor @@ -55,6 +55,11 @@ func newTicketPurchaseModal(l *load.Load) *ticketPurchaseModal { return tp } +func (tp *ticketPurchaseModal) TicketPurchased(ticketsPurchased func()) *ticketPurchaseModal { + tp.ticketsPurchased = ticketsPurchased + return tp +} + func (tp *ticketPurchaseModal) OnResume() { tp.initializeAccountSelector() err := tp.accountSelector.SelectFirstWalletValidAccount() @@ -238,32 +243,6 @@ func (tp *ticketPurchaseModal) calculateTotals() { tp.balanceLessCost = account.Balance.Spendable - tp.totalCost } -func (tp *ticketPurchaseModal) purchaseTickets(password []byte) { - tp.Dismiss() - tp.Toast.Notify(fmt.Sprintf("Attempting to purchase %v ticket(s)", tp.ticketCount())) - - go func() { - selectedVSP := tp.vspSelector.SelectedVSP() - - account := tp.accountSelector.SelectedAccount() - wal := tp.WL.MultiWallet.WalletWithID(account.WalletID) - - vsp, err := tp.WL.MultiWallet.NewVSPClient(selectedVSP.Host, account.WalletID, uint32(account.Number)) - if err != nil { - tp.Toast.NotifyError(err.Error()) - return - } - - err = vsp.PurchaseTickets(int32(tp.ticketCount()), wal.GetBestBlock()+256, password) - if err != nil { - tp.Toast.NotifyError(err.Error()) - return - } - - tp.Toast.Notify(fmt.Sprintf("%v ticket(s) purchased successfully", tp.ticketCount())) - }() -} - func (tp *ticketPurchaseModal) Handle() { // reselect vsp if there's a delay in fetching the VSP List if !tp.vspIsFetched && len((*tp.WL.VspInfo).List) > 0 { @@ -294,6 +273,7 @@ func (tp *ticketPurchaseModal) Handle() { BalanceLessCost(tp.balanceLessCost). TicketPurchased(func() { tp.Dismiss() + tp.ticketsPurchased() }). Show() } From 66442f4f43add8d95716142c8e266b89a7af3d2f Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Wed, 15 Sep 2021 18:10:48 +0100 Subject: [PATCH 26/27] Add tooltip to wallet name --- ui/page/tickets/utils.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/page/tickets/utils.go b/ui/page/tickets/utils.go index 2f92426e4..b156704dd 100644 --- a/ui/page/tickets/utils.go +++ b/ui/page/tickets/utils.go @@ -32,6 +32,7 @@ type transactionItem struct { ticketAge string statusTooltip *decredmaterial.Tooltip + walletNameTooltip *decredmaterial.Tooltip dateTooltip *decredmaterial.Tooltip daysBehindTooltip *decredmaterial.Tooltip durationTooltip *decredmaterial.Tooltip @@ -131,6 +132,7 @@ func ticketsToTransactionItems(l *load.Load, txs []dcrlibwallet.Transaction, new ticketAge: ticketAge, statusTooltip: l.Theme.Tooltip(), + walletNameTooltip: l.Theme.Tooltip(), dateTooltip: l.Theme.Tooltip(), daysBehindTooltip: l.Theme.Tooltip(), durationTooltip: l.Theme.Tooltip(), @@ -423,9 +425,13 @@ func ticketCard(gtx layout.Context, l *load.Load, tx *transactionItem, showWalle return D{} } - txt := l.Theme.Label(values.MarginPadding14, wal.Name) + txt := l.Theme.Label(values.TextSize14, wal.Name) txt.Color = l.Theme.Color.Gray - return txt.Layout(gtx) + txtLayout := txt.Layout(gtx) + ticketCardTooltip(gtx, txtLayout, tx.walletNameTooltip, func(gtx C) D { + return titleDescTooltip(gtx, l, "Wallet name", txt.Text) + }) + return txtLayout }), ) }) From acebd081e6db77eb9576fc6c308c698233ff598d Mon Sep 17 00:00:00 2001 From: Olanrewaju Collins Date: Wed, 15 Sep 2021 19:15:21 +0100 Subject: [PATCH 27/27] Use dcrlibwallet master(31878a6) --- go.mod | 8 ++------ go.sum | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1148d5395..492d2c2e2 100644 --- a/go.mod +++ b/go.mod @@ -20,16 +20,12 @@ require ( github.com/jrick/logrotate v1.0.0 github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 - github.com/planetdecred/dcrlibwallet v1.6.1-0.20210816165030-bb3af17a746a + github.com/planetdecred/dcrlibwallet v1.6.1-rc1.0.20210915175038-31878a61e002 github.com/yeqown/go-qrcode v1.5.1 golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect golang.org/x/text v0.3.3 ) -replace ( - github.com/decred/dcrdata/txhelpers/v4 => github.com/decred/dcrdata/txhelpers/v4 v4.0.0-20200108145420-f82113e7e212 - github.com/planetdecred/dcrlibwallet => github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210912175524-041481a23c8b -) +replace github.com/decred/dcrdata/txhelpers/v4 => github.com/decred/dcrdata/txhelpers/v4 v4.0.0-20200108145420-f82113e7e212 diff --git a/go.sum b/go.sum index fc5e033b9..2d687d1e6 100644 --- a/go.sum +++ b/go.sum @@ -44,12 +44,6 @@ github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIo github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e h1:x6AYXRKp/C56x6lsUEUQ6+QhBM9xO07KXt2kBnePaDs= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210825093648-ec344982588e/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2 h1:EHZLz82ivykxAkm6BI15FBrPL/PdUsUD2gtrb6pnFpY= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210910121332-e44a98c834c2/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210912175524-041481a23c8b h1:JvcNx/P2fSfgXpySx/HVbnRyYo6ZPgnpyVmnar6SuE4= -github.com/C-ollins/mobilewallet v1.0.0-rc1.0.20210912175524-041481a23c8b/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.4.8 h1:Rpmta4xZ/MgZnriKNd24iZMhGpP5dvUcs/uqfBapKZY= github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -687,6 +681,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/planetdecred/dcrlibwallet v1.6.1-rc1.0.20210915175038-31878a61e002 h1:W5ZELmwSjtNub4ugEujJtP4XrJ7gnmAacEDnIKNJckE= +github.com/planetdecred/dcrlibwallet v1.6.1-rc1.0.20210915175038-31878a61e002/go.mod h1:sRwfsPrOEnpGBNL54KS83Dpxx2kp2AzZ0Je5vKRRE4o= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=