From b7c14463a8384ce97dd215924130021317146f33 Mon Sep 17 00:00:00 2001 From: Philemon Ukane Date: Fri, 27 Oct 2023 16:18:17 +0100 Subject: [PATCH] reviews, refactor andd improved UI changes Signed-off-by: Philemon Ukane --- ui/cryptomaterial/dropdown.go | 458 ++++---- ui/cryptomaterial/segmented_control.go | 12 +- ui/cryptomaterial/theme.go | 8 +- ui/cryptomaterial/toggle_button.go | 101 -- ui/modal/info_modal.go | 10 +- ui/page/components/components.go | 30 +- ui/page/dcrdex/dcrdex_page.go | 24 +- ui/page/dcrdex/dex_onboarding_page.go | 157 +-- ui/page/dcrdex/market.go | 1338 ++++++++++------------ ui/page/exchange/order_history_page.go | 8 +- ui/page/governance/consensus_page.go | 21 +- ui/page/governance/proposals_page.go | 19 +- ui/page/transaction/transactions_page.go | 14 +- ui/values/dimensions.go | 4 + ui/values/localizable/en.go | 2 + ui/values/strings.go | 2 + 16 files changed, 1001 insertions(+), 1207 deletions(-) delete mode 100644 ui/cryptomaterial/toggle_button.go diff --git a/ui/cryptomaterial/dropdown.go b/ui/cryptomaterial/dropdown.go index 34b6d5e9a..8ab6c6120 100644 --- a/ui/cryptomaterial/dropdown.go +++ b/ui/cryptomaterial/dropdown.go @@ -11,7 +11,11 @@ import ( ) const ( - DropdownBasePos uint = 0 + DropdownBasePos = 0 + // maxDropdownItemTextLen is the maximum len of a dropdown item text. + // Dropdown item text that exceed maxDropdownItemTextLen will be truncated + // and an ellipsis will be shown at the end. + maxDropdownItemTextLen = 20 ) var MaxWidth = unit.Dp(800) @@ -19,71 +23,103 @@ var MaxWidth = unit.Dp(800) type DropDown struct { theme *Theme items []DropDownItem - isOpen bool + expanded bool backdrop *widget.Clickable - Position uint + GroupPosition uint revs bool selectedIndex int - Color color.NRGBA - background color.NRGBA dropdownIcon *widget.Icon navigationIcon *widget.Icon clickable *Clickable - group uint - closeAllDropdown func(group uint) - isOpenDropdownGroup func(group uint) bool - Width unit.Dp - linearLayout *LinearLayout - padding layout.Inset - shadow *Shadow - - noSelectedItemText string - extraDisplay func(gtx C) D - FontWeight font.Weight - BorderWidth unit.Dp - BorderColor *color.NRGBA - NavigationIconColor *color.NRGBA - Hoverable bool + group uint + closeAllDropdown func(group uint) + isDropdownGroupCollapsed func(group uint) bool + Width unit.Dp + linearLayout *LinearLayout + padding layout.Inset + shadow *Shadow + expandedViewAlignment layout.Direction + + noSelectedItemText string + FontWeight font.Weight + BorderWidth unit.Dp + BorderColor *color.NRGBA + Background *color.NRGBA + // SelectedItemIconColor is a custom color that will be applied to the icon + // use in identifying selected item when this dropdown is expanded. + SelectedItemIconColor *color.NRGBA + CollapsedLayoutTextDirection layout.Direction + // Set Hoverable to false to make this dropdown's collapsed layout + // non-hoverable (default: true). + Hoverable bool + // Set MakeCollapsedLayoutVisibleWhenExpanded to true to make this + // dropdown's collapsed layout visible when its dropdown is expanded. + MakeCollapsedLayoutVisibleWhenExpanded bool + // ExpandedLayoutInset is information about this dropdown's expanded layout + // position. It's Top value must be set if + // MakeCollapsedLayoutVisibleWhenExpanded is true. + ExpandedLayoutInset layout.Inset + collapsedLayoutInset layout.Inset } type DropDownItem struct { Text string Icon *Image clickable *Clickable - // DisplayFn is an alternate display function that conforms to the dropdown - // item list display. + // DisplayFn is an alternate display function that can be used to layout the + // item instead of using the default item layout. DisplayFn func(gtx C) D + // Set to true for items that cannot be selected. + PreventSelection bool } -// DropDown returns a dropdown component. {pos} parameter signifies the position -// of the dropdown in a dropdown group on the UI, the first dropdown should be assigned -// pos 0, next 1..etc. incorrectly assigned Dropdown pos will result in inconsistent -// dropdown backdrop. -func (t *Theme) DropDown(items []DropDownItem, group uint, pos uint) *DropDown { +// DropDown is like DropdownWithCustomPos but uses default values for +// groupPosition, and dropdownInset parameters. +func (t *Theme) DropDown(items []DropDownItem, group uint, reversePos bool) *DropDown { + return t.DropdownWithCustomPos(items, group, 0, 0, reversePos) +} + +// DropdownWithCustomPos returns a dropdown component. {groupPosition} parameter +// signifies the position of the dropdown in a dropdown group on the UI, the +// first dropdown should be assigned pos 0, next 1..etc. incorrectly assigned +// Dropdown pos will result in inconsistent dropdown backdrop. {dropdownInset} +// parameter is the left inset for the dropdown if {reversePos} is false, else +// it'll become the right inset for the dropdown. +func (t *Theme) DropdownWithCustomPos(items []DropDownItem, group uint, groupPosition uint, dropdownInset int, reversePos bool) *DropDown { d := &DropDown{ theme: t, - isOpen: false, - Position: pos, + expanded: false, + GroupPosition: groupPosition, selectedIndex: 0, - Color: t.Color.Gray2, - background: t.Color.Surface, dropdownIcon: t.dropDownIcon, navigationIcon: t.navigationCheckIcon, + Hoverable: true, clickable: t.NewClickable(true), backdrop: new(widget.Clickable), - group: group, - closeAllDropdown: t.closeAllDropdownMenus, - isOpenDropdownGroup: t.isOpenDropdownGroup, + group: group, + closeAllDropdown: t.closeAllDropdownMenus, + isDropdownGroupCollapsed: t.isDropdownGroupCollapsed, linearLayout: &LinearLayout{ Width: WrapContent, Height: WrapContent, Border: Border{Radius: Radius(8)}, }, - padding: layout.Inset{Top: values.MarginPadding8, Bottom: values.MarginPadding8}, - shadow: t.Shadow(), + padding: layout.Inset{Top: values.MarginPadding8, Bottom: values.MarginPadding8}, + shadow: t.Shadow(), + CollapsedLayoutTextDirection: layout.W, + } + + d.revs = reversePos + d.expandedViewAlignment = layout.NW + d.ExpandedLayoutInset = layout.Inset{Left: unit.Dp(dropdownInset)} + if d.revs { + d.expandedViewAlignment = layout.NE + d.ExpandedLayoutInset.Left = values.MarginPadding10 + d.ExpandedLayoutInset.Right = unit.Dp(dropdownInset) } + d.collapsedLayoutInset = d.ExpandedLayoutInset d.clickable.ChangeStyle(t.Styles.DropdownClickableStyle) d.clickable.Radius = Radius(8) @@ -116,23 +152,22 @@ func (d *DropDown) Len() int { return len(d.items) } -func (d *DropDown) SetExtraDisplay(extraDisplay func(gtx C) D) { - d.extraDisplay = extraDisplay -} - func (d *DropDown) handleEvents() { - if d.isOpen { + if d.expanded { for i := range d.items { index := i - for d.items[index].clickable.Clicked() { - d.selectedIndex = index - d.isOpen = false + item := d.items[index] + for item.clickable.Clicked() { + d.expanded = false + if !item.PreventSelection { + d.selectedIndex = index + } break } } } else { for d.clickable.Clicked() { - d.isOpen = true + d.expanded = true } } @@ -142,12 +177,17 @@ func (d *DropDown) handleEvents() { } func (d *DropDown) Changed() bool { - if d.isOpen { + if d.expanded { for i := range d.items { index := i - for d.items[index].clickable.Clicked() { + item := d.items[index] + for item.clickable.Clicked() { + d.expanded = false + if item.PreventSelection { + return false + } + d.selectedIndex = index - d.isOpen = false return true } } @@ -156,229 +196,209 @@ func (d *DropDown) Changed() bool { return false } -func (d *DropDown) layoutActiveIcon(gtx C, index int) D { - var icon *Icon - if !d.isOpen { - icon = NewIcon(d.dropdownIcon) - icon.Color = d.theme.Color.Gray1 - } else if index == d.selectedIndex { - icon = NewIcon(d.navigationIcon) - if d.NavigationIconColor != nil { - icon.Color = *d.NavigationIconColor - } else { - icon.Color = d.theme.Color.Gray1 - } +// defaultDropdownWidth returns the default width for a dropdown depending on +// it's position. +func defaultDropdownWidth(reversePosition bool) unit.Dp { + if reversePosition { + return values.MarginPadding140 } + return values.MarginPadding180 +} - return layout.E.Layout(gtx, func(gtx C) D { - if icon == nil { - return D{} +func (d *DropDown) Reversed() bool { + return d.revs +} + +func (d *DropDown) Layout(gtx C) D { + d.handleEvents() + + if d.MakeCollapsedLayoutVisibleWhenExpanded { + return d.collapsedAndExpandedLayout(gtx) + } + + if d.GroupPosition == DropdownBasePos && d.isDropdownGroupCollapsed(d.group) { + maxY := unit.Dp(len(d.items)) * values.MarginPadding50 + gtx.Constraints.Max.Y = gtx.Dp(maxY) + if d.expanded { + return d.backdrop.Layout(gtx, func(gtx C) D { + return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layout.Stacked(d.expandedLayout)) + }) } - return icon.Layout(gtx, values.MarginPadding20) - }) + + return d.backdrop.Layout(gtx, func(gtx C) D { + return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layout.Stacked(d.collapsedLayout)) + }) + } else if d.expanded { + return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layout.Stacked(d.expandedLayout)) + } + + return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layout.Stacked(d.collapsedLayout)) } -func (d *DropDown) layoutOption(gtx C, itemIndex int) D { - item := d.items[itemIndex] - radius := Radius(0) - clickable := item.clickable - if !d.isOpen { - radius = Radius(8) - clickable = d.clickable - clickable.Hoverable = d.Hoverable +// collapsedAndExpandedLayout stacks the expanded view right below the collapsed +// view (only if d.expanded = true) such that both the current selection and the +// list of items are visible. +func (d *DropDown) collapsedAndExpandedLayout(gtx C) D { + layoutContents := []layout.StackChild{layout.Expanded(func(gtx C) D { + expanded := d.expanded + d.expanded = false // enforce a collapsed layout display before creating the layout Dimensions and undo later. + display := d.collapsedLayout(gtx) + d.expanded = expanded + return display + })} + + // No need to display expanded view when there is only one item. + if d.expanded && len(d.items) > 1 { + layoutContents = append(layoutContents, layout.Expanded(func(gtx layout.Context) layout.Dimensions { + // Adding d.ExpandedLayoutInset.Top accounts for the the extra + // shift in vertical space set by d.ExpandedLayoutInset.Top to + // ensure the expanded view has enough space for its elements. + maxY := unit.Dp(len(d.items))*values.MarginPadding50 + d.ExpandedLayoutInset.Top + gtx.Constraints.Max.Y = gtx.Dp(maxY) + return d.expandedLayout(gtx) + })) } + return layout.Stack{Alignment: d.expandedViewAlignment}.Layout(gtx, layoutContents...) +} + +// expandedLayout computes dropdown layout when dropdown is opened. +func (d *DropDown) expandedLayout(gtx C) D { + return d.ExpandedLayoutInset.Layout(gtx, func(gtx C) D { + return d.drawLayout(gtx, func(gtx C) D { + list := &layout.List{Axis: layout.Vertical} + return list.Layout(gtx, len(d.items), func(gtx C, index int) D { + if len(d.items) == 0 { + return D{} + } + + item := d.items[index] + body := LinearLayout{ + Width: MatchParent, + Height: WrapContent, + Padding: layout.Inset{Right: values.MarginPadding5}, + Direction: layout.W, + } + + return d.itemLayout(gtx, index, item.clickable, &item, 8, &body) + }) + }) + }) +} + +// collapsedLayout computes dropdown layout when dropdown is closed. +func (d *DropDown) collapsedLayout(gtx C) D { + return d.collapsedLayoutInset.Layout(gtx, func(gtx C) D { + return d.drawLayout(gtx, func(gtx C) D { + var selectedItem DropDownItem + if len(d.items) > 0 && d.selectedIndex > -1 { + selectedItem = d.items[d.selectedIndex] + } else { + selectedItem = DropDownItem{ + Text: d.noSelectedItemText, + PreventSelection: true, + } + } + + bodyLayout := LinearLayout{ + Width: MatchParent, + Height: WrapContent, + Padding: layout.Inset{Right: values.MarginPadding5}, + Direction: d.CollapsedLayoutTextDirection, + } + + // d.Hoverable is set after creating the dropdown but before drawing + // the layout. + d.clickable.Hoverable = d.Hoverable + return d.itemLayout(gtx, d.selectedIndex, d.clickable, &selectedItem, 8, &bodyLayout) + }) + }) +} + +func (d *DropDown) itemLayout(gtx C, index int, clickable *Clickable, item *DropDownItem, radius int, bodyLayout *LinearLayout) D { padding := values.MarginPadding10 - if d.isOpen { + if item.Icon != nil { padding = values.MarginPadding8 } - dropdownWidth := gtx.Dp(d.Width) - dropdownItemWidth := dropdownWidth - if dropdownWidth <= 0 { - dropdownWidth = gtx.Dp(defaultDropdownWidth(d.revs)) - } - return LinearLayout{ - Width: dropdownWidth, + Width: MatchParent, Height: WrapContent, Clickable: clickable, Padding: layout.UniformInset(padding), - Border: Border{Radius: radius}, + Border: Border{Radius: Radius(radius)}, }.Layout(gtx, layout.Rigid(func(gtx C) D { if item.Icon == nil { return D{} } - dropdownItemWidth -= gtx.Dp(values.MarginPadding24) // account for the dropdown Icon - return item.Icon.Layout20dp(gtx) + return item.Icon.Layout24dp(gtx) }), - layout.Rigid(func(gtx C) D { - gtx.Constraints.Max.X = dropdownItemWidth - gtx.Dp(values.MarginPadding50) // give some space for the dropdown Icon - gtx.Constraints.Min.X = gtx.Constraints.Max.X + layout.Flexed(1, func(gtx C) D { if item.DisplayFn != nil { return item.DisplayFn(gtx) } - return layout.Inset{ - Right: unit.Dp(5), - Left: unit.Dp(5), - }.Layout(gtx, func(gtx C) D { + return bodyLayout.Layout2(gtx, func(gtx C) D { lbl := d.theme.Body2(item.Text) - if !d.isOpen && len(item.Text) > 20 { - lbl.Text = item.Text[:20-3] + "..." + if !d.expanded && len(item.Text) > maxDropdownItemTextLen { + lbl.Text = item.Text[:maxDropdownItemTextLen-3 /* subtract space for the ellipsis */] + "..." } lbl.Font.Weight = d.FontWeight return lbl.Layout(gtx) }) }), layout.Rigid(func(gtx C) D { - return d.layoutActiveIcon(gtx, itemIndex) + if !item.PreventSelection && len(d.items) > 1 { + return d.layoutActiveIcon(gtx, index) + } + return D{} }), ) } -// defaultDropdownWidth returns the default width for a dropdown depending on -// it's position. -func defaultDropdownWidth(reversePosition bool) unit.Dp { - if reversePosition { - return values.MarginPadding140 +func (d *DropDown) layoutActiveIcon(gtx C, index int) D { + var icon *Icon + if !d.expanded { + icon = NewIcon(d.dropdownIcon) + } else if index == d.selectedIndex { + icon = NewIcon(d.navigationIcon) } - return values.MarginPadding180 -} -func (d *DropDown) Layout(gtx C, dropPos int, reversePos bool) D { - d.handleEvents() - - iLeft := dropPos - iRight := 0 - alig := layout.NW - d.revs = reversePos - if reversePos { - alig = layout.NE - iLeft = 10 - iRight = dropPos + if icon == nil { + return D{} // return early } - if d.Position == DropdownBasePos && d.isOpenDropdownGroup(d.group) { - maxY := unit.Dp(len(d.items)) * values.MarginPadding50 - if d.extraDisplay != nil { - maxY += values.MarginPadding50 - } - - gtx.Constraints.Max.Y = gtx.Dp(maxY) - if d.isOpen { - return d.backdrop.Layout(gtx, func(gtx C) D { - return layout.Stack{Alignment: alig}.Layout(gtx, - layout.Stacked(func(gtx C) D { - return d.openedLayout(gtx, iLeft, iRight) - }), - ) - }) - } - - return d.backdrop.Layout(gtx, func(gtx C) D { - return layout.Stack{Alignment: alig}.Layout(gtx, - layout.Stacked(func(gtx C) D { - return d.closedLayout(gtx, iLeft, iRight) - }), - ) - }) - - } else if d.isOpen { - return layout.Stack{Alignment: alig}.Layout(gtx, - layout.Stacked(func(gtx C) D { - return d.openedLayout(gtx, iLeft, iRight) - }), - ) + icon.Color = d.theme.Color.Gray1 + if d.expanded && d.SelectedItemIconColor != nil { + icon.Color = *d.SelectedItemIconColor } - return layout.Stack{Alignment: alig}.Layout(gtx, - layout.Stacked(func(gtx C) D { - return d.closedLayout(gtx, iLeft, iRight) - }), - ) -} - -// openedLayout computes dropdown layout when dropdown is opened. -func (d *DropDown) openedLayout(gtx C, iLeft int, iRight int) D { - return layout.Inset{ - Left: unit.Dp(float32(iLeft)), - Right: unit.Dp(float32(iRight)), - }.Layout(gtx, func(gtx C) D { - return d.dropDownItemMenu(gtx) - }) -} - -// closedLayout computes dropdown layout when dropdown is closed. -func (d *DropDown) closedLayout(gtx C, iLeft int, iRight int) D { - return layout.Inset{ - Left: unit.Dp(float32(iLeft)), - Right: unit.Dp(float32(iRight)), - }.Layout(gtx, func(gtx C) D { - return d.drawLayout(gtx, func(gtx C) D { - lay := layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - if len(d.items) == 0 { - if d.extraDisplay != nil { - return d.extraDisplay(gtx) - } else { - return d.theme.Body2(d.noSelectedItemText).Layout(gtx) - } - } - return d.layoutOption(gtx, d.selectedIndex) - })) - if d.Width <= 0 { - d.Width = defaultDropdownWidth(d.revs) - } - return lay - }) - }) -} - -func (d *DropDown) dropDownItemMenu(gtx C) D { - listItems := len(d.items) - return d.drawLayout(gtx, func(gtx C) D { - list := &layout.List{Axis: layout.Vertical} - return list.Layout(gtx, listItems, func(gtx C, index int) D { - if (index == listItems-1 || listItems == 0) && d.extraDisplay != nil { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return d.layoutOption(gtx, index) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - dropdownWidth := gtx.Dp(d.Width) - dropdownItemWidth := dropdownWidth - if dropdownWidth <= 0 { - dropdownWidth = gtx.Dp(defaultDropdownWidth(d.revs)) - } - - gtx.Constraints.Max.X = dropdownItemWidth - return d.extraDisplay(gtx) - }), - ) - } else if listItems != 0 { - return d.layoutOption(gtx, index) - } - - return D{} - }) - }) + return icon.Layout(gtx, values.MarginPadding20) } // drawLayout wraps the page tx and sync section in a card layout func (d *DropDown) drawLayout(gtx C, body layout.Widget) D { - if d.isOpen { - d.linearLayout.Background = d.background + if d.Width <= 0 { + d.Width = defaultDropdownWidth(d.revs) + } + 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 { - d.linearLayout.Background = d.Color + d.linearLayout.Background = d.theme.Color.Gray2 d.linearLayout.Padding = layout.Inset{} d.linearLayout.Shadow = nil } + if d.Background != nil { + d.linearLayout.Background = *d.Background + } + if d.BorderWidth > 0 { d.linearLayout.Border.Width = d.BorderWidth } @@ -400,10 +420,10 @@ func DisplayOneDropdown(dropdowns ...*DropDown) { var menus []*DropDown for i, menu := range dropdowns { if menu.clickable.Clicked() { - menu.isOpen = true + menu.expanded = true menus = ResliceDropdown(dropdowns, i) for _, menusToClose := range menus { - menusToClose.isOpen = false + menusToClose.expanded = false } } } diff --git a/ui/cryptomaterial/segmented_control.go b/ui/cryptomaterial/segmented_control.go index 20e418c17..2f65641fb 100644 --- a/ui/cryptomaterial/segmented_control.go +++ b/ui/cryptomaterial/segmented_control.go @@ -16,6 +16,8 @@ type SegmentedControl struct { leftNavBtn, rightNavBtn *Clickable + Padding layout.Inset + selectedIndex int segmentTitles []string @@ -33,6 +35,7 @@ func (t *Theme) SegmentedControl(segmentTitles []string) *SegmentedControl { segmentTitles: segmentTitles, leftNavBtn: t.NewClickable(false), rightNavBtn: t.NewClickable(false), + Padding: layout.UniformInset(values.MarginPadding8), } } @@ -57,14 +60,17 @@ func (sc *SegmentedControl) Layout(gtx C) D { txt.Color = sc.theme.Color.Text border = Border{Radius: Radius(8)} } - return LinearLayout{ + + ll := LinearLayout{ Width: WrapContent, Height: WrapContent, - Padding: layout.UniformInset(values.MarginPadding8), Background: bg, Margin: layout.UniformInset(values.MarginPadding5), Border: border, - }.Layout2(gtx, txt.Layout) + Padding: sc.Padding, + } + + return ll.Layout2(gtx, txt.Layout) }) }) }), diff --git a/ui/cryptomaterial/theme.go b/ui/cryptomaterial/theme.go index 27dad9c7f..46c44c770 100644 --- a/ui/cryptomaterial/theme.go +++ b/ui/cryptomaterial/theme.go @@ -216,17 +216,17 @@ func mulAlpha(c color.NRGBA, alpha uint8) color.NRGBA { func (t *Theme) closeAllDropdownMenus(group uint) { for _, dropDown := range t.dropDownMenus { if dropDown.group == group { - dropDown.isOpen = false + dropDown.expanded = false } } } -// isOpenDropdownGroup iterate over Dropdowns registered as a member +// isDropdownGroupCollapsed iterate over Dropdowns registered as a member // of {group}, returns true if any of the drop down state is open. -func (t *Theme) isOpenDropdownGroup(group uint) bool { +func (t *Theme) isDropdownGroupCollapsed(group uint) bool { for _, dropDown := range t.dropDownMenus { if dropDown.group == group { - if dropDown.isOpen { + if dropDown.expanded { return true } } diff --git a/ui/cryptomaterial/toggle_button.go b/ui/cryptomaterial/toggle_button.go deleted file mode 100644 index 0a2f52094..000000000 --- a/ui/cryptomaterial/toggle_button.go +++ /dev/null @@ -1,101 +0,0 @@ -package cryptomaterial - -import ( - "gioui.org/layout" -) - -type ToggleButton struct { - btns []*Button - theme *Theme - radius CornerRadius // this radius is used by the clickable - selectedItem int // defaults to first btn - - colorInverted bool - callback func(selectedItem int) -} - -func (t *Theme) ToggleButton(btns []*Button, colorInverted bool) *ToggleButton { - tb := &ToggleButton{ - theme: t, - btns: btns, - selectedItem: -1, - colorInverted: colorInverted, - } - - for i := range tb.btns { - b := tb.btns[i] - if tb.colorInverted { - b.HighlightColor = tb.theme.Color.Gray5 - } else { - b.HighlightColor = tb.theme.Color.Surface - } - b.Color = tb.theme.Color.Text - } - - return tb -} - -func (tb *ToggleButton) SelectItemAtIndex(index int) { - itemsLen := len(tb.btns) - if index > itemsLen || index < 0 { - return // no-op - } - - tb.selectedItem = index - if tb.callback != nil { - tb.callback(index) - } -} - -func (tb *ToggleButton) SetToggleButtonCallback(callback func(selectedItem int)) { - tb.callback = callback -} - -func (tb *ToggleButton) handleClickables() { - for index := range tb.btns { - b := tb.btns[index] - for b.Clicked() { - tb.selectedItem = index - if tb.callback != nil { - tb.callback(tb.selectedItem) - } - } - } -} - -func (tb *ToggleButton) Layout(gtx layout.Context) layout.Dimensions { - tb.handleClickables() - var btns []layout.FlexChild - for index := range tb.btns { - b := tb.btns[index] - if index == tb.selectedItem { - if tb.colorInverted { - b.Background = tb.theme.Color.Gray5 - } else { - b.Background = tb.theme.Color.Surface - } - } else { - if tb.colorInverted { - b.Background = tb.theme.Color.Surface - } else { - b.Background = tb.theme.Color.Gray5 - } - } - btns = append(btns, layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.UniformInset(5).Layout(gtx, b.Layout) - })) - } - - bg := tb.theme.Color.Gray5 - if tb.colorInverted { - bg = tb.theme.Color.Surface - } - return LinearLayout{ - Width: WrapContent, - Height: WrapContent, - Background: bg, - Border: Border{ - Radius: Radius(8), - }, - }.Layout(gtx, btns...) -} diff --git a/ui/modal/info_modal.go b/ui/modal/info_modal.go index cfea10eea..e93929680 100644 --- a/ui/modal/info_modal.go +++ b/ui/modal/info_modal.go @@ -231,8 +231,7 @@ func (in *InfoModal) NegativeButtonStyle(background, text color.NRGBA) *InfoModa return in } -// for backwards compatibility. template can be a translated string or an -// existing template. +// for backwards compatibility. func (in *InfoModal) SetupWithTemplate(template string) *InfoModal { title := in.dialogTitle subtitle := in.subtitle @@ -260,13 +259,6 @@ func (in *InfoModal) SetupWithTemplate(template string) *InfoModal { customTemplate = totalValueInfo(in.Theme) case BondStrengthInfoTemplate: customTemplate = bondStrengthInfo(in.Theme) - default: - prepTemplate := func(th *cryptomaterial.Theme) func(gtx C) D { - return func(gtx layout.Context) layout.Dimensions { - return th.Body2(template).Layout(gtx) - } - } - customTemplate = append(customTemplate, prepTemplate(in.Theme)) } in.dialogTitle = title diff --git a/ui/page/components/components.go b/ui/page/components/components.go index f9dba694c..077aca55f 100644 --- a/ui/page/components/components.go +++ b/ui/page/components/components.go @@ -849,7 +849,7 @@ func BrowserURLWidget(gtx C, l *load.Load, url string, copyRedirect *cryptomater }) }) }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { + layout.Stacked(func(gtx C) D { return layout.Inset{ Top: values.MarginPaddingMinus10, Left: values.MarginPadding10, @@ -940,3 +940,31 @@ func FlexLayout(gtx C, options FlexOptions, widgets []func(gtx C) D) D { Alignment: options.Alignment, }.Layout(gtx, flexChildren...) } + +// IconButton creates the display for an icon button. The default icon and text +// color is Theme.Color.Primary. +func IconButton(icon *widget.Icon, txt string, inset layout.Inset, th *cryptomaterial.Theme, clickable *cryptomaterial.Clickable) func(gtx C) D { + return func(gtx C) D { + return inset.Layout(gtx, func(gtx C) D { + color := th.Color.Primary + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.MatchParent, + Height: cryptomaterial.WrapContent, + Orientation: layout.Horizontal, + Alignment: layout.Start, + Clickable: clickable, + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return icon.Layout(gtx, color) + }), + layout.Rigid(layout.Spacer{Width: values.MarginPadding5}.Layout), + layout.Rigid(func(gtx C) D { + label := th.Label(values.TextSize16, txt) + label.Color = color + label.Font.Weight = font.SemiBold + return label.Layout(gtx) + }), + ) + }) + } +} diff --git a/ui/page/dcrdex/dcrdex_page.go b/ui/page/dcrdex/dcrdex_page.go index 3d544ecfe..0ec8fedfc 100644 --- a/ui/page/dcrdex/dcrdex_page.go +++ b/ui/page/dcrdex/dcrdex_page.go @@ -34,6 +34,9 @@ func NewDEXPage(l *load.Load) *DEXPage { MasterPage: app.NewMasterPage(DCRDEXID), openTradeMainPage: l.Theme.NewClickable(false), } + + // TODO: Set dp.inited + dp.inited = true return dp } @@ -50,13 +53,12 @@ func (pg *DEXPage) ID() string { // Part of the load.Page interface. func (pg *DEXPage) OnNavigatedTo() { pg.ctx, pg.ctxCancel = context.WithCancel(context.TODO()) - // TODO: set pg.inited - if !pg.inited { - pg.Display(NewDEXOnboarding(pg.Load)) - } else if pg.CurrentPage() == nil { + if pg.CurrentPage() != nil { + pg.CurrentPage().OnNavigatedTo() + } else if pg.inited { pg.Display(NewDEXMarketPage(pg.Load)) } else { - pg.CurrentPage().OnNavigatedTo() + pg.Display(NewDEXOnboarding(pg.Load)) } } @@ -64,17 +66,7 @@ func (pg *DEXPage) OnNavigatedTo() { // eventually drawn on screen. // Part of the load.Page interface. func (pg *DEXPage) Layout(gtx C) D { - return layout.Stack{}.Layout(gtx, - layout.Expanded(func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.MatchParent, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Flexed(1, pg.CurrentPage().Layout), - ) - }), - ) + return pg.CurrentPage().Layout(gtx) } // HandleUserInteractions is called just before Layout() to determine if any diff --git a/ui/page/dcrdex/dex_onboarding_page.go b/ui/page/dcrdex/dex_onboarding_page.go index 22e358bdf..0d4f04cb2 100644 --- a/ui/page/dcrdex/dex_onboarding_page.go +++ b/ui/page/dcrdex/dex_onboarding_page.go @@ -5,7 +5,6 @@ import ( "image" "image/color" "strconv" - "time" "gioui.org/font" "gioui.org/layout" @@ -41,14 +40,13 @@ var ( }}, } - // formWidth is the maximum width for form elements on the onboarding DEX - // page. + // formWidth is the width for form elements on the onboarding DEX page. formWidth = values.MarginPadding450 - u20 = values.MarginPadding20 - u16 = values.MarginPadding16 - u2 = values.MarginPadding2 - u10 = values.MarginPadding10 + dp20 = values.MarginPadding20 + dp16 = values.MarginPadding16 + dp2 = values.MarginPadding2 + dp10 = values.MarginPadding10 ) // onboardingStep is each step of the flow required for a user to create a DEX @@ -112,8 +110,6 @@ type DEXOnboarding struct { materialLoader material.LoaderStyle showLoader bool isLoading bool - - redirected bool // TODO: Remove } func NewDEXOnboarding(l *load.Load) *DEXOnboarding { @@ -125,7 +121,7 @@ func NewDEXOnboarding(l *load.Load) *DEXOnboarding { currentStep: onboardingSetPassword, passwordEditor: newPasswordEditor(th, values.String(values.StrNewPassword)), confirmPasswordEditor: newPasswordEditor(th, values.String(values.StrConfirmPassword)), - serverDropDown: th.DropDown(knownDEXServers[l.AssetsManager.NetType()], values.DEXServerDropdownGroup, 0), + serverDropDown: th.DropDown(knownDEXServers[l.AssetsManager.NetType()], values.DEXServerDropdownGroup, false), addServerBtn: th.NewClickable(false), serverURLEditor: newTextEditor(th, values.String(values.StrServerURL), values.String(values.StrInputURL), false), serverCertEditor: newTextEditor(th, values.String(values.StrCertificateOPtional), values.String(values.StrInputCertificate), true), @@ -197,9 +193,8 @@ func (pg *DEXOnboarding) Layout(gtx C) D { Orientation: layout.Vertical, Background: pg.Theme.Color.Surface, Margin: layout.Inset{ - Bottom: values.MarginPadding50, - Right: u20, - Left: u20, + Right: dp20, + Left: dp20, }, Border: cryptomaterial.Border{ Radius: cryptomaterial.Radius(r), @@ -209,7 +204,7 @@ func (pg *DEXOnboarding) Layout(gtx C) D { layout.Rigid(func(gtx C) D { txt := pg.Theme.Body1(values.String(values.StrDCRDEXWelcomeMessage)) txt.Font.Weight = font.Bold - return pg.centerLayout(gtx, u16, u20, txt.Layout) + return pg.centerLayout(gtx, dp16, dp20, txt.Layout) }), layout.Rigid(func(gtx C) D { return pg.onBoardingStepRow(gtx) @@ -310,16 +305,14 @@ func (pg *DEXOnboarding) onBoardingStep(gtx C, step onboardingStep, stepDesc str lb := pg.Theme.Label(values.TextSize16, fmt.Sprintf("%d", step)) lb.Color = textColor lb.Font.Weight = font.SemiBold - return layout.Inset{Top: u10, Bottom: u10}.Layout(gtx, lb.Layout) + return layout.Inset{Top: dp10, Bottom: dp10}.Layout(gtx, lb.Layout) }), ) }), layout.Rigid(func(gtx C) D { - inset := layout.Inset{Top: u10, Bottom: u10} + inset := layout.Inset{Top: dp10, Bottom: dp10} if !activeStep { - return inset.Layout(gtx, func(gtx C) D { - return semiBoldLabelGrey3(pg.Theme, gtx, stepDesc) - }) + return inset.Layout(gtx, semiBoldLabelGrey3(pg.Theme, stepDesc).Layout) } lb := pg.semiBoldLabel(stepDesc) @@ -341,10 +334,10 @@ func (pg *DEXOnboarding) stepSetPassword(gtx C) D { return pg.centerLayout(gtx, 0, 0, pg.Theme.Body1(values.String(values.StrSetTradePasswordDesc)).Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16}.Layout(gtx, pg.passwordEditor.Layout) + return layout.Inset{Top: dp16}.Layout(gtx, pg.passwordEditor.Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16}.Layout(gtx, pg.confirmPasswordEditor.Layout) + return layout.Inset{Top: dp16}.Layout(gtx, pg.confirmPasswordEditor.Layout) }), layout.Rigid(func(gtx C) D { return pg.formFooterButtons(gtx) @@ -358,7 +351,7 @@ func (pg *DEXOnboarding) stepSetPassword(gtx C) D { func (pg *DEXOnboarding) stepChooseServer(gtx C) D { layoutFlex := layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - return pg.centerLayout(gtx, u20, values.MarginPadding12, pg.Theme.H6(values.String(values.StrSelectServer)).Layout) + return pg.centerLayout(gtx, dp20, values.MarginPadding12, pg.Theme.H6(values.String(values.StrSelectServer)).Layout) }), layout.Rigid(func(gtx C) D { return pg.centerLayout(gtx, 0, 0, pg.Theme.Body1(values.String(values.StrSelectDEXServerDesc)).Layout) @@ -366,38 +359,12 @@ func (pg *DEXOnboarding) stepChooseServer(gtx C) D { layout.Rigid(func(gtx C) D { l := pg.Theme.Label(values.TextSize16, values.String(values.StrServer)) l.Font.Weight = font.Bold - return layout.Inset{Top: u20}.Layout(gtx, l.Layout) - }), - layout.Rigid(func(gtx C) D { - return pg.serverDropDown.Layout(gtx, 0, false) - }), - layout.Rigid(func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Start}.Layout(gtx, - layout.Rigid(func(gtx C) D { - color := pg.Theme.Color.Primary - return cryptomaterial.LinearLayout{ - Width: gtx.Dp(values.MarginPadding110), - Height: cryptomaterial.WrapContent, - Orientation: layout.Horizontal, - Margin: layout.Inset{Top: u16}, - Direction: layout.W, - Alignment: layout.Middle, - Clickable: pg.addServerBtn, - }.Layout(gtx, - layout.Rigid(func(gtx C) D { - icon := pg.Theme.Icons.ContentAdd - return icon.Layout(gtx, color) - }), - layout.Rigid(func(gtx C) D { - label := pg.Theme.Label(values.TextSize16, values.String(values.StrAddServer)) - label.Color = color - label.Font.Weight = font.SemiBold - return layout.Inset{Left: values.MarginPadding5}.Layout(gtx, label.Layout) - }), - ) - }), - ) + return layout.Inset{Top: dp20}.Layout(gtx, l.Layout) }), + layout.Rigid(pg.serverDropDown.Layout), + layout.Rigid(components.IconButton(pg.Theme.Icons.ContentAdd, values.String(values.StrAddServer), + layout.Inset{Top: dp16}, pg.Theme, pg.addServerBtn), + ), layout.Rigid(func(gtx C) D { return pg.formFooterButtons(gtx) }), @@ -418,7 +385,7 @@ func (pg *DEXOnboarding) subStepAddServer(gtx C) D { Width: width, Height: cryptomaterial.WrapContent, Orientation: layout.Horizontal, - Margin: layout.Inset{Top: values.MarginPadding20, Bottom: u16}, + Margin: layout.Inset{Top: values.MarginPadding20, Bottom: dp16}, Alignment: layout.Middle, }.Layout(gtx, layout.Rigid(func(gtx C) D { @@ -448,10 +415,10 @@ func (pg *DEXOnboarding) subStepAddServer(gtx C) D { return pg.centerLayout(gtx, 0, 0, pg.Theme.Body1(values.String(values.StrAddServerDesc)).Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16}.Layout(gtx, pg.serverURLEditor.Layout) + return layout.Inset{Top: dp16}.Layout(gtx, pg.serverURLEditor.Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16}.Layout(gtx, pg.serverCertEditor.Layout) + return layout.Inset{Top: dp16}.Layout(gtx, pg.serverCertEditor.Layout) }), layout.Rigid(func(gtx C) D { return pg.formFooterButtons(gtx) @@ -511,16 +478,16 @@ func (pg *DEXOnboarding) formFooterButtons(gtx C) D { func (pg *DEXOnboarding) stepPostBond(gtx C) D { layoutFlex := layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - return pg.centerLayout(gtx, u20, values.MarginPadding12, pg.Theme.H6(values.String(values.StrPostBond)).Layout) + return pg.centerLayout(gtx, dp20, values.MarginPadding12, pg.Theme.H6(values.String(values.StrPostBond)).Layout) }), layout.Rigid(func(gtx C) D { return pg.centerLayout(gtx, 0, 0, pg.Theme.Body1(values.String(values.StrSelectBondWalletMsg)).Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u20}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrSupportedWallets)).Layout) + return layout.Inset{Top: dp20}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrSupportedWallets)).Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u2}.Layout(gtx, func(gtx C) D { + return layout.Inset{Top: dp2}.Layout(gtx, func(gtx C) D { if pg.bondSourceWalletSelector == nil { return D{} // TODO: return btn to create wallet } @@ -528,10 +495,10 @@ func (pg *DEXOnboarding) stepPostBond(gtx C) D { }) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u20}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrAccount)).Layout) + return layout.Inset{Top: dp20}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrAccount)).Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u2}.Layout(gtx, func(gtx C) D { + return layout.Inset{Top: dp2}.Layout(gtx, func(gtx C) D { if pg.bondSourceAccountSelector == nil { return D{} } @@ -539,13 +506,13 @@ func (pg *DEXOnboarding) stepPostBond(gtx C) D { }) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u20 * u2, Bottom: u20 * u2}.Layout(gtx, pg.Theme.Separator().Layout) + return layout.Inset{Top: dp20 * dp2, Bottom: dp20 * dp2}.Layout(gtx, pg.Theme.Separator().Layout) }), layout.Rigid(func(gtx C) D { return layout.S.Layout(gtx, pg.Theme.Body1(values.String(values.StrSelectBondStrengthMsg)).Layout) }), layout.Rigid(func(gtx C) D { - return pg.centerLayout(gtx, u20, u16, renderers.RenderHTML(values.String(values.StrPostBondDesc), pg.Theme).Layout) + return pg.centerLayout(gtx, dp20, dp16, renderers.RenderHTML(values.String(values.StrPostBondDesc), pg.Theme).Layout) }), layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -564,7 +531,7 @@ func (pg *DEXOnboarding) stepPostBond(gtx C) D { layout.Flexed(0.5, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16, Right: u10}.Layout(gtx, func(gtx C) D { + return layout.Inset{Top: dp16, Right: dp10}.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(pg.semiBoldLabel(values.String(values.StrBondStrength)).Layout), layout.Rigid(func(gtx C) D { @@ -572,24 +539,24 @@ func (pg *DEXOnboarding) stepPostBond(gtx C) D { Width: cryptomaterial.WrapContent, Height: cryptomaterial.WrapContent, Clickable: pg.bondStrengthMoreInfo, - Padding: layout.Inset{Top: u2, Left: u2}, + Padding: layout.Inset{Top: dp2, Left: dp2}, }.Layout2(gtx, pg.Theme.Icons.InfoAction.Layout16dp) }), ) }) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Right: u10}.Layout(gtx, pg.bondStrengthEditor.Layout) + return layout.Inset{Right: dp10}.Layout(gtx, pg.bondStrengthEditor.Layout) }), ) }), layout.Flexed(0.5, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16, Left: u10}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrNewTier)).Layout) + return layout.Inset{Top: dp16, Left: dp10}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrNewTier)).Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Left: u10}.Layout(gtx, pg.viewOnlyCard(nil, func(gtx C) D { + return layout.Inset{Left: dp10}.Layout(gtx, pg.viewOnlyCard(nil, func(gtx C) D { return pg.Theme.Label(values.TextSize16, fmt.Sprintf("%d", pg.newTier)).Layout(gtx) })) }), @@ -602,7 +569,7 @@ func (pg *DEXOnboarding) stepPostBond(gtx C) D { layout.Flexed(0.3, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrCurrency)).Layout) + return layout.Inset{Top: dp16}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrCurrency)).Layout) }), layout.Rigid(func(gtx C) D { return pg.viewOnlyCard(&pg.Theme.Color.Gray2, func(gtx C) D { @@ -625,10 +592,10 @@ func (pg *DEXOnboarding) stepPostBond(gtx C) D { layout.Flexed(0.7, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16, Left: u10}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrTotalCost)).Layout) + return layout.Inset{Top: dp16, Left: dp10}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrTotalCost)).Layout) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Left: u10}.Layout(gtx, pg.viewOnlyCard(nil, func(gtx C) D { + return layout.Inset{Left: dp10}.Layout(gtx, pg.viewOnlyCard(nil, func(gtx C) D { return pg.bondAmountInfoDisplay(gtx) })) }), @@ -637,7 +604,7 @@ func (pg *DEXOnboarding) stepPostBond(gtx C) D { ) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u16}.Layout(gtx, pg.formFooterButtons) + return layout.Inset{Top: dp16}.Layout(gtx, pg.formFooterButtons) }), ) @@ -665,12 +632,12 @@ func (pg *DEXOnboarding) viewOnlyCard(bg *color.NRGBA, info func(gtx C) D) func( Orientation: layout.Vertical, Border: cryptomaterial.Border{ Radius: cryptomaterial.Radius(8), - Width: u2, + Width: dp2, Color: pg.Theme.Color.Gray2, }, Margin: layout.Inset{ - Top: u2, - Bottom: u2, + Top: dp2, + Bottom: dp2, }, Padding: layout.Inset{ Top: u12, @@ -688,7 +655,7 @@ func (pg *DEXOnboarding) stepWaitForBondConfirmation(gtx C) D { gtx.Constraints.Max.X = gtx.Dp(width) layoutFlex := layout.Flex{Axis: layout.Vertical, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - return pg.centerLayout(gtx, u20, values.MarginPadding12, pg.Theme.H6(values.String(values.StrPostBond)).Layout) + return pg.centerLayout(gtx, dp20, values.MarginPadding12, pg.Theme.H6(values.String(values.StrPostBond)).Layout) }), layout.Rigid(func(gtx C) D { return pg.centerLayout(gtx, 0, 0, renderers.RenderHTML(values.String(values.StrPostBondDesc), pg.Theme).Layout) @@ -700,26 +667,26 @@ func (pg *DEXOnboarding) stepWaitForBondConfirmation(gtx C) D { Background: pg.Theme.Color.Gray4, Orientation: layout.Vertical, Margin: layout.Inset{ - Top: u20, + Top: dp20, Bottom: u30, }, Border: cryptomaterial.Border{ Radius: cryptomaterial.Radius(8), }, - Padding: layout.UniformInset(u16), + Padding: layout.UniformInset(dp16), }.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Top: u2}.Layout(gtx, pg.Theme.Icons.TimerIcon.Layout16dp) + return layout.Inset{Top: dp2}.Layout(gtx, pg.Theme.Icons.TimerIcon.Layout16dp) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Left: u10}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrWaitingForConfirmation)).Layout) + return layout.Inset{Left: dp10}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrWaitingForConfirmation)).Layout) }), ) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Top: 10, Bottom: u10}.Layout(gtx, pg.Theme.Body1(values.StringF(values.StrDEXBondConfirmationMsg, "dex.decred.org", 2 /* TODO: use real values */)).Layout) + return layout.Inset{Top: 10, Bottom: dp10}.Layout(gtx, pg.Theme.Body1(values.StringF(values.StrDEXBondConfirmationMsg, "dex.decred.org", 2 /* TODO: use real values */)).Layout) }), layout.Rigid(func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, @@ -734,7 +701,7 @@ func (pg *DEXOnboarding) stepWaitForBondConfirmation(gtx C) D { ) }), layout.Rigid(func(gtx C) D { - return layout.Inset{Bottom: u20}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrPaymentDetails)).Layout) + return layout.Inset{Bottom: dp20}.Layout(gtx, pg.semiBoldLabel(values.String(values.StrPaymentDetails)).Layout) }), layout.Rigid(func(gtx C) D { return layout.Inset{Bottom: values.MarginPadding60}.Layout(gtx, func(gtx C) D { @@ -742,9 +709,7 @@ func (pg *DEXOnboarding) stepWaitForBondConfirmation(gtx C) D { layout.Flexed(0.33, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Bottom: 5}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.StrNewTier)) - }) + return layout.Inset{Bottom: 5}.Layout(gtx, semiBoldLabelGrey3(pg.Theme, values.String(values.StrNewTier)).Layout) }), layout.Rigid(func(gtx C) D { return pg.Theme.Body1(fmt.Sprintf("%d", pg.newTier)).Layout(gtx) @@ -754,9 +719,7 @@ func (pg *DEXOnboarding) stepWaitForBondConfirmation(gtx C) D { layout.Flexed(0.33, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Bottom: 5}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.StrBondStrength)) - }) + return layout.Inset{Bottom: 5}.Layout(gtx, semiBoldLabelGrey3(pg.Theme, values.String(values.StrBondStrength)).Layout) }), layout.Rigid(func(gtx C) D { return pg.Theme.Body1(fmt.Sprintf("%d", pg.newTier /* TODO: Use real value */)).Layout(gtx) @@ -766,9 +729,7 @@ func (pg *DEXOnboarding) stepWaitForBondConfirmation(gtx C) D { layout.Flexed(0.33, func(gtx C) D { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Inset{Bottom: 5}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.StrTotalCost)) - }) + return layout.Inset{Bottom: 5}.Layout(gtx, semiBoldLabelGrey3(pg.Theme, values.String(values.StrTotalCost)).Layout) }), layout.Rigid(func(gtx C) D { return pg.bondAmountInfoDisplay(gtx) @@ -935,19 +896,11 @@ func (pg *DEXOnboarding) HandleUserInteractions() { hasEnough := pg.bondAccountHasEnough() bondStrengthOk := pg.validateBondStrength() if !hasEnough || !bondStrengthOk { - // return + return } // TODO: Post bond, wait for confirmations and redirect to market page. pg.currentStep = onBoardingStepWaitForConfirmation - if !pg.redirected { - pg.redirected = true - log.Info("Redirecting to market page...") - time.AfterFunc(5*time.Second, func() { - pg.ParentNavigator().ClearStackAndDisplay(NewDEXMarketPage(pg.Load)) - }) - } - // Scroll to the top of the confirmation page after leaving the long // post bond form. pg.scrollContainer.Position.Offset = 0 @@ -982,11 +935,11 @@ func (pg *DEXOnboarding) validateBondStrength() bool { return ok } -func semiBoldLabelGrey3(th *cryptomaterial.Theme, gtx C, text string) D { +func semiBoldLabelGrey3(th *cryptomaterial.Theme, text string) cryptomaterial.Label { lb := th.Label(values.TextSize16, text) lb.Color = th.Color.GrayText3 lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) + return lb } func (pg *DEXOnboarding) passwordsMatch(editors ...*widget.Editor) bool { diff --git a/ui/page/dcrdex/market.go b/ui/page/dcrdex/market.go index 3b17e7f5c..1de2fc977 100644 --- a/ui/page/dcrdex/market.go +++ b/ui/page/dcrdex/market.go @@ -21,18 +21,24 @@ import ( ) const ( - DEXMarketPageID = "dex_market" + DEXMarketPageID = "dex_market" + // maxOrderDisplayedInOrderBook is the maximum number of orders that can be + // accommodated/displayed on the order book. maxOrderDisplayedInOrderBook = 7 ) var ( - u5 = values.MarginPadding5 - u300 = unit.Dp(300) - orderFormAndOrderBookWidth = (values.AppWidth / 2) - 30 - orderFormAndOrderBookHeight = unit.Dp(515) - - limitOrderIndex = 0 - orderTypes = []cryptomaterial.DropDownItem{ + dp5 = values.MarginPadding5 + dp8 = values.MarginPadding8 + dp300 = values.DP300 + orderFormAndOrderBookWidth = (values.AppWidth / 2) - 40 // Minus 40 px to allow for margin between the order form and order book. + // orderFormAndOrderBookHeight is a an arbitrary height that accommodates + // the current order form elements and maxOrderDisplayedInOrderBook. Use + // this to ensure they (order form and orderbook) have the same height as + // they are displayed sided by side. + orderFormAndOrderBookHeight = values.DP515 + + orderTypes = []cryptomaterial.DropDownItem{ { Text: values.String(values.StrLimit), }, @@ -40,6 +46,15 @@ var ( Text: values.String(values.StrMarket), }, } + + buyBtnStringIndex = 0 + buyAndSellBtnStrings = []string{ + values.String(values.StrBuy), + values.String(values.StrSell), + } + + vertical = layout.Vertical + horizontal = layout.Horizontal ) type DEXMarketPage struct { @@ -50,122 +65,85 @@ type DEXMarketPage struct { // and the root WindowNavigator. *app.GenericPageModal - scrollContainer *widget.List - pageContainer layout.List - sellOrdersContainer layout.List - buyOrdersContainer layout.List - openOrdersContainer *widget.List - tradeHistoryContainer *widget.List + scrollContainer *widget.List + openOrdersAndOrderHistoryContainer *widget.List serverSelector *cryptomaterial.DropDown addServerBtn *cryptomaterial.Clickable marketSelector *cryptomaterial.DropDown - toggleBuyAndSellBtn *cryptomaterial.ToggleButton + toggleBuyAndSellBtn *cryptomaterial.SegmentedControl orderTypesDropdown *cryptomaterial.DropDown - isMarketOrder bool priceEditor cryptomaterial.Editor switchLotsOrAmount *cryptomaterial.Switch - lotsAmountEditor cryptomaterial.Editor + lotsOrAmountEditor cryptomaterial.Editor totalEditor cryptomaterial.Editor seeFullOrderBookBtn cryptomaterial.Button - buySellBtn cryptomaterial.Button - buyOrder bool - immediateOrder cryptomaterial.CheckBoxStyle - immediateMoreInfo *cryptomaterial.Clickable + createOrderBtn cryptomaterial.Button + immediateOrderCheckbox cryptomaterial.CheckBoxStyle + immediateOrderInfoBtn *cryptomaterial.Clickable + + openOrdersBtn cryptomaterial.Button + orderHistoryBtn cryptomaterial.Button + openOrdersDisplayed bool - orders []*order - toggleOpenAndHistoryOrderBtn *cryptomaterial.ToggleButton + ordersTableHorizontalScroll *widget.List - noOrderMsg string showLoader bool } func NewDEXMarketPage(l *load.Load) *DEXMarketPage { th := l.Theme pg := &DEXMarketPage{ - Load: l, - pageContainer: layout.List{ - Axis: layout.Vertical, - Alignment: layout.Middle, - }, - buyOrdersContainer: layout.List{Axis: layout.Vertical, Alignment: layout.Middle}, - sellOrdersContainer: layout.List{Axis: layout.Vertical, Alignment: layout.Middle}, - openOrdersContainer: &widget.List{List: layout.List{Axis: layout.Vertical, Alignment: layout.Middle}}, - tradeHistoryContainer: &widget.List{List: layout.List{Axis: layout.Vertical, Alignment: layout.Middle}}, - GenericPageModal: app.NewGenericPageModal(DEXAccountOnboardingID), - scrollContainer: &widget.List{List: layout.List{Axis: layout.Vertical, Alignment: layout.Middle}}, - priceEditor: newTextEditor(l.Theme, values.String(values.StrPrice), "0", false), - totalEditor: newTextEditor(th, values.String(values.StrTotal), "", false), - switchLotsOrAmount: l.Theme.Switch(), // TODO: Set last user choice, default is unchecked. - lotsAmountEditor: newTextEditor(l.Theme, values.String(values.StrLots), "0", false), - buySellBtn: th.Button(values.String(values.StrBuy)), // TODO: toggle - buyOrder: true, - immediateOrder: th.CheckBox(new(widget.Bool), values.String(values.StrImmediate)), - orderTypesDropdown: th.DropDown(orderTypes, values.DEXOrderTypes, 0), - immediateMoreInfo: th.NewClickable(false), - addServerBtn: th.NewClickable(false), - seeFullOrderBookBtn: th.Button(values.String(values.StrSeeMore)), + Load: l, + openOrdersAndOrderHistoryContainer: &widget.List{List: layout.List{Axis: vertical, Alignment: layout.Middle}}, + GenericPageModal: app.NewGenericPageModal(DEXAccountOnboardingID), + scrollContainer: &widget.List{List: layout.List{Axis: vertical, Alignment: layout.Middle}}, + priceEditor: newTextEditor(l.Theme, values.String(values.StrPrice), "0", false), + totalEditor: newTextEditor(th, values.String(values.StrTotal), "", false), + switchLotsOrAmount: l.Theme.Switch(), // TODO: Set last user choice, default is unchecked. + lotsOrAmountEditor: newTextEditor(l.Theme, values.String(values.StrLots), "0", false), + createOrderBtn: th.Button(values.String(values.StrBuy)), // TODO: toggle + immediateOrderCheckbox: th.CheckBox(new(widget.Bool), values.String(values.StrImmediate)), + orderTypesDropdown: th.DropDown(orderTypes, values.DEXOrderTypes, true), + immediateOrderInfoBtn: th.NewClickable(false), + addServerBtn: th.NewClickable(false), + seeFullOrderBookBtn: th.Button(values.String(values.StrSeeMore)), + toggleBuyAndSellBtn: th.SegmentedControl(buyAndSellBtnStrings), + openOrdersBtn: th.Button(values.String(values.StrOpenOrders)), + orderHistoryBtn: th.Button(values.String(values.StrTradeHistory)), + openOrdersDisplayed: true, + ordersTableHorizontalScroll: &widget.List{List: layout.List{Axis: horizontal, Alignment: layout.Middle}}, } + btnPadding := layout.Inset{Top: dp8, Right: dp20, Left: dp20, Bottom: dp8} + pg.toggleBuyAndSellBtn.Padding = btnPadding + pg.openOrdersBtn.Inset, pg.orderHistoryBtn.Inset = btnPadding, btnPadding + pg.openOrdersBtn.Font.Weight, pg.orderHistoryBtn.Font.Weight = font.SemiBold, font.SemiBold + + pg.orderTypesDropdown.CollapsedLayoutTextDirection = layout.E pg.orderTypesDropdown.Width = values.MarginPadding120 - pg.orderTypesDropdown.Color = pg.Theme.Color.Surface pg.orderTypesDropdown.FontWeight = font.SemiBold pg.orderTypesDropdown.Hoverable = false - pg.orderTypesDropdown.NavigationIconColor = &pg.Theme.Color.Primary - - buyBtn := th.Button(values.String(values.StrBuy)) - buyBtn.Font.Weight = font.SemiBold - sellBtn := th.Button(values.String(values.StrSell)) - sellBtn.Font.Weight = font.SemiBold - - toggleBtns := []*cryptomaterial.Button{&buyBtn, &sellBtn} - pg.toggleBuyAndSellBtn = th.ToggleButton(toggleBtns, false) - pg.toggleBuyAndSellBtn.SetToggleButtonCallback(func(selectedItem int) { - if selectedItem == 0 { // Buy - pg.buyOrder = true - pg.buySellBtn.Text = values.String(values.StrBuy) - pg.buySellBtn.Background = pg.Theme.Color.Green500 - pg.buySellBtn.HighlightColor = pg.Theme.Color.Success - } else if selectedItem == 1 { // Sell - pg.buyOrder = false - pg.buySellBtn.Text = values.String(values.StrSell) - pg.buySellBtn.Background = pg.Theme.Color.Orange - pg.buySellBtn.HighlightColor = pg.Theme.Color.OrangeRipple - } - }) - pg.toggleBuyAndSellBtn.SelectItemAtIndex(0) - - openOrderBtn := th.Button(values.String(values.StrOpenOrders)) - openOrderBtn.Font.Weight = font.SemiBold - historyOrderBtn := th.Button(values.String(values.StrTradeHistory)) - historyOrderBtn.Font.Weight = font.SemiBold - - toggleOpenAndHistoryOrderBtn := []*cryptomaterial.Button{&openOrderBtn, &historyOrderBtn} - pg.toggleOpenAndHistoryOrderBtn = th.ToggleButton(toggleOpenAndHistoryOrderBtn, true) - pg.toggleOpenAndHistoryOrderBtn.SetToggleButtonCallback(func(selectedItem int) { - if selectedItem == 0 { // TODO: Fetch open orders - pg.noOrderMsg = values.String(values.StrNoOpenOrdersMsg) - } else if selectedItem == 1 { // TODO: Fetch History orders - pg.noOrderMsg = values.String(values.StrNoTradeHistoryMsg) - } - }) - pg.toggleOpenAndHistoryOrderBtn.SelectItemAtIndex(0) + pg.orderTypesDropdown.SelectedItemIconColor = &pg.Theme.Color.Primary - pg.priceEditor.IsTitleLabel = false - pg.lotsAmountEditor.IsTitleLabel = false - pg.totalEditor.IsTitleLabel = false + pg.orderTypesDropdown.ExpandedLayoutInset = layout.Inset{Top: values.MarginPadding35} + pg.orderTypesDropdown.MakeCollapsedLayoutVisibleWhenExpanded = true + + pg.priceEditor.IsTitleLabel, pg.lotsOrAmountEditor.IsTitleLabel, pg.totalEditor.IsTitleLabel = false, false, false pg.seeFullOrderBookBtn.HighlightColor, pg.seeFullOrderBookBtn.Background = color.NRGBA{}, color.NRGBA{} pg.seeFullOrderBookBtn.Color = th.Color.Primary pg.seeFullOrderBookBtn.Font.Weight = font.SemiBold pg.seeFullOrderBookBtn.Inset = layout.Inset{} - pg.immediateOrder.Font.Weight = font.SemiBold + pg.immediateOrderCheckbox.Font.Weight = font.SemiBold + + pg.setBuyOrSell() return pg } @@ -179,15 +157,22 @@ func (pg *DEXMarketPage) OnNavigatedTo() { // TODO: Use real server values saved by user and it should be the default selected in dropdown position. var servers []cryptomaterial.DropDownItem - for _, s := range knownDEXServers[pg.WL.AssetsManager.NetType()] { + for _, s := range knownDEXServers[pg.AssetsManager.NetType()] { servers = append(servers, cryptomaterial.DropDownItem{ Text: s.Text, Icon: s.Icon, }) } - pg.serverSelector = pg.Theme.DropDown(servers, values.DEXServerDropdownGroup, 0 /* TODO: use real value */) - pg.serverSelector.SetExtraDisplay(pg.addServerDisplay()) + // Include the "Add Server" button as part of pg.serverSelector items. The + // pg.addServerBtn should open a modal or page to add a new server to DEX + // when clicked. + servers = append(servers, cryptomaterial.DropDownItem{ + DisplayFn: components.IconButton(pg.Theme.Icons.ContentAdd, values.String(values.StrAddServer), layout.Inset{}, pg.Theme, pg.addServerBtn), + PreventSelection: true, + }) + + pg.serverSelector = pg.Theme.DropDown(servers, values.DEXServerDropdownGroup, false) pg.marketSelector = pg.Theme.DropDown([]cryptomaterial.DropDownItem{ { Text: "DCR/BTC", @@ -197,64 +182,53 @@ func (pg *DEXMarketPage) OnNavigatedTo() { Text: "DCR/LTC", DisplayFn: pg.marketDropdownListItem(libutils.DCRWalletAsset, libutils.LTCWalletAsset), }, - }, values.DEXCurrencyPairGroup, 0) - - pg.serverSelector.Color = pg.Theme.Color.Surface - pg.serverSelector.BorderWidth = values.MarginPadding2 - pg.serverSelector.BorderColor = &pg.Theme.Color.Gray5 - pg.serverSelector.NavigationIconColor = &pg.Theme.Color.Primary - - pg.marketSelector.Color = pg.Theme.Color.Surface - pg.marketSelector.BorderWidth = values.MarginPadding2 - pg.marketSelector.BorderColor = &pg.Theme.Color.Gray5 - pg.marketSelector.NavigationIconColor = &pg.Theme.Color.Primary + { + Text: "LTC/BTC", + DisplayFn: pg.marketDropdownListItem(libutils.LTCWalletAsset, libutils.BTCWalletAsset), + }, + }, values.DEXCurrencyPairGroup, false) + pg.serverSelector.Width, pg.marketSelector.Width = dp300, dp300 + pg.serverSelector.MakeCollapsedLayoutVisibleWhenExpanded, pg.marketSelector.MakeCollapsedLayoutVisibleWhenExpanded = true, true + inset := layout.Inset{Top: values.DP45} + pg.serverSelector.ExpandedLayoutInset, pg.marketSelector.ExpandedLayoutInset = inset, inset + pg.serverSelector.BorderWidth, pg.marketSelector.BorderWidth = dp2, dp2 pg.serverSelector.Hoverable, pg.marketSelector.Hoverable = false, false - pg.serverSelector.Width, pg.marketSelector.Width = u300, u300 -} + pg.serverSelector.SelectedItemIconColor, pg.marketSelector.SelectedItemIconColor = &pg.Theme.Color.Primary, &pg.Theme.Color.Primary -func assetIcon(th *cryptomaterial.Theme, assetType libutils.AssetType) *cryptomaterial.Image { - switch assetType { - case libutils.DCRWalletAsset: - return th.Icons.DCR - case libutils.BTCWalletAsset: - return th.Icons.BTC - case libutils.LTCWalletAsset: - return th.Icons.LTC - } - return nil + // TODO: Fetch orders or order history. } func (pg *DEXMarketPage) marketDropdownListItem(baseAsset, quoteAsset libutils.AssetType) func(gtx C) D { baseIcon, quoteIcon := assetIcon(pg.Theme, baseAsset), assetIcon(pg.Theme, quoteAsset) return func(gtx cryptomaterial.C) cryptomaterial.D { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(func(gtx C) D { if baseIcon == nil { return D{} } return baseIcon.Layout20dp(gtx) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Right: u2, Left: u2}.Layout(gtx, pg.Theme.Label(values.TextSize16, baseAsset.String()).Layout) + layout.Rigid(func(gtx C) D { + return layout.Inset{Right: dp2, Left: dp2}.Layout(gtx, pg.Theme.Label(values.TextSize16, baseAsset.String()).Layout) }), ) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Right: u2, Left: u2}.Layout(gtx, pg.Theme.Label(values.TextSize16, "/").Layout) + layout.Rigid(func(gtx C) D { + return layout.Inset{Right: dp2, Left: dp2}.Layout(gtx, pg.Theme.Label(values.TextSize16, "/").Layout) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(func(gtx C) D { if quoteIcon == nil { return D{} } return quoteIcon.Layout20dp(gtx) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Right: u2, Left: u2}.Layout(gtx, pg.Theme.Label(values.TextSize16, quoteAsset.String()).Layout) + layout.Rigid(func(gtx C) D { + return layout.Inset{Right: dp2, Left: dp2}.Layout(gtx, pg.Theme.Label(values.TextSize16, quoteAsset.String()).Layout) }), ) }), @@ -262,6 +236,18 @@ func (pg *DEXMarketPage) marketDropdownListItem(baseAsset, quoteAsset libutils.A } } +func assetIcon(th *cryptomaterial.Theme, assetType libutils.AssetType) *cryptomaterial.Image { + switch assetType { + case libutils.DCRWalletAsset: + return th.Icons.DCR + case libutils.BTCWalletAsset: + return th.Icons.BTC + case libutils.LTCWalletAsset: + return th.Icons.LTC + } + return nil +} + // OnNavigatedFrom is called when the page is about to be removed from // the displayed window. This method should ideally be used to disable // features that are irrelevant when the page is NOT displayed. @@ -275,600 +261,437 @@ func (pg *DEXMarketPage) OnNavigatedFrom() {} // to be eventually drawn on screen. // Part of the load.Page interface. func (pg *DEXMarketPage) Layout(gtx C) D { - return pg.pageContentLayout(gtx) -} - -func (pg *DEXMarketPage) pageContentLayout(gtx C) D { pageContent := []func(gtx C) D{ - pg.priceAndVolumeDetail(), - func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Orientation: layout.Horizontal, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.W.Layout(gtx, pg.orderForm()) - }), - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { - return layout.E.Layout(gtx, pg.orderbook()) - }), - ) - }, + pg.priceAndVolumeDetail, + pg.orderFormAndOrderBook, pg.openOrdersAndHistory, } return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.MatchParent, + Width: gtx.Dp(values.AppWidth - 50 /* allow for left and right margin */), + Height: cryptomaterial.MatchParent, + Margin: layout.Inset{ + Bottom: values.MarginPadding30, + Right: dp10, + Left: dp10, + }, Direction: layout.Center, - Padding: layout.UniformInset(values.MarginPadding20), }.Layout2(gtx, func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: gtx.Dp(values.AppWidth - 40), - Height: cryptomaterial.MatchParent, - Margin: layout.Inset{ - Bottom: values.MarginPadding30, - Right: u10, - Left: u10, - }, - }.Layout2(gtx, func(gtx C) D { - return pg.Theme.List(pg.scrollContainer).Layout(gtx, 1, func(gtx layout.Context, index int) layout.Dimensions { - return layout.Stack{}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Top: 110}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return pg.pageContainer.Layout(gtx, len(pageContent), func(gtx C, i int) D { - return pageContent[i](gtx) - }) + return pg.Theme.List(pg.scrollContainer).Layout(gtx, 1, func(gtx C, index int) D { + return layout.Stack{}.Layout(gtx, + layout.Expanded(func(gtx C) D { + return layout.Inset{Top: 110}.Layout(gtx, func(gtx C) D { + l := &layout.List{Axis: vertical} + return l.Layout(gtx, len(pageContent), func(gtx C, i int) D { + return pageContent[i](gtx) }) - }), - layout.Expanded(pg.serverAndCurrencySelection()), - ) - }) + }) + }), + layout.Stacked(pg.serverAndCurrencySelection), + ) }) }) } -func (pg DEXMarketPage) addServerDisplay() func(gtx C) D { - return func(gtx cryptomaterial.C) cryptomaterial.D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.Theme.Separator().Layout(gtx) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Start}.Layout(gtx, +func (pg *DEXMarketPage) serverAndCurrencySelection(gtx C) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.MatchParent, + Height: gtx.Dp(100), + Background: pg.Theme.Color.Surface, + Padding: layout.UniformInset(dp16), + Border: cryptomaterial.Border{ + Radius: cryptomaterial.Radius(8), + }, + }.Layout(gtx, + layout.Flexed(0.5, func(gtx C) D { + return layout.Flex{Axis: vertical}.Layout(gtx, + layout.Rigid(pg.semiBoldLabelText(values.String(values.StrServer)).Layout), + layout.Rigid(func(gtx C) D { + pg.serverSelector.Background = &pg.Theme.Color.Surface + pg.serverSelector.BorderColor = &pg.Theme.Color.Gray5 + return layout.Inset{Top: dp2}.Layout(gtx, pg.serverSelector.Layout) + }), + ) + }), + layout.Flexed(0.5, func(gtx C) D { + return layout.Inset{Left: values.MarginPadding60}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: vertical, Alignment: layout.End}.Layout(gtx, + layout.Rigid(pg.semiBoldLabelText(values.String(values.StrCurrencyPair)).Layout), layout.Rigid(func(gtx C) D { - color := pg.Theme.Color.Primary - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Orientation: layout.Horizontal, - Padding: layout.UniformInset(u10), - Direction: layout.W, - Alignment: layout.Middle, - Clickable: pg.addServerBtn, - }.Layout(gtx, - layout.Rigid(func(gtx C) D { - icon := pg.Theme.Icons.ContentAdd - return icon.Layout(gtx, color) - }), - layout.Rigid(func(gtx C) D { - label := pg.Theme.Label(values.TextSize16, values.String(values.StrAddServer)) - label.Color = color - label.Font.Weight = font.SemiBold - return layout.Inset{Left: values.MarginPadding5}.Layout(gtx, label.Layout) - }), - ) + pg.marketSelector.Background = &pg.Theme.Color.Surface + pg.marketSelector.BorderColor = &pg.Theme.Color.Gray5 + return layout.Inset{Top: dp2}.Layout(gtx, pg.marketSelector.Layout) }), ) - }), - ) - } + }) + }), + ) } -func (pg *DEXMarketPage) serverAndCurrencySelection() func(gtx C) D { - return func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: gtx.Dp(100), - Background: pg.Theme.Color.Surface, - Padding: layout.UniformInset(u16), - Border: cryptomaterial.Border{ - Radius: cryptomaterial.Radius(8), - }, - }.Layout(gtx, - layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.semiBoldLabelText(gtx, values.String(values.StrServer)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Stack{}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Top: u2}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return pg.serverSelector.Layout(gtx, 0, false) - }) - }), - ) - }), - ) - }), - layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Left: values.MarginPadding60}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical, Alignment: layout.End}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.semiBoldLabelText(gtx, values.String(values.StrMarket)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Top: u2}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return pg.marketSelector.Layout(gtx, 0, false) - }) - }), - ) - }) - }), - ) + +func (pg *DEXMarketPage) priceAndVolumeDetail(gtx C) D { + // TODO: Fetch rate for the base asset from DEX server and exchange rate from rate source! + rate := 200.0 + tradeRate := 0.0034353 + var rateStr string + if rate == 0 { + rateStr = pg.Printer.Sprintf("%f", tradeRate) + } else { + rateStr = pg.Printer.Sprintf("%f (~ %s)", tradeRate, utils.FormatAsUSDString(pg.Printer, tradeRate*rate)) } + + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.MatchParent, + Height: cryptomaterial.WrapContent, + Padding: layout.UniformInset(16), + Margin: layout.Inset{Top: dp5, Bottom: dp5}, + Background: pg.Theme.Color.Surface, + Border: cryptomaterial.Border{ + Radius: cryptomaterial.Radius(8), + }, + }.Layout(gtx, + layout.Flexed(0.33, func(gtx C) D { + return pg.priceAndVolumeColume(gtx, + values.String(values.StrPrice), + pg.semiBoldLabelSize14(rateStr).Layout, + values.String(values.Str24hLow), + pg.Printer.Sprintf("%f", 0.0034353 /* TODO: use DEX server value */), + ) + }), + layout.Flexed(0.33, func(gtx C) D { + return pg.priceAndVolumeColume(gtx, + values.String(values.Str24hChange), + func(gtx C) D { + // TODO: Use real values. + priceChange := 0.0010353 + priceChangePercent := 0.18 + lb := pg.semiBoldLabelSize14(pg.Printer.Sprintf(`%f (%.2f`, priceChange, priceChangePercent) + "%)") + if priceChangePercent < 0 { + lb.Color = pg.Theme.Color.OrangeRipple + } else if priceChangePercent > 0 { + lb.Color = pg.Theme.Color.GreenText + } + return lb.Layout(gtx) + }, + values.StringF(values.Str24hVolume, "DCR" /* TODO: use market base asset symbol */), + pg.Printer.Sprintf("%f", 4400.0477380 /* TODO: use DEX server value */), + ) + }), + layout.Flexed(0.33, func(gtx C) D { + return pg.priceAndVolumeColume(gtx, + values.String(values.Str24hHigh), + pg.semiBoldLabelSize14(pg.Printer.Sprintf("%f", 0.0034353 /* TODO: USe DEX server price */)).Layout, + values.StringF(values.Str24hVolume, "BTC" /* TODO: use market quote asset*/), + pg.Printer.Sprintf("%f", 2.3445532 /* TODO: use DEX server value */), + ) + }), + ) } -func (pg *DEXMarketPage) priceAndVolumeDetail() func(gtx C) D { - return func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Padding: layout.UniformInset(16), - Margin: layout.Inset{Top: u5, Bottom: u5}, - Background: pg.Theme.Color.Surface, - Border: cryptomaterial.Border{ - Radius: cryptomaterial.Radius(8), - }, - }.Layout(gtx, - layout.Flexed(0.33, func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.WrapContent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Bottom: u20}, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.StrPrice)) +func (pg *DEXMarketPage) priceAndVolumeColume(gtx C, title1 string, body1 func(gtx C) D, title2, body2 string) D { + return layout.Flex{Axis: vertical}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.WrapContent, + Height: cryptomaterial.WrapContent, + Margin: layout.Inset{Bottom: dp20}, + Orientation: vertical, + }.Layout(gtx, + layout.Rigid(semiBoldLabelGrey3(pg.Theme, title1).Layout), + layout.Rigid(body1), + ) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: vertical}.Layout(gtx, + layout.Rigid(semiBoldLabelGrey3(pg.Theme, title2).Layout), + layout.Rigid(pg.semiBoldLabelSize14(body2).Layout), + ) + }), + ) +} + +func (pg *DEXMarketPage) semiBoldLabelSize14(txt string) cryptomaterial.Label { + lb := pg.Theme.Label(values.TextSize14, txt) + lb.Font.Weight = font.SemiBold + return lb +} + +func (pg *DEXMarketPage) orderFormAndOrderBook(gtx C) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.MatchParent, + Height: cryptomaterial.WrapContent, + Orientation: horizontal, + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.W.Layout(gtx, pg.orderForm) + }), + layout.Flexed(1, func(gtx C) D { + return layout.E.Layout(gtx, pg.orderbook) + }), + ) +} + +func (pg *DEXMarketPage) orderForm(gtx C) D { + return cryptomaterial.LinearLayout{ + Width: gtx.Dp(orderFormAndOrderBookWidth), + Height: gtx.Dp(orderFormAndOrderBookHeight), + Background: pg.Theme.Color.Surface, + Margin: layout.Inset{Top: dp5, Bottom: dp5}, + Padding: layout.UniformInset(dp16), + Direction: layout.E, + Border: cryptomaterial.Border{ + Radius: cryptomaterial.Radius(8), + }, + Orientation: vertical, + }.Layout2(gtx, func(gtx C) D { + return layout.Stack{Alignment: layout.NW}.Layout(gtx, + layout.Expanded(func(gtx C) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.MatchParent, + Height: cryptomaterial.WrapContent, + Margin: layout.Inset{Top: values.MarginPadding70}, + Orientation: vertical, + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return orderFormRow(gtx, vertical, []layout.FlexChild{ + layout.Rigid(pg.semiBoldLabelText(values.String(values.StrPrice)).Layout), + layout.Rigid(pg.priceEditor.Layout), + }) + }), + layout.Rigid(func(gtx C) D { + return orderFormRow(gtx, vertical, []layout.FlexChild{ + layout.Rigid(func(gtx C) D { + return layout.Inset{Bottom: dp5}.Layout(gtx, func(gtx C) D { + var labelText string + if pg.switchLotsOrAmount.IsChecked() { + labelText = values.String(values.StrAmount) + } else { + labelText = values.String(values.StrLots) + } + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(pg.semiBoldLabelText(labelText).Layout), + layout.Flexed(1, func(gtx C) D { + return layout.E.Layout(gtx, pg.switchLotsOrAmount.Layout) + }), + ) + }) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - // TODO: Fetch rate for the base asset from DEX server and exchange rate from rate source! - rate := 200.0 - tradeRate := 0.0034353 - var lb cryptomaterial.Label - if rate == 0 { - lb = pg.Theme.Label(values.TextSize14, pg.Printer.Sprintf("%f", tradeRate)) + layout.Rigid(pg.lotsOrAmountEditor.Layout), + layout.Rigid(func(gtx C) D { + // TODO: Calculate max buy or max lot + // depending on user balance of buy or sell + // asset and use real values below. + var maxStr string + if pg.switchLotsOrAmount.IsChecked() { // Amount + if pg.toggleBuyAndSellBtn.SelectedIndex() == buyBtnStringIndex { + maxStr = values.StringF(values.StrMaxBuy, 10.089382, "DCR") + } else { + maxStr = values.StringF(values.StrMaxSell, 10.008245, "DCR") + } } else { - lb = pg.Theme.Label(values.TextSize14, pg.Printer.Sprintf("%f (~ %s)", tradeRate, utils.FormatAsUSDString(pg.Printer, tradeRate*rate))) + maxStr = values.StringF(values.StrMaxLots, 10) } - lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + return layout.E.Layout(gtx, pg.Theme.Label(values.TextSize12, maxStr).Layout) + }), + ) }), - ) + }) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.Str24hLow)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - lb := pg.Theme.Label(values.TextSize14, pg.Printer.Sprintf("%f", 0.0034353 /* TODO: use DEX server value */)) - lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) + layout.Rigid(func(gtx C) D { + return orderFormRow(gtx, vertical, []layout.FlexChild{ + layout.Rigid(func(gtx C) D { + return layout.Inset{Bottom: dp5}.Layout(gtx, pg.semiBoldLabelText(values.String(values.StrTotal)).Layout) }), + layout.Rigid(pg.totalEditor.Layout), + }) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(semiBoldLabelGrey3(pg.Theme, values.String(values.StrEstimatedFee)).Layout), + layout.Rigid(pg.Theme.Label(values.TextSize16, pg.Printer.Sprintf("%f %s", 0.0023434, "DCR" /* TODO: use real value */)).Layout), ) }), - ) - }), - layout.Flexed(0.33, func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.WrapContent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Bottom: u20}, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.Str24hChange)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - priceChange := 0.0010353 - priceChangePercent := 0.18 - lb := pg.Theme.Label(values.TextSize14, pg.Printer.Sprintf(`%f (%.2f`, priceChange, priceChangePercent)+"%)") - lb.Font.Weight = font.SemiBold - if priceChangePercent < 0 { - lb.Color = pg.Theme.Color.OrangeRipple - } else if priceChangePercent > 0 { - lb.Color = pg.Theme.Color.GreenText - } - return lb.Layout(gtx) - }), + layout.Rigid(func(gtx C) D { + return orderFormRow(gtx, horizontal, []layout.FlexChild{ + layout.Rigid(pg.immediateOrderCheckbox.Layout), + layout.Rigid(func(gtx C) D { + return layout.Inset{Top: dp10, Left: dp2}.Layout(gtx, func(gtx C) D { + return pg.immediateOrderInfoBtn.Layout(gtx, pg.Theme.Icons.InfoAction.Layout16dp) + }) + })}, ) }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.StringF(values.Str24hVolume, "DCR" /* TODO: use market base asset symbol */)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - lb := pg.Theme.Label(values.TextSize14, pg.Printer.Sprintf("%f", 4400.0477380 /* TODO: use DEX server value */)) - lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) - }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: horizontal, Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(1, pg.createOrderBtn.Layout), ) }), ) }), - layout.Flexed(0.33, func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.WrapContent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Bottom: u20}, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.Str24hHigh)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - lb := pg.Theme.Label(values.TextSize14, pg.Printer.Sprintf("%f", 0.0034353 /* TODO: USe DEX server price */)) - lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) - }), - ) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.StringF(values.Str24hVolume, "BTC" /* TODO: use market quote asset*/)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - lb := pg.Theme.Label(values.TextSize14, pg.Printer.Sprintf("%f", 2.3445532 /* TODO: use DEX server value */)) - lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) - }), - ) + layout.Stacked(func(gtx C) D { + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(pg.toggleBuyAndSellBtn.Layout), + layout.Flexed(1, func(gtx C) D { + pg.orderTypesDropdown.Background = &pg.Theme.Color.Surface + return layout.Inset{Bottom: dp5, Top: dp5}.Layout(gtx, pg.orderTypesDropdown.Layout) }), ) }), ) - } -} - -func (pg *DEXMarketPage) orderForm() func(gtx C) D { - return func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: gtx.Dp(orderFormAndOrderBookWidth), - Height: gtx.Dp(orderFormAndOrderBookHeight), - Background: pg.Theme.Color.Surface, - Margin: layout.Inset{Top: u5, Bottom: u5}, - Padding: layout.UniformInset(u16), - Direction: layout.E, - Border: cryptomaterial.Border{ - Radius: cryptomaterial.Radius(8), - }, - Orientation: layout.Vertical, - }.Layout2(gtx, func(gtx layout.Context) layout.Dimensions { - return layout.Stack{Alignment: layout.NW}.Layout(gtx, - layout.Expanded(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Top: 70}, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Bottom: u10, Top: u10}, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.semiBoldLabelText(gtx, values.String(values.StrPrice)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.priceEditor.Layout(gtx) - }), - ) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Bottom: u10, Top: u10}, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Bottom: u5}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - var labelText string - if pg.switchLotsOrAmount.IsChecked() { - labelText = values.String(values.StrAmount) - } else { - labelText = values.String(values.StrLots) - } - return pg.semiBoldLabelText(gtx, labelText) - }), - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { - return layout.E.Layout(gtx, pg.switchLotsOrAmount.Layout) - }), - ) - }) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.lotsAmountEditor.Layout(gtx) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { - // TODO: Calculate max buy or max lot - // depending on user balance of buy or sell - // asset and use real values below. - var maxStr string - if pg.switchLotsOrAmount.IsChecked() { // Amount - if pg.buyOrder { - maxStr = values.StringF(values.StrMaxBuy, 10.089382, "DCR") - } else { - maxStr = values.StringF(values.StrMaxSell, 10.008245, "DCR") - } - } else { - maxStr = values.StringF(values.StrMaxLots, 10) - } - return layout.E.Layout(gtx, pg.Theme.Label(values.TextSize12, maxStr).Layout) - }), - ) - }), - ) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Bottom: u10, Top: u10}, - Orientation: layout.Vertical, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Bottom: u5}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return pg.semiBoldLabelText(gtx, values.String(values.StrTotal)) - }) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.totalEditor.Layout(gtx) - }), - ) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return semiBoldLabelGrey3(pg.Theme, gtx, values.String(values.StrEstimatedFee)) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.Theme.Label(values.TextSize16, pg.Printer.Sprintf("%f %s", 0.0023434, "DCR" /* TODO: use real value */)).Layout(gtx) - }), - ) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Margin: layout.Inset{Bottom: u10, Top: u10}, - Orientation: layout.Horizontal, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.immediateOrder.Layout(gtx) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.WrapContent, - Height: cryptomaterial.WrapContent, - Clickable: pg.immediateMoreInfo, - Padding: layout.Inset{Top: u10, Left: u2}, - }.Layout2(gtx, pg.Theme.Icons.InfoAction.Layout16dp) - }), - ) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Flexed(1, pg.buySellBtn.Layout), - ) - }), - ) - }), - layout.Stacked(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.toggleBuyAndSellBtn.Layout(gtx) - }), - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { - return layout.Inset{Bottom: u10, Top: u10}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return pg.orderTypesDropdown.Layout(gtx, 0, true) - }) - }), - ) - }), - ) - }) - } + }) } -type orderBook struct { - price float64 - amount float64 - epoch uint64 +func orderFormRow(gtx C, orientation layout.Axis, children []layout.FlexChild) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.MatchParent, + Height: cryptomaterial.WrapContent, + Margin: layout.Inset{Bottom: dp10, Top: dp10}, + Orientation: orientation, + }.Layout(gtx, children...) } -func (pg *DEXMarketPage) orderbook() func(gtx C) D { +func (pg *DEXMarketPage) orderbook(gtx C) D { // TODO: Use real values - var mockOrderBook []*orderBook - for i := 0; i < maxOrderDisplayedInOrderBook; i++ { - mockOrderBook = append(mockOrderBook, &orderBook{ - price: 0.001268, - amount: 1.003422, - epoch: 34534566, - }) - } - return func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: gtx.Dp(orderFormAndOrderBookWidth), - Height: gtx.Dp(orderFormAndOrderBookHeight), // TODO... - Background: pg.Theme.Color.Surface, - Margin: layout.Inset{Top: u5, Bottom: u5}, - Padding: layout.UniformInset(u16), - Border: cryptomaterial.Border{ - Radius: cryptomaterial.Radius(8), - }, - Orientation: layout.Vertical, - Direction: layout.Center, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.semiBoldLabelText(gtx, values.String(values.StrOrderBooks)) - }), - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { - return layout.E.Layout(gtx, pg.seeFullOrderBookBtn.Layout) - }), + makeOrderBookBuyOrSell := func(sell bool) []layout.FlexChild { + var mockOrderBook []layout.FlexChild + for i := 0; i < maxOrderDisplayedInOrderBook; i++ { + ord := &order{ + isSell: sell, + price: 0.001268, + amount: 1.003422, + epoch: 34534566, + } + + mockOrderBook = append(mockOrderBook, layout.Rigid(func(gtx C) D { + return pg.orderBookRow(gtx, + textBuyOrSell(pg.Theme, ord.isSell, pg.Printer.Sprintf("%f", ord.price)), + pg.Theme.Body2(pg.Printer.Sprintf("%f", ord.amount)), + pg.Theme.Body2(fmt.Sprintf("%d", ord.epoch)), ) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Orientation: layout.Horizontal, - Direction: layout.Center, - Spacing: layout.SpaceBetween, - Margin: layout.Inset{Top: u10, Bottom: u10}, - }.Layout(gtx, - layout.Flexed(0.33, func(gtx layout.Context) layout.Dimensions { - return semiBoldGray3Size14(pg.Theme, gtx, values.StringF(values.StrAssetPrice, "BTC")) - }), // Price - layout.Flexed(0.33, func(gtx layout.Context) layout.Dimensions { - return semiBoldGray3Size14(pg.Theme, gtx, values.StringF(values.StrAssetAmount, "DCR")) - }), // Amount - layout.Flexed(0.33, func(gtx layout.Context) layout.Dimensions { - return semiBoldGray3Size14(pg.Theme, gtx, values.String(values.StrEpoch)) - }), // Epoch + })) + } + return mockOrderBook + } + + return cryptomaterial.LinearLayout{ + Width: gtx.Dp(orderFormAndOrderBookWidth), + Height: gtx.Dp(orderFormAndOrderBookHeight), + Background: pg.Theme.Color.Surface, + Margin: layout.Inset{Top: dp5, Bottom: dp5}, + Padding: layout.UniformInset(dp16), + Border: cryptomaterial.Border{Radius: cryptomaterial.Radius(8)}, + Orientation: vertical, + Direction: layout.Center, + }.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(pg.semiBoldLabelText(values.String(values.StrOrderBooks)).Layout), + layout.Flexed(1, func(gtx C) D { + return layout.E.Layout(gtx, pg.seeFullOrderBookBtn.Layout) + }), + ) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{Top: dp10}.Layout(gtx, func(gtx C) D { + return pg.orderBookRow(gtx, + semiBoldGray3Size14(pg.Theme, values.StringF(values.StrAssetPrice, "BTC")), + semiBoldGray3Size14(pg.Theme, values.StringF(values.StrAssetAmount, "DCR")), + semiBoldGray3Size14(pg.Theme, values.String(values.StrEpoch)), ) - }), - layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions { - return pg.sellOrdersContainer.Layout(gtx, len(mockOrderBook), func(gtx layout.Context, index int) layout.Dimensions { - sell := true - ord := mockOrderBook[index] - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Orientation: layout.Horizontal, - Spacing: layout.SpaceBetween, - Alignment: layout.Middle, - Direction: layout.Center, - }.Layout(gtx, - layout.Flexed(0.33, colorOrderBookText(pg.Theme, &sell, pg.Printer.Sprintf("%f", ord.price))), // Price - layout.Flexed(0.33, colorOrderBookText(pg.Theme, nil, pg.Printer.Sprintf("%f", ord.amount))), // Amount - layout.Flexed(0.33, colorOrderBookText(pg.Theme, nil, fmt.Sprintf("%d", ord.epoch))), // Epoch - ) - }) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.MatchParent, - Height: cryptomaterial.WrapContent, - Orientation: layout.Horizontal, - Margin: layout.Inset{ - Bottom: u5, - Top: u5, - }, - Direction: layout.Center, - }.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Stack{Alignment: layout.Center}.Layout(gtx, - layout.Stacked(func(gtx C) D { - sep := pg.Theme.Separator() - return layout.Inset{Top: u5}.Layout(gtx, sep.Layout) - }), - layout.Expanded(func(gtx C) D { - return cryptomaterial.LinearLayout{ - Width: cryptomaterial.WrapContent, - Height: cryptomaterial.WrapContent, - Background: pg.Theme.Color.Gray3, - Padding: layout.Inset{ - Top: u5, - Bottom: u5, - Right: u16, - Left: u16, - }, - Border: cryptomaterial.Border{ - Radius: cryptomaterial.Radius(16), - }, - Direction: layout.Center, - Orientation: layout.Horizontal, - }.Layout2(gtx, func(gtx layout.Context) layout.Dimensions { - // TODO: Fetch rate for the base asset from DEX server and exchange rate from rate source! - fiatRate := 34000.00 - price := 0.0222445 - lb := pg.Theme.Label(values.TextSize16, pg.Printer.Sprintf("%f %s", price, "DCR")) - if fiatRate > 0 { - lb = pg.Theme.Label(values.TextSize16, pg.Printer.Sprintf("%f %s (~ %s)", price, "BTC", utils.FormatAsUSDString(pg.Printer, fiatRate*price))) - } - lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) - }) - }), - ) + }) + }), + layout.Flexed(0.5, func(gtx C) D { + return layout.Flex{Axis: vertical}.Layout(gtx, makeOrderBookBuyOrSell(true)...) + }), + layout.Rigid(func(gtx C) D { + return layout.Inset{Top: dp5, Bottom: dp10}.Layout(gtx, func(gtx C) D { + return layout.Stack{Alignment: layout.Center}.Layout(gtx, + layout.Stacked(pg.Theme.Separator().Layout), + layout.Expanded(func(gtx C) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.WrapContent, + Height: cryptomaterial.WrapContent, + Background: pg.Theme.Color.Gray3, + Padding: layout.Inset{Top: dp5, Bottom: dp5, Right: dp16, Left: dp16}, + Border: cryptomaterial.Border{Radius: cryptomaterial.Radius(16)}, + Direction: layout.Center, + Orientation: horizontal, + }.Layout2(gtx, func(gtx C) D { + // TODO: Fetch rate for the base asset from DEX server and exchange rate from rate source! + fiatRate := 34000.00 + price := 0.0222445 + lb := pg.Theme.Label(values.TextSize16, pg.Printer.Sprintf("%f %s", price, "DCR")) + if fiatRate > 0 { + lb = pg.Theme.Label(values.TextSize16, pg.Printer.Sprintf("%f %s (~ %s)", price, "BTC", utils.FormatAsUSDString(pg.Printer, fiatRate*price))) + } + lb.Font.Weight = font.SemiBold + return lb.Layout(gtx) + }) }), ) - }), - layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions { - return pg.buyOrdersContainer.Layout(gtx, len(mockOrderBook), func(gtx layout.Context, index int) layout.Dimensions { - ord := mockOrderBook[index] - sell := false - return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween}.Layout(gtx, - layout.Flexed(0.33, colorOrderBookText(pg.Theme, &sell, pg.Printer.Sprintf("%f", ord.price))), // Price - layout.Flexed(0.33, colorOrderBookText(pg.Theme, nil, pg.Printer.Sprintf("%f", ord.amount))), // Amount - layout.Flexed(0.33, colorOrderBookText(pg.Theme, nil, fmt.Sprintf("%d", ord.epoch))), // Epoch - ) - }) - }), - ) - } + }) + }), + layout.Flexed(0.5, func(gtx C) D { + return layout.Flex{Axis: vertical}.Layout(gtx, makeOrderBookBuyOrSell(false)...) + }), + ) +} + +func (pg *DEXMarketPage) semiBoldLabelText(title string) cryptomaterial.Label { + lb := pg.Theme.Label(values.TextSize16, title) + lb.Font.Weight = font.SemiBold + lb.Color = pg.Theme.Color.Text + return lb +} + +func (pg *DEXMarketPage) orderBookRow(gtx C, priceColumn, amountColumn, epochColumn cryptomaterial.Label) D { + return cryptomaterial.LinearLayout{ + Width: cryptomaterial.MatchParent, + Height: cryptomaterial.WrapContent, + Margin: layout.Inset{Bottom: values.MarginPadding9}, + Spacing: layout.SpaceBetween, + }.Layout(gtx, + layout.Flexed(0.33, priceColumn.Layout), // Price + layout.Flexed(0.33, func(gtx C) D { + return layout.E.Layout(gtx, amountColumn.Layout) + }), // Amount + layout.Flexed(0.33, func(gtx C) D { + return layout.E.Layout(gtx, epochColumn.Layout) + }), // Epoch + ) } -func colorOrderBookText(th *cryptomaterial.Theme, sell *bool, txt string) func(gtx C) D { +func textBuyOrSell(th *cryptomaterial.Theme, sell bool, txt string) cryptomaterial.Label { lb := th.Body2(txt) - if sell != nil { - if *sell { - lb.Color = th.Color.OrangeRipple - } else { - lb.Color = th.Color.Green500 - } - } - return func(gtx C) D { - return layout.Inset{Bottom: u10}.Layout(gtx, lb.Layout) + if sell { + lb.Color = th.Color.OrangeRipple + } else { + lb.Color = th.Color.Green500 } + return lb } type order struct { ordType string + isSell bool market string age string price float64 amount float64 filled float64 settled float64 + epoch uint64 + status string } func (pg *DEXMarketPage) openOrdersAndHistory(gtx C) D { - sectionWidth := values.AppWidth - values.MarginPadding80 - columnWidth := sectionWidth / 7 + headers := []string{values.String(values.StrType), values.String(values.StrPair), values.String(values.StrAge), values.StringF(values.StrAssetPrice, "BTC"), values.StringF(values.StrAssetAmount, "DCR"), values.String(values.StrFilled), values.String(values.StrSettled), values.String(values.StrStatus)} + + sectionWidth := values.AppWidth + columnWidth := sectionWidth / unit.Dp(len(headers)) sepWidth := sectionWidth - values.MarginPadding60 - headers := []string{values.String(values.StrType), values.String(values.StrPair), values.String(values.StrAge), values.StringF(values.StrAssetPrice, "BTC"), values.StringF(values.StrAssetAmount, "DCR"), values.String(values.StrFilled), values.String(values.StrSettled)} // TODO: Use real values var headersFn []layout.FlexChild for _, header := range headers { @@ -880,12 +703,14 @@ func (pg *DEXMarketPage) openOrdersAndHistory(gtx C) D { for i := 0; i < 10; i++ { ord := &order{ ordType: "Sell", + isSell: true, market: "DCR/BTC", age: "23h 11m", price: 0.0023456, amount: 23.00457, filled: 100.0, settled: 70.5, + status: "booked", } orders = append(orders, ord) @@ -893,56 +718,89 @@ func (pg *DEXMarketPage) openOrdersAndHistory(gtx C) D { return cryptomaterial.LinearLayout{ Width: cryptomaterial.MatchParent, - Height: gtx.Dp(400), // TODO... + Height: gtx.Dp(400), Background: pg.Theme.Color.Surface, - Margin: layout.Inset{Top: u5, Bottom: 30}, - Padding: layout.UniformInset(u10), + Margin: layout.Inset{Top: dp5, Bottom: 30}, + Padding: layout.UniformInset(dp10), Border: cryptomaterial.Border{ Radius: cryptomaterial.Radius(8), }, - Orientation: layout.Vertical, + Orientation: vertical, }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return pg.toggleOpenAndHistoryOrderBtn.Layout(gtx) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween, Alignment: layout.Middle}.Layout(gtx, headersFn...) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - if len(orders) == 0 { - return components.LayoutNoOrderHistoryWithMsg(gtx, pg.Load, false, pg.noOrderMsg) + layout.Rigid(func(gtx C) D { + gr2 := pg.Theme.Color.Gray2 + pg.openOrdersBtn.HighlightColor, pg.orderHistoryBtn.HighlightColor = gr2, gr2 + if pg.openOrdersDisplayed { + pg.openOrdersBtn.Background = gr2 + pg.openOrdersBtn.Color = pg.Theme.Color.GrayText1 + pg.orderHistoryBtn.Background = pg.Theme.Color.SurfaceHighlight + pg.orderHistoryBtn.Color = pg.Theme.Color.Text + } else { + pg.openOrdersBtn.Background = pg.Theme.Color.SurfaceHighlight + pg.openOrdersBtn.Color = pg.Theme.Color.Text + pg.orderHistoryBtn.Background = gr2 + pg.orderHistoryBtn.Color = pg.Theme.Color.GrayText1 } - - return pg.Theme.List(pg.openOrdersContainer).Layout(gtx, len(orders), func(gtx layout.Context, index int) layout.Dimensions { - ord := orders[index] - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(func(gtx C) D { - if index == 0 { - sep := pg.Theme.Separator() - sep.Width = gtx.Dp(sepWidth) - return layout.Center.Layout(gtx, sep.Layout) - } - return D{} - }), + return layout.Flex{Axis: horizontal}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Inset{Left: dp5, Right: dp10}.Layout(gtx, pg.openOrdersBtn.Layout) + }), + layout.Rigid(pg.orderHistoryBtn.Layout), + ) + }), + layout.Rigid(func(gtx C) D { + return pg.Theme.List(pg.ordersTableHorizontalScroll).Layout(gtx, 1, func(gtx C, index int) D { + gtx.Constraints.Max.X = gtx.Dp(sectionWidth) + gtx.Constraints.Min.X = gtx.Constraints.Max.X + return layout.Flex{Axis: vertical, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { - return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween, Alignment: layout.Middle}.Layout(gtx, - pg.orderColumn(false, ord.ordType, columnWidth), - pg.orderColumn(false, ord.market, columnWidth), - pg.orderColumn(false, ord.age, columnWidth), - pg.orderColumn(false, pg.Printer.Sprintf("%f", ord.price), columnWidth), - pg.orderColumn(false, pg.Printer.Sprintf("%f", ord.amount), columnWidth), - pg.orderColumn(false, pg.Printer.Sprintf("%.1f", ord.filled), columnWidth), - pg.orderColumn(false, pg.Printer.Sprintf("%.1f", ord.price), columnWidth), - ) + return layout.Flex{Axis: horizontal, Spacing: layout.SpaceBetween, Alignment: layout.Middle}.Layout(gtx, headersFn...) }), layout.Rigid(func(gtx C) D { - // No divider for last row - if index == len(orders)-1 { - return layout.Dimensions{} + if len(orders) == 0 { + var noOrderMsg string + if pg.openOrdersDisplayed { // TODO: Fetch open orders + noOrderMsg = values.String(values.StrNoOpenOrdersMsg) + } else { // TODO: Fetch History orders + noOrderMsg = values.String(values.StrNoTradeHistoryMsg) + } + return components.LayoutNoOrderHistoryWithMsg(gtx, pg.Load, false, noOrderMsg) } - sep := pg.Theme.Separator() - sep.Width = gtx.Dp(sepWidth) - return layout.Center.Layout(gtx, sep.Layout) + + return pg.Theme.List(pg.openOrdersAndOrderHistoryContainer).Layout(gtx, len(orders), func(gtx C, index int) D { + ord := orders[index] + return layout.Flex{Axis: vertical}.Layout(gtx, + layout.Rigid(func(gtx C) D { + if index == 0 { + sep := pg.Theme.Separator() + sep.Width = gtx.Dp(sepWidth) + return layout.Center.Layout(gtx, sep.Layout) + } + return D{} + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{Axis: horizontal, Spacing: layout.SpaceBetween, Alignment: layout.Middle}.Layout(gtx, + pg.orderColumn(false, ord.ordType, columnWidth), + pg.orderColumn(false, ord.market, columnWidth), + pg.orderColumn(false, ord.age, columnWidth), + pg.orderColumn(false, pg.Printer.Sprintf("%f", ord.price), columnWidth), + pg.orderColumn(false, pg.Printer.Sprintf("%f", ord.amount), columnWidth), + pg.orderColumn(false, pg.Printer.Sprintf("%.1f", ord.filled), columnWidth), + pg.orderColumn(false, pg.Printer.Sprintf("%.1f", ord.price), columnWidth), + pg.orderColumn(false, values.String(ord.status), columnWidth), + ) + }), + layout.Rigid(func(gtx C) D { + // No divider for last row + if index == len(orders)-1 { + return D{} + } + sep := pg.Theme.Separator() + sep.Width = gtx.Dp(sepWidth) + return layout.Center.Layout(gtx, sep.Layout) + }), + ) + }) }), ) }) @@ -950,25 +808,25 @@ func (pg *DEXMarketPage) openOrdersAndHistory(gtx C) D { ) } -func semiBoldGray3Size14(th *cryptomaterial.Theme, gtx C, text string) D { +func semiBoldGray3Size14(th *cryptomaterial.Theme, text string) cryptomaterial.Label { lb := th.Label(values.TextSize14, text) lb.Color = th.Color.GrayText3 lb.Font.Weight = font.SemiBold - return lb.Layout(gtx) + return lb } func (pg *DEXMarketPage) orderColumn(header bool, txt string, columnWidth unit.Dp) layout.FlexChild { - return layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Rigid(func(gtx C) D { return cryptomaterial.LinearLayout{ Width: gtx.Dp(columnWidth), Height: cryptomaterial.WrapContent, - Orientation: layout.Horizontal, + Orientation: horizontal, Alignment: layout.Middle, - Padding: layout.Inset{Top: u16, Bottom: u16}, + Padding: layout.Inset{Top: dp16, Bottom: dp16}, Direction: layout.Center, - }.Layout2(gtx, func(gtx layout.Context) layout.Dimensions { + }.Layout2(gtx, func(gtx C) D { if header { - return semiBoldGray3Size14(pg.Theme, gtx, txt) + return semiBoldGray3Size14(pg.Theme, txt).Layout(gtx) } lb := pg.Theme.Body2(txt) @@ -978,6 +836,20 @@ func (pg *DEXMarketPage) orderColumn(header bool, txt string, columnWidth unit.D }) } +func (pg *DEXMarketPage) setBuyOrSell() { + if pg.toggleBuyAndSellBtn.SelectedIndex() == buyBtnStringIndex { // Buy + pg.createOrderBtn.Text = values.String(values.StrBuy) + pg.createOrderBtn.Background = pg.Theme.Color.Green500 + pg.createOrderBtn.HighlightColor = pg.Theme.Color.Success + return + } + + // Sell + pg.createOrderBtn.Text = values.String(values.StrSell) + pg.createOrderBtn.Background = pg.Theme.Color.Orange + pg.createOrderBtn.HighlightColor = pg.Theme.Color.OrangeRipple +} + // HandleUserInteractions is called just before Layout() to determine if any // user interaction recently occurred on the page and may be used to update the // page's UI components shortly before they are displayed. @@ -985,27 +857,44 @@ func (pg *DEXMarketPage) orderColumn(header bool, txt string, columnWidth unit.D func (pg *DEXMarketPage) HandleUserInteractions() { if pg.serverSelector.Changed() { // TODO: Update the order form with required lots. - fmt.Println("New sever selected: ", pg.serverSelector.Selected()) + log.Info("New sever selected: ", pg.serverSelector.Selected()) } for pg.addServerBtn.Clicked() { // TODO: Display modal to add server - fmt.Println("Add server clicked") + log.Info("Add server clicked") + } + + for pg.openOrdersBtn.Clicked() { + pg.openOrdersDisplayed = true + // TODO: Fetch orders and set pg.orders? + } + + for pg.orderHistoryBtn.Clicked() { + pg.openOrdersDisplayed = false + // TODO: Fetch orders and set pg.orders? } if pg.orderTypesDropdown.Changed() { - pg.isMarketOrder = pg.orderTypesDropdown.SelectedIndex() != limitOrderIndex + // TODO: handle order type change + log.Info("Order type has been changed") + } + + if pg.toggleBuyAndSellBtn.Changed() { + pg.setBuyOrSell() } for pg.seeFullOrderBookBtn.Clicked() { // TODO: display full order book - fmt.Println("Display full order book") + log.Info("Display full order book") } - for pg.immediateMoreInfo.Clicked() { + for pg.immediateOrderInfoBtn.Clicked() { infoModal := modal.NewCustomModal(pg.Load). Title(values.String(values.StrImmediateOrder)). - SetupWithTemplate(values.String(values.StrImmediateExplanation)). + UseCustomWidget(func(gtx layout.Context) layout.Dimensions { + return pg.Theme.Body2(values.String(values.StrImmediateExplanation)).Layout(gtx) + }). SetCancelable(true). SetContentAlignment(layout.W, layout.W, layout.Center). SetPositiveButtonText(values.String(values.StrOk)) @@ -1013,47 +902,38 @@ func (pg *DEXMarketPage) HandleUserInteractions() { } // editor event listener - isSubmit, isChanged := cryptomaterial.HandleEditorEvents(pg.priceEditor.Editor, pg.lotsAmountEditor.Editor) + isSubmit, isChanged := cryptomaterial.HandleEditorEvents(pg.priceEditor.Editor, pg.lotsOrAmountEditor.Editor) if isChanged { // reset error when any editor is modified pg.priceEditor.SetError("") - pg.lotsAmountEditor.SetError("") + pg.lotsOrAmountEditor.SetError("") price := pg.priceEditor.Editor.Text() if price != "" { if price, err := strconv.ParseFloat(price, 64); err != nil || price <= 0 { pg.priceEditor.SetError(values.String(values.StrInvalidAmount)) - } else { - // TODO: calculate and update total } + // TODO: calculate and update total } - lotsOrAmt := pg.lotsAmountEditor.Editor.Text() + lotsOrAmt := pg.lotsOrAmountEditor.Editor.Text() if lotsOrAmt != "" { if pg.switchLotsOrAmount.IsChecked() { // Amount if amt, err := strconv.ParseFloat(lotsOrAmt, 64); err != nil || amt <= 0 { - pg.lotsAmountEditor.SetError(values.String(values.StrInvalidAmount)) - } else { - // TODO: calculate and update total + pg.lotsOrAmountEditor.SetError(values.String(values.StrInvalidAmount)) } + // TODO: calculate and update total } else { if lot, err := strconv.Atoi(lotsOrAmt); err != nil || lot <= 0 { - pg.lotsAmountEditor.SetError(values.String(values.StrInvalidLot)) - } else { - // TODO: calculate and update total + pg.lotsOrAmountEditor.SetError(values.String(values.StrInvalidLot)) } + // TODO: calculate and update total } } } if isSubmit { // TODO: Validate form + log.Infof("Order form has been submitted..") } } - -func (pg *DEXMarketPage) semiBoldLabelText(gtx C, title string) D { - lb := pg.Theme.Label(values.TextSize16, title) - lb.Font.Weight = font.SemiBold - lb.Color = pg.Theme.Color.Text - return lb.Layout(gtx) -} diff --git a/ui/page/exchange/order_history_page.go b/ui/page/exchange/order_history_page.go index 0f4b637f5..843332016 100644 --- a/ui/page/exchange/order_history_page.go +++ b/ui/page/exchange/order_history_page.go @@ -56,13 +56,13 @@ func NewOrderHistoryPage(l *load.Load) *OrderHistoryPage { pg.ordersList = pg.Theme.NewClickableList(layout.Vertical) pg.ordersList.IsShadowEnabled = true - pg.statusDropdown = l.Theme.DropDown([]cryptomaterial.DropDownItem{ + pg.statusDropdown = l.Theme.DropdownWithCustomPos([]cryptomaterial.DropDownItem{ {Text: api.OrderStatusWaitingForDeposit.String()}, {Text: api.OrderStatusDepositReceived.String()}, {Text: api.OrderStatusNew.String()}, {Text: api.OrderStatusCompleted.String()}, {Text: api.OrderStatusExpired.String()}, - }, values.OrderStatusDropdownGroup, 0) + }, values.OrderStatusDropdownGroup, 0, 10, true) return pg } @@ -202,9 +202,7 @@ func (pg *OrderHistoryPage) layout(gtx C) D { Top: values.MarginPadding60, }.Layout(gtx, pg.layoutHistory) }), - layout.Expanded(func(gtx C) D { - return pg.statusDropdown.Layout(gtx, 10, true) - }), + layout.Expanded(pg.statusDropdown.Layout), ) }) }), diff --git a/ui/page/governance/consensus_page.go b/ui/page/governance/consensus_page.go index 07c1428cb..15ae09d37 100644 --- a/ui/page/governance/consensus_page.go +++ b/ui/page/governance/consensus_page.go @@ -73,14 +73,20 @@ func NewConsensusPage(l *load.Load) *ConsensusPage { pg.infoButton.Size = values.MarginPadding20 pg.navigateToSettingsBtn = pg.Theme.Button(values.StringF(values.StrEnableAPI, values.String(values.StrGovernance))) - pg.statusDropDown = l.Theme.DropDown([]cryptomaterial.DropDownItem{ + pg.statusDropDown = l.Theme.DropdownWithCustomPos([]cryptomaterial.DropDownItem{ {Text: values.String(values.StrAll)}, {Text: values.String(values.StrUpcoming)}, {Text: values.String(values.StrInProgress)}, {Text: values.String(values.StrFailed)}, {Text: values.String(values.StrLockedIn)}, {Text: values.String(values.StrFinished)}, - }, values.ConsensusDropdownGroup, 0) + }, values.ConsensusDropdownGroup, 0, 10, true) + + if pg.statusDropDown.Reversed() { + pg.statusDropDown.ExpandedLayoutInset.Right = values.DP55 + } else { + pg.statusDropDown.ExpandedLayoutInset.Left = values.DP55 + } return pg } @@ -312,7 +318,12 @@ func (pg *ConsensusPage) layoutDesktop(gtx layout.Context) layout.Dimensions { }.Layout(gtx, pg.layoutContent) }), layout.Expanded(func(gtx C) D { - return pg.statusDropDown.Layout(gtx, 10, true) + if pg.statusDropDown.Reversed() { + pg.statusDropDown.ExpandedLayoutInset.Right = values.MarginPadding10 + } else { + pg.statusDropDown.ExpandedLayoutInset.Left = values.MarginPadding10 + } + return pg.statusDropDown.Layout(gtx) }), ) }) @@ -354,9 +365,7 @@ func (pg *ConsensusPage) layoutMobile(gtx layout.Context) layout.Dimensions { }) }) }), - layout.Expanded(func(gtx C) D { - return pg.statusDropDown.Layout(gtx, 55, true) - }), + layout.Expanded(pg.statusDropDown.Layout), ) }) }), diff --git a/ui/page/governance/proposals_page.go b/ui/page/governance/proposals_page.go index 31eccdc18..238276945 100644 --- a/ui/page/governance/proposals_page.go +++ b/ui/page/governance/proposals_page.go @@ -74,13 +74,19 @@ func NewProposalsPage(l *load.Load) *ProposalsPage { _, pg.infoButton = components.SubpageHeaderButtons(l) pg.infoButton.Size = values.MarginPadding20 - pg.statusDropDown = l.Theme.DropDown([]cryptomaterial.DropDownItem{ + pg.statusDropDown = l.Theme.DropdownWithCustomPos([]cryptomaterial.DropDownItem{ {Text: values.String(values.StrAll)}, {Text: values.String(values.StrUnderReview)}, {Text: values.String(values.StrApproved)}, {Text: values.String(values.StrRejected)}, {Text: values.String(values.StrAbandoned)}, - }, values.ProposalDropdownGroup, 0) + }, values.ProposalDropdownGroup, 0, 0, true) + + if pg.statusDropDown.Reversed() { + pg.statusDropDown.ExpandedLayoutInset.Right = values.MarginPadding10 + } else { + pg.statusDropDown.ExpandedLayoutInset.Left = values.MarginPadding10 + } return pg } @@ -232,7 +238,7 @@ func (pg *ProposalsPage) layoutDesktop(gtx layout.Context) layout.Dimensions { return layout.Inset{Top: values.MarginPadding60}.Layout(gtx, pg.layoutContent) }), layout.Expanded(func(gtx C) D { - return pg.statusDropDown.Layout(gtx, 10, true) + return pg.statusDropDown.Layout(gtx) }), ) }) @@ -264,7 +270,12 @@ func (pg *ProposalsPage) layoutMobile(gtx layout.Context) layout.Dimensions { }) }), layout.Expanded(func(gtx C) D { - return pg.statusDropDown.Layout(gtx, 55, true) + if pg.statusDropDown.Reversed() { + pg.statusDropDown.ExpandedLayoutInset.Right = values.DP55 + } else { + pg.statusDropDown.ExpandedLayoutInset.Left = values.DP55 + } + return pg.statusDropDown.Layout(gtx) }), ) }) diff --git a/ui/page/transaction/transactions_page.go b/ui/page/transaction/transactions_page.go index 1132225d7..383caa43d 100644 --- a/ui/page/transaction/transactions_page.go +++ b/ui/page/transaction/transactions_page.go @@ -133,7 +133,7 @@ func (pg *TransactionsPage) initWalletSelector() { items = append(items, item) } - pg.walletDropDown = pg.Theme.DropDown(items, values.WalletsDropdownGroup, 0) + pg.walletDropDown = pg.Theme.DropDown(items, values.WalletsDropdownGroup, false) pg.walletDropDown.ClearSelection("Select a wallet") } else { pg.selectedWallet = pg.assetWallets[0] @@ -159,7 +159,7 @@ func (pg *TransactionsPage) refreshAvailableTxType() { for _, name := range keysinfo { items = append(items, cryptomaterial.DropDownItem{Text: name}) } - pg.txTypeDropDown = pg.Theme.DropDown(items, values.TxDropdownGroup, 2) + pg.txTypeDropDown = pg.Theme.DropdownWithCustomPos(items, values.TxDropdownGroup, 0, 2, true) // only show tx count for regular txs, not staking if pg.txCategoryTab.SelectedSegment() == values.String(values.StrTxOverview) { @@ -187,7 +187,7 @@ func (pg *TransactionsPage) refreshAvailableTxType() { }) } - pg.txTypeDropDown = pg.Theme.DropDown(items, values.TxDropdownGroup, 2) + pg.txTypeDropDown = pg.Theme.DropdownWithCustomPos(items, values.TxDropdownGroup, 0, 2, true) pg.showLoader = false pg.ParentWindow().Reload() }() @@ -399,7 +399,7 @@ func (pg *TransactionsPage) desktopLayoutContent(gtx C) D { if pg.walletDropDown != nil { pageElements = append(pageElements, layout.Expanded(func(gtx C) D { - return pg.walletDropDown.Layout(gtx, 0, false) + return pg.walletDropDown.Layout(gtx) })) } @@ -408,7 +408,7 @@ func (pg *TransactionsPage) desktopLayoutContent(gtx C) D { pg.ParentWindow().Reload() //refresh UI to display updated txType dropdown txDropdownWidget := layout.Expanded(func(gtx C) D { - return pg.txTypeDropDown.Layout(gtx, 0, true) + return pg.txTypeDropDown.Layout(gtx) }) pageElements = append(pageElements, txDropdownWidget) } @@ -468,9 +468,7 @@ func (pg *TransactionsPage) layoutMobile(gtx C) D { }) }), layout.Expanded(func(gtx C) D { - return layout.Inset{Right: values.MarginPadding10}.Layout(gtx, func(gtx C) D { - return pg.txTypeDropDown.Layout(gtx, 0, true) - }) + return layout.Inset{Right: values.MarginPadding10}.Layout(gtx, pg.txTypeDropDown.Layout) }), ) }), diff --git a/ui/values/dimensions.go b/ui/values/dimensions.go index 277a372d5..6535d09f1 100644 --- a/ui/values/dimensions.go +++ b/ui/values/dimensions.go @@ -48,9 +48,11 @@ var ( MarginPadding38 = unit.Dp(38) MarginPadding40 = unit.Dp(40) MarginPadding44 = unit.Dp(44) + DP45 = unit.Dp(45) MarginPadding48 = unit.Dp(48) MarginPadding50 = unit.Dp(50) MarginPadding52 = unit.Dp(52) + DP55 = unit.Dp(55) MarginPadding56 = unit.Dp(56) MarginPadding60 = unit.Dp(60) MarginPadding62 = unit.Dp(62) @@ -83,6 +85,7 @@ var ( MarginPadding250 = unit.Dp(250) MarginPaddingMinus230 = unit.Dp(-230) MarginPadding280 = unit.Dp(280) + DP300 = unit.Dp(300) MarginPadding350 = unit.Dp(350) MarginPadding368 = unit.Dp(368) MarginPadding372 = unit.Dp(372) @@ -90,6 +93,7 @@ var ( MarginPadding390 = unit.Dp(390) MarginPadding450 = unit.Dp(450) MarginPadding500 = unit.Dp(500) + DP515 = unit.Dp(515) MarginPadding550 = unit.Dp(550) MarginPadding600 = unit.Dp(600) diff --git a/ui/values/localizable/en.go b/ui/values/localizable/en.go index b876a38c2..e1d77e9c0 100644 --- a/ui/values/localizable/en.go +++ b/ui/values/localizable/en.go @@ -825,4 +825,6 @@ const EN = ` "market" = "Market" "assetPrice" = "Price (%s)" "assetAmount" = "Amount (%s)" +"booked" = "Booked" +"executed" = "Executed" ` diff --git a/ui/values/strings.go b/ui/values/strings.go index a80ddcbec..59eb476b1 100644 --- a/ui/values/strings.go +++ b/ui/values/strings.go @@ -934,4 +934,6 @@ const ( StrSeeMore = "seeMore" StrAssetPrice = "assetPrice" StrAssetAmount = "assetAmount" + StrBooked = "booked" + StrExecuted = "executed" )