diff --git a/app_instance/app_instance.go b/app_instance/app_instance.go index 06db695..b081f7d 100644 --- a/app_instance/app_instance.go +++ b/app_instance/app_instance.go @@ -1,18 +1,35 @@ package app_instance import ( + "image/color" + "gioui.org/app" + "gioui.org/unit" "gioui.org/x/explorer" + expl "gioui.org/x/explorer" "github.com/g45t345rt/g45w/router" + "github.com/g45t345rt/g45w/settings" ) var Window *app.Window var Router *router.Router var Explorer *explorer.Explorer -const ( - PAGE_SETTINGS = "page_settings" - PAGE_NODE = "page_node" - PAGE_WALLET = "page_wallet" - PAGE_WALLET_SELECT = "page_wallet_select" -) +func Load() { + minSizeX := unit.Dp(375) + minSizeY := unit.Dp(600) + maxSizeX := unit.Dp(500) + maxSizeY := unit.Dp(1000) + + Window = app.NewWindow( + app.Title(settings.Name), + app.MinSize(minSizeX, minSizeY), + app.Size(minSizeX, minSizeY), + app.MaxSize(maxSizeX, maxSizeY), + app.PortraitOrientation.Option(), + app.NavigationColor(color.NRGBA{A: 0}), + ) + + Explorer = expl.NewExplorer(Window) + Router = router.NewRouter() +} diff --git a/components/input.go b/components/input.go index ef70b22..952cd44 100644 --- a/components/input.go +++ b/components/input.go @@ -24,9 +24,10 @@ type Input struct { Inset layout.Inset Clickable *widget.Clickable - submitted bool - submitText string - activeSubmit bool + keyboardClick *widget.Clickable + submitted bool + submitText string + activeSubmit bool } func NewInput() *Input { @@ -41,9 +42,10 @@ func NewInput() *Input { } return &Input{ - Editor: editor, - Border: border, - Clickable: new(widget.Clickable), + Editor: editor, + Border: border, + Clickable: new(widget.Clickable), + keyboardClick: new(widget.Clickable), Inset: layout.Inset{ Top: unit.Dp(15), Bottom: unit.Dp(15), Left: unit.Dp(12), Right: unit.Dp(12), @@ -51,6 +53,13 @@ func NewInput() *Input { } } +func NewNumberInput() *Input { + input := NewInput() + input.Editor.Filter = "0123456789." + input.Editor.InputHint = key.HintNumeric + return input +} + func NewPasswordInput() *Input { editor := new(widget.Editor) editor.SingleLine = true @@ -64,9 +73,10 @@ func NewPasswordInput() *Input { } return &Input{ - Editor: editor, - Border: border, - Clickable: new(widget.Clickable), + Editor: editor, + Border: border, + Clickable: new(widget.Clickable), + keyboardClick: new(widget.Clickable), Inset: layout.Inset{ Top: unit.Dp(15), Bottom: unit.Dp(15), Left: unit.Dp(12), Right: unit.Dp(12), @@ -106,28 +116,37 @@ func (t *Input) Layout(gtx layout.Context, th *material.Theme, hint string) layo gtx.Constraints.Min.Y = t.EditorMinY + if t.keyboardClick.Clicked() { + // on mobile if the keyboard popups and the input lose focus it will automatically close the keyboard + // so we have to manually force keyboard request to avoid this issue + key.SoftKeyboardOp{Show: true}.Add(gtx.Ops) + } + return t.Clickable.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return t.Border.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - macro := op.Record(gtx.Ops) - dims := t.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - editorStyle := material.Editor(th, t.Editor, hint) - if t.TextSize != 0 { - editorStyle.TextSize = t.TextSize - } - if t.FontWeight != font.Normal { - editorStyle.Font.Weight = t.FontWeight - } - return editorStyle.Layout(gtx) + return t.keyboardClick.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return t.Border.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + macro := op.Record(gtx.Ops) + dims := t.Inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + editorStyle := material.Editor(th, t.Editor, hint) + if t.TextSize != 0 { + editorStyle.TextSize = t.TextSize + } + if t.FontWeight != font.Normal { + editorStyle.Font.Weight = t.FontWeight + } + return editorStyle.Layout(gtx) + }) + call := macro.Stop() + + paint.FillShape(gtx.Ops, color.NRGBA{R: 255, G: 255, B: 255, A: 255}, clip.UniformRRect( + image.Rectangle{Max: dims.Size}, + int(t.Border.CornerRadius), + ).Op(gtx.Ops)) + + call.Add(gtx.Ops) + return dims }) - call := macro.Stop() - - paint.FillShape(gtx.Ops, color.NRGBA{R: 255, G: 255, B: 255, A: 255}, clip.UniformRRect( - image.Rectangle{Max: dims.Size}, - int(t.Border.CornerRadius), - ).Op(gtx.Ops)) - call.Add(gtx.Ops) - return dims }) }) } diff --git a/components/textfield.go b/components/textfield.go index 25f50c8..19ec613 100644 --- a/components/textfield.go +++ b/components/textfield.go @@ -20,6 +20,13 @@ func NewTextField() *TextField { } } +func NewNumberTextField() *TextField { + input := NewNumberInput() + return &TextField{ + Input: input, + } +} + func NewPasswordTextField() *TextField { input := NewPasswordInput() diff --git a/containers/bottom_bar/bottom_bar.go b/containers/bottom_bar/bottom_bar.go index 96c312a..c684eb5 100644 --- a/containers/bottom_bar/bottom_bar.go +++ b/containers/bottom_bar/bottom_bar.go @@ -18,6 +18,7 @@ import ( "github.com/g45t345rt/g45w/components" "github.com/g45t345rt/g45w/containers/recent_txs_modal" "github.com/g45t345rt/g45w/lang" + "github.com/g45t345rt/g45w/pages" "github.com/g45t345rt/g45w/router" "github.com/g45t345rt/g45w/wallet_manager" "golang.org/x/exp/shiny/materialdesign/icons" @@ -153,24 +154,24 @@ func (b *BottomBar) Layout(gtx layout.Context, th *material.Theme) layout.Dimens } if b.confirmClose.ClickedYes() { - b.appRouter.SetCurrent(app_instance.PAGE_WALLET_SELECT) + b.appRouter.SetCurrent(pages.PAGE_WALLET_SELECT) wallet_manager.CloseOpenedWallet() } if b.ButtonNode.Button.Clicked() { - b.appRouter.SetCurrent(app_instance.PAGE_NODE) + b.appRouter.SetCurrent(pages.PAGE_NODE) } if b.ButtonWallet.Button.Clicked() { - if b.appRouter.Current == app_instance.PAGE_WALLET { - b.appRouter.SetCurrent(app_instance.PAGE_WALLET_SELECT) + if b.appRouter.Current == pages.PAGE_WALLET { + b.appRouter.SetCurrent(pages.PAGE_WALLET_SELECT) } else { - b.appRouter.SetCurrent(app_instance.PAGE_WALLET) + b.appRouter.SetCurrent(pages.PAGE_WALLET) } } if b.ButtonSettings.Button.Clicked() { - b.appRouter.SetCurrent(app_instance.PAGE_SETTINGS) + b.appRouter.SetCurrent(pages.PAGE_SETTINGS) } if b.ButtonTxs.Button.Clicked() { diff --git a/containers/containers.go b/containers/containers.go new file mode 100644 index 0000000..dc83b37 --- /dev/null +++ b/containers/containers.go @@ -0,0 +1,17 @@ +package containers + +import ( + "github.com/g45t345rt/g45w/containers/bottom_bar" + "github.com/g45t345rt/g45w/containers/build_tx_modal.go" + "github.com/g45t345rt/g45w/containers/node_status_bar" + "github.com/g45t345rt/g45w/containers/notification_modals" + "github.com/g45t345rt/g45w/containers/recent_txs_modal" +) + +func Load() { + bottom_bar.LoadInstance() + node_status_bar.LoadInstance() + notification_modals.LoadInstance() + recent_txs_modal.LoadInstance() + build_tx_modal.LoadInstance() +} diff --git a/containers/node_status_bar/node_status_bar.go b/containers/node_status_bar/node_status_bar.go index 772d60a..4b304c1 100644 --- a/containers/node_status_bar/node_status_bar.go +++ b/containers/node_status_bar/node_status_bar.go @@ -17,6 +17,7 @@ import ( "github.com/g45t345rt/g45w/integrated_node" "github.com/g45t345rt/g45w/lang" "github.com/g45t345rt/g45w/node_manager" + "github.com/g45t345rt/g45w/pages" page_node "github.com/g45t345rt/g45w/pages/node" "github.com/g45t345rt/g45w/wallet_manager" ) @@ -109,7 +110,7 @@ func (n *NodeStatusBar) Layout(gtx layout.Context, th *material.Theme) layout.Di } if n.clickable.Clicked() { - app_instance.Router.SetCurrent(app_instance.PAGE_NODE) + app_instance.Router.SetCurrent(pages.PAGE_NODE) op.InvalidateOp{}.Add(gtx.Ops) } diff --git a/containers/recent_txs_modal/recent_txs_modal.go b/containers/recent_txs_modal/recent_txs_modal.go index 677f012..e93366c 100644 --- a/containers/recent_txs_modal/recent_txs_modal.go +++ b/containers/recent_txs_modal/recent_txs_modal.go @@ -2,11 +2,16 @@ package recent_txs_modal import ( "fmt" + "image" "image/color" + "strings" "time" "gioui.org/font" "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" "gioui.org/text" "gioui.org/unit" "gioui.org/widget" @@ -14,6 +19,7 @@ import ( "github.com/deroproject/derohe/walletapi" "github.com/g45t345rt/g45w/app_instance" "github.com/g45t345rt/g45w/components" + "github.com/g45t345rt/g45w/containers/notification_modals" "github.com/g45t345rt/g45w/lang" "github.com/g45t345rt/g45w/router" "github.com/g45t345rt/g45w/utils" @@ -23,8 +29,10 @@ import ( ) type RecentTxsModal struct { - modal *components.Modal - list *widget.List + modal *components.Modal + list *widget.List + buttonClear *components.Button + confirmClear *components.Confirm txItems []TxItem } @@ -44,19 +52,38 @@ func LoadInstance() { Backdrop: components.NewModalBackground(), }) + confirmClear := components.NewConfirm(layout.Center) + list := new(widget.List) list.Axis = layout.Vertical + deleteIcon, _ := widget.NewIcon(icons.ContentDeleteSweep) + buttonClear := components.NewButton(components.ButtonStyle{ + Icon: deleteIcon, + TextColor: color.NRGBA{R: 0, G: 0, B: 0, A: 100}, + HoverTextColor: &color.NRGBA{R: 0, G: 0, B: 0, A: 255}, + Animation: components.NewButtonAnimationScale(.95), + }) + Instance = &RecentTxsModal{ - modal: modal, - list: list, + modal: modal, + list: list, + buttonClear: buttonClear, + confirmClear: confirmClear, } Instance.startCheckingPendingTxs() app_instance.Router.AddLayout(router.KeyLayout{ DrawIndex: 2, - Layout: Instance.layout, + Layout: func(gtx layout.Context, th *material.Theme) { + Instance.layout(gtx, th) + + confirmClear.Prompt = lang.Translate("Are you sure you want to clear outgoing txs?") + confirmClear.NoText = lang.Translate("NO") + confirmClear.YesText = lang.Translate("YES") + confirmClear.Layout(gtx, th) + }, }) } @@ -114,6 +141,25 @@ func (r *RecentTxsModal) SetVisible(visible bool) { } func (r *RecentTxsModal) layout(gtx layout.Context, th *material.Theme) { + if r.buttonClear.Clicked() { + r.confirmClear.SetVisible(true) + } + + wallet := wallet_manager.OpenedWallet + if r.confirmClear.ClickedYes() { + err := wallet.ClearOutgoingTxs() + if err != nil { + notification_modals.ErrorInstance.SetText(lang.Translate("Error"), err.Error()) + notification_modals.ErrorInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) + } else { + notification_modals.SuccessInstance.SetText(lang.Translate("Success"), lang.Translate("Outgoing txs cleared.")) + notification_modals.SuccessInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) + r.LoadOutgoingTxs() + } + } + + r.buttonClear.Disabled = wallet == nil + r.modal.Layout(gtx, nil, func(gtx layout.Context) layout.Dimensions { return layout.Inset{ Top: unit.Dp(15), Bottom: unit.Dp(15), @@ -121,14 +167,22 @@ func (r *RecentTxsModal) layout(gtx layout.Context, th *material.Theme) { }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { - lbl := material.Label(th, unit.Sp(20), fmt.Sprintf("%s (%d)", lang.Translate("Outgoing Transactions"), len(r.txItems))) - lbl.Font.Weight = font.Bold - return lbl.Layout(gtx) + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(th, unit.Sp(20), fmt.Sprintf("%s (%d)", lang.Translate("Outgoing Transactions"), len(r.txItems))) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(10)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return r.buttonClear.Layout(gtx, th) + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(15)}.Layout), + ) }), layout.Rigid(layout.Spacer{Height: unit.Dp(15)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { gtx.Constraints.Max.Y = gtx.Dp(250) - wallet := wallet_manager.OpenedWallet if wallet == nil { lbl := material.Label(th, unit.Sp(16), lang.Translate("Wallet is not opened.")) return lbl.Layout(gtx) @@ -144,7 +198,7 @@ func (r *RecentTxsModal) layout(gtx layout.Context, th *material.Theme) { return listStyle.Layout(gtx, len(r.txItems), func(gtx layout.Context, index int) layout.Dimensions { bottomInset := 0 if index < len(r.txItems)-1 { - bottomInset = 10 + bottomInset = 5 } return layout.Inset{ @@ -161,8 +215,9 @@ func (r *RecentTxsModal) layout(gtx layout.Context, th *material.Theme) { } type TxItem struct { - tx wallet_manager.OutgoingTx - buttonOpen *components.Button + tx wallet_manager.OutgoingTx + buttonOpen *components.Button + buttonRemove *components.Button } func NewTxItem(tx wallet_manager.OutgoingTx) *TxItem { @@ -176,9 +231,18 @@ func NewTxItem(tx wallet_manager.OutgoingTx) *TxItem { HoverTextColor: &textHoverColor, }) + remoteIcon, _ := widget.NewIcon(icons.ActionDelete) + buttonRemove := components.NewButton(components.ButtonStyle{ + Icon: remoteIcon, + TextColor: textColor, + HoverTextColor: &textHoverColor, + Animation: components.NewButtonAnimationScale(.95), + }) + return &TxItem{ - tx: tx, - buttonOpen: buttonOpen, + tx: tx, + buttonOpen: buttonOpen, + buttonRemove: buttonRemove, } } @@ -189,9 +253,17 @@ func (item *TxItem) Layout(gtx layout.Context, th *material.Theme) layout.Dimens switch item.tx.Status.String { case "valid": - status = lang.Translate("Successful transaction") + height := uint64(walletapi.Get_Daemon_Height()) - confirmations = height - uint64(item.tx.BlockHeight.Int64) + + if height > 0 { + confirmations = height - uint64(item.tx.BlockHeight.Int64) + } else { + confirmations = 0 + } + + value := lang.Translate("{} confirmations") + status = strings.Replace(value, "{}", fmt.Sprint(confirmations), -1) case "invalid": status = lang.Translate("Invalid transaction") default: @@ -204,15 +276,32 @@ func (item *TxItem) Layout(gtx layout.Context, th *material.Theme) layout.Dimens go open.Run(fmt.Sprintf("https://explorer.dero.io/tx/%s", txId)) } - return layout.Flex{ - Axis: layout.Horizontal, - Spacing: layout.SpaceBetween, - Alignment: layout.Middle, - }.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { + if item.buttonRemove.Clicked() { + wallet := wallet_manager.OpenedWallet + err := wallet.DelOutgoingTx(item.tx.TxId) + + if err != nil { + notification_modals.ErrorInstance.SetText(lang.Translate("Error"), err.Error()) + notification_modals.ErrorInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) + } else { + notification_modals.SuccessInstance.SetText(lang.Translate("Success"), lang.Translate("Transaction ref remove.")) + notification_modals.SuccessInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) + Instance.LoadOutgoingTxs() + } + } + + r := op.Record(gtx.Ops) + dims := layout.Inset{ + Top: unit.Dp(5), Bottom: unit.Dp(5), + Left: unit.Dp(5), Right: unit.Dp(5), + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + var flexChilds []layout.FlexChild + + flexChilds = append(flexChilds, layout.Rigid(func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { lbl := material.Label(th, unit.Sp(16), utils.ReduceTxId(txId)) + lbl.Font.Weight = font.Bold return lbl.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { @@ -220,25 +309,58 @@ func (item *TxItem) Layout(gtx layout.Context, th *material.Theme) layout.Dimens return lbl.Layout(gtx) }), ) - }), - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + })) + + flexChilds = append(flexChilds, layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { - confirmations := fmt.Sprintf("%d confirmations", confirmations) - lbl := material.Label(th, unit.Sp(16), confirmations) + lbl := material.Label(th, unit.Sp(16), fmt.Sprint(item.tx.BlockHeight.Int64)) lbl.Alignment = text.End return lbl.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - lbl := material.Label(th, unit.Sp(16), date.Format("2006-01-02")) + lbl := material.Label(th, unit.Sp(16), lang.TimeAgo(date)) lbl.Alignment = text.End return lbl.Layout(gtx) }), ) - }), - layout.Rigid(layout.Spacer{Width: unit.Dp(10)}.Layout), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return item.buttonOpen.Layout(gtx, th) - }), + })) + + flexChilds = append(flexChilds, + layout.Rigid(layout.Spacer{Width: unit.Dp(10)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return item.buttonOpen.Layout(gtx, th) + }), + ) + + if confirmations > 1 { + flexChilds = append(flexChilds, + layout.Rigid(layout.Spacer{Width: unit.Dp(5)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return item.buttonRemove.Layout(gtx, th) + }), + ) + } + + return layout.Flex{ + Axis: layout.Horizontal, + Spacing: layout.SpaceBetween, + Alignment: layout.Middle, + }.Layout(gtx, + flexChilds..., + ) + }) + c := r.Stop() + + paint.FillShape(gtx.Ops, + color.NRGBA{A: 10}, + clip.RRect{ + Rect: image.Rectangle{Max: dims.Size}, + SE: gtx.Dp(5), SW: gtx.Dp(5), + NW: gtx.Dp(5), NE: gtx.Dp(5), + }.Op(gtx.Ops), ) + + c.Add(gtx.Ops) + return dims } diff --git a/go.mod b/go.mod index a73c711..ad24e8f 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.0 // indirect + github.com/xeonx/timeago v1.0.0-rc5 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/tools v0.7.0 // indirect diff --git a/go.sum b/go.sum index ebd4ca9..f45727d 100644 --- a/go.sum +++ b/go.sum @@ -212,6 +212,8 @@ github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OL github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xeonx/timeago v1.0.0-rc5 h1:pwcQGpaH3eLfPtXeyPA4DmHWjoQt0Ea7/++FwpxqLxg= +github.com/xeonx/timeago v1.0.0-rc5/go.mod h1:qDLrYEFynLO7y5Ho7w3GwgtYgpy5UfhcXIIQvMKVDkA= github.com/xtaci/kcp-go/v5 v5.6.2 h1:pSXMa5MOsb+EIZKe4sDBqlTExu2A/2Z+DFhoX2qtt2A= github.com/xtaci/kcp-go/v5 v5.6.2/go.mod h1:LsinWoru+lWWJHb+EM9HeuqYxV6bb9rNcK12v67jYzQ= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= diff --git a/lang/lang.go b/lang/lang.go index f13f0fc..8abcd44 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -2,9 +2,11 @@ package lang import ( "fmt" + "time" "github.com/g45t345rt/g45w/assets" "github.com/g45t345rt/g45w/settings" + "github.com/xeonx/timeago" ) type Lang struct { @@ -39,6 +41,17 @@ func Load() error { langValues[lang.Key] = values } + // TODO: add more predefined configuration language + max := 100 * 365 * 24 * time.Hour + timeago.English.Max = max + timeago.Chinese.Max = max + timeago.German.Max = max + timeago.Korean.Max = max + timeago.French.Max = max + timeago.Portuguese.Max = max + timeago.Spanish.Max = max + timeago.Turkish.Max = max + return nil } diff --git a/lang/time_ago.go b/lang/time_ago.go new file mode 100644 index 0000000..99435a4 --- /dev/null +++ b/lang/time_ago.go @@ -0,0 +1,281 @@ +package lang + +import ( + "time" + + "github.com/g45t345rt/g45w/settings" + "github.com/xeonx/timeago" +) + +var english = timeago.Config{ + PastPrefix: "", + PastSuffix: " ago", + FuturePrefix: "in ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "about a second", Many: "%d seconds"}, + {D: time.Minute, One: "about a minute", Many: "%d minutes"}, + {D: time.Hour, One: "about an hour", Many: "%d hours"}, + {D: timeago.Day, One: "one day", Many: "%d days"}, + {D: timeago.Month, One: "one month", Many: "%d months"}, + {D: timeago.Year, One: "one year", Many: "%d years"}, + }, + + Zero: "about a second", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "2006-01-02", +} + +var portuguese = timeago.Config{ + PastPrefix: "há ", + PastSuffix: "", + FuturePrefix: "daqui a ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "um segundo", Many: "%d segundos"}, + {D: time.Minute, One: "um minuto", Many: "%d minutos"}, + {D: time.Hour, One: "uma hora", Many: "%d horas"}, + {D: timeago.Day, One: "um dia", Many: "%d dias"}, + {D: timeago.Month, One: "um mês", Many: "%d meses"}, + {D: timeago.Year, One: "um ano", Many: "%d anos"}, + }, + + Zero: "menos de um segundo", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02-01-2006", +} + +var spanish = timeago.Config{ + PastPrefix: "hace ", + PastSuffix: "", + FuturePrefix: "en ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "un segundo", Many: "%d segundos"}, + {D: time.Minute, One: "un minuto", Many: "%d minutos"}, + {D: time.Hour, One: "una hora", Many: "%d horas"}, + {D: timeago.Day, One: "un día", Many: "%d días"}, + {D: timeago.Month, One: "un mes", Many: "%d meses"}, + {D: timeago.Year, One: "un año", Many: "%d años"}, + }, + + Zero: "menos de un segundo", + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02-01-2006", +} + +var chinese = timeago.Config{ + PastPrefix: "", + PastSuffix: "前", + FuturePrefix: "于 ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "1 秒", Many: "%d 秒"}, + {D: time.Minute, One: "1 分钟", Many: "%d 分钟"}, + {D: time.Hour, One: "1 小时", Many: "%d 小时"}, + {D: timeago.Day, One: "1 天", Many: "%d 天"}, + {D: timeago.Month, One: "1 月", Many: "%d 月"}, + {D: timeago.Year, One: "1 年", Many: "%d 年"}, + }, + + Zero: "1 秒", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "2006-01-02", +} + +var french = timeago.Config{ + PastPrefix: "il y a ", + PastSuffix: "", + FuturePrefix: "dans ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "environ une seconde", Many: "moins d'une minute"}, + {D: time.Minute, One: "environ une minute", Many: "%d minutes"}, + {D: time.Hour, One: "environ une heure", Many: "%d heures"}, + {D: timeago.Day, One: "un jour", Many: "%d jours"}, + {D: timeago.Month, One: "un mois", Many: "%d mois"}, + {D: timeago.Year, One: "un an", Many: "%d ans"}, + }, + + Zero: "environ une seconde", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02/01/2006", +} + +var german = timeago.Config{ + PastPrefix: "vor ", + PastSuffix: "", + FuturePrefix: "in ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "einer Sekunde", Many: "%d Sekunden"}, + {D: time.Minute, One: "einer Minute", Many: "%d Minuten"}, + {D: time.Hour, One: "einer Stunde", Many: "%d Stunden"}, + {D: timeago.Day, One: "einem Tag", Many: "%d Tagen"}, + {D: timeago.Month, One: "einem Monat", Many: "%d Monaten"}, + {D: timeago.Year, One: "einem Jahr", Many: "%d Jahren"}, + }, + + Zero: "einer Sekunde", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02.01.2006", +} + +var turkish = timeago.Config{ + PastPrefix: "", + PastSuffix: " önce", + FuturePrefix: "", + FutureSuffix: " içinde", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "yaklaşık bir saniye", Many: "%d saniye"}, + {D: time.Minute, One: "yaklaşık bir dakika", Many: "%d dakika"}, + {D: time.Hour, One: "yaklaşık bir saat", Many: "%d saat"}, + {D: timeago.Day, One: "bir gün", Many: "%d gün"}, + {D: timeago.Month, One: "bir ay", Many: "%d ay"}, + {D: timeago.Year, One: "bir yıl", Many: "%d yıl"}, + }, + + Zero: "yaklaşık bir saniye", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02/01/2006", +} + +var korean = timeago.Config{ + PastPrefix: "", + PastSuffix: " 전", + FuturePrefix: "", + FutureSuffix: " 후", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "약 1초", Many: "%d초"}, + {D: time.Minute, One: "약 1분", Many: "%d분"}, + {D: time.Hour, One: "약 한시간", Many: "%d시간"}, + {D: timeago.Day, One: "하루", Many: "%d일"}, + {D: timeago.Month, One: "1개월", Many: "%d개월"}, + {D: timeago.Year, One: "1년", Many: "%d년"}, + }, + + Zero: "방금", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "2006-01-02", +} + +var italian = timeago.Config{ + PastPrefix: "fa ", + PastSuffix: "", + FuturePrefix: "tra ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "circa un secondo", Many: "meno di un minuto"}, + {D: time.Minute, One: "circa un minuto", Many: "%d minuti"}, + {D: time.Hour, One: "circa un'ora", Many: "%d ore"}, + {D: timeago.Day, One: "un giorno", Many: "%d giorni"}, + {D: timeago.Month, One: "un mese", Many: "%d mesi"}, + {D: timeago.Year, One: "un anno", Many: "%d anni"}, + }, + + Zero: "circa un secondo", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02/01/2006", +} + +var russian = timeago.Config{ + PastPrefix: "назад ", + PastSuffix: "", + FuturePrefix: "через ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "около одной секунды", Many: "менее минуты"}, + {D: time.Minute, One: "около одной минуты", Many: "%d минут"}, + {D: time.Hour, One: "около одного часа", Many: "%d часов"}, + {D: timeago.Day, One: "один день", Many: "%d дней"}, + {D: timeago.Month, One: "один месяц", Many: "%d месяцев"}, + {D: timeago.Year, One: "один год", Many: "%d лет"}, + }, + + Zero: "около одной секунды", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02/01/2006", +} + +var romanian = timeago.Config{ + PastPrefix: "acum ", + PastSuffix: "", + FuturePrefix: "peste ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "aproximativ un secundă", Many: "mai puțin de un minut"}, + {D: time.Minute, One: "aproximativ un minut", Many: "%d minute"}, + {D: time.Hour, One: "aproximativ o oră", Many: "%d ore"}, + {D: timeago.Day, One: "o zi", Many: "%d zile"}, + {D: timeago.Month, One: "o lună", Many: "%d luni"}, + {D: timeago.Year, One: "un an", Many: "%d ani"}, + }, + + Zero: "aproximativ un secundă", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02/01/2006", +} + +var dutch = timeago.Config{ + PastPrefix: "ongeveer ", + PastSuffix: " geleden", + FuturePrefix: "over ", + FutureSuffix: "", + + Periods: []timeago.FormatPeriod{ + {D: time.Second, One: "ongeveer een seconde", Many: "minder dan een minuut"}, + {D: time.Minute, One: "ongeveer een minuut", Many: "%d minuten"}, + {D: time.Hour, One: "ongeveer een uur", Many: "%d uur"}, + {D: timeago.Day, One: "een dag", Many: "%d dagen"}, + {D: timeago.Month, One: "een maand", Many: "%d maanden"}, + {D: timeago.Year, One: "een jaar", Many: "%d jaar"}, + }, + + Zero: "ongeveer een seconde", + + Max: 100 * 365 * 24 * time.Hour, + DefaultLayout: "02-01-2006", +} + +func TimeAgo(date time.Time) string { + lang := settings.App.Language + switch lang { + case "pt": + return portuguese.Format(date) + case "es": + return spanish.Format(date) + case "fr": + return french.Format(date) + case "it": + return italian.Format(date) + case "ru": + return russian.Format(date) + case "ro": + return romanian.Format(date) + case "nl": + return dutch.Format(date) + } + + return english.Format(date) +} diff --git a/main.go b/main.go index 0db24a0..dc51fc2 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "image/color" "log" "os" @@ -13,20 +12,15 @@ import ( "gioui.org/io/system" "gioui.org/layout" "gioui.org/op" - "gioui.org/unit" "gioui.org/widget/material" - expl "gioui.org/x/explorer" "github.com/deroproject/derohe/globals" "github.com/deroproject/derohe/walletapi" "github.com/g45t345rt/g45w/app_data" "github.com/g45t345rt/g45w/app_instance" - "github.com/g45t345rt/g45w/containers/bottom_bar" - "github.com/g45t345rt/g45w/containers/build_tx_modal.go" - "github.com/g45t345rt/g45w/containers/node_status_bar" - "github.com/g45t345rt/g45w/containers/notification_modals" - "github.com/g45t345rt/g45w/containers/recent_txs_modal" + "github.com/g45t345rt/g45w/containers" "github.com/g45t345rt/g45w/lang" "github.com/g45t345rt/g45w/node_manager" + "github.com/g45t345rt/g45w/pages" page_node "github.com/g45t345rt/g45w/pages/node" page_settings "github.com/g45t345rt/g45w/pages/settings" page_wallet "github.com/g45t345rt/g45w/pages/wallet" @@ -49,18 +43,37 @@ func loadFontCollection() ([]font.FontFace, error) { } fontCollection := []font.FontFace{} - //gofont.Collection() fontCollection = append(fontCollection, font.FontFace{Font: font.Font{}, Face: robotoRegular}) fontCollection = append(fontCollection, font.FontFace{Font: font.Font{Weight: font.Bold}, Face: robotoBold}) return fontCollection, nil } -func runApp(th *material.Theme) error { - var ops op.Ops +func loadPages(router *router.Router) { + pageNode := page_node.New() + pageWallet := page_wallet.New() + pageWalletSelect := page_wallet_select.New() + pageSettings := page_settings.New() + + router.Add(pages.PAGE_NODE, pageNode) + router.Add(pages.PAGE_WALLET, pageWallet) + router.Add(pages.PAGE_WALLET_SELECT, pageWalletSelect) + router.Add(pages.PAGE_SETTINGS, pageSettings) + router.SetCurrent(pages.PAGE_WALLET_SELECT) +} + +func runApp() error { + globals.Arguments["--testnet"] = false + globals.Arguments["--debug"] = false + globals.Arguments["--flog-level"] = nil + globals.Arguments["--log-dir"] = nil + globals.Arguments["--help"] = false + globals.Arguments["--version"] = false + var ops op.Ops + app_instance.Load() window := app_instance.Window - router := app_instance.Router explorer := app_instance.Explorer + router := app_instance.Router loadState := NewLoadState(window) @@ -106,23 +119,28 @@ func runApp(th *material.Theme) error { } loadState.SetStatus(lang.Translate("Loading pages"), nil) - bottom_bar.LoadInstance() - node_status_bar.LoadInstance() - notification_modals.LoadInstance() - recent_txs_modal.LoadInstance() - build_tx_modal.LoadInstance() - - router.Add(app_instance.PAGE_NODE, page_node.New()) - router.Add(app_instance.PAGE_WALLET, page_wallet.New()) - router.Add(app_instance.PAGE_WALLET_SELECT, page_wallet_select.New()) - router.Add(app_instance.PAGE_SETTINGS, page_settings.New()) - router.SetCurrent(app_instance.PAGE_WALLET_SELECT) + containers.Load() + loadPages(router) loadState.logoSplash.animation.Pause() loadState.SetStatus(lang.Translate("Done"), nil) loadState.Complete() }() + fontCollection, err := loadFontCollection() + if err != nil { + log.Fatal(err) + } + + th := material.NewTheme(fontCollection) + th.WithPalette(material.Palette{ + Fg: utils.HexColor(0x000000), + Bg: utils.HexColor(0xffffff), + ContrastBg: utils.HexColor(0x3f51b5), + ContrastFg: utils.HexColor(0xffffff), + }) + th.FingerSize = 48 + for { e := <-window.Events() explorer.ListenEvents(e) @@ -144,56 +162,8 @@ func runApp(th *material.Theme) error { } func main() { - globals.Arguments["--testnet"] = false - globals.Arguments["--debug"] = false - globals.Arguments["--flog-level"] = nil - globals.Arguments["--log-dir"] = nil - globals.Arguments["--help"] = false - globals.Arguments["--version"] = false - - // window - minSizeX := unit.Dp(375) - minSizeY := unit.Dp(600) - maxSizeX := unit.Dp(500) - maxSizeY := unit.Dp(1000) - - window := app.NewWindow( - app.Title(settings.Name), - app.MinSize(minSizeX, minSizeY), - app.Size(minSizeX, minSizeY), - app.MaxSize(maxSizeX, maxSizeY), - app.PortraitOrientation.Option(), - app.NavigationColor(color.NRGBA{A: 0}), - ) - - explorer := expl.NewExplorer(window) - - // font - fontCollection, err := loadFontCollection() - if err != nil { - log.Fatal(err) - } - - // theme - theme := material.NewTheme(fontCollection) - theme.WithPalette(material.Palette{ - Fg: utils.HexColor(0x000000), - Bg: utils.HexColor(0xffffff), - ContrastBg: utils.HexColor(0x3f51b5), - ContrastFg: utils.HexColor(0xffffff), - }) - theme.FingerSize = 48 - - // main router - appRouter := router.NewRouter() - - // app instance to give guick access to every package - app_instance.Window = window - app_instance.Router = appRouter - app_instance.Explorer = explorer - go func() { - err := runApp(theme) + err := runApp() if err != nil { log.Fatal(err) } diff --git a/pages/node/page.go b/pages/node/page.go index 23fda3c..35bf8c2 100644 --- a/pages/node/page.go +++ b/pages/node/page.go @@ -146,7 +146,7 @@ func (p *Page) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions } } - defer prefabs.PaintLinearGradient(gtx).Pop() + defer prefabs.PaintGrayLinearGradient(gtx).Pop() return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { diff --git a/pages/pages.go b/pages/pages.go new file mode 100644 index 0000000..be1216c --- /dev/null +++ b/pages/pages.go @@ -0,0 +1,8 @@ +package pages + +const ( + PAGE_SETTINGS = "page_settings" + PAGE_NODE = "page_node" + PAGE_WALLET = "page_wallet" + PAGE_WALLET_SELECT = "page_wallet_select" +) diff --git a/pages/settings/page.go b/pages/settings/page.go index 3e8cb32..e5e57b9 100644 --- a/pages/settings/page.go +++ b/pages/settings/page.go @@ -130,7 +130,7 @@ func (p *Page) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions } } - defer prefabs.PaintLinearGradient(gtx).Pop() + defer prefabs.PaintGrayLinearGradient(gtx).Pop() return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { diff --git a/pages/wallet/balance_tokens.go b/pages/wallet/balance_tokens.go index 102c7d1..a859b65 100644 --- a/pages/wallet/balance_tokens.go +++ b/pages/wallet/balance_tokens.go @@ -479,7 +479,7 @@ func (p *PageBalanceTokens) Layout(gtx layout.Context, th *material.Theme) layou Top: unit.Dp(0), Bottom: unit.Dp(20), Left: unit.Dp(30), Right: unit.Dp(30), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - lbl := material.Label(th, unit.Sp(16), lang.Translate("You don't have any txs. Try adjusting filering options.")) + lbl := material.Label(th, unit.Sp(16), lang.Translate("You don't have any txs. Try adjusting filering options or wait for wallet to sync.")) return lbl.Layout(gtx) }) }) @@ -1076,7 +1076,9 @@ func (item *TxListItem) Layout(gtx layout.Context, th *material.Theme) layout.Di } if item.clickable.Clicked() { - + page_instance.pageTransaction.entry = &item.entry + page_instance.pageRouter.SetCurrent(PAGE_TRANSACTION) + page_instance.header.AddHistory(PAGE_TRANSACTION) } m := op.Record(gtx.Ops) diff --git a/pages/wallet/page.go b/pages/wallet/page.go index 6a677a4..8dc8d3c 100644 --- a/pages/wallet/page.go +++ b/pages/wallet/page.go @@ -16,6 +16,7 @@ import ( "github.com/g45t345rt/g45w/app_instance" "github.com/g45t345rt/g45w/containers/bottom_bar" "github.com/g45t345rt/g45w/containers/node_status_bar" + "github.com/g45t345rt/g45w/pages" "github.com/g45t345rt/g45w/prefabs" "github.com/g45t345rt/g45w/router" "github.com/g45t345rt/g45w/wallet_manager" @@ -127,6 +128,7 @@ func New() *Page { pageSendOptionsForm: pageSendOptionsForm, pageSCFolders: pageSCFolders, pageContacts: pageContacts, + pageTransaction: pageTransaction, pageRouter: pageRouter, } @@ -158,7 +160,7 @@ func (p *Page) Enter() { p.pageRouter.SetCurrent(PAGE_BALANCE_TOKENS) } } else { - app_instance.Router.SetCurrent(app_instance.PAGE_WALLET_SELECT) + app_instance.Router.SetCurrent(pages.PAGE_WALLET_SELECT) } } @@ -220,7 +222,7 @@ func (p *Page) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions }) }), layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { - defer prefabs.PaintLinearGradient(gtx).Pop() + defer prefabs.PaintGrayLinearGradient(gtx).Pop() return p.pageRouter.Layout(gtx, th) }), diff --git a/pages/wallet/sc_token.go b/pages/wallet/sc_token.go index 3b32f3f..e0cc451 100644 --- a/pages/wallet/sc_token.go +++ b/pages/wallet/sc_token.go @@ -92,6 +92,9 @@ func NewPageSCToken() *PageSCToken { components.NewTabBarItem("txs", components.TabBarItemStyle{ TextSize: unit.Sp(18), }), + components.NewTabBarItem("info", components.TabBarItemStyle{ + TextSize: unit.Sp(18), + }), } tabBars := components.NewTabBars("txs", tabBarsItems) @@ -387,25 +390,32 @@ func (p *PageSCToken) Layout(gtx layout.Context, th *material.Theme) layout.Dime widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { text := make(map[string]string) text["txs"] = lang.Translate("Transactions") + text["info"] = lang.Translate("Info") return p.tabBars.Layout(gtx, th, text) }) - widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { - return p.txBar.Layout(gtx, th) - }) - - if len(p.txItems) == 0 { + if p.tabBars.Key == "txs" { widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { - lbl := material.Label(th, unit.Sp(16), lang.Translate("You don't have any txs. Try adjusting filering options.")) - return lbl.Layout(gtx) + return p.txBar.Layout(gtx, th) }) + + if len(p.txItems) == 0 { + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(th, unit.Sp(16), lang.Translate("You don't have any txs. Try adjusting filering options or wait for wallet to sync.")) + return lbl.Layout(gtx) + }) + } + + for i := range p.txItems { + idx := i + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return p.txItems[idx].Layout(gtx, th) + }) + } } - for i := range p.txItems { - idx := i - widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { - return p.txItems[idx].Layout(gtx, th) - }) + if p.tabBars.Key == "info" { + } return listStyle.Layout(gtx, len(widgets), func(gtx layout.Context, index int) layout.Dimensions { diff --git a/pages/wallet/send_form.go b/pages/wallet/send_form.go index f622ef5..1bd931f 100644 --- a/pages/wallet/send_form.go +++ b/pages/wallet/send_form.go @@ -7,7 +7,6 @@ import ( "strconv" "gioui.org/font" - "gioui.org/io/key" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" @@ -70,10 +69,9 @@ func NewPageSendForm() *PageSendForm { buttonBuildTx.Label.Alignment = text.Middle buttonBuildTx.Style.Font.Weight = font.Bold - txtAmount := components.NewTextField() + txtAmount := components.NewNumberTextField() txtAmount.Input.TextSize = unit.Sp(26) txtAmount.Input.FontWeight = font.Bold - txtAmount.Editor().InputHint = key.HintNumeric txtWalletAddr := components.NewInput() @@ -270,28 +268,28 @@ func (p *PageSendForm) Layout(gtx layout.Context, th *material.Theme) layout.Dim addr := p.txtWalletAddr.Editor.Text() wallet := wallet_manager.OpenedWallet - - contact, _ := wallet.GetContact(addr) - - if contact != nil { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Rigid(layout.Spacer{Height: unit.Dp(3)}.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 { - lbl := material.Label(th, unit.Sp(16), lang.Translate("Matching contact:")) - return lbl.Layout(gtx) - }), - layout.Rigid(layout.Spacer{Width: unit.Dp(3)}.Layout), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - - lbl := material.Label(th, unit.Sp(16), contact.Name) - lbl.Font.Weight = font.Bold - return lbl.Layout(gtx) - }), - ) - }), - ) + if wallet != nil { + contact, _ := wallet.GetContact(addr) + if contact != nil { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(layout.Spacer{Height: unit.Dp(3)}.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 { + lbl := material.Label(th, unit.Sp(16), lang.Translate("Matching contact:")) + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(3)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + + lbl := material.Label(th, unit.Sp(16), contact.Name) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + ) + }), + ) + } } return layout.Dimensions{} diff --git a/pages/wallet/send_options_form.go b/pages/wallet/send_options_form.go index 56f632c..ec3c71c 100644 --- a/pages/wallet/send_options_form.go +++ b/pages/wallet/send_options_form.go @@ -1,7 +1,6 @@ package page_wallet import ( - "gioui.org/io/key" "gioui.org/layout" "gioui.org/op" "gioui.org/unit" @@ -37,8 +36,7 @@ func NewPageSendOptionsForm() *PageSendOptionsForm { txtDescription := components.NewTextField() txtDescription.Editor().SingleLine = false txtDescription.Editor().Submit = false - txtDstPort := components.NewTextField() - txtDstPort.Editor().InputHint = key.HintNumeric + txtDstPort := components.NewNumberTextField() animationEnter := animation.NewAnimation(false, gween.NewSequence( gween.New(-1, 0, .25, ease.Linear), diff --git a/pages/wallet/settings.go b/pages/wallet/settings.go index 3167b9c..0223965 100644 --- a/pages/wallet/settings.go +++ b/pages/wallet/settings.go @@ -1,6 +1,8 @@ package page_wallet import ( + "encoding/csv" + "fmt" "image/color" "gioui.org/font" @@ -17,6 +19,7 @@ import ( "github.com/g45t345rt/g45w/components" "github.com/g45t345rt/g45w/containers/notification_modals" "github.com/g45t345rt/g45w/lang" + "github.com/g45t345rt/g45w/pages" "github.com/g45t345rt/g45w/prefabs" "github.com/g45t345rt/g45w/router" "github.com/g45t345rt/g45w/wallet_manager" @@ -35,6 +38,7 @@ type PageSettings struct { buttonSave *components.Button modalWalletPassword *prefabs.PasswordModal buttonCleanWallet *components.Button + buttonExportTxs *components.Button animationEnter *animation.Animation animationLeave *animation.Animation @@ -107,6 +111,26 @@ func NewPageSettings() *PageSettings { buttonInfo.Label.Alignment = text.Middle buttonInfo.Style.Font.Weight = font.Bold + loadingIcon, _ := widget.NewIcon(icons.NavigationRefresh) + exportIcon, _ := widget.NewIcon(icons.EditorPublish) + buttonExportTxs := components.NewButton(components.ButtonStyle{ + Icon: exportIcon, + TextColor: color.NRGBA{R: 0, G: 0, B: 0, A: 255}, + BackgroundColor: color.NRGBA{A: 0}, + TextSize: unit.Sp(16), + IconGap: unit.Dp(10), + Inset: layout.UniformInset(unit.Dp(10)), + Animation: components.NewButtonAnimationDefault(), + Border: widget.Border{ + Color: color.NRGBA{R: 0, G: 0, B: 0, A: 255}, + Width: unit.Dp(2), + CornerRadius: unit.Dp(5), + }, + LoadingIcon: loadingIcon, + }) + buttonExportTxs.Label.Alignment = text.Middle + buttonExportTxs.Style.Font.Weight = font.Bold + modalWalletPassword := prefabs.NewPasswordModal() app_instance.Router.AddLayout(router.KeyLayout{ @@ -141,6 +165,7 @@ func NewPageSettings() *PageSettings { buttonSave: buttonSave, buttonInfo: buttonInfo, buttonCleanWallet: buttonCleanWallet, + buttonExportTxs: buttonExportTxs, } } @@ -189,6 +214,11 @@ func (p *PageSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dim p.modalWalletPassword.Modal.SetVisible(true) } + if p.buttonExportTxs.Clicked() { + p.action = "export_txs" + p.modalWalletPassword.Modal.SetVisible(true) + } + submitted, password := p.modalWalletPassword.Input.Submitted() if submitted { wallet := wallet_manager.OpenedWallet @@ -197,13 +227,12 @@ func (p *PageSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dim if !validPassword { p.modalWalletPassword.StartWrongPassAnimation() } else { + p.modalWalletPassword.Modal.SetVisible(false) err := p.submitForm(gtx, password) if err != nil { notification_modals.ErrorInstance.SetText(lang.Translate("Error"), err.Error()) notification_modals.ErrorInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) - } else { - p.modalWalletPassword.Modal.SetVisible(false) } } } @@ -260,6 +289,10 @@ func (p *PageSettings) Layout(gtx layout.Context, th *material.Theme) layout.Dim return layout.Dimensions{Size: gtx.Constraints.Max} }, + func(gtx layout.Context) layout.Dimensions { + p.buttonExportTxs.Text = lang.Translate("EXPORT TRANSACTIONS") + return p.buttonExportTxs.Layout(gtx, th) + }, func(gtx layout.Context) layout.Dimensions { p.buttonCleanWallet.Text = lang.Translate("CLEAN WALLET") @@ -313,11 +346,62 @@ func (p *PageSettings) submitForm(gtx layout.Context, password string) error { } page_instance.header.GoBack() - app_instance.Router.SetCurrent(app_instance.PAGE_WALLET_SELECT) + app_instance.Router.SetCurrent(pages.PAGE_WALLET_SELECT) wallet_manager.CloseOpenedWallet() notification_modals.SuccessInstance.SetText(lang.Translate("Success"), lang.Translate("Wallet deleted")) notification_modals.SuccessInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) + case "export_txs": + go func() error { + setError := func(err error) error { + p.buttonExportTxs.SetLoading(false) + notification_modals.ErrorInstance.SetText(lang.Translate("Error"), err.Error()) + notification_modals.ErrorInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) + return err + } + + notification_modals.InfoInstance.SetText(lang.Translate("Info"), lang.Translate("Exporting transactions...")) + notification_modals.InfoInstance.SetVisible(true, 0) + + account := wallet.Memory.GetAccount() + p.buttonExportTxs.SetLoading(true) + + file, err := app_instance.Explorer.CreateFile("transactions.csv") + if err != nil { + return setError(err) + } + + writer := csv.NewWriter(file) + defer writer.Flush() + + header := []string{"SCID", "TXID", "Height", "Blockhash", + "Coinbase", "Incoming", "Destination", "Atomic Amount", + "Atomic Burn", "Atomic Fees", "Proof", "Time", "EWData", + "Sender", "Destination Port", "Source Port"} + err = writer.Write(header) + if err != nil { + return setError(err) + } + + for scId, entries := range account.EntriesNative { + for _, entry := range entries { + row := []string{scId.String(), entry.TXID, fmt.Sprint(entry.Height), entry.BlockHash, + fmt.Sprint(entry.Coinbase), fmt.Sprint(entry.Incoming), entry.Destination, fmt.Sprint(entry.Amount), + fmt.Sprint(entry.Burn), fmt.Sprint(entry.Fees), entry.Proof, entry.Time.String(), entry.EWData, + entry.Sender, fmt.Sprint(entry.DestinationPort), fmt.Sprint(entry.SourcePort)} + err = writer.Write(row) + if err != nil { + return setError(err) + } + } + } + + p.buttonExportTxs.SetLoading(false) + notification_modals.InfoInstance.SetVisible(false, 0) + notification_modals.SuccessInstance.SetText(lang.Translate("Success"), lang.Translate("Transactions exported.")) + notification_modals.SuccessInstance.SetVisible(true, notification_modals.CLOSE_AFTER_DEFAULT) + return nil + }() case "save_changes": newWalletName := p.txtWalletName.Value() if wallet.Info.Name != newWalletName { diff --git a/pages/wallet/transaction.go b/pages/wallet/transaction.go index bfe3bdd..81b1ba0 100644 --- a/pages/wallet/transaction.go +++ b/pages/wallet/transaction.go @@ -1,13 +1,26 @@ package page_wallet import ( + "fmt" + "image" + "image/color" + + "gioui.org/font" "gioui.org/layout" "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" + "github.com/deroproject/derohe/globals" + "github.com/deroproject/derohe/rpc" "github.com/g45t345rt/g45w/animation" + "github.com/g45t345rt/g45w/assets" + "github.com/g45t345rt/g45w/components" + "github.com/g45t345rt/g45w/lang" "github.com/g45t345rt/g45w/router" + "github.com/g45t345rt/g45w/utils" "github.com/tanema/gween" "github.com/tanema/gween/ease" ) @@ -17,6 +30,25 @@ type PageTransaction struct { animationEnter *animation.Animation animationLeave *animation.Animation + entry *rpc.Entry + + srcImgCoinbase paint.ImageOp + srcImgDown paint.ImageOp + srcImgUp paint.ImageOp + txTypeImg components.Image + + txIdEditor *widget.Editor + senderDestinationEditor *widget.Editor + blockHashEditor *widget.Editor + proofEditor *widget.Editor + amountEditor *widget.Editor + burnEditor *widget.Editor + feesEditor *widget.Editor + dateEditor *widget.Editor + timeAgoEditor *widget.Editor + blockHeightEditor *widget.Editor + + payloadArgInfoList []PayloadArgInfo list *widget.List } @@ -32,6 +64,19 @@ func NewPageTransaction() *PageTransaction { gween.New(0, 1, .25, ease.Linear), )) + imgUp, _ := assets.GetImage("arrow_up_arc.png") + srcImgUp := paint.NewImageOp(imgUp) + + imgDown, _ := assets.GetImage("arrow_down_arc.png") + srcImgDown := paint.NewImageOp(imgDown) + + imgCoinbase, _ := assets.GetImage("coinbase.png") + srcImgCoinbase := paint.NewImageOp(imgCoinbase) + + txTypeImg := components.Image{ + Fit: components.Cover, + } + list := new(widget.List) list.Axis = layout.Vertical @@ -39,7 +84,22 @@ func NewPageTransaction() *PageTransaction { animationEnter: animationEnter, animationLeave: animationLeave, - list: list, + list: list, + txIdEditor: &widget.Editor{ReadOnly: true}, + senderDestinationEditor: &widget.Editor{ReadOnly: true}, + blockHashEditor: &widget.Editor{ReadOnly: true}, + proofEditor: &widget.Editor{ReadOnly: true}, + amountEditor: &widget.Editor{ReadOnly: true}, + burnEditor: &widget.Editor{ReadOnly: true}, + feesEditor: &widget.Editor{ReadOnly: true}, + dateEditor: &widget.Editor{ReadOnly: true}, + timeAgoEditor: &widget.Editor{ReadOnly: true}, + blockHeightEditor: &widget.Editor{ReadOnly: true}, + + srcImgCoinbase: srcImgCoinbase, + srcImgDown: srcImgDown, + srcImgUp: srcImgUp, + txTypeImg: txTypeImg, } } @@ -47,8 +107,73 @@ func (p *PageTransaction) IsActive() bool { return p.isActive } +func (p *PageTransaction) Clear() { + p.payloadArgInfoList = make([]PayloadArgInfo, 0) + p.txIdEditor.SetText("") + p.senderDestinationEditor.SetText("") + p.blockHashEditor.SetText("") + p.proofEditor.SetText("") + p.amountEditor.SetText("") + p.feesEditor.SetText("") + p.burnEditor.SetText("") + p.dateEditor.SetText("") + p.timeAgoEditor.SetText("") + p.blockHeightEditor.SetText("") +} + func (p *PageTransaction) Enter() { + p.Clear() + p.txIdEditor.SetText(p.entry.TXID) + + for _, arg := range p.entry.Payload_RPC { + p.payloadArgInfoList = append(p.payloadArgInfoList, *NewPayloadArgLayout(arg)) + } + + if p.entry.Incoming { + sender := p.entry.Sender + if sender == "" { + sender = "?" + } + + p.senderDestinationEditor.SetText(sender) + p.txTypeImg.Src = p.srcImgDown + } else { + p.senderDestinationEditor.SetText(p.entry.Destination) + p.txTypeImg.Src = p.srcImgUp + } + + p.blockHashEditor.SetText(p.entry.BlockHash) + p.proofEditor.SetText(p.entry.Proof) + + amount := globals.FormatMoney(p.entry.Amount) + p.amountEditor.SetText(amount) + fees := globals.FormatMoney(p.entry.Fees) + p.feesEditor.SetText(fees) + burn := globals.FormatMoney(p.entry.Burn) + p.burnEditor.SetText(burn) + date := p.entry.Time.Format("2006-01-02 15:04") + p.dateEditor.SetText(date) + timeAgo := lang.TimeAgo(p.entry.Time) + p.timeAgoEditor.SetText(timeAgo) + blockHeight := fmt.Sprint(p.entry.Height) + p.blockHeightEditor.SetText(blockHeight) + + page_instance.header.SetTitle(lang.Translate("Transaction")) + page_instance.header.Subtitle = func(gtx layout.Context, th *material.Theme) layout.Dimensions { + txId := utils.ReduceTxId(p.entry.TXID) + if txId == "" { + txId = lang.Translate("From Coinbase") + } + + lbl := material.Label(th, unit.Sp(16), txId) + return lbl.Layout(gtx) + } + p.isActive = true + if !page_instance.header.IsHistory(PAGE_TRANSACTION) { + p.animationEnter.Start() + p.animationLeave.Reset() + } } func (p *PageTransaction) Leave() { @@ -78,6 +203,131 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout. widgets := []layout.Widget{} + if p.entry.TXID != "" { + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + r := op.Record(gtx.Ops) + dims := layout.UniformInset(unit.Dp(15)).Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{ + Axis: layout.Horizontal, + Alignment: layout.Middle, + }.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + gtx.Constraints.Max.X = gtx.Dp(50) + gtx.Constraints.Max.Y = gtx.Dp(50) + return p.txTypeImg.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(10)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + editor := material.Editor(th, p.txIdEditor, "") + return editor.Layout(gtx) + }), + ) + }) + c := r.Stop() + + paint.FillShape(gtx.Ops, color.NRGBA{R: 255, G: 255, B: 255, A: 255}, + clip.UniformRRect( + image.Rectangle{Max: dims.Size}, + gtx.Dp(15), + ).Op(gtx.Ops)) + + c.Add(gtx.Ops) + return dims + }), + ) + }) + } + + if !p.entry.Coinbase { + if p.entry.Incoming { + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(th, unit.Sp(16), lang.Translate("Sender")) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + editor := material.Editor(th, p.senderDestinationEditor, "") + return editor.Layout(gtx) + }), + ) + }) + } else { + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(th, unit.Sp(16), lang.Translate("Destination")) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + editor := material.Editor(th, p.senderDestinationEditor, "") + return editor.Layout(gtx) + }), + ) + }) + } + } + + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + InfoRowLayout{Editor: p.amountEditor}.Layout(gtx, th, lang.Translate("Amount")), + InfoRowLayout{Editor: p.feesEditor}.Layout(gtx, th, lang.Translate("Fees")), + InfoRowLayout{Editor: p.burnEditor}.Layout(gtx, th, lang.Translate("Burn")), + InfoRowLayout{Editor: p.blockHeightEditor}.Layout(gtx, th, lang.Translate("Block Height")), + InfoRowLayout{Editor: p.dateEditor}.Layout(gtx, th, lang.Translate("Date")), + InfoRowLayout{Editor: p.timeAgoEditor}.Layout(gtx, th, lang.Translate("Time")), + ) + }) + + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(th, unit.Sp(16), lang.Translate("Block Hash")) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + editor := material.Editor(th, p.blockHashEditor, "") + return editor.Layout(gtx) + }), + ) + }) + + if !p.entry.Coinbase { + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(th, unit.Sp(16), lang.Translate("Proof")) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + editor := material.Editor(th, p.proofEditor, "") + return editor.Layout(gtx) + }), + ) + }) + } + + for i := range p.payloadArgInfoList { + idx := i + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return p.payloadArgInfoList[idx].Layout(gtx, th) + }) + } + + widgets = append(widgets, func(gtx layout.Context) layout.Dimensions { + return layout.Spacer{Height: unit.Dp(30)}.Layout(gtx) + }) + listStyle := material.List(th, p.list) listStyle.AnchorStrategy = material.Overlay @@ -88,3 +338,72 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout. }.Layout(gtx, widgets[index]) }) } + +type PayloadArgInfo struct { + arg rpc.Argument + editor *widget.Editor +} + +func NewPayloadArgLayout(arg rpc.Argument) *PayloadArgInfo { + editor := new(widget.Editor) + editor.ReadOnly = true + editor.SetText(fmt.Sprint(arg.Value)) + + return &PayloadArgInfo{ + editor: editor, + arg: arg, + } +} + +func (p *PayloadArgInfo) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions { + var name string + switch p.arg.Name { + case "D": + name = lang.Translate("Destination Port") + case "S": + name = lang.Translate("Source Port") + case "V": + name = lang.Translate("Value Transfer") + case "C": + name = lang.Translate("Comment") + case "E": + name = lang.Translate("Expiry") + case "R": + name = lang.Translate("Replyback Address") + case "N": + name = lang.Translate("Needs Replyback Address") + } + + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + lbl := material.Label(th, unit.Sp(16), name) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + editor := material.Editor(th, p.editor, "") + return editor.Layout(gtx) + }), + ) +} + +type InfoRowLayout struct { + Editor *widget.Editor +} + +func (i InfoRowLayout) Layout(gtx layout.Context, th *material.Theme, title string) layout.FlexChild { + return 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 { + lbl := material.Label(th, unit.Sp(16), title) + lbl.Font.Weight = font.Bold + return lbl.Layout(gtx) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + editor := material.Editor(th, i.Editor, "") + return editor.Layout(gtx) + }), + ) + }) +} diff --git a/pages/wallet_select/page.go b/pages/wallet_select/page.go index 77cd368..2349ca9 100644 --- a/pages/wallet_select/page.go +++ b/pages/wallet_select/page.go @@ -9,6 +9,7 @@ import ( "github.com/g45t345rt/g45w/animation" "github.com/g45t345rt/g45w/app_instance" "github.com/g45t345rt/g45w/containers/bottom_bar" + "github.com/g45t345rt/g45w/pages" "github.com/g45t345rt/g45w/prefabs" "github.com/g45t345rt/g45w/router" "github.com/tanema/gween" @@ -113,7 +114,7 @@ func (p *Page) Leave() { func (p *Page) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions { if bottom_bar.Instance.ButtonWallet.Button.Clicked() { - app_instance.Router.SetCurrent(app_instance.PAGE_WALLET) + app_instance.Router.SetCurrent(pages.PAGE_WALLET) } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -137,7 +138,7 @@ func (p *Page) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions } } - defer prefabs.PaintLinearGradient(gtx).Pop() + defer prefabs.PaintGrayLinearGradient(gtx).Pop() return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { diff --git a/pages/wallet_select/select_wallet.go b/pages/wallet_select/select_wallet.go index bdf4ece..917eca6 100644 --- a/pages/wallet_select/select_wallet.go +++ b/pages/wallet_select/select_wallet.go @@ -21,6 +21,7 @@ import ( "github.com/g45t345rt/g45w/components" "github.com/g45t345rt/g45w/containers/notification_modals" "github.com/g45t345rt/g45w/lang" + "github.com/g45t345rt/g45w/pages" "github.com/g45t345rt/g45w/prefabs" "github.com/g45t345rt/g45w/router" "github.com/g45t345rt/g45w/utils" @@ -199,7 +200,7 @@ func (p *PageSelectWallet) Layout(gtx layout.Context, th *material.Theme) layout wallet := wallet_manager.OpenedWallet wallet.Memory.SetOnlineMode() p.modalWalletPassword.Modal.SetVisible(false) - app_instance.Router.SetCurrent(app_instance.PAGE_WALLET) + app_instance.Router.SetCurrent(pages.PAGE_WALLET) } else { if err.Error() == "Invalid Password" { p.modalWalletPassword.StartWrongPassAnimation() diff --git a/prefabs/background.go b/prefabs/background.go index 9fabc51..8c96683 100644 --- a/prefabs/background.go +++ b/prefabs/background.go @@ -10,13 +10,20 @@ import ( "gioui.org/op/paint" ) -func PaintLinearGradient(gtx layout.Context) clip.Stack { +func PaintGrayLinearGradient(gtx layout.Context) clip.Stack { + return PaintLinearGradient(gtx, + color.NRGBA{A: 5}, + color.NRGBA{A: 50}, + ) +} + +func PaintLinearGradient(gtx layout.Context, colorStart color.NRGBA, colorEnd color.NRGBA) clip.Stack { dr := image.Rectangle{Max: gtx.Constraints.Min} paint.LinearGradientOp{ Stop1: f32.Pt(0, float32(dr.Min.Y)), Stop2: f32.Pt(0, float32(dr.Max.Y)), - Color1: color.NRGBA{R: 0, G: 0, B: 0, A: 5}, - Color2: color.NRGBA{R: 0, G: 0, B: 0, A: 50}, + Color1: colorStart, + Color2: colorEnd, }.Add(gtx.Ops) stack := clip.Rect(dr).Push(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) diff --git a/router/router.go b/router/router.go index 90fd119..c3528af 100644 --- a/router/router.go +++ b/router/router.go @@ -16,12 +16,6 @@ type KeyLayout struct { Layout LayoutFunc } -type SortKeyLayout []KeyLayout - -func (k SortKeyLayout) Len() int { return len(k) } -func (k SortKeyLayout) Swap(i, j int) { k[i], k[j] = k[j], k[i] } -func (k SortKeyLayout) Less(i, j int) bool { return k[i].DrawIndex < k[j].DrawIndex } - type Router struct { Pages map[interface{}]Page // does not keep ordering with range (use drawOrder) Current interface{} @@ -64,12 +58,15 @@ func (router *Router) SetCurrent(tag interface{}) { func (router *Router) AddLayout(keyLayout KeyLayout) { router.keyLayouts = append(router.keyLayouts, keyLayout) - sort.Sort(SortKeyLayout(router.keyLayouts)) + sort.Slice(router.keyLayouts, func(i, j int) bool { + return router.keyLayouts[i].DrawIndex < router.keyLayouts[j].DrawIndex + }) } func (router *Router) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions { if router.closeKeyboard { // mobile: force close keyboard on page change + // we probably don't need this since the keyboard automatically close when an input lose focus key.SoftKeyboardOp{Show: false}.Add(gtx.Ops) router.closeKeyboard = false } diff --git a/wallet_manager/outgoing_txs.go b/wallet_manager/outgoing_txs.go index 3d4360c..00e004c 100644 --- a/wallet_manager/outgoing_txs.go +++ b/wallet_manager/outgoing_txs.go @@ -274,3 +274,10 @@ func (w *Wallet) DelOutgoingTx(txId string) error { `, txId) return err } + +func (w *Wallet) ClearOutgoingTxs() error { + _, err := w.DB.Exec(` + DELETE FROM outgoing_txs + `) + return err +} diff --git a/wallet_manager/transfers.go b/wallet_manager/transfers.go index c6c2d1d..44ec568 100644 --- a/wallet_manager/transfers.go +++ b/wallet_manager/transfers.go @@ -87,7 +87,13 @@ func (w *Wallet) GetTransfers(scId string, params GetTransfersParams) []rpc.Entr workers := runtime.NumCPU() var wg sync.WaitGroup entryChan := make(chan rpc.Entry) + chunkSize := totalEntries / workers + if chunkSize < 50 { + chunkSize = totalEntries + workers = 1 + } + for i := 0; i < workers; i++ { start := i * chunkSize end := (i + 1) * chunkSize