diff --git a/ui/page/privacy/manual_mixer_setup_page.go b/ui/page/privacy/manual_mixer_setup_page.go index ac2aeb019..21bd82e1d 100644 --- a/ui/page/privacy/manual_mixer_setup_page.go +++ b/ui/page/privacy/manual_mixer_setup_page.go @@ -1,8 +1,6 @@ package privacy import ( - "context" - "gioui.org/layout" "github.com/crypto-power/cryptopower/app" @@ -12,7 +10,6 @@ import ( "github.com/crypto-power/cryptopower/ui/load" "github.com/crypto-power/cryptopower/ui/modal" "github.com/crypto-power/cryptopower/ui/page/components" - "github.com/crypto-power/cryptopower/ui/renderers" "github.com/crypto-power/cryptopower/ui/values" ) @@ -26,15 +23,14 @@ type ManualMixerSetupPage struct { // and the root WindowNavigator. *app.GenericPageModal - ctx context.Context // page context - ctxCancel context.CancelFunc - mixedAccountSelector *components.WalletAndAccountSelector unmixedAccountSelector *components.WalletAndAccountSelector backButton cryptomaterial.IconButton infoButton cryptomaterial.IconButton + backClickable *cryptomaterial.Clickable toPrivacySetup cryptomaterial.Button + backIcon *cryptomaterial.Icon dcrWallet *dcr.Asset } @@ -46,6 +42,9 @@ func NewManualMixerSetupPage(l *load.Load, dcrWallet *dcr.Asset) *ManualMixerSet toPrivacySetup: l.Theme.Button(values.String(values.StrSetUp)), dcrWallet: dcrWallet, } + pg.backClickable = pg.Theme.NewClickable(true) + pg.backIcon = cryptomaterial.NewIcon(pg.Theme.Icons.NavigationArrowBack) + pg.backIcon.Color = pg.Theme.Color.Gray1 // Mixed account picker pg.mixedAccountSelector = components.NewWalletAndAccountSelector(l). @@ -105,8 +104,6 @@ func NewManualMixerSetupPage(l *load.Load, dcrWallet *dcr.Asset) *ManualMixerSet // the page is displayed. // Part of the load.Page interface. func (pg *ManualMixerSetupPage) OnNavigatedTo() { - pg.ctx, pg.ctxCancel = context.WithCancel(context.TODO()) - pg.mixedAccountSelector.SelectFirstValidAccount(pg.dcrWallet) pg.unmixedAccountSelector.SelectFirstValidAccount(pg.dcrWallet) } @@ -114,69 +111,119 @@ func (pg *ManualMixerSetupPage) OnNavigatedTo() { // Layout draws the page UI components into the provided layout context // to be eventually drawn on screen. // Part of the load.Page interface. -func (pg *ManualMixerSetupPage) Layout(gtx layout.Context) layout.Dimensions { - body := func(gtx C) D { - page := components.SubPage{ - Load: pg.Load, - Title: values.String(values.StrManualSetUp), - BackButton: pg.backButton, - Back: func() { - pg.ParentNavigator().CloseCurrentPage() - }, - Body: func(gtx C) D { - return pg.Theme.Card().Layout(gtx, func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X +func (pg *ManualMixerSetupPage) Layout(gtx C) D { + return pg.Theme.Card().Layout(gtx, func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Start}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return pg.backClickable.Layout(gtx, pg.backLayout) + }), + layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Flexed(1, func(gtx C) D { + layout.Rigid(func(gtx C) D { + return pg.mixerAccountSections(gtx, values.String(values.StrMixedAccount), func(gtx C) D { + return pg.mixedAccountSelector.Layout(pg.ParentWindow(), gtx) + }) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{Top: values.MarginPaddingMinus15}.Layout(gtx, func(gtx C) D { + return pg.mixerAccountSections(gtx, values.String(values.StrUnmixedAccount), func(gtx C) D { + return pg.unmixedAccountSelector.Layout(pg.ParentWindow(), gtx) + }) + }) + }), + layout.Rigid(layout.Spacer{Height: values.MarginPadding15}.Layout), + layout.Rigid(pg.cautionCard), + layout.Rigid(layout.Spacer{Height: values.MarginPadding15}.Layout), + ) + }), + layout.Rigid(func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.UniformInset(values.MarginPadding15).Layout(gtx, pg.toPrivacySetup.Layout) + }), + ) + }) + }) +} + +func (pg *ManualMixerSetupPage) cautionCard(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Inset{ + Left: values.MarginPadding15, + Right: values.MarginPadding15, + }.Layout(gtx, func(gtx C) D { + card := pg.Theme.Card() + card.Color = pg.Theme.Color.Gray4 + return card.Layout(gtx, func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + gtx.Constraints.Min.Y = int(values.MarginPadding100) + gtx.Constraints.Max.Y = gtx.Constraints.Min.Y + return layout.UniformInset(values.MarginPadding15).Layout(gtx, func(gtx C) D { + return layout.Flex{Alignment: layout.Start}.Layout(gtx, + layout.Flexed(1, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle, Spacing: layout.SpaceAround}.Layout(gtx, + layout.Rigid(func(gtx C) D { + gtx.Constraints.Max.X = int(values.MarginPadding40) + return pg.Theme.Icons.ActionInfo.Layout(gtx, pg.Theme.Color.Gray1) + }), + ) + }), + layout.Flexed(7, func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding10, + }.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - return pg.mixerAccountSections(gtx, values.String(values.StrMixedAccount), func(gtx layout.Context) layout.Dimensions { - return pg.mixedAccountSelector.Layout(pg.ParentWindow(), gtx) - }) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{Top: values.MarginPaddingMinus15}.Layout(gtx, func(gtx C) D { - return pg.mixerAccountSections(gtx, values.String(values.StrUnmixedAccount), func(gtx layout.Context) layout.Dimensions { - return pg.unmixedAccountSelector.Layout(pg.ParentWindow(), gtx) - }) - }) + label := pg.Theme.H6(values.String(values.StrSetUpStakeShuffleWarningTitle)) + return label.Layout(gtx) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: values.MarginPadding10, Left: values.MarginPadding16, Right: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - return layout.Flex{ - Axis: layout.Horizontal, - }.Layout(gtx, - layout.Rigid(func(gtx C) D { - return pg.Theme.Icons.ActionInfo.Layout(gtx, pg.Theme.Color.Gray1) - }), - layout.Rigid(func(gtx C) D { - txt := ` - Make sure to select the same accounts from the previous privacy setup.
Failing to do so could compromise wallet privacy.
You may not select the same account for mixed and unmixed. -
` - return layout.Inset{ - Left: values.MarginPadding8, - }.Layout(gtx, renderers.RenderHTML(txt, pg.Theme).Layout) - }), - ) - }) + label := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleWarningDesc)) + return label.Layout(gtx) }), ) - }), - layout.Rigid(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.UniformInset(values.MarginPadding15).Layout(gtx, pg.toPrivacySetup.Layout) - }), - ) - }) - }, - } - return page.Layout(pg.ParentWindow(), gtx) - } + }) + }), + ) + }) + }) + }) +} - return cryptomaterial.UniformPadding(gtx, body) +func (pg *ManualMixerSetupPage) backLayout(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + // Setting a minimum Y larger than the label allows it to be centered. + gtx.Constraints.Min.Y = int(values.MarginPadding50) + return layout.Flex{Alignment: layout.Start}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding15, + Right: values.MarginPadding15, + }.Layout(gtx, func(gtx C) D { + return layout.Flex{ + Axis: layout.Vertical, + Alignment: layout.Middle, + Spacing: layout.SpaceAround, + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return pg.backIcon.Layout(gtx, values.MarginPadding30) + }), + ) + }) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle, Spacing: layout.SpaceAround}.Layout(gtx, + layout.Rigid(func(gtx C) D { + label := pg.Theme.H6(values.String(values.StrSetUpStakeShuffleManualTitle)) + return layout.E.Layout(gtx, label.Layout) + }), + ) + }), + ) } -func (pg *ManualMixerSetupPage) mixerAccountSections(gtx layout.Context, title string, body layout.Widget) layout.Dimensions { +func (pg *ManualMixerSetupPage) mixerAccountSections(gtx C, title string, body layout.Widget) D { return pg.Theme.Card().Layout(gtx, func(gtx C) D { return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -242,21 +289,34 @@ func (pg *ManualMixerSetupPage) showModalSetupMixerAcct() { // displayed. // Part of the load.Page interface. func (pg *ManualMixerSetupPage) HandleUserInteractions() { + if pg.backClickable.Clicked() { + pg.ParentNavigator().CloseCurrentPage() + } + if pg.toPrivacySetup.Clicked() { go pg.showModalSetupMixerAcct() } + enableToPriv := func() { + mixed, unmixed := pg.mixedAccountSelector.SelectedAccount(), pg.unmixedAccountSelector.SelectedAccount() + if mixed == nil || unmixed == nil { + pg.toPrivacySetup.SetEnabled(false) + return + } - if pg.mixedAccountSelector.SelectedAccount().Number == pg.unmixedAccountSelector.SelectedAccount().Number { - pg.toPrivacySetup.SetEnabled(false) - } else { - pg.toPrivacySetup.SetEnabled(true) - } + if mixed.Number == unmixed.Number { + pg.toPrivacySetup.SetEnabled(false) + return + } - // Disable set up button if either mixed or unmixed account is the default account. - if pg.mixedAccountSelector.SelectedAccount().Number == dcr.DefaultAccountNum || - pg.unmixedAccountSelector.SelectedAccount().Number == dcr.DefaultAccountNum { - pg.toPrivacySetup.SetEnabled(false) + // Disable set up button if either mixed or unmixed account is the default account. + if mixed.Number == dcr.DefaultAccountNum || + unmixed.Number == dcr.DefaultAccountNum { + pg.toPrivacySetup.SetEnabled(false) + return + } + pg.toPrivacySetup.SetEnabled(true) } + enableToPriv() } // OnNavigatedFrom is called when the page is about to be removed from @@ -266,6 +326,4 @@ func (pg *ManualMixerSetupPage) HandleUserInteractions() { // OnNavigatedTo() will be called again. This method should not destroy UI // components unless they'll be recreated in the OnNavigatedTo() method. // Part of the load.Page interface. -func (pg *ManualMixerSetupPage) OnNavigatedFrom() { - pg.ctxCancel() -} +func (pg *ManualMixerSetupPage) OnNavigatedFrom() {} diff --git a/ui/page/privacy/setup_mixer_accounts_page.go b/ui/page/privacy/setup_mixer_accounts_page.go index bfa8655b2..9e9713b65 100644 --- a/ui/page/privacy/setup_mixer_accounts_page.go +++ b/ui/page/privacy/setup_mixer_accounts_page.go @@ -1,18 +1,15 @@ package privacy import ( - "context" - "gioui.org/layout" - "gioui.org/text" "gioui.org/widget" "github.com/crypto-power/cryptopower/app" "github.com/crypto-power/cryptopower/libwallet/assets/dcr" "github.com/crypto-power/cryptopower/ui/cryptomaterial" "github.com/crypto-power/cryptopower/ui/load" + "github.com/crypto-power/cryptopower/ui/modal" "github.com/crypto-power/cryptopower/ui/page/components" - "github.com/crypto-power/cryptopower/ui/renderers" "github.com/crypto-power/cryptopower/ui/values" ) @@ -27,14 +24,13 @@ type SetupMixerAccountsPage struct { *app.GenericPageModal dcrWallet *dcr.Asset - ctx context.Context // page context - ctxCancel context.CancelFunc + backButton cryptomaterial.IconButton + infoButton cryptomaterial.IconButton + autoSetupClickable *cryptomaterial.Clickable + manualSetupClickable *cryptomaterial.Clickable + nextIcon *cryptomaterial.Icon - backButton cryptomaterial.IconButton - infoButton cryptomaterial.IconButton - autoSetupClickable *cryptomaterial.Clickable - manualSetupClickable *cryptomaterial.Clickable - autoSetupIcon, nextIcon *cryptomaterial.Icon + manualEnabled bool } func NewSetupMixerAccountsPage(l *load.Load, dcrWallet *dcr.Asset) *SetupMixerAccountsPage { @@ -43,17 +39,11 @@ func NewSetupMixerAccountsPage(l *load.Load, dcrWallet *dcr.Asset) *SetupMixerAc GenericPageModal: app.NewGenericPageModal(SetupMixerAccountsPageID), dcrWallet: dcrWallet, } - pg.backButton, pg.infoButton = components.SubpageHeaderButtons(l) - - pg.autoSetupIcon = cryptomaterial.NewIcon(pg.Theme.Icons.ActionCheckCircle) - pg.autoSetupIcon.Color = pg.Theme.Color.Success - pg.nextIcon = cryptomaterial.NewIcon(pg.Theme.Icons.NavigationArrowForward) pg.nextIcon.Color = pg.Theme.Color.Gray1 - pg.autoSetupClickable = pg.Theme.NewClickable(true) pg.manualSetupClickable = pg.Theme.NewClickable(true) - + pg.backButton, pg.infoButton = components.SubpageHeaderButtons(l) return pg } @@ -62,162 +52,150 @@ func NewSetupMixerAccountsPage(l *load.Load, dcrWallet *dcr.Asset) *SetupMixerAc // the page is displayed. // Part of the load.Page interface. func (pg *SetupMixerAccountsPage) OnNavigatedTo() { - pg.ctx, pg.ctxCancel = context.WithCancel(context.TODO()) + accts, err := pg.dcrWallet.GetAccountsRaw() + if err != nil { + log.Errorf("Unable to get accounts to set up mixer: %v", err) + return + } + pg.manualEnabled = len(accts.Accounts) > 2 } // Layout draws the page UI components into the provided layout context // to be eventually drawn on screen. // Part of the load.Page interface. -func (pg *SetupMixerAccountsPage) Layout(gtx layout.Context) layout.Dimensions { - body := func(gtx C) D { - page := components.SubPage{ - Load: pg.Load, - Title: values.String(values.StrSetUpNeededAccs), - BackButton: pg.backButton, - Back: func() { - pg.ParentNavigator().CloseCurrentPage() - }, - Body: func(gtx C) D { - return pg.Theme.Card().Layout(gtx, func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Flexed(1, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - txt := pg.Theme.Body1(values.String(values.StrMultipleMixerAccNeeded) + ":") - txt.Alignment = text.Start - ic := cryptomaterial.NewIcon(pg.Theme.Icons.ImageBrightness1) - ic.Color = pg.Theme.Color.Gray1 - return layout.Inset{Top: values.MarginPadding16, Left: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical, Alignment: layout.Start}.Layout(gtx, - layout.Rigid(txt.Layout), - layout.Rigid(func(gtx C) D { - return layout.Inset{Top: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{Bottom: values.MarginPadding12}.Layout(gtx, func(gtx C) D { - return ic.Layout(gtx, values.MarginPadding8) - }) - }), - layout.Rigid(func(gtx C) D { - txt2 := ` - Mixed account will be the outbounding spending account. - ` - - return layout.Inset{ - Left: values.MarginPadding8, - }.Layout(gtx, renderers.RenderHTML(txt2, pg.Theme).Layout) - }), - ) - }) - }), - layout.Rigid(func(gtx C) D { - return layout.Inset{Top: values.MarginPadding16}.Layout(gtx, func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{Bottom: values.MarginPadding12}.Layout(gtx, func(gtx C) D { - return ic.Layout(gtx, values.MarginPadding8) - }) - }), - layout.Rigid(func(gtx C) D { - txt3 := ` - Unmixed account will be the change handling account. - ` - - return layout.Inset{ - Left: values.MarginPadding8, - }.Layout(gtx, renderers.RenderHTML(txt3, pg.Theme).Layout) - }), - ) - }) - }), - ) - }) - }), - ) - }), - layout.Rigid(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return pg.autoSetupClickable.Layout(gtx, pg.autoSetupLayout) - }), - layout.Rigid(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return pg.manualSetupClickable.Layout(gtx, pg.manualSetupLayout) - }), - ) - }) - }, - } - return page.Layout(pg.ParentWindow(), gtx) - } - - return cryptomaterial.UniformPadding(gtx, body) +func (pg *SetupMixerAccountsPage) Layout(gtx C) D { + return pg.Theme.Card().Layout(gtx, func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Inset{Top: values.MarginPadding25}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Start}.Layout(gtx, + layout.Rigid(func(gtx C) D { + label := pg.Theme.H6(values.String(values.StrSetUpStakeShuffleAutoOrManualA)) + return layout.Inset{ + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) + }), + layout.Rigid(func(gtx C) D { + label := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleAutoOrManualB)) + return layout.Inset{ + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) + }), + layout.Rigid(func(gtx C) D { + // TODO: Find a way to make Mixed and Unmixed bold while keeping to the theme. + label := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleAutoOrManualC)) + return layout.Inset{ + Top: values.MarginPadding10, + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) + }), + layout.Rigid(func(gtx C) D { + label := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleAutoOrManualD)) + return layout.Inset{ + Top: values.MarginPadding10, + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) + }), + layout.Rigid(func(gtx C) D { + return layout.Spacer{Height: values.MarginPadding80}.Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + line := pg.Theme.Separator() + return layout.Inset{ + Top: values.MarginPadding10, + Left: values.MarginPadding24, + Right: values.MarginPadding24, + }.Layout(gtx, line.Layout) + }), + layout.Rigid(func(gtx C) D { + return pg.autoSetupClickable.Layout(gtx, pg.autoSetupLayout) + }), + layout.Rigid(func(gtx C) D { + return pg.manualSetupClickable.Layout(gtx, pg.manualSetupLayout) + }), + layout.Rigid(func(gtx C) D { + return layout.Spacer{Height: values.MarginPadding10}.Layout(gtx) + }), + ) + }) + }) } func (pg *SetupMixerAccountsPage) autoSetupLayout(gtx C) D { gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { + gtx.Constraints.Min.Y = int(values.MarginPadding70) + gtx.Constraints.Max.Y = gtx.Constraints.Min.Y + return layout.Inset{ + Top: values.MarginPadding10, + Bottom: values.MarginPadding10, + }.Layout(gtx, func(gtx C) D { return layout.Flex{Spacing: layout.SpaceBetween}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(5, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Start}.Layout(gtx, + layout.Rigid(func(gtx C) D { + label := pg.Theme.H6(values.String(values.StrSetUpStakeShuffleAutoTitle)) + return layout.Inset{ + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) + }), layout.Rigid(func(gtx C) D { - return pg.autoSetupIcon.Layout(gtx, values.MarginPadding20) + label := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleAutoDesc)) + return layout.Inset{ + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) }), + ) + }), + layout.Flexed(1, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle, Spacing: layout.SpaceAround}.Layout(gtx, layout.Rigid(func(gtx C) D { - autoSetupText := pg.Theme.H6(values.String(values.StrAutoSetUp)) - txt := pg.Theme.Body2(values.String(values.StrCreateNSetUpAccs)) return layout.Inset{ - Left: values.MarginPadding16, + Right: values.MarginPadding24, }.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(autoSetupText.Layout), - layout.Rigid(txt.Layout), - ) + return pg.nextIcon.Layout(gtx, values.MarginPadding30) }) }), ) }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Right: values.MarginPadding4, - Top: values.MarginPadding10, - }.Layout(gtx, func(gtx C) D { - return pg.nextIcon.Layout(gtx, values.MarginPadding20) - }) - }), ) }) } func (pg *SetupMixerAccountsPage) manualSetupLayout(gtx C) D { gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.UniformInset(values.MarginPadding16).Layout(gtx, func(gtx C) D { + gtx.Constraints.Min.Y = int(values.MarginPadding70) + gtx.Constraints.Max.Y = gtx.Constraints.Min.Y + return layout.Inset{ + Top: values.MarginPadding10, + Bottom: values.MarginPadding10, + }.Layout(gtx, func(gtx C) D { return layout.Flex{Spacing: layout.SpaceBetween}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(pg.Theme.Icons.EditIcon.Layout24dp), + layout.Flexed(5, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Start}.Layout(gtx, + layout.Rigid(func(gtx C) D { + label := pg.Theme.H6(values.String(values.StrSetUpStakeShuffleManualTitle)) + return layout.Inset{ + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) + }), layout.Rigid(func(gtx C) D { - autoSetupText := pg.Theme.H6(values.String(values.StrManualSetUp)) - txt := pg.Theme.Body2(values.String(values.StrWalletsEnabledPrivacy)) + label := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleManualDesc)) return layout.Inset{ - Left: values.MarginPadding16, + Left: values.MarginPadding24, + }.Layout(gtx, label.Layout) + }), + ) + }), + layout.Flexed(1, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle, Spacing: layout.SpaceAround}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Right: values.MarginPadding24, }.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(autoSetupText.Layout), - layout.Rigid(txt.Layout), - ) + return pg.nextIcon.Layout(gtx, values.MarginPadding30) }) }), ) }), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Right: values.MarginPadding4, - Top: values.MarginPadding10, - }.Layout(gtx, func(gtx C) D { - return pg.nextIcon.Layout(gtx, values.MarginPadding20) - }) - }), ) }) } @@ -238,7 +216,13 @@ func (pg *SetupMixerAccountsPage) HandleUserInteractions() { } if pg.manualSetupClickable.Clicked() { - pg.ParentNavigator().Display(NewManualMixerSetupPage(pg.Load, pg.dcrWallet)) + if !pg.manualEnabled { + notEnoughAccounts := values.String(values.StrNotEnoughAccounts) + info := modal.NewErrorModal(pg.Load, notEnoughAccounts, modal.DefaultClickFunc()) + pg.ParentWindow().ShowModal(info) + } else { + pg.ParentNavigator().Display(NewManualMixerSetupPage(pg.Load, pg.dcrWallet)) + } } } @@ -249,6 +233,4 @@ func (pg *SetupMixerAccountsPage) HandleUserInteractions() { // OnNavigatedTo() will be called again. This method should not destroy UI // components unless they'll be recreated in the OnNavigatedTo() method. // Part of the load.Page interface. -func (pg *SetupMixerAccountsPage) OnNavigatedFrom() { - pg.ctxCancel() -} +func (pg *SetupMixerAccountsPage) OnNavigatedFrom() {} diff --git a/ui/page/privacy/setup_privacy_page.go b/ui/page/privacy/setup_privacy_page.go index f67512641..10963c665 100644 --- a/ui/page/privacy/setup_privacy_page.go +++ b/ui/page/privacy/setup_privacy_page.go @@ -1,11 +1,8 @@ package privacy import ( - "context" - "gioui.org/layout" "gioui.org/text" - "gioui.org/widget" "github.com/crypto-power/cryptopower/app" "github.com/crypto-power/cryptopower/libwallet/assets/dcr" @@ -31,10 +28,6 @@ type SetupPrivacyPage struct { *app.GenericPageModal wallet *dcr.Asset - ctx context.Context // page context - ctxCancel context.CancelFunc - - pageContainer layout.List toPrivacySetup cryptomaterial.Button backButton cryptomaterial.IconButton @@ -46,8 +39,7 @@ func NewSetupPrivacyPage(l *load.Load, wallet *dcr.Asset) *SetupPrivacyPage { Load: l, GenericPageModal: app.NewGenericPageModal(SetupPrivacyPageID), wallet: wallet, - pageContainer: layout.List{Axis: layout.Vertical}, - toPrivacySetup: l.Theme.Button(values.String(values.StrSetupStakeShuffle)), + toPrivacySetup: l.Theme.Button(values.String(values.StrSetUpStakeShuffleIntroButton)), } pg.backButton, pg.infoButton = components.SubpageHeaderButtons(l) @@ -59,72 +51,86 @@ func NewSetupPrivacyPage(l *load.Load, wallet *dcr.Asset) *SetupPrivacyPage { // may be used to initialize page features that are only relevant when // the page is displayed. // Part of the load.Page interface. -func (pg *SetupPrivacyPage) OnNavigatedTo() { - pg.ctx, pg.ctxCancel = context.WithCancel(context.TODO()) -} +func (pg *SetupPrivacyPage) OnNavigatedTo() {} // Layout draws the page UI components into the provided layout context // to be eventually drawn on screen. // Part of the load.Page interface. -func (pg *SetupPrivacyPage) Layout(gtx layout.Context) layout.Dimensions { - return cryptomaterial.UniformPadding(gtx, pg.privacyIntroLayout) +func (pg *SetupPrivacyPage) Layout(gtx C) D { + return pg.privacyIntroLayout(gtx) } -func (pg *SetupPrivacyPage) privacyIntroLayout(gtx layout.Context) layout.Dimensions { - return layout.Inset{Top: values.MarginPadding40}.Layout(gtx, func(gtx C) D { - return pg.Theme.Card().Layout(gtx, func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Center.Layout(gtx, func(gtx C) D { - return layout.Inset{Top: values.MarginPadding25}.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Bottom: values.MarginPadding24, - }.Layout(gtx, func(gtx C) D { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding5, - }.Layout(gtx, pg.Theme.Icons.TransactionFingerprint.Layout48dp) - }), - layout.Rigid(pg.Theme.Icons.ArrowForward.Layout24dp), - layout.Rigid(func(gtx C) D { - return pg.Theme.Icons.Mixer.LayoutSize(gtx, values.MarginPadding120) - }), - layout.Rigid(pg.Theme.Icons.ArrowForward.Layout24dp), - layout.Rigid(func(gtx C) D { - return layout.Inset{ - Left: values.MarginPadding5, - }.Layout(gtx, pg.Theme.Icons.TransactionsIcon.Layout48dp) - }), - ) - }) - }), - layout.Rigid(func(gtx C) D { +func (pg *SetupPrivacyPage) privacyIntroLayout(gtx C) D { + return pg.Theme.Card().Layout(gtx, func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Center.Layout(gtx, func(gtx C) D { + return layout.Inset{Top: values.MarginPadding15}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.W.Layout(gtx, func(gtx C) D { title := pg.Theme.H6(values.String(values.StrStakeShuffle)) - subtitle := pg.Theme.Body1(values.String(values.StrSetUpPrivacy)) - - title.Alignment, subtitle.Alignment = text.Middle, text.Middle - + title.Alignment = text.Start + return layout.Inset{ + Left: values.MarginPadding24, + }.Layout(gtx, title.Layout) + }) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Top: values.MarginPadding24, + Bottom: values.MarginPadding24, + }.Layout(gtx, func(gtx C) D { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding5, + }.Layout(gtx, pg.Theme.Icons.TransactionFingerprint.Layout48dp) + }), + layout.Rigid(pg.Theme.Icons.ArrowForward.Layout24dp), + layout.Rigid(func(gtx C) D { + return pg.Theme.Icons.Mixer.LayoutSize(gtx, values.MarginPadding120) + }), + layout.Rigid(pg.Theme.Icons.ArrowForward.Layout24dp), + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding5, + }.Layout(gtx, pg.Theme.Icons.TransactionsIcon.Layout48dp) + }), + ) + }) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{ + Left: values.MarginPadding24, + Right: values.MarginPadding24, + }.Layout(gtx, func(gtx C) D { + introA := pg.Theme.H6(values.String(values.StrSetUpStakeShuffleIntro)) + introB := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleIntroDesc)) + introC := pg.Theme.Body1(values.String(values.StrSetUpStakeShuffleIntroSubDesc)) + introA.Alignment, introB.Alignment, introC.Alignment = text.Middle, text.Middle, text.Middle return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(title.Layout), + layout.Rigid(introA.Layout), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: values.MarginPadding10}.Layout(gtx, subtitle.Layout) + return layout.Inset{Top: values.MarginPadding20}.Layout(gtx, introB.Layout) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{Top: values.MarginPadding20}.Layout(gtx, introC.Layout) }), ) - }), - ) - }) + }) + }), + ) }) - }), - layout.Rigid(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - return layout.UniformInset(values.MarginPadding30).Layout(gtx, pg.toPrivacySetup.Layout) - }), - ) - }) + }) + }), + layout.Rigid(func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.UniformInset(values.MarginPadding30).Layout(gtx, pg.toPrivacySetup.Layout) + }), + ) }) } @@ -135,29 +141,7 @@ func (pg *SetupPrivacyPage) privacyIntroLayout(gtx layout.Context) layout.Dimens // Part of the load.Page interface. func (pg *SetupPrivacyPage) HandleUserInteractions() { if pg.toPrivacySetup.Clicked() { - accounts, err := pg.wallet.GetAccountsRaw() - if err != nil { - log.Error(err) - } - - walCount := len(accounts.Accounts) - // Filter out imported account and default account. - for _, v := range accounts.Accounts { - if v.Number == dcr.ImportedAccountNumber || v.Number == dcr.DefaultAccountNum { - walCount-- - } - } - - if walCount <= 1 { - go showModalSetupMixerInfo(&sharedModalConfig{ - Load: pg.Load, - window: pg.ParentWindow(), - pageNavigator: pg.ParentNavigator(), - checkBox: pg.Theme.CheckBox(new(widget.Bool), values.String(values.StrMoveFundsFrmDefaultToUnmixed)), - }, pg.wallet) - } else { - pg.ParentNavigator().Display(NewSetupMixerAccountsPage(pg.Load, pg.wallet)) - } + pg.ParentNavigator().Display(NewSetupMixerAccountsPage(pg.Load, pg.wallet)) } } @@ -168,6 +152,4 @@ func (pg *SetupPrivacyPage) HandleUserInteractions() { // OnNavigatedTo() will be called again. This method should not destroy UI // components unless they'll be recreated in the OnNavigatedTo() method. // Part of the load.Page interface. -func (pg *SetupPrivacyPage) OnNavigatedFrom() { - pg.ctxCancel() -} +func (pg *SetupPrivacyPage) OnNavigatedFrom() {} diff --git a/ui/page/privacy/shared_modals.go b/ui/page/privacy/shared_modals.go index fbea4b296..ae7fcdfaa 100644 --- a/ui/page/privacy/shared_modals.go +++ b/ui/page/privacy/shared_modals.go @@ -69,6 +69,7 @@ func showModalSetupMixerAcct(conf *sharedModalConfig, dcrWallet *dcr.Asset, move EnableConfirmPassword(false). Title(values.String(values.StrConfirmToCreateAccs)). SetPositiveButtonCallback(func(_, password string, pm *modal.CreatePasswordModal) bool { + defer pm.Dismiss() err := dcrWallet.CreateMixerAccounts(values.String(values.StrMixed), values.String(values.StrUnmixed), password) if err != nil { pm.SetError(err.Error()) @@ -86,8 +87,6 @@ func showModalSetupMixerAcct(conf *sharedModalConfig, dcrWallet *dcr.Asset, move } } - pm.Dismiss() - conf.pageNavigator.Display(NewAccountMixerPage(conf.Load, dcrWallet)) return true @@ -105,6 +104,13 @@ func moveFundsFromDefaultToUnmixed(conf *sharedModalConfig, dcrWallet *dcr.Asset // get the first account in the wallet as this is the default sourceAccount := acc.Accounts[0] + + balAtom := sourceAccount.Balance.Spendable.ToInt() + if balAtom <= 0 { + // Nothing to do. + return nil + } + destinationAccount := dcrWallet.UnmixedAccountNumber() destinationAddress, err := dcrWallet.CurrentAddress(destinationAccount) diff --git a/ui/page/root/main_page.go b/ui/page/root/main_page.go index b566dd316..106b04265 100644 --- a/ui/page/root/main_page.go +++ b/ui/page/root/main_page.go @@ -415,7 +415,8 @@ func (swmp *SingleWalletMasterPage) layoutDesktop(gtx C) D { } switch swmp.CurrentPage().ID() { case ReceivePageID, send.SendPageID, staking.OverviewPageID, - transaction.TransactionsPageID, privacy.AccountMixerPageID: + transaction.TransactionsPageID, privacy.AccountMixerPageID, + privacy.SetupPrivacyPageID: // Disable page functionality if a page is not synced or rescanning is in progress. if !swmp.selectedWallet.IsSynced() || swmp.selectedWallet.IsRescanning() { return components.DisablePageWithOverlay(swmp.Load, swmp.CurrentPage(), gtx, diff --git a/ui/values/localizable/en.go b/ui/values/localizable/en.go index 568a84a42..7e729dddc 100644 --- a/ui/values/localizable/en.go +++ b/ui/values/localizable/en.go @@ -552,8 +552,21 @@ const EN = ` "setupMixerInfo" = "%v Two dedicated accounts %v mixed %v & %v unmixed %v will be created in order to use the mixer. %v This action cannot be undone.%v" "mixingNotSetUp" = "Set up mixing from the StakeShuffle tab in order to disable." "setUpNeededAccs" = "Set up needed accounts" -"setUpPrivacy" = "Using StakeShuffle increases the privacy of your wallet transactions." -"setUpStakeShuffle" = "Set up StakeShuffle" +"setUpStakeShuffleIntro" = "How does StakeShuffle++ mixer enhance your privacy?" +"setUpStakeShuffleIntroDesc" = "The Shuffle++ mixer can mix your DCR through CoinJoin transactions." +"setUpStakeShuffleIntroSubDesc" = "Using mixed DCR protects you from exposing your financial activities to the public (e.g. how much you own, who pays you)." +"setUpStakeShuffleIntroButton" = "Set up mixer for this wallet." +"setUpStakeShuffleAutoOrManualA" = "Mixer Setup" +"setUpStakeShuffleAutoOrManualB" = "Two dedicated accounts will be set up to use the mixer:" +"setUpStakeShuffleAutoOrManualC" = " • Mixed account will be the outbounding spending account." +"setUpStakeShuffleAutoOrManualD" = " • Unmixed account will be the inbound receiving." +"setUpStakeShuffleAutoTitle" = "Auto Setup" +"setUpStakeShuffleAutoDesc" = "Create and setup the needed accounts for you." +"setUpStakeShuffleManualTitle" = "Manual Setup" +"setUpStakeShuffleManualDesc" = "For wallets that have enabled privacy before." +"setUpStakeShuffleWarningTitle" = "Account Selection Warning" +"setUpStakeShuffleWarningDesc" = "Make sure to select the corresponding accounts to the previous setup. Failing to do so could damage wallet privacy." +"notEnoughAccounts" = "Not enough accounts found to set up Mixing. Create more in Settings or try automatic setup." "setupStartupPassword" = "Set up startup password" "signature" = "Signature" "signCopied" = "Signature copied" diff --git a/ui/values/strings.go b/ui/values/strings.go index c2cb53b66..2c3e3b6bb 100644 --- a/ui/values/strings.go +++ b/ui/values/strings.go @@ -662,8 +662,6 @@ const ( StrSetupMixerInfo = "setupMixerInfo" StrMixingNotSetUp = "mixingNotSetUp" StrSetUpNeededAccs = "setUpNeededAccs" - StrSetUpPrivacy = "setUpPrivacy" - StrSetupStakeShuffle = "setUpStakeShuffle" StrSetupStartupPassword = "setupStartupPassword" StrSignature = "signature" StrSignCopied = "signCopied" @@ -684,6 +682,21 @@ const ( StrStakeAge = "stakeAge" StrStaked = "staked" StrStakeShuffle = "stakeShuffle" + StrSetUpStakeShuffleIntro = "setUpStakeShuffleIntro" + StrSetUpStakeShuffleIntroDesc = "setUpStakeShuffleIntroDesc" + StrSetUpStakeShuffleIntroSubDesc = "setUpStakeShuffleIntroSubDesc" + StrSetUpStakeShuffleIntroButton = "setUpStakeShuffleIntroButton" + StrSetUpStakeShuffleAutoOrManualA = "setUpStakeShuffleAutoOrManualA" + StrSetUpStakeShuffleAutoOrManualB = "setUpStakeShuffleAutoOrManualB" + StrSetUpStakeShuffleAutoOrManualC = "setUpStakeShuffleAutoOrManualC" + StrSetUpStakeShuffleAutoOrManualD = "setUpStakeShuffleAutoOrManualD" + StrSetUpStakeShuffleAutoTitle = "setUpStakeShuffleAutoTitle" + StrSetUpStakeShuffleAutoDesc = "setUpStakeShuffleAutoDesc" + StrSetUpStakeShuffleManualTitle = "setUpStakeShuffleManualTitle" + StrSetUpStakeShuffleManualDesc = "setUpStakeShuffleManualDesc" + StrSetUpStakeShuffleWarningTitle = "setUpStakeShuffleWarningTitle" + StrSetUpStakeShuffleWarningDesc = "setUpStakeShuffleWarningDesc" + StrNotEnoughAccounts = "notEnoughAccounts" StrStaking = "staking" StrStakingActivity = "stakingActivity" StrStart = "start"