diff --git a/ui/account_details_page.go b/ui/account_details_page.go index f8d5334a1..a4f055a8a 100644 --- a/ui/account_details_page.go +++ b/ui/account_details_page.go @@ -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() @@ -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 { @@ -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) @@ -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]) }) @@ -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 @@ -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 @@ -200,7 +221,7 @@ 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{ @@ -208,7 +229,7 @@ func (pg *acctDetailsPage) accountInfoLayout(gtx layout.Context) layout.Dimensio 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 { @@ -216,10 +237,7 @@ func (pg *acctDetailsPage) accountInfoLayout(gtx layout.Context) layout.Dimensio 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) }) }), ) @@ -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 }) diff --git a/ui/decredmaterial/clickable_list.go b/ui/decredmaterial/clickable_list.go new file mode 100644 index 000000000..d0576167b --- /dev/null +++ b/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) + }) + + }) +} diff --git a/ui/main_page.go b/ui/main_page.go index 07f2d9050..1f061a3ea 100644 --- a/ui/main_page.go +++ b/ui/main_page.go @@ -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 } @@ -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) } diff --git a/ui/page.go b/ui/page.go index 9644cc58c..54a8a4297 100644 --- a/ui/page.go +++ b/ui/page.go @@ -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 @@ -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, @@ -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) @@ -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) @@ -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 { diff --git a/ui/privacy_page.go b/ui/privacy_page.go index e70522617..a60d85177 100644 --- a/ui/privacy_page.go +++ b/ui/privacy_page.go @@ -9,6 +9,7 @@ import ( "gioui.org/widget/material" "golang.org/x/exp/shiny/materialdesign/icons" + "github.com/decred/dcrd/dcrutil/v3" "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/values" @@ -18,21 +19,22 @@ import ( const PagePrivacy = "Privacy" type privacyPage struct { + wallet *dcrlibwallet.Wallet theme *decredmaterial.Theme common *pageCommon pageContainer layout.List toggleMixer, allowUnspendUnmixedAcct *widget.Bool toPrivacySetup decredmaterial.Button dangerZoneCollapsible *decredmaterial.Collapsible - errorReceiver chan error acctMixerStatus *chan *wallet.AccountMixer backButton decredmaterial.IconButton infoButton decredmaterial.IconButton } -func PrivacyPage(common *pageCommon) Page { +func PrivacyPage(common *pageCommon, wallet *dcrlibwallet.Wallet) Page { pg := &privacyPage{ + wallet: wallet, theme: common.theme, common: common, pageContainer: layout.List{Axis: layout.Vertical}, @@ -40,7 +42,6 @@ func PrivacyPage(common *pageCommon) Page { allowUnspendUnmixedAcct: new(widget.Bool), toPrivacySetup: common.theme.Button(new(widget.Clickable), "Set up mixer for this wallet"), dangerZoneCollapsible: common.theme.Collapsible(), - errorReceiver: make(chan error), acctMixerStatus: common.acctMixerStatus, } pg.toPrivacySetup.Background = pg.theme.Color.Primary @@ -58,7 +59,7 @@ func (pg *privacyPage) Layout(gtx layout.Context) layout.Dimensions { d := func(gtx C) D { load := SubPage{ title: "Privacy", - walletName: c.info.Wallets[*c.selectedWallet].Name, + walletName: pg.wallet.Name, backButton: pg.backButton, infoButton: pg.infoButton, back: func() { @@ -66,7 +67,7 @@ func (pg *privacyPage) Layout(gtx layout.Context) layout.Dimensions { }, infoTemplate: PrivacyInfoTemplate, body: func(gtx layout.Context) layout.Dimensions { - if c.wallet.IsAccountMixerConfigSet(c.info.Wallets[*c.selectedWallet].ID) { + if pg.wallet.AccountMixerConfigIsSet() { widgets := []func(gtx C) D{ func(gtx C) D { return pg.mixerInfoLayout(gtx, c) @@ -160,7 +161,7 @@ func (pg *privacyPage) mixerInfoStatusTextLayout(gtx layout.Context, c *pageComm subtxt.Color = c.theme.Color.Gray iconVisibility := false - if c.wallet.IsAccountMixerActive(c.info.Wallets[*c.selectedWallet].ID) { + if pg.wallet.IsAccountMixerActive() { txt.Text = "Mixer is running..." subtxt.Text = "Keep this app opened" iconVisibility = true @@ -212,12 +213,12 @@ func (pg *privacyPage) mixerInfoLayout(gtx layout.Context, c *pageCommon) layout return layout.UniformInset(values.MarginPadding15).Layout(gtx, func(gtx C) D { var mixedBalance = "0.00" var unmixedBalance = "0.00" - for _, acct := range c.info.Wallets[*c.selectedWallet].Accounts { - if acct.Name == "mixed" { - mixedBalance = acct.TotalBalance - } - if acct.Name == "unmixed" { - unmixedBalance = acct.TotalBalance + accounts, _ := pg.wallet.GetAccountsRaw() + for _, acct := range accounts.Acc { + if acct.Number == pg.wallet.MixedAccountNumber() { + mixedBalance = dcrutil.Amount(acct.TotalBalance).String() + } else if acct.Number == pg.wallet.UnmixedAccountNumber() { + unmixedBalance = dcrutil.Amount(acct.TotalBalance).String() } } @@ -230,17 +231,11 @@ func (pg *privacyPage) mixerInfoLayout(gtx layout.Context, c *pageCommon) layout return txt.Layout(gtx) }), layout.Rigid(func(gtx C) D { - if c.wallet.IsAccountMixerActive(c.info.Wallets[*c.selectedWallet].ID) { - return c.layoutBalance(gtx, unmixedBalance, true) - } return c.layoutBalance(gtx, unmixedBalance, true) }), ) }), layout.Rigid(func(gtx C) D { - if !c.wallet.IsAccountMixerActive(c.info.Wallets[*c.selectedWallet].ID) { - return layout.Dimensions{} - } c.icons.arrowDownIcon.Scale = 1.0 return layout.Center.Layout(gtx, c.icons.arrowDownIcon.Layout) }), @@ -347,13 +342,11 @@ func (pg *privacyPage) handle() { if pg.toggleMixer.Value { go pg.showModalPasswordStartAccountMixer(common) } else { - common.wallet.StopAccountMixer(common.info.Wallets[*common.selectedWallet].ID, pg.errorReceiver) + go common.multiWallet.StopAccountMixer(pg.wallet.ID) } } select { - case err := <-pg.errorReceiver: - common.notify(err.Error(), false) case stt := <-*pg.acctMixerStatus: if stt.RunStatus == wallet.MixerStarted { common.notify("Start Successfully", true) @@ -376,7 +369,8 @@ func (pg *privacyPage) showModalSetupMixerInfo(common *pageCommon) { } func (pg *privacyPage) showModalSetupMixerAcct(common *pageCommon) { - for _, acct := range common.info.Wallets[*common.selectedWallet].Accounts { + accounts, _ := pg.wallet.GetAccountsRaw() + for _, acct := range accounts.Acc { if acct.Name == "mixed" || acct.Name == "unmixed" { alert := mustIcon(widget.NewIcon(icons.AlertError)) alert.Color = pg.theme.Color.DeepBlue @@ -398,8 +392,7 @@ func (pg *privacyPage) showModalSetupMixerAcct(common *pageCommon) { negativeButton("Cancel", func() {}). positiveButton("Confirm", func(password string, pm *passwordModal) bool { go func() { - wal := common.wallet.GetMultiWallet().WalletWithID(pg.common.info.Wallets[*common.selectedWallet].ID) - err := wal.CreateMixerAccounts("mixed", "unmixed", password) + err := pg.wallet.CreateMixerAccounts("mixed", "unmixed", password) if err != nil { pm.setError(err.Error()) pm.setLoading(false) @@ -419,8 +412,8 @@ func (pg *privacyPage) showModalPasswordStartAccountMixer(common *pageCommon) { negativeButton("Cancel", func() {}). positiveButton("Confirm", func(password string, pm *passwordModal) bool { go func() { - walID := pg.common.info.Wallets[*common.selectedWallet].ID - err := common.wallet.GetMultiWallet().StartAccountMixer(walID, password) + + err := common.multiWallet.StartAccountMixer(pg.wallet.ID, password) if err != nil { pm.setError(err.Error()) pm.setLoading(false) diff --git a/ui/sign_message_page.go b/ui/sign_message_page.go index d18783aff..437164ab1 100644 --- a/ui/sign_message_page.go +++ b/ui/sign_message_page.go @@ -10,18 +10,15 @@ import ( "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/values" - "github.com/planetdecred/godcr/wallet" ) const PageSignMessage = "SignMessage" type signMessagePage struct { - common *pageCommon - theme *decredmaterial.Theme - container layout.List - wallet *wallet.Wallet - walletID int - errorReceiver chan error + common *pageCommon + theme *decredmaterial.Theme + container layout.List + wallet *dcrlibwallet.Wallet isSigningMessage bool titleLabel, errorLabel, signedMessageLabel decredmaterial.Label @@ -35,7 +32,7 @@ type signMessagePage struct { infoButton decredmaterial.IconButton } -func SignMessagePage(common *pageCommon) Page { +func SignMessagePage(common *pageCommon, wallet *dcrlibwallet.Wallet) Page { addressEditor := common.theme.Editor(new(widget.Editor), "Address") addressEditor.Editor.SingleLine, addressEditor.Editor.Submit = true, true messageEditor := common.theme.Editor(new(widget.Editor), "Message") @@ -53,7 +50,7 @@ func SignMessagePage(common *pageCommon) Page { }, common: common, theme: common.theme, - wallet: common.wallet, + wallet: wallet, titleLabel: common.theme.H5("Sign Message"), signedMessageLabel: common.theme.Body1(""), @@ -66,7 +63,6 @@ func SignMessagePage(common *pageCommon) Page { copyButton: common.theme.Button(new(widget.Clickable), "Copy"), copySignature: new(widget.Clickable), copyIcon: copyIcon, - errorReceiver: make(chan error), } pg.signedMessageLabel.Color = common.theme.Color.Gray @@ -84,12 +80,11 @@ func (pg *signMessagePage) Layout(gtx layout.Context) layout.Dimensions { pg.gtx = >x } common := pg.common - pg.walletID = common.info.Wallets[*common.selectedWallet].ID body := func(gtx C) D { page := SubPage{ title: "Sign message", - walletName: common.info.Wallets[*common.selectedWallet].Name, + walletName: pg.wallet.Name, backButton: pg.backButton, infoButton: pg.infoButton, back: func() { @@ -228,8 +223,7 @@ func (pg *signMessagePage) handle() { positiveButton("Confirm", func(password string, pm *passwordModal) bool { go func() { - wal := common.wallet.GetMultiWallet().WalletWithID(pg.walletID) - sig, err := wal.SignMessage([]byte(password), address, message) + sig, err := pg.wallet.SignMessage([]byte(password), address, message) if err != nil { pm.setError(err.Error()) pm.setLoading(false) @@ -248,12 +242,6 @@ func (pg *signMessagePage) handle() { if pg.copySignature.Clicked() { clipboard.WriteOp{Text: pg.signedMessageLabel.Text}.Add(gtx.Ops) } - - select { - case err := <-pg.errorReceiver: - common.notify(err.Error(), false) - default: - } } func (pg *signMessagePage) validate(ignoreEmpty bool) bool { @@ -275,13 +263,13 @@ func (pg *signMessagePage) validateAddress(ignoreEmpty bool) bool { } if address != "" { - isValid, _ := pg.wallet.IsAddressValid(address) + isValid := pg.common.multiWallet.IsAddressValid(address) if !isValid { pg.addressEditor.SetError("Invalid address") return false } - exist, _ := pg.wallet.HaveAddress(address) + exist := pg.wallet.HaveAddress(address) if !exist { pg.addressEditor.SetError("Address not owned by this wallet") diff --git a/ui/wallet_page.go b/ui/wallet_page.go index d421bf9c9..d5a8788b1 100644 --- a/ui/wallet_page.go +++ b/ui/wallet_page.go @@ -1,12 +1,9 @@ package ui import ( - "image" "image/color" - "strings" "gioui.org/gesture" - "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/op" "gioui.org/unit" @@ -17,85 +14,80 @@ import ( "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/values" - "github.com/planetdecred/godcr/wallet" ) const PageWallet = "Wallets" +type walletListItem struct { + wal *dcrlibwallet.Wallet + accounts []*dcrlibwallet.Account + + totalBalance string + optionsMenu []menuItem + accountsList *decredmaterial.ClickableList + + // normal wallets + collapsible *decredmaterial.CollapsibleWithOption + addAcctBtn decredmaterial.IconButton + backupAcctBtn decredmaterial.IconButton + + // watch only + moreButton decredmaterial.IconButton +} + type menuItem struct { text string + id string button *widget.Clickable action func(*pageCommon) separate bool } -type collapsible struct { - collapsible *decredmaterial.CollapsibleWithOption - addAcctBtn decredmaterial.IconButton - backupAcctBtn decredmaterial.IconButton -} - type walletPage struct { - common *pageCommon - walletInfo *wallet.MultiWalletInfo - wallet *wallet.Wallet - walletAccount **wallet.Account - theme *decredmaterial.Theme - current wallet.InfoShort - walletIcon *widget.Image - accountIcon *widget.Image - walletAlertIcon *widget.Image - container, accountsList, walletsList, list layout.List - collapsibles map[int]collapsible - toAcctDetails []*gesture.Click - iconButton decredmaterial.IconButton - errorReceiver chan error - card decredmaterial.Card - backdrops []*widget.Clickable - backdropList *layout.List - optionsMenuCard decredmaterial.Card - optionsMenu []menuItem - addWalletMenu []menuItem - watchOnlyWalletMenu []menuItem - openPopupIndex int - openAddWalletPopupButton *widget.Clickable - isAddWalletMenuOpen bool - watchOnlyWalletLabel decredmaterial.Label - watchOnlyWalletIcon *widget.Image - watchOnlyWalletMoreButtons map[int]decredmaterial.IconButton - shadowBox *decredmaterial.Shadow - separator decredmaterial.Line + multiWallet *dcrlibwallet.MultiWallet + listItems []*walletListItem + + common *pageCommon + theme *decredmaterial.Theme + + walletIcon *widget.Image + accountIcon *widget.Image + walletAlertIcon *widget.Image + container, walletsList layout.List + watchWalletsList *decredmaterial.ClickableList + toAcctDetails []*gesture.Click + iconButton decredmaterial.IconButton + card decredmaterial.Card + backdrop *widget.Clickable + optionsMenuCard decredmaterial.Card + addWalletMenu []menuItem + openPopupIndex int + openAddWalletPopupButton *widget.Clickable + isAddWalletMenuOpen bool + watchOnlyWalletLabel decredmaterial.Label + watchOnlyWalletIcon *widget.Image + shadowBox *decredmaterial.Shadow + separator decredmaterial.Line } func WalletPage(common *pageCommon) Page { pg := &walletPage{ common: common, - walletInfo: common.info, + multiWallet: common.multiWallet, container: layout.List{Axis: layout.Vertical}, - accountsList: layout.List{Axis: layout.Vertical}, walletsList: layout.List{Axis: layout.Vertical}, - list: layout.List{Axis: layout.Vertical}, + watchWalletsList: common.theme.NewClickableList(layout.Vertical), theme: common.theme, - wallet: common.wallet, card: common.theme.Card(), - walletAccount: common.walletAccount, - backdropList: &layout.List{Axis: layout.Vertical}, - errorReceiver: make(chan error), + backdrop: new(widget.Clickable), openAddWalletPopupButton: new(widget.Clickable), openPopupIndex: -1, shadowBox: common.theme.Shadow(), separator: common.theme.Separator(), } - for i := 0; i < 4; i++ { - pg.backdrops = append(pg.backdrops, new(widget.Clickable)) - } - pg.separator.Color = common.theme.Color.Background - pg.collapsibles = make(map[int]collapsible) - pg.watchOnlyWalletMoreButtons = make(map[int]decredmaterial.IconButton) - pg.watchOnlyWalletLabel = pg.theme.Body1(values.String(values.StrWatchOnlyWallets)) pg.watchOnlyWalletLabel.Color = pg.theme.Color.Gray @@ -117,7 +109,7 @@ func WalletPage(common *pageCommon) Page { pg.walletAlertIcon = common.icons.walletAlertIcon pg.walletAlertIcon.Scale = 1 - pg.initializeWalletMenu() + pg.initializeFloatingMenu() pg.watchOnlyWalletIcon = common.icons.watchOnlyWalletIcon pg.toAcctDetails = make([]*gesture.Click, 0) @@ -126,43 +118,108 @@ func WalletPage(common *pageCommon) Page { } func (pg *walletPage) OnResume() { + wallets := pg.common.sortedWalletList() + + pg.listItems = make([]*walletListItem, 0) + for _, wal := range wallets { + accountsResult, err := wal.GetAccountsRaw() + if err != nil { + continue + } + + var totalBalance int64 + for _, acc := range accountsResult.Acc { + totalBalance += acc.TotalBalance + } + + listItem := &walletListItem{ + wal: wal, + accounts: accountsResult.Acc, + totalBalance: dcrutil.Amount(totalBalance).String(), + optionsMenu: pg.getWalletMenu(wal), + accountsList: pg.theme.NewClickableList(layout.Vertical), + } + + if wal.IsWatchingOnlyWallet() { + moreBtn := decredmaterial.IconButton{ + IconButtonStyle: material.IconButtonStyle{ + Button: new(widget.Clickable), + Icon: pg.common.icons.navigationMore, + Size: values.MarginPadding25, + Background: color.NRGBA{}, + Color: pg.theme.Color.Text, + Inset: layout.UniformInset(values.MarginPadding0), + }, + } + listItem.moreButton = moreBtn + } else { + addAcctBtn := pg.theme.IconButton(new(widget.Clickable), pg.common.icons.contentAdd) + addAcctBtn.Inset = layout.UniformInset(values.MarginPadding0) + addAcctBtn.Size = values.MarginPadding25 + addAcctBtn.Background = color.NRGBA{} + addAcctBtn.Color = pg.theme.Color.Text + + backupBtn := pg.theme.PlainIconButton(new(widget.Clickable), pg.common.icons.navigationArrowForward) + backupBtn.Color = pg.theme.Color.Surface + backupBtn.Inset = layout.UniformInset(values.MarginPadding0) + backupBtn.Size = values.MarginPadding20 + + listItem.addAcctBtn = addAcctBtn + listItem.backupAcctBtn = backupBtn + listItem.collapsible = pg.theme.CollapsibleWithOption() + } + + pg.listItems = append(pg.listItems, listItem) + } } -func (pg *walletPage) initializeWalletMenu() { - pg.optionsMenu = []menuItem{ +func (pg *walletPage) initializeFloatingMenu() { + pg.addWalletMenu = []menuItem{ { - text: values.String(values.StrSignMessage), + text: values.String(values.StrCreateANewWallet), button: new(widget.Clickable), - action: func(common *pageCommon) { - common.changePage(PageSignMessage) - }, + action: pg.showAddWalletModal, }, { - text: values.String(values.StrVerifyMessage), - button: new(widget.Clickable), - separate: true, + text: values.String(values.StrImportExistingWallet), + button: new(widget.Clickable), action: func(common *pageCommon) { - *common.returnPage = PageWallet - common.setReturnPage(PageWallet) - common.changePage(PageVerifyMessage) + common.changeWindowPage(CreateRestorePage(common), true) }, }, + { + text: values.String(values.StrImportWatchingOnlyWallet), + button: new(widget.Clickable), + action: pg.showImportWatchOnlyWalletModal, + }, + } +} + +func (pg *walletPage) getWalletMenu(wal *dcrlibwallet.Wallet) []menuItem { + if wal.IsWatchingOnlyWallet() { + return pg.getWatchOnlyWalletMenu(wal) + } + + return []menuItem{ + { + text: values.String(values.StrSignMessage), + button: new(widget.Clickable), + id: PageSignMessage, + }, { text: values.String(values.StrViewProperty), button: new(widget.Clickable), separate: true, action: func(common *pageCommon) { - common.changePage(PageHelp) + common.changeFragment(HelpPage(common), PageHelp) }, }, { text: values.String(values.StrStakeShuffle), button: new(widget.Clickable), separate: true, - action: func(common *pageCommon) { - common.changePage(PagePrivacy) - }, + id: PagePrivacy, }, { text: values.String(values.StrRename), @@ -171,8 +228,8 @@ func (pg *walletPage) initializeWalletMenu() { textModal := newTextInputModal(common). hint("Wallet name"). positiveButton(values.String(values.StrRename), func(newName string, tim *textInputModal) bool { - id := common.info.Wallets[*common.selectedWallet].ID - common.wallet.RenameWallet(id, newName, pg.errorReceiver) + // todo handle error + pg.multiWallet.RenameWallet(wal.ID, newName) return true }) @@ -184,39 +241,17 @@ func (pg *walletPage) initializeWalletMenu() { { text: values.String(values.StrSettings), button: new(widget.Clickable), - action: func(common *pageCommon) { - common.changePage(PageWalletSettings) - }, - }, - } - - pg.addWalletMenu = []menuItem{ - { - text: values.String(values.StrCreateANewWallet), - button: new(widget.Clickable), - action: pg.showAddWalletModal, - }, - { - text: values.String(values.StrImportExistingWallet), - button: new(widget.Clickable), - action: func(common *pageCommon) { - common.changeWindowPage(CreateRestorePage(common), true) - }, - }, - { - text: values.String(values.StrImportWatchingOnlyWallet), - button: new(widget.Clickable), - action: pg.showImportWatchOnlyWalletModal, + id: PageSettings, }, } +} - pg.watchOnlyWalletMenu = []menuItem{ +func (pg *walletPage) getWatchOnlyWalletMenu(wal *dcrlibwallet.Wallet) []menuItem { + return []menuItem{ { text: values.String(values.StrSettings), button: new(widget.Clickable), - action: func(common *pageCommon) { - common.changePage(PageWalletSettings) - }, + id: PageSettings, }, { text: values.String(values.StrRename), @@ -225,8 +260,8 @@ func (pg *walletPage) initializeWalletMenu() { textModal := newTextInputModal(common). hint("Wallet name"). positiveButton(values.String(values.StrRename), func(newName string, tim *textInputModal) bool { - id := common.info.Wallets[*common.selectedWallet].ID - common.wallet.RenameWallet(id, newName, pg.errorReceiver) + //TODO + pg.multiWallet.RenameWallet(wal.ID, newName) return true }) @@ -244,7 +279,7 @@ func (pg *walletPage) showAddWalletModal(common *pageCommon) { enableName(true). passwordCreated(func(walletName, password string, m *createPasswordModal) bool { go func() { - _, err := pg.wallet.GetMultiWallet().CreateNewWallet(walletName, password, dcrlibwallet.PassphraseTypePass) + _, err := pg.multiWallet.CreateNewWallet(walletName, password, dcrlibwallet.PassphraseTypePass) if err != nil { m.setError(err.Error()) m.setLoading(false) @@ -260,13 +295,13 @@ func (pg *walletPage) showImportWatchOnlyWalletModal(common *pageCommon) { newCreateWatchOnlyModal(common). watchOnlyCreated(func(walletName, extPubKey string, m *createWatchOnlyModal) bool { go func() { - err := pg.wallet.ImportWatchOnlyWallet(walletName, extPubKey) + _, err := pg.multiWallet.CreateWatchOnlyWallet(walletName, extPubKey) if err != nil { common.notify(err.Error(), false) m.setError(err.Error()) m.setLoading(false) } else { - pg.wallet.GetMultiWalletInfo() + // pg.wallet.GetMultiWalletInfo() TODO common.notify(values.String(values.StrWatchOnlyWalletImported), true) m.Dismiss() } @@ -278,60 +313,13 @@ func (pg *walletPage) showImportWatchOnlyWalletModal(common *pageCommon) { // Layout lays out the widgets for the main wallets pg. func (pg *walletPage) Layout(gtx layout.Context) layout.Dimensions { common := pg.common - // if *pg.refreshPage { - // common.refreshWindow() - // *pg.refreshPage = false - // } - - if common.info.LoadedWallets == 0 { - return common.UniformPadding(gtx, func(gtx C) D { - return layout.Center.Layout(gtx, common.theme.H3(values.String(values.StrNoWalletLoaded)).Layout) - }) - } - - for index := 0; index < common.info.LoadedWallets; index++ { - if common.info.Wallets[index].IsWatchingOnly { - if _, ok := pg.watchOnlyWalletMoreButtons[index]; !ok { - pg.watchOnlyWalletMoreButtons[index] = decredmaterial.IconButton{ - IconButtonStyle: material.IconButtonStyle{ - Button: new(widget.Clickable), - Icon: common.icons.navigationMore, - Size: values.MarginPadding25, - Background: color.NRGBA{}, - Color: common.theme.Color.Text, - Inset: layout.UniformInset(values.MarginPadding0), - }, - } - } - } else { - if _, ok := pg.collapsibles[index]; !ok { - addAcctBtn := common.theme.IconButton(new(widget.Clickable), common.icons.contentAdd) - addAcctBtn.Inset = layout.UniformInset(values.MarginPadding0) - addAcctBtn.Size = values.MarginPadding25 - addAcctBtn.Background = color.NRGBA{} - addAcctBtn.Color = common.theme.Color.Text - - backupBtn := common.theme.PlainIconButton(new(widget.Clickable), common.icons.navigationArrowForward) - backupBtn.Color = common.theme.Color.Surface - backupBtn.Inset = layout.UniformInset(values.MarginPadding0) - backupBtn.Size = values.MarginPadding20 - - pg.collapsibles[index] = collapsible{ - collapsible: pg.theme.CollapsibleWithOption(), - addAcctBtn: addAcctBtn, - backupAcctBtn: backupBtn, - } - } - - } - } pageContent := []func(gtx C) D{ func(gtx C) D { return pg.walletSection(gtx, common) }, func(gtx C) D { - return pg.watchOnlyWalletSection(gtx, common) + return pg.watchOnlyWalletSection(gtx) }, } @@ -358,29 +346,22 @@ func (pg *walletPage) Layout(gtx layout.Context) layout.Dimensions { }), layout.Expanded(func(gtx C) D { if pg.isAddWalletMenuOpen || pg.openPopupIndex != -1 { - halfHeight := gtx.Constraints.Max.Y / 2 - return pg.container.Layout(gtx, len(pg.backdrops), func(gtx C, i int) D { - gtx.Constraints.Min.Y = halfHeight - return pg.backdrops[i].Layout(gtx) - }) + return pg.backdrop.Layout(gtx) } return D{} }), ) } -func (pg *walletPage) layoutOptionsMenu(gtx layout.Context, optionsMenuIndex int, isWatchOnlyWalletMenu bool) { +func (pg *walletPage) layoutOptionsMenu(gtx layout.Context, optionsMenuIndex int, listItem *walletListItem) { if pg.openPopupIndex != optionsMenuIndex { return } - var menu []menuItem var leftInset float32 - if isWatchOnlyWalletMenu { - menu = pg.watchOnlyWalletMenu + if listItem.wal.IsWatchingOnlyWallet() { leftInset = -35 } else { - menu = pg.optionsMenu leftInset = -120 } @@ -388,6 +369,7 @@ func (pg *walletPage) layoutOptionsMenu(gtx layout.Context, optionsMenuIndex int Top: unit.Dp(30), Left: unit.Dp(leftInset), } + menu := listItem.optionsMenu m := op.Record(gtx.Ops) inset.Layout(gtx, func(gtx C) D { @@ -422,20 +404,18 @@ func (pg *walletPage) layoutOptionsMenu(gtx layout.Context, optionsMenuIndex int } func (pg *walletPage) walletSection(gtx layout.Context, common *pageCommon) layout.Dimensions { - return pg.walletsList.Layout(gtx, common.info.LoadedWallets, func(gtx C, i int) D { - if common.info.Wallets[i].IsWatchingOnly { + return pg.walletsList.Layout(gtx, len(pg.listItems), func(gtx C, i int) D { + listItem := pg.listItems[i] + if listItem.wal.IsWatchingOnlyWallet() { return D{} } - accounts := common.info.Wallets[i].Accounts - pg.updateAcctDetailsButtons(&accounts) - collapsibleMore := func(gtx C) { - pg.layoutOptionsMenu(gtx, i, false) + pg.layoutOptionsMenu(gtx, i, listItem) } collapsibleHeader := func(gtx C) D { - return pg.layoutCollapsibleHeader(gtx, common.info.Wallets[i]) + return pg.layoutCollapsibleHeader(gtx, listItem) } collapsibleBody := func(gtx C) D { @@ -451,12 +431,8 @@ func (pg *walletPage) walletSection(gtx layout.Context, common *pageCommon) layo }.Layout(gtx, pg.theme.Separator().Layout) }), layout.Rigid(func(gtx C) D { - return pg.accountsList.Layout(gtx, len(accounts), func(gtx C, x int) D { - click := pg.toAcctDetails[x] - pointer.Rect(image.Rectangle{Max: gtx.Constraints.Max}).Add(gtx.Ops) - click.Add(gtx.Ops) - pg.goToAcctDetails(gtx, common, &accounts[x], i, click) - return pg.walletAccountsLayout(gtx, accounts[x].Name, accounts[x].TotalBalance, dcrutil.Amount(accounts[x].SpendableBalance).String(), common) + return listItem.accountsList.Layout(gtx, len(listItem.accounts), func(gtx C, x int) D { + return pg.walletAccountsLayout(gtx, listItem.accounts[x]) }) }), layout.Rigid(func(gtx C) D { @@ -466,7 +442,7 @@ func (pg *walletPage) walletSection(gtx layout.Context, common *pageCommon) layo return layout.Inset{ Right: values.MarginPadding10, Left: values.MarginPadding38, - }.Layout(gtx, pg.collapsibles[i].addAcctBtn.Layout) + }.Layout(gtx, listItem.addAcctBtn.Layout) }), layout.Rigid(func(gtx C) D { txt := pg.theme.H6(values.String(values.StrAddNewAccount)) @@ -483,10 +459,10 @@ func (pg *walletPage) walletSection(gtx layout.Context, common *pageCommon) layo return layout.Inset{Bottom: values.MarginPadding10}.Layout(gtx, func(gtx C) D { var children []layout.FlexChild children = append(children, layout.Rigid(func(gtx C) D { - return pg.collapsibles[i].collapsible.Layout(gtx, collapsibleHeader, collapsibleBody, collapsibleMore) + return listItem.collapsible.Layout(gtx, collapsibleHeader, collapsibleBody, collapsibleMore) })) - if len(common.info.Wallets[i].Seed) > 0 { + if len(listItem.wal.EncryptedSeed) > 0 { children = append(children, layout.Rigid(func(gtx C) D { return layout.Inset{Top: unit.Dp(-10)}.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -499,7 +475,7 @@ func (pg *walletPage) walletSection(gtx layout.Context, common *pageCommon) layo pg.card.Color = pg.theme.Color.Danger pg.card.Radius = decredmaterial.CornerRadius{SW: 10, SE: 10} return pg.card.Layout(gtx, func(gtx C) D { - return pg.backupSeedNotification(gtx, common, i) + return pg.backupSeedNotification(gtx, listItem) }) }), ) @@ -511,14 +487,15 @@ func (pg *walletPage) walletSection(gtx layout.Context, common *pageCommon) layo }) } -func (pg *walletPage) watchOnlyWalletSection(gtx layout.Context, common *pageCommon) layout.Dimensions { - watchOnlyWalletCount := 0 - for i := range common.info.Wallets { - if common.info.Wallets[i].IsWatchingOnly { - watchOnlyWalletCount++ +func (pg *walletPage) watchOnlyWalletSection(gtx layout.Context) layout.Dimensions { + hasWatchOnly := false + for _, listItem := range pg.listItems { + if listItem.wal.IsWatchingOnlyWallet() { + hasWatchOnly = true + break } } - if watchOnlyWalletCount == 0 { + if !hasWatchOnly { return D{} } card := pg.card @@ -536,7 +513,7 @@ func (pg *walletPage) watchOnlyWalletSection(gtx layout.Context, common *pageCom }), layout.Rigid(func(gtx C) D { return layout.Inset{Right: values.MarginPadding10}.Layout(gtx, func(gtx C) D { - return pg.layoutWatchOnlyWallets(gtx, common) + return pg.layoutWatchOnlyWallets(gtx) }) }), ) @@ -544,11 +521,13 @@ func (pg *walletPage) watchOnlyWalletSection(gtx layout.Context, common *pageCom }) } -func (pg *walletPage) layoutWatchOnlyWallets(gtx layout.Context, common *pageCommon) D { - return (&layout.List{Axis: layout.Vertical}).Layout(gtx, common.info.LoadedWallets, func(gtx C, i int) D { - if !common.info.Wallets[i].IsWatchingOnly { +func (pg *walletPage) layoutWatchOnlyWallets(gtx layout.Context) D { + return pg.watchWalletsList.Layout(gtx, len(pg.listItems), func(gtx C, i int) D { + listItem := pg.listItems[i] + if !listItem.wal.IsWatchingOnlyWallet() { return D{} } + m := values.MarginPadding10 return layout.Inset{Top: m, Bottom: m}.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -561,17 +540,16 @@ func (pg *walletPage) layoutWatchOnlyWallets(gtx layout.Context, common *pageCom pg.watchOnlyWalletIcon.Scale = 1.0 return inset.Layout(gtx, pg.watchOnlyWalletIcon.Layout) }), - layout.Rigid(pg.theme.Body2(common.info.Wallets[i].Name).Layout), + layout.Rigid(pg.theme.Body2(listItem.wal.Name).Layout), layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, func(gtx C) D { return layout.Flex{}.Layout(gtx, layout.Rigid(func(gtx C) D { - spendableBalanceText := dcrutil.Amount(common.info.Wallets[i].SpendableBalance).String() - return pg.theme.Body2(spendableBalanceText).Layout(gtx) + return pg.theme.Body2(listItem.totalBalance).Layout(gtx) }), layout.Rigid(func(gtx C) D { - pg.layoutOptionsMenu(gtx, i, true) - return layout.Inset{Top: unit.Dp(-3)}.Layout(gtx, pg.watchOnlyWalletMoreButtons[i].Layout) + pg.layoutOptionsMenu(gtx, i, listItem) + return layout.Inset{Top: unit.Dp(-3)}.Layout(gtx, listItem.moreButton.Layout) }), ) }) @@ -580,7 +558,7 @@ func (pg *walletPage) layoutWatchOnlyWallets(gtx layout.Context, common *pageCom }), layout.Rigid(func(gtx C) D { return layout.Inset{Top: values.MarginPadding10, Left: values.MarginPadding38, Right: values.MarginPaddingMinus10}.Layout(gtx, func(gtx C) D { - if i == common.info.LoadedWallets-1 { + if i == len(pg.listItems)-1 { return D{} } return pg.theme.Separator().Layout(gtx) @@ -591,7 +569,7 @@ func (pg *walletPage) layoutWatchOnlyWallets(gtx layout.Context, common *pageCom }) } -func (pg *walletPage) layoutCollapsibleHeader(gtx layout.Context, walletInfo wallet.InfoShort) D { +func (pg *walletPage) layoutCollapsibleHeader(gtx layout.Context, listItem *walletListItem) D { return layout.Flex{Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Inset{ @@ -601,10 +579,10 @@ func (pg *walletPage) layoutCollapsibleHeader(gtx layout.Context, walletInfo wal layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return pg.theme.Body1(strings.Title(strings.ToLower(walletInfo.Name))).Layout(gtx) + return pg.theme.Body1(listItem.wal.Name).Layout(gtx) }), layout.Rigid(func(gtx C) D { - if len(walletInfo.Seed) > 0 { + if len(listItem.wal.EncryptedSeed) > 0 { txt := pg.theme.Caption(values.String(values.StrNotBackedUp)) txt.Color = pg.theme.Color.Danger return txt.Layout(gtx) @@ -615,7 +593,7 @@ func (pg *walletPage) layoutCollapsibleHeader(gtx layout.Context, walletInfo wal }), layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, func(gtx C) D { - balanceLabel := pg.theme.Body1(walletInfo.Balance) + balanceLabel := pg.theme.Body1(listItem.totalBalance) balanceLabel.Color = pg.theme.Color.Gray return layout.Inset{Right: values.MarginPadding5}.Layout(gtx, balanceLabel.Layout) }) @@ -623,22 +601,10 @@ func (pg *walletPage) layoutCollapsibleHeader(gtx layout.Context, walletInfo wal ) } -func (pg *walletPage) tableLayout(gtx layout.Context, leftLabel, rightLabel decredmaterial.Label, isIcon bool, seed int) layout.Dimensions { +func (pg *walletPage) tableLayout(gtx layout.Context, leftLabel, rightLabel decredmaterial.Label) layout.Dimensions { m := values.MarginPadding0 - if seed > 0 { - m = values.MarginPaddingMinus5 - } return layout.Flex{}.Layout(gtx, - layout.Rigid(func(gtx C) D { - if isIcon { - inset := layout.Inset{ - Right: values.MarginPadding10, - } - return inset.Layout(gtx, pg.walletIcon.Layout) - } - return layout.Dimensions{} - }), layout.Rigid(func(gtx C) D { inset := layout.Inset{ Top: m, @@ -648,19 +614,6 @@ func (pg *walletPage) tableLayout(gtx layout.Context, leftLabel, rightLabel decr layout.Rigid(func(gtx C) D { return leftLabel.Layout(gtx) }), - layout.Rigid(func(gtx C) D { - if isIcon { - if seed > 0 { - txt := pg.theme.Caption(values.String(values.StrNotBackedUp)) - txt.Color = pg.theme.Color.Danger - inset := layout.Inset{ - Bottom: values.MarginPadding5, - } - return inset.Layout(gtx, txt.Layout) - } - } - return layout.Dimensions{} - }), ) }) }), @@ -670,10 +623,15 @@ func (pg *walletPage) tableLayout(gtx layout.Context, leftLabel, rightLabel decr ) } -func (pg *walletPage) walletAccountsLayout(gtx layout.Context, name, totalBal, spendableBal string, common *pageCommon) layout.Dimensions { +func (pg *walletPage) walletAccountsLayout(gtx layout.Context, account *dcrlibwallet.Account) layout.Dimensions { + common := pg.common + pg.accountIcon = common.icons.accountIcon - if name == "imported" { + if account.Number == MaxInt32 { pg.accountIcon = common.icons.importedAccountIcon + if account.TotalBalance == 0 { + return D{} + } } pg.accountIcon.Scale = 1.0 @@ -702,11 +660,11 @@ func (pg *walletPage) walletAccountsLayout(gtx layout.Context, name, totalBal, s return inset.Layout(gtx, func(gtx C) D { return layout.Flex{}.Layout(gtx, layout.Rigid(func(gtx C) D { - acctName := strings.Title(strings.ToLower(name)) - return pg.theme.H6(acctName).Layout(gtx) + return pg.theme.H6(account.Name).Layout(gtx) }), layout.Flexed(1, func(gtx C) D { return layout.E.Layout(gtx, func(gtx C) D { + totalBal := dcrutil.Amount(account.Balance.Spendable).String() return common.layoutBalance(gtx, totalBal, true) }) }), @@ -718,11 +676,13 @@ func (pg *walletPage) walletAccountsLayout(gtx layout.Context, name, totalBal, s Right: values.MarginPadding10, } return inset.Layout(gtx, func(gtx C) D { - spendibleLabel := pg.theme.Body2(values.String(values.StrLabelSpendable)) - spendibleLabel.Color = pg.theme.Color.Gray - spendibleBalLabel := pg.theme.Body2(spendableBal) - spendibleBalLabel.Color = pg.theme.Color.Gray - return pg.tableLayout(gtx, spendibleLabel, spendibleBalLabel, false, 0) + spendableLabel := pg.theme.Body2(values.String(values.StrLabelSpendable)) + spendableLabel.Color = pg.theme.Color.Gray + + spendableBal := dcrutil.Amount(account.Balance.Spendable).String() + spendableBalLabel := pg.theme.Body2(spendableBal) + spendableBalLabel.Color = pg.theme.Color.Gray + return pg.tableLayout(gtx, spendableLabel, spendableBalLabel) }) }), ) @@ -739,9 +699,9 @@ func (pg *walletPage) walletAccountsLayout(gtx layout.Context, name, totalBal, s ) } -func (pg *walletPage) backupSeedNotification(gtx layout.Context, common *pageCommon, i int) layout.Dimensions { +func (pg *walletPage) backupSeedNotification(gtx layout.Context, listItem *walletListItem) layout.Dimensions { gtx.Constraints.Min.X = gtx.Constraints.Max.X - textColour := common.theme.Color.InvText + textColour := pg.theme.Color.InvText return layout.UniformInset(values.MarginPadding10).Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -771,7 +731,7 @@ func (pg *walletPage) backupSeedNotification(gtx layout.Context, common *pageCom Top: values.MarginPadding5, } return inset.Layout(gtx, func(gtx C) D { - return layout.E.Layout(gtx, pg.collapsibles[i].backupAcctBtn.Layout) + return layout.E.Layout(gtx, listItem.backupAcctBtn.Layout) }) }), ) @@ -820,120 +780,105 @@ func (pg *walletPage) layoutAddWalletSection(gtx layout.Context, common *pageCom }) } -func (pg *walletPage) updateAcctDetailsButtons(walAcct *[]wallet.Account) { - if len(*walAcct) != len(pg.toAcctDetails) { - for i := 0; i < len(*walAcct); i++ { - pg.toAcctDetails = append(pg.toAcctDetails, &gesture.Click{}) - } - } -} - -func (pg *walletPage) goToAcctDetails(gtx layout.Context, common *pageCommon, acct *wallet.Account, index int, click *gesture.Click) { - for _, e := range click.Events(gtx) { - if e.Type == gesture.TypeClick { - *pg.walletAccount = acct - common.changePage(PageAccountDetails) - *common.selectedWallet = index - } - } -} - -func (pg *walletPage) isCollapsibleMenuOpen() bool { - return pg.openPopupIndex > -1 -} - func (pg *walletPage) closePopups() { pg.openPopupIndex = -1 pg.isAddWalletMenuOpen = false } -func (pg *walletPage) openPopup(common *pageCommon, index int) { - *common.selectedWallet = index +func (pg *walletPage) openPopup(index int) { + if pg.openPopupIndex >= 0 { + if pg.openPopupIndex == index { + pg.closePopups() + return + } + pg.closePopups() + } + pg.openPopupIndex = index } func (pg *walletPage) handle() { common := pg.common - for index := range pg.backdrops { - for pg.backdrops[index].Clicked() { - pg.closePopups() - } + for pg.backdrop.Clicked() { + pg.closePopups() } - for index := range pg.collapsibles { - for pg.collapsibles[index].collapsible.MoreTriggered() { - if pg.isCollapsibleMenuOpen() { - if pg.openPopupIndex == index { - pg.closePopups() - } else { - pg.closePopups() - pg.openPopup(common, index) - } - } else { - pg.openPopup(common, index) - } - } - - for pg.collapsibles[index].addAcctBtn.Button.Clicked() { - walletID := pg.walletInfo.Wallets[index].ID + if ok, selectedItem := pg.watchWalletsList.ItemClicked(); ok { + listItem := pg.listItems[selectedItem] + // TODO: find default account using number + pg.common.changeFragment(AcctDetailsPage(common, listItem.accounts[0]), PageAccountDetails) + } - textModal := newTextInputModal(pg.common). - hint("Account name"). - positiveButton(values.String(values.StrCreate), func(accountName string, tim *textInputModal) bool { - if accountName != "" { - newPasswordModal(pg.common). - title(values.String(values.StrCreateNewAccount)). - hint("Spending password"). - negativeButton(values.String(values.StrCancel), func() {}). - positiveButton(values.String(values.StrConfirm), func(password string, pm *passwordModal) bool { - go func() { + for index, listItem := range pg.listItems { + *common.selectedWallet = index - pg.wallet.AddAccount(walletID, accountName, []byte(password), pg.errorReceiver, func(acct *dcrlibwallet.Account) { + if ok, selectedItem := listItem.accountsList.ItemClicked(); ok { + pg.common.changeFragment(AcctDetailsPage(common, listItem.accounts[selectedItem]), PageAccountDetails) + } - }) - pm.Dismiss() - }() + if listItem.wal.IsWatchingOnlyWallet() { + for listItem.moreButton.Button.Clicked() { + pg.openPopup(index) + } + } else { + for listItem.collapsible.MoreTriggered() { + pg.openPopup(index) + } - return false - }).Show() - } - return true - }) + for listItem.addAcctBtn.Button.Clicked() { + walletID := listItem.wal.ID + + textModal := newTextInputModal(pg.common). + hint("Account name"). + positiveButton(values.String(values.StrCreate), func(accountName string, tim *textInputModal) bool { + if accountName != "" { + newPasswordModal(pg.common). + title(values.String(values.StrCreateNewAccount)). + hint("Spending password"). + negativeButton(values.String(values.StrCancel), func() {}). + positiveButton(values.String(values.StrConfirm), func(password string, pm *passwordModal) bool { + go func() { + + wal := pg.multiWallet.WalletWithID(walletID) + wal.CreateNewAccount(accountName, []byte(password)) // TODO + pm.Dismiss() + }() + + return false + }).Show() + } + return true + }) - textModal.title(values.String(values.StrCreateNewAccount)). - negativeButton(values.String(values.StrCancel), func() {}) - textModal.Show() - break - } + textModal.title(values.String(values.StrCreateNewAccount)). + negativeButton(values.String(values.StrCancel), func() {}) + textModal.Show() + break + } - for pg.collapsibles[index].backupAcctBtn.Button.Clicked() { - *common.selectedWallet = index - pg.current = pg.walletInfo.Wallets[index] - common.changePage(PageSeedBackup) + for listItem.backupAcctBtn.Button.Clicked() { + common.changePage(PageSeedBackup) + } } - } - for walletIndex, button := range pg.watchOnlyWalletMoreButtons { - for button.Button.Clicked() { - *common.selectedWallet = walletIndex - pg.openPopupIndex = walletIndex - } - } + for _, menu := range listItem.optionsMenu { + if menu.button.Clicked() { + switch menu.id { + case PageSignMessage: + common.changeFragment(SignMessagePage(common, listItem.wal), PageSignMessage) + case PagePrivacy: + common.changeFragment(PrivacyPage(common, listItem.wal), PagePrivacy) + case PageSettings: + common.changeFragment(WalletSettingsPage(common, listItem.wal), PageWalletSettings) + default: + menu.action(common) + } - for index := range pg.optionsMenu { - if pg.optionsMenu[index].button.Clicked() { - pg.openPopupIndex = -1 - common.setReturnPage(PageWallet) - pg.optionsMenu[index].action(common) + pg.openPopupIndex = -1 + } } - } - for index := range pg.watchOnlyWalletMenu { - if pg.watchOnlyWalletMenu[index].button.Clicked() { - pg.openPopupIndex = -1 - pg.watchOnlyWalletMenu[index].action(common) - } } for index := range pg.addWalletMenu { @@ -946,17 +891,6 @@ func (pg *walletPage) handle() { for pg.openAddWalletPopupButton.Clicked() { pg.isAddWalletMenuOpen = !pg.isAddWalletMenuOpen } - - select { - case err := <-pg.errorReceiver: - if err.Error() == dcrlibwallet.ErrInvalidPassphrase { - e := values.String(values.StrInvalidPassphrase) - common.notify(e, false) - return - } - common.notify(err.Error(), false) - default: - } } func (pg *walletPage) onClose() { diff --git a/ui/wallet_settings_page.go b/ui/wallet_settings_page.go index 92e988630..3c105c042 100644 --- a/ui/wallet_settings_page.go +++ b/ui/wallet_settings_page.go @@ -7,38 +7,32 @@ import ( "github.com/planetdecred/dcrlibwallet" "github.com/planetdecred/godcr/ui/decredmaterial" "github.com/planetdecred/godcr/ui/values" - "github.com/planetdecred/godcr/wallet" ) const PageWalletSettings = "WalletSettings" type walletSettingsPage struct { - theme *decredmaterial.Theme - common *pageCommon - walletInfo *wallet.MultiWalletInfo - wal *wallet.Wallet + theme *decredmaterial.Theme + common *pageCommon + wallet *dcrlibwallet.Wallet changePass, rescan, deleteWallet *widget.Clickable notificationW *widget.Bool - errorReceiver chan error chevronRightIcon *widget.Icon backButton decredmaterial.IconButton } -func WalletSettingsPage(common *pageCommon) Page { +func WalletSettingsPage(common *pageCommon, wal *dcrlibwallet.Wallet) Page { pg := &walletSettingsPage{ theme: common.theme, common: common, - walletInfo: common.info, - wal: common.wallet, + wallet: wal, notificationW: new(widget.Bool), - errorReceiver: make(chan error), - - changePass: new(widget.Clickable), - rescan: new(widget.Clickable), - deleteWallet: new(widget.Clickable), + changePass: new(widget.Clickable), + rescan: new(widget.Clickable), + deleteWallet: new(widget.Clickable), chevronRightIcon: common.icons.chevronRight, } @@ -56,8 +50,8 @@ func (pg *walletSettingsPage) OnResume() { func (pg *walletSettingsPage) Layout(gtx layout.Context) layout.Dimensions { common := pg.common - beep := pg.wal.ReadBoolConfigValueForKey(dcrlibwallet.BeepNewBlocksConfigKey) - pg.notificationW.Value = false + beep := pg.wallet.ReadBoolConfigValueForKey(dcrlibwallet.BeepNewBlocksConfigKey, false) + pg.notificationW.Value = beep if beep { pg.notificationW.Value = true } @@ -65,7 +59,7 @@ func (pg *walletSettingsPage) Layout(gtx layout.Context) layout.Dimensions { body := func(gtx C) D { page := SubPage{ title: values.String(values.StrSettings), - walletName: common.info.Wallets[*common.selectedWallet].Name, + walletName: pg.wallet.Name, backButton: pg.backButton, back: func() { common.changePage(PageWallet) @@ -184,22 +178,19 @@ func (pg *walletSettingsPage) bottomSectionLabel(title string) layout.Widget { func (pg *walletSettingsPage) handle() { common := pg.common for pg.changePass.Clicked() { - walletID := pg.walletInfo.Wallets[*common.selectedWallet].ID - newPasswordModal(common). title(values.String(values.StrChangeSpendingPass)). hint("Current spending password"). negativeButton(values.String(values.StrCancel), func() {}). positiveButton(values.String(values.StrConfirm), func(password string, pm *passwordModal) bool { go func() { - wal := pg.wal.GetMultiWallet().WalletWithID(walletID) - err := wal.UnlockWallet([]byte(password)) + err := pg.wallet.UnlockWallet([]byte(password)) if err != nil { pm.setError(err.Error()) pm.setLoading(false) return } - wal.LockWallet() + pg.wallet.LockWallet() pm.Dismiss() // change password @@ -210,7 +201,7 @@ func (pg *walletSettingsPage) handle() { confirmPasswordHint("Confirm new spending password"). passwordCreated(func(walletName, newPassword string, m *createPasswordModal) bool { go func() { - err := pg.wal.GetMultiWallet().ChangePrivatePassphraseForWallet(walletID, []byte(password), + err := pg.common.multiWallet.ChangePrivatePassphraseForWallet(pg.wallet.ID, []byte(password), []byte(newPassword), dcrlibwallet.PassphraseTypePass) if err != nil { m.setError(err.Error()) @@ -230,7 +221,6 @@ func (pg *walletSettingsPage) handle() { } for pg.rescan.Clicked() { - walletID := pg.walletInfo.Wallets[*common.selectedWallet].ID go func() { info := newInfoModal(common). title(values.String(values.StrRescanBlockchain)). @@ -238,7 +228,7 @@ func (pg *walletSettingsPage) handle() { " blockchain for transactions"). negativeButton(values.String(values.StrCancel), func() {}). positiveButton(values.String(values.StrRescan), func() { - err := pg.wal.RescanBlocks(walletID) + err := pg.common.multiWallet.RescanBlocks(pg.wallet.ID) if err != nil { if err.Error() == dcrlibwallet.ErrNotConnected { common.notify(values.String(values.StrNotConnected), false) @@ -257,7 +247,7 @@ func (pg *walletSettingsPage) handle() { } if pg.notificationW.Changed() { - pg.wal.SaveConfigValueForKey(dcrlibwallet.BeepNewBlocksConfigKey, pg.notificationW.Value) + pg.wallet.SetBoolConfigValueForKey(dcrlibwallet.BeepNewBlocksConfigKey, pg.notificationW.Value) } for pg.deleteWallet.Clicked() { @@ -266,29 +256,28 @@ func (pg *walletSettingsPage) handle() { body("Make sure to have the seed phrase backed up before removing the wallet"). negativeButton(values.String(values.StrCancel), func() {}). positiveButton(values.String(values.StrRemove), func() { - walletID := pg.walletInfo.Wallets[*common.selectedWallet].ID newPasswordModal(common). title(values.String(values.StrConfirmToRemove)). negativeButton(values.String(values.StrCancel), func() {}). positiveButtonStyle(common.theme.Color.Surface, common.theme.Color.Danger). positiveButton(values.String(values.StrConfirm), func(password string, pm *passwordModal) bool { - pg.wal.DeleteWallet(walletID, []byte(password), pg.errorReceiver) - return true + go func() { + err := pg.common.multiWallet.DeleteWallet(pg.wallet.ID, []byte(password)) + if err != nil { + pm.setError(err.Error()) + pm.setLoading(false) + return + } + pm.Dismiss() + pm.changePage(PageWallet) + }() + return false }).Show() }).Show() break } - - select { - case err := <-pg.errorReceiver: - if err.Error() == dcrlibwallet.ErrInvalidPassphrase { - e := values.String(values.StrInvalidPassphrase) - common.notify(e, false) - } - default: - } } func (pg *walletSettingsPage) onClose() {}