Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup wallets page & its sub pages #493

Merged
merged 12 commits into from Jul 2, 2021
143 changes: 83 additions & 60 deletions ui/account_details_page.go
Expand Up @@ -2,42 +2,52 @@ package ui

import (
"fmt"
"strings"
"strconv"

"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/values"
"github.com/planetdecred/godcr/wallet"
)

const PageAccountDetails = "AccountDetails"

type acctDetailsPage struct {
common *pageCommon
wallet *wallet.Wallet
current wallet.InfoShort
common *pageCommon
wallet *dcrlibwallet.Wallet
account *dcrlibwallet.Account

theme *decredmaterial.Theme
acctDetailsPageContainer layout.List
backButton decredmaterial.IconButton
acctInfo **wallet.Account
editAccount *widget.Clickable
errorReceiver chan error

stakingBalance int64
totalBalance string
spendable string
immatureRewards string
lockedByTickets string
votingAuthority string
immatureStakeGen string
hdPath string
keys string
}

func AcctDetailsPage(common *pageCommon) Page {
func AcctDetailsPage(common *pageCommon, account *dcrlibwallet.Account) Page {
pg := &acctDetailsPage{
common: common,
wallet: common.multiWallet.WalletWithID(account.WalletID),
account: account,

theme: common.theme,
acctDetailsPageContainer: layout.List{
Axis: layout.Vertical,
},
common: common,
wallet: common.wallet,
acctInfo: common.walletAccount,
theme: common.theme,
editAccount: new(widget.Clickable),
errorReceiver: make(chan error),
backButton: common.theme.PlainIconButton(new(widget.Clickable), common.icons.navigationArrowBack),
editAccount: new(widget.Clickable),
}

pg.backButton, _ = common.SubPageHeaderButtons()
Expand All @@ -47,6 +57,24 @@ func AcctDetailsPage(common *pageCommon) Page {

func (pg *acctDetailsPage) OnResume() {

balance := pg.account.Balance

pg.stakingBalance = balance.ImmatureReward + balance.LockedByTickets + balance.VotingAuthority +
balance.ImmatureStakeGeneration

pg.totalBalance = dcrutil.Amount(balance.Total).String()
pg.spendable = dcrutil.Amount(balance.Spendable).String()
pg.immatureRewards = dcrutil.Amount(balance.ImmatureReward).String()
pg.lockedByTickets = dcrutil.Amount(balance.LockedByTickets).String()
pg.votingAuthority = dcrutil.Amount(balance.VotingAuthority).String()
pg.immatureStakeGen = dcrutil.Amount(balance.ImmatureStakeGeneration).String()

pg.hdPath = pg.common.HDPrefix() + strconv.Itoa(int(pg.account.Number)) + "'"

ext := pg.account.ExternalKeyCount
internal := pg.account.InternalKeyCount
imp := pg.account.ImportedKeyCount
pg.keys = fmt.Sprintf("%d external, %d internal, %d imported", ext, internal, imp)
}

func (pg *acctDetailsPage) Layout(gtx layout.Context) layout.Dimensions {
Expand All @@ -67,15 +95,10 @@ func (pg *acctDetailsPage) Layout(gtx layout.Context) layout.Dimensions {
},
}

title := "Not found"
if *pg.acctInfo != nil {
title = (*pg.acctInfo).Name
}
acctName := strings.Title(strings.ToLower(title))
body := func(gtx C) D {
page := SubPage{
title: acctName,
walletName: common.info.Wallets[*common.selectedWallet].Name,
title: pg.account.Name,
walletName: pg.wallet.Name,
backButton: pg.backButton,
back: func() {
common.changePage(PageWallet)
Expand All @@ -84,9 +107,6 @@ func (pg *acctDetailsPage) Layout(gtx layout.Context) layout.Dimensions {
return layout.Inset{Bottom: values.MarginPadding7}.Layout(gtx, func(gtx C) D {
return pg.theme.Card().Layout(gtx, func(gtx C) D {
return layout.Inset{Top: values.MarginPadding5}.Layout(gtx, func(gtx C) D {
if *pg.acctInfo == nil {
return layout.Dimensions{}
}
return pg.acctDetailsPageContainer.Layout(gtx, len(widgets), func(gtx C, i int) D {
return layout.Inset{}.Layout(gtx, widgets[i])
})
Expand All @@ -109,21 +129,10 @@ func (pg *acctDetailsPage) Layout(gtx layout.Context) layout.Dimensions {
}

func (pg *acctDetailsPage) accountBalanceLayout(gtx layout.Context, common *pageCommon) layout.Dimensions {
totalBalanceMain, totalBalanceSub := breakBalance(common.printer, (*pg.acctInfo).TotalBalance)
spendable := dcrutil.Amount((*pg.acctInfo).SpendableBalance).String()
spendableMain, spendableSub := breakBalance(common.printer, spendable)
immatureRewards := dcrutil.Amount((*pg.acctInfo).Balance.ImmatureReward).String()
rewardBalanceMain, rewardBalanceSub := breakBalance(common.printer, immatureRewards)
lockedByTickets := dcrutil.Amount((*pg.acctInfo).Balance.LockedByTickets).String()
lockBalanceMain, lockBalanceSub := breakBalance(common.printer, lockedByTickets)
votingAuthority := dcrutil.Amount((*pg.acctInfo).Balance.VotingAuthority).String()
voteBalanceMain, voteBalanceSub := breakBalance(common.printer, votingAuthority)
immatureStakeGen := dcrutil.Amount((*pg.acctInfo).Balance.ImmatureStakeGeneration).String()
stakeBalanceMain, stakeBalanceSub := breakBalance(common.printer, immatureStakeGen)

return pg.pageSections(gtx, func(gtx C) D {
accountIcon := common.icons.accountIcon
if (*pg.acctInfo).Name == "imported" { //TODO
if pg.account.Number == MaxInt32 {
accountIcon = common.icons.importedAccountIcon
}
accountIcon.Scale = 1
Expand All @@ -139,36 +148,48 @@ func (pg *acctDetailsPage) accountBalanceLayout(gtx layout.Context, common *page
}.Layout(gtx, accountIcon.Layout)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Total Balance", totalBalanceMain, totalBalanceSub, true)
return pg.acctBalLayout(gtx, "Total Balance", pg.totalBalance, true)
}),
)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Spendable", spendableMain, spendableSub, false)
return pg.acctBalLayout(gtx, "Spendable", pg.spendable, false)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Immature Rewards", rewardBalanceMain, rewardBalanceSub, false)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Locked By Tickets", lockBalanceMain, lockBalanceSub, false)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Voting Authority", voteBalanceMain, voteBalanceSub, false)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Immature Stake Gen", stakeBalanceMain, stakeBalanceSub, false)
if pg.stakingBalance == 0 {
return layout.Dimensions{}
}

return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Immature Rewards", pg.immatureRewards, false)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Locked By Tickets", pg.lockedByTickets, false)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Voting Authority", pg.votingAuthority, false)
}),
layout.Rigid(func(gtx C) D {
return pg.acctBalLayout(gtx, "Immature Stake Gen", pg.immatureStakeGen, false)
}),
)
}),
)
})
}

func (pg *acctDetailsPage) acctBalLayout(gtx layout.Context, balType string, mainBalance, subBalance string, isFirst bool) layout.Dimensions {
func (pg *acctDetailsPage) acctBalLayout(gtx layout.Context, balType string, balance string, isTotalBalance bool) layout.Dimensions {

mainBalance, subBalance := breakBalance(pg.common.printer, balance)

mainLabel := pg.theme.Body1(mainBalance)
subLabel := pg.theme.Caption(subBalance)
subLabel.Color = pg.theme.Color.DeepBlue
marginTop := values.MarginPadding16
marginLeft := values.MarginPadding35
if isFirst {

if isTotalBalance {
mainLabel = pg.theme.H4(mainBalance)
subLabel = pg.theme.Body1(subBalance)
marginTop = values.MarginPadding0
Expand Down Expand Up @@ -200,26 +221,23 @@ func (pg *acctDetailsPage) accountInfoLayout(gtx layout.Context) layout.Dimensio
m := values.MarginPadding10
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return pg.acctInfoLayout(gtx, "Account Number", fmt.Sprint((*pg.acctInfo).Number))
return pg.acctInfoLayout(gtx, "Account Number", fmt.Sprint(pg.account.Number))
}),
layout.Rigid(func(gtx C) D {
inset := layout.Inset{
Top: m,
Bottom: m,
}
return inset.Layout(gtx, func(gtx C) D {
return pg.acctInfoLayout(gtx, "HD Path", (*pg.acctInfo).HDPath)
return pg.acctInfoLayout(gtx, "HD Path", pg.hdPath)
})
}),
layout.Rigid(func(gtx C) D {
inset := layout.Inset{
Bottom: m,
}
return inset.Layout(gtx, func(gtx C) D {
ext := (*pg.acctInfo).Keys.External
int := (*pg.acctInfo).Keys.Internal
imp := (*pg.acctInfo).Keys.Imported
return pg.acctInfoLayout(gtx, "Key", ext+" external, "+int+" internal, "+imp+" imported")
return pg.acctInfoLayout(gtx, "Keys", pg.keys)
})
}),
)
Expand Down Expand Up @@ -253,12 +271,17 @@ func (pg *acctDetailsPage) handle() {
common := pg.common

if pg.editAccount.Clicked() {
pg.current = common.info.Wallets[*common.selectedWallet]
textModal := newTextInputModal(common).
hint("Account name").
positiveButton(values.String(values.StrRename), func(newName string, tim *textInputModal) bool {
pg.wallet.RenameAccount(pg.current.ID, (*pg.acctInfo).Number, newName, pg.errorReceiver)
(*pg.acctInfo).Name = newName
err := pg.wallet.RenameAccount(pg.account.Number, newName)
if err != nil {
tim.setError(err.Error())
tim.isLoading = false
return false
}

pg.account.Name = newName
return true
})

Expand Down
52 changes: 52 additions & 0 deletions ui/decredmaterial/clickable_list.go
@@ -0,0 +1,52 @@
package decredmaterial

import (
"gioui.org/layout"
"gioui.org/widget"
)

type ClickableList struct {
layout.List
clickables []*widget.Clickable
selectedItem int
}

func (t *Theme) NewClickableList(axis layout.Axis) *ClickableList {
return &ClickableList{
List: layout.List{Axis: layout.Vertical},
selectedItem: -1,
}
}

func (cl *ClickableList) ItemClicked() (bool, int) {
defer func() {
cl.selectedItem = -1
}()
return cl.selectedItem != -1, cl.selectedItem
}

func (cl *ClickableList) handleClickables(count int) {
if len(cl.clickables) != count {

cl.clickables = make([]*widget.Clickable, count)
for i := 0; i < count; i++ {
cl.clickables[i] = new(widget.Clickable)
}
}

for index, clickable := range cl.clickables {
for clickable.Clicked() {
cl.selectedItem = index
}
}
}

func (cl *ClickableList) Layout(gtx layout.Context, count int, w layout.ListElement) layout.Dimensions {
cl.handleClickables(count)
return cl.List.Layout(gtx, count, func(gtx layout.Context, i int) layout.Dimensions {
return Clickable(gtx, cl.clickables[i], func(gtx layout.Context) layout.Dimensions {
return w(gtx, i)
})

})
}
6 changes: 2 additions & 4 deletions ui/main_page.go
Expand Up @@ -223,10 +223,6 @@ func (mp *mainPage) unlockWalletForSyncing(wal *dcrlibwallet.Wallet) {

func (mp *mainPage) handle() {

// TODO: This function should be only called when
// dcrlibwallet update notifications are receieved
mp.updateBalance()

for mp.minimizeNavDrawerButton.Button.Clicked() {
mp.isNavDrawerMinimized = true
}
Expand All @@ -248,6 +244,8 @@ func (mp *mainPage) handle() {
mp.changeFragment(OverviewPage(mp.pageCommon), PageOverview)
} else if i == 1 {
mp.changeFragment(TransactionsPage(mp.pageCommon), PageTransactions)
} else if i == 2 {
mp.changeFragment(WalletPage(mp.pageCommon), PageWallet)
} else {
mp.changePage(mp.drawerNavItems[i].page)
}
Expand Down
18 changes: 13 additions & 5 deletions ui/page.go
Expand Up @@ -87,6 +87,7 @@ type DCRUSDTBittrex struct {
type pageCommon struct {
printer *message.Printer
multiWallet *dcrlibwallet.MultiWallet
network string
notificationsUpdate chan interface{}
wallet *wallet.Wallet
walletAccount **wallet.Account
Expand Down Expand Up @@ -217,6 +218,7 @@ func (win *Window) newPageCommon(decredIcons map[string]image.Image) *pageCommon
common := &pageCommon{
printer: message.NewPrinter(language.English),
multiWallet: win.wallet.GetMultiWallet(),
network: win.wallet.Net,
notificationsUpdate: make(chan interface{}, 10),
wallet: win.wallet,
walletAccount: &win.walletAccount,
Expand Down Expand Up @@ -264,15 +266,12 @@ func (common *pageCommon) loadPages() map[string]Page {

pages := make(map[string]Page)

pages[PageWallet] = WalletPage(common)
pages[PageMore] = MorePage(common)
pages[PageReceive] = ReceivePage(common)
pages[PageSend] = SendPage(common)
pages[PageSignMessage] = SignMessagePage(common)
pages[PageVerifyMessage] = VerifyMessagePage(common)
pages[PageSeedBackup] = BackupPage(common)
pages[PageSettings] = SettingsPage(common)
pages[PageWalletSettings] = WalletSettingsPage(common)
pages[PageSecurityTools] = SecurityToolsPage(common)
pages[PageProposals] = ProposalsPage(common)
pages[PageProposalDetails] = ProposalDetailsPage(common)
Expand All @@ -282,8 +281,6 @@ func (common *pageCommon) loadPages() map[string]Page {
pages[PageAbout] = AboutPage(common)
pages[PageHelp] = HelpPage(common)
pages[PageUTXO] = UTXOPage(common)
pages[PageAccountDetails] = AcctDetailsPage(common)
pages[PagePrivacy] = PrivacyPage(common)
pages[PageTickets] = TicketPage(common)
pages[ValidateAddress] = ValidateAddressPage(common)
pages[PageTicketsList] = TicketPageList(common)
Expand Down Expand Up @@ -333,6 +330,17 @@ func (common *pageCommon) sortedWalletList() []*dcrlibwallet.Wallet {
return wallets
}

func (common *pageCommon) HDPrefix() string {
switch common.network {
case "testnet3": // should use a constant
return dcrlibwallet.TestnetHDPath
case "mainnet":
return dcrlibwallet.MainnetHDPath
default:
return ""
}
}

// Container is simply a wrapper for the Inset type. Its purpose is to differentiate the use of an inset as a padding or
// margin, making it easier to visualize the structure of a layout when reading UI code.
type Container struct {
Expand Down