From 8f9e5f50e6be07d31d985a9b678388bba602cc17 Mon Sep 17 00:00:00 2001 From: BugJT <89973752+bugjt@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:17:28 +0700 Subject: [PATCH] refactor dropdown layout (#622) * refactor dropdown layout * show dropdown and dropdown menu with same size * add scroll for dropdown menu, move the dropdown and balance to the same line on mobile --------- Co-authored-by: bugjt <@bugjt> --- ui/cryptomaterial/dropdown.go | 216 ++++++++++++---------- ui/page/wallet/single_wallet_main_page.go | 126 ++++++------- 2 files changed, 177 insertions(+), 165 deletions(-) diff --git a/ui/cryptomaterial/dropdown.go b/ui/cryptomaterial/dropdown.go index ed33fc5d8..c336795f8 100644 --- a/ui/cryptomaterial/dropdown.go +++ b/ui/cryptomaterial/dropdown.go @@ -42,6 +42,9 @@ type DropDown struct { shadow *Shadow expandedViewAlignment layout.Direction + list *widget.List + scroll ListStyle + noSelectedItem DropDownItem FontWeight font.Weight @@ -132,8 +135,11 @@ func (t *Theme) DropdownWithCustomPos(items []DropDownItem, group uint, groupPos padding: layout.Inset{Top: values.MarginPadding8, Bottom: values.MarginPadding8}, shadow: t.Shadow(), CollapsedLayoutTextDirection: layout.W, + list: &widget.List{ + List: layout.List{Axis: layout.Vertical, Alignment: layout.Middle}, + }, } - + d.scroll = t.List(d.list) d.revs = reversePos d.expandedViewAlignment = layout.NW d.ExpandedLayoutInset = layout.Inset{Left: unit.Dp(dropdownInset)} @@ -316,55 +322,18 @@ func (d *DropDown) collapsedAndExpandedLayout(gtx C) D { // expandedLayout computes dropdown layout when dropdown is opened. func (d *DropDown) expandedLayout(gtx C) D { m := op.Record(gtx.Ops) - gtx.Constraints.Max.Y = inf - d.layout(gtx, d.ExpandedLayoutInset, d.listItemLayout) + gtx.Constraints.Min.Y = gtx.Constraints.Max.Y + d.updateDropdownWidth(gtx, true) + d.updateDropdownBackground(true) + d.ExpandedLayoutInset.Layout(gtx, func(gtx C) D { + return d.linearLayout.Layout2(gtx, d.listItemLayout) + }) op.Defer(gtx.Ops, m.Stop()) return D{} } -func (d *DropDown) layout(gtx C, pos layout.Inset, wdgt layout.Widget) D { - return pos.Layout(gtx, func(gtx C) D { - return layout.Stack{}.Layout(gtx, - layout.Stacked(func(gtx C) D { - if d.Width < 0 { - d.linearLayout.Width = int(d.Width) - } else if d.Width == 0 { - d.Width = defaultDropdownWidth(d.revs) - d.linearLayout.Width = gtx.Dp(d.Width) - } else { - d.linearLayout.Width = gtx.Dp(d.Width) - } - - if d.expanded { - d.linearLayout.Background = d.theme.Color.Surface - d.linearLayout.Padding = d.padding - d.linearLayout.Shadow = d.shadow - } else { - if d.Background != nil { - d.linearLayout.Background = *d.Background - } else { - d.linearLayout.Background = d.theme.Color.Gray2 - } - d.linearLayout.Padding = layout.Inset{} - d.linearLayout.Shadow = nil - } - - if d.BorderWidth > 0 { - d.linearLayout.Border.Width = d.BorderWidth - } - - if d.BorderColor != nil { - d.linearLayout.Border.Color = *d.BorderColor - } - return d.linearLayout.Layout2(gtx, wdgt) - }), - ) - }) -} - func (d *DropDown) listItemLayout(gtx C) D { - list := &layout.List{Axis: layout.Vertical} - return list.Layout(gtx, len(d.items), func(gtx C, index int) D { + return d.scroll.Layout(gtx, len(d.items), func(gtx C, index int) D { if len(d.items) == 0 { return D{} } @@ -383,8 +352,10 @@ func (d *DropDown) listItemLayout(gtx C) D { // collapsedLayout computes dropdown layout when dropdown is closed. func (d *DropDown) collapsedLayout(gtx C) D { + d.updateDropdownWidth(gtx, false) + d.updateDropdownBackground(false) return d.collapsedLayoutInset.Layout(gtx, func(gtx C) D { - return d.drawLayout(gtx, func(gtx C) D { + return d.linearLayout.Layout2(gtx, func(gtx C) D { var selectedItem DropDownItem if len(d.items) > 0 && d.selectedIndex > -1 { selectedItem = d.items[d.selectedIndex] @@ -424,7 +395,7 @@ func (d *DropDown) itemLayout(gtx C, index int, clickable *Clickable, item *Drop if item.Icon == nil { return D{} } - gtx.Constraints.Min.Y = item.getItemHeight(gtx) + gtx.Constraints.Min.Y = item.getItemSize(gtx, d).Y 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.MarginPadding5}.Layout(gtx, item.Icon.Layout24dp) @@ -436,16 +407,7 @@ func (d *DropDown) itemLayout(gtx C, index int, clickable *Clickable, item *Drop return item.DisplayFn(gtx) } - return bodyLayout.Layout2(gtx, func(gtx C) D { - lbl := d.theme.Body2(item.Text) - lbl.MaxLines = 1 - if !d.expanded && len(item.Text) > d.maxTextLeng { - lbl.Text = item.Text[:d.maxTextLeng-3 /* subtract space for the ellipsis */] + "..." - } - lbl.Font.Weight = d.FontWeight - lbl.TextSize = d.getTextSize(values.TextSize14) - return lbl.Layout(gtx) - }) + return bodyLayout.Layout2(gtx, d.renderItemLabel(item.Text)) }), layout.Rigid(func(gtx C) D { if !item.PreventSelection { @@ -480,7 +442,7 @@ func (d *DropDown) layoutActiveIcon(gtx C, item *DropDownItem, index int) D { if d.expanded && d.SelectedItemIconColor != nil { icon.Color = *d.SelectedItemIconColor } - gtx.Constraints.Min.Y = item.getItemHeight(gtx) + gtx.Constraints.Min.Y = item.getItemSize(gtx, d).Y return layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle, Spacing: layout.SpaceAround}.Layout(gtx, layout.Rigid(func(gtx C) D { return icon.Layout(gtx, values.MarginPadding20) @@ -488,41 +450,6 @@ func (d *DropDown) layoutActiveIcon(gtx C, item *DropDownItem, index int) D { ) } -// drawLayout wraps the page tx and sync section in a card layout -func (d *DropDown) drawLayout(gtx C, body layout.Widget) D { - if d.Width < 0 { - d.linearLayout.Width = int(d.Width) - } else if d.Width == 0 { - d.Width = defaultDropdownWidth(d.revs) - d.linearLayout.Width = gtx.Dp(d.Width) - } else { - d.linearLayout.Width = gtx.Dp(d.Width) - } - if d.expanded { - d.linearLayout.Background = d.theme.Color.Surface - d.linearLayout.Padding = d.padding - d.linearLayout.Shadow = d.shadow - } else { - if d.Background != nil { - d.linearLayout.Background = *d.Background - } else { - d.linearLayout.Background = d.theme.Color.Surface - } - d.linearLayout.Padding = layout.Inset{} - d.linearLayout.Shadow = nil - } - - if d.BorderWidth > 0 { - d.linearLayout.Border.Width = d.BorderWidth - } - - if d.BorderColor != nil { - d.linearLayout.Border.Color = *d.BorderColor - } - - return d.linearLayout.Layout2(gtx, body) -} - // Reslice the dropdowns func ResliceDropdown(dropdowns []*DropDown, indexToRemove int) []*DropDown { return append(dropdowns[:indexToRemove], dropdowns[indexToRemove+1:]...) @@ -560,14 +487,103 @@ func (d *DropDown) ItemsLen() int { return len(d.items) } -func (i *DropDownItem) getItemHeight(gtx C) int { +func (i *DropDownItem) getItemSize(gtx C, d *DropDown) image.Point { + tmpGtx := layout.Context{ + Ops: new(op.Ops), + Constraints: gtx.Constraints, + Metric: gtx.Metric, + } if i.DisplayFn != nil { - tmpGtx := layout.Context{ - Ops: new(op.Ops), - Constraints: gtx.Constraints, - Metric: gtx.Metric, + return i.DisplayFn(tmpGtx).Size + } + return LinearLayout{ + Width: MatchParent, + Height: WrapContent, + Padding: layout.Inset{Right: values.MarginPadding5}, + Direction: layout.W, + }.Layout2(tmpGtx, d.renderItemLabel(i.Text)).Size +} + +func (d *DropDown) renderItemLabel(text string) layout.Widget { + return func(gtx C) D { + lbl := d.theme.Body2(text) + lbl.MaxLines = 1 + if !d.expanded && len(text) > d.maxTextLeng { + lbl.Text = text[:d.maxTextLeng-3 /* subtract space for the ellipsis */] + "..." + } + lbl.Font.Weight = d.FontWeight + lbl.TextSize = d.getTextSize(values.TextSize14) + return lbl.Layout(gtx) + } +} + +func (d *DropDown) getCurrentSize(gtx C) image.Point { + var selectedItem DropDownItem + if len(d.items) > 0 && d.selectedIndex > -1 { + selectedItem = d.items[d.selectedIndex] + } else { + selectedItem = d.noSelectedItem + } + return selectedItem.getItemSize(gtx, d) +} + +func (d *DropDown) getMaxWidth(gtx C) int { + maxWidth := 0 + if len(d.items) > 0 { + for _, item := range d.items { + itemWidth := item.getItemSize(gtx, d).X + if itemWidth > maxWidth { + maxWidth = itemWidth + } + } + } + return maxWidth +} + +func (d *DropDown) updateDropdownBackground(expanded bool) { + if expanded { + d.linearLayout.Background = d.theme.Color.Surface + d.linearLayout.Padding = d.padding + d.linearLayout.Shadow = d.shadow + } else { + if d.Background != nil { + d.linearLayout.Background = *d.Background + } else { + d.linearLayout.Background = d.theme.Color.Gray2 + } + d.linearLayout.Padding = layout.Inset{} + d.linearLayout.Shadow = nil + } + + if d.BorderWidth > 0 { + d.linearLayout.Border.Width = d.BorderWidth + } + + if d.BorderColor != nil { + d.linearLayout.Border.Color = *d.BorderColor + } +} + +func (d *DropDown) updateDropdownWidth(gtx C, expanded bool) { + switch d.Width { + case WrapContent: + width := 0 + if expanded { + width = d.getMaxWidth(gtx) + } else { + width = d.getCurrentSize(gtx).X + } + if width > 0 { + d.linearLayout.Width = width + gtx.Dp(values.MarginPadding70) + } else { + d.linearLayout.Width = gtx.Dp(defaultDropdownWidth(d.revs)) } - return i.DisplayFn(tmpGtx).Size.Y + case MatchParent: + d.linearLayout.Width = MatchParent + case 0: + d.linearLayout.Width = gtx.Dp(defaultDropdownWidth(d.revs)) + default: + d.linearLayout.Width = gtx.Dp(d.Width) + } - return 1 } diff --git a/ui/page/wallet/single_wallet_main_page.go b/ui/page/wallet/single_wallet_main_page.go index a6e93b1dd..1f8b1b821 100644 --- a/ui/page/wallet/single_wallet_main_page.go +++ b/ui/page/wallet/single_wallet_main_page.go @@ -128,7 +128,7 @@ func (swmp *SingleWalletMasterPage) createWalletDropdown() *cryptomaterial.DropD } items = append(items, item) } - dropdown := swmp.Theme.NewCommonDropDown(items, &selectedItem, values.MarginPadding180, values.WalletsDropdownGroup, false) + dropdown := swmp.Theme.NewCommonDropDown(items, &selectedItem, cryptomaterial.WrapContent, values.WalletsDropdownGroup, false) color := values.TransparentColor(values.TransparentWhite, 1) dropdown.Background = &color return dropdown @@ -528,7 +528,6 @@ func (swmp *SingleWalletMasterPage) Layout(gtx C) D { } func (swmp *SingleWalletMasterPage) LayoutTopBar(gtx C) D { - isMobile := swmp.Load.IsMobileView() assetType := swmp.selectedWallet.GetAssetType() return cryptomaterial.LinearLayout{ Width: cryptomaterial.MatchParent, @@ -538,15 +537,11 @@ func (swmp *SingleWalletMasterPage) LayoutTopBar(gtx C) D { layout.Rigid(func(gtx C) D { h := values.MarginPadding24 v := values.MarginPadding8 - orientation := layout.Horizontal - if isMobile { - orientation = layout.Vertical - } return cryptomaterial.LinearLayout{ Width: cryptomaterial.MatchParent, Height: cryptomaterial.WrapContent, - Orientation: orientation, - Alignment: layout.Start, + Orientation: layout.Horizontal, + Alignment: layout.Middle, Padding: layout.Inset{ Right: h, Left: values.MarginPadding10, @@ -555,54 +550,50 @@ func (swmp *SingleWalletMasterPage) LayoutTopBar(gtx C) D { }, }.GradientLayout(gtx, assetType, layout.Rigid(func(gtx C) D { - return layout.Flex{ - Axis: layout.Horizontal, - Alignment: layout.Middle, - }.Layout(gtx, - layout.Rigid(func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.WrapContent, - Height: cryptomaterial.WrapContent, - Orientation: orientation, - }.Layout2(gtx, swmp.openWalletSelector.Layout) - }), - layout.Flexed(1, func(gtx C) D { - return layout.Center.Layout(gtx, func(gtx C) D { - alignment := layout.Start - orientation := layout.Horizontal - if isMobile { - alignment = layout.Middle - orientation = layout.Vertical - } - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.WrapContent, - Height: cryptomaterial.WrapContent, - Orientation: orientation, - Alignment: alignment, - }.Layout(gtx, - layout.Rigid(func(gtx C) D { - return swmp.walletDropdownLayout(gtx) - }), - layout.Rigid(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Constraints.Max.X - layoutPosition := layout.E - if isMobile { - layoutPosition = layout.Center - } - return layoutPosition.Layout(gtx, func(gtx C) D { - return layout.Flex{}.Layout(gtx, - layout.Rigid(func(gtx C) D { - icon := swmp.Theme.Icons.VisibilityOffIcon - if swmp.isBalanceHidden { - icon = swmp.Theme.Icons.VisibilityIcon - } - return layout.Inset{ - Top: values.MarginPadding5, - Right: values.MarginPadding9, - }.Layout(gtx, func(gtx C) D { - return swmp.hideBalanceButton.Layout(gtx, swmp.Theme.NewIcon(icon).Layout20dp) - }) - }), + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.WrapContent, + Height: cryptomaterial.WrapContent, + Orientation: layout.Horizontal, + }.Layout2(gtx, swmp.openWalletSelector.Layout) + }), + layout.Flexed(1, func(gtx C) D { + return layout.Center.Layout(gtx, func(gtx C) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.WrapContent, + Height: cryptomaterial.WrapContent, + Orientation: layout.Horizontal, + Alignment: layout.Middle, + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return swmp.walletDropdownLayout(gtx) + }), + layout.Flexed(1, func(gtx C) D { + gtx.Constraints.Min.X = gtx.Constraints.Max.X + layoutPosition := layout.E + return layoutPosition.Layout(gtx, func(gtx C) D { + return layout.Flex{}.Layout(gtx, + layout.Rigid(func(gtx C) D { + icon := swmp.Theme.Icons.VisibilityOffIcon + if swmp.isBalanceHidden { + icon = swmp.Theme.Icons.VisibilityIcon + } + return layout.Inset{ + Top: values.MarginPadding5, + Right: values.MarginPadding9, + }.Layout(gtx, func(gtx C) D { + return swmp.hideBalanceButton.Layout(gtx, swmp.Theme.NewIcon(icon).Layout20dp) + }) + }), + layout.Rigid(func(gtx C) D { + orientation := layout.Horizontal + if swmp.IsMobileView() { + orientation = layout.Vertical + } + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.WrapContent, + Height: cryptomaterial.WrapContent, + Orientation: orientation, + }.Layout(gtx, layout.Rigid(swmp.totalAssetBalance), layout.Rigid(func(gtx C) D { if !swmp.isBalanceHidden { @@ -611,12 +602,12 @@ func (swmp *SingleWalletMasterPage) LayoutTopBar(gtx C) D { return D{} }), ) - }) - }), - ) - }) - }), - ) + }), + ) + }) + }), + ) + }) }), ) }), @@ -638,7 +629,7 @@ func (swmp *SingleWalletMasterPage) walletDropdownLayout(gtx C) D { }.Layout(gtx, swmp.walletDropdown.Layout) }), layout.Rigid(func(gtx C) D { - if !swmp.selectedWallet.IsWatchingOnlyWallet() { + if !swmp.selectedWallet.IsWatchingOnlyWallet() || swmp.IsMobileView() { return D{} } @@ -680,8 +671,13 @@ func (swmp *SingleWalletMasterPage) LayoutUSDBalance(gtx C) D { textSize = values.TextSize16 } lbl := swmp.Theme.Label(textSize, fmt.Sprintf("/ %s", swmp.totalBalanceUSD)) + marginLeft := values.MarginPadding8 + if swmp.IsMobileView() { + lbl = swmp.Theme.Label(textSize, swmp.totalBalanceUSD) + marginLeft = 0 + } lbl.Color = swmp.Theme.Color.PageNavText - inset := layout.Inset{Left: values.MarginPadding8} + inset := layout.Inset{Left: marginLeft} return inset.Layout(gtx, lbl.Layout) default: return D{} @@ -694,7 +690,7 @@ func (swmp *SingleWalletMasterPage) totalAssetBalance(gtx C) D { textSize = values.TextSize16 } if swmp.isBalanceHidden || swmp.walletBalance == nil { - hiddenBalanceText := swmp.Theme.Label(textSize*0.8, "*******************") + hiddenBalanceText := swmp.Theme.Label(textSize*0.8, "****************") return layout.Inset{Bottom: values.MarginPadding0, Top: values.MarginPadding5}.Layout(gtx, func(gtx C) D { hiddenBalanceText.Color = swmp.Theme.Color.PageNavText return hiddenBalanceText.Layout(gtx)