From 3d6b0a7291a9c93f47b76ed553c7b12666330c80 Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Mon, 31 Jul 2023 13:18:38 -0400
Subject: [PATCH 01/11] numeric input & soft keyboard closing fix

---
 components/input.go               | 75 +++++++++++++++++++------------
 components/textfield.go           |  7 +++
 pages/wallet/send_form.go         |  4 +-
 pages/wallet/send_options_form.go |  4 +-
 router/router.go                  | 11 ++---
 5 files changed, 60 insertions(+), 41 deletions(-)

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/pages/wallet/send_form.go b/pages/wallet/send_form.go
index f622ef5..1c72042 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()
 
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/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
 	}

From b8820fc52bab606bd694ae45a5af2cc3b628efaf Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Mon, 31 Jul 2023 15:35:08 -0400
Subject: [PATCH 02/11] outgoing tx item bg & clear button

---
 .../recent_txs_modal/recent_txs_modal.go      | 176 +++++++++++++-----
 pages/node/page.go                            |   2 +-
 pages/settings/page.go                        |   2 +-
 pages/wallet/page.go                          |   2 +-
 pages/wallet_select/page.go                   |   2 +-
 prefabs/background.go                         |  13 +-
 wallet_manager/outgoing_txs.go                |   7 +
 7 files changed, 149 insertions(+), 55 deletions(-)

diff --git a/containers/recent_txs_modal/recent_txs_modal.go b/containers/recent_txs_modal/recent_txs_modal.go
index 677f012..3b8299e 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,24 @@ 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.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 +166,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 +197,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{
@@ -189,9 +242,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,41 +265,60 @@ 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 {
-			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))
-					return lbl.Layout(gtx)
-				}),
-				layout.Rigid(func(gtx layout.Context) layout.Dimensions {
-					lbl := material.Label(th, unit.Sp(16), status)
-					return lbl.Layout(gtx)
-				}),
-			)
-		}),
-		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.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.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)
-		}),
+	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 {
+		return layout.Flex{
+			Axis:      layout.Horizontal,
+			Spacing:   layout.SpaceBetween,
+			Alignment: layout.Middle,
+		}.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 {
+						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 {
+						lbl := material.Label(th, unit.Sp(16), status)
+						return lbl.Layout(gtx)
+					}),
+				)
+			}),
+			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 {
+						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.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)
+			}),
+		)
+	})
+	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/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/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/page.go b/pages/wallet/page.go
index 6a677a4..e65c6bf 100644
--- a/pages/wallet/page.go
+++ b/pages/wallet/page.go
@@ -220,7 +220,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_select/page.go b/pages/wallet_select/page.go
index 77cd368..3e24323 100644
--- a/pages/wallet_select/page.go
+++ b/pages/wallet_select/page.go
@@ -137,7 +137,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/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/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
+}

From f948c123a31b407c45550a31308ba00f1aa2c895 Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Mon, 31 Jul 2023 21:06:48 -0400
Subject: [PATCH 03/11] new tx page & timeago lang func

---
 go.mod                         |   1 +
 go.sum                         |   2 +
 lang/lang.go                   |  27 ++++
 pages/wallet/balance_tokens.go |   4 +-
 pages/wallet/page.go           |   1 +
 pages/wallet/transaction.go    | 218 ++++++++++++++++++++++++++++++++-
 6 files changed, 251 insertions(+), 2 deletions(-)

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..924ddea 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
 }
 
@@ -56,3 +69,17 @@ func Translate(eng string) string {
 
 	return value
 }
+
+func TimeAgo(date time.Time) string {
+	lang := settings.App.Language
+	switch lang {
+	case "pt":
+		return timeago.Portuguese.Format(date)
+	case "es":
+		return timeago.Spanish.Format(date)
+	case "fr":
+		return timeago.French.Format(date)
+	}
+
+	return timeago.English.Format(date)
+}
diff --git a/pages/wallet/balance_tokens.go b/pages/wallet/balance_tokens.go
index 102c7d1..6677ebd 100644
--- a/pages/wallet/balance_tokens.go
+++ b/pages/wallet/balance_tokens.go
@@ -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 e65c6bf..a6791b6 100644
--- a/pages/wallet/page.go
+++ b/pages/wallet/page.go
@@ -127,6 +127,7 @@ func New() *Page {
 		pageSendOptionsForm: pageSendOptionsForm,
 		pageSCFolders:       pageSCFolders,
 		pageContacts:        pageContacts,
+		pageTransaction:     pageTransaction,
 
 		pageRouter: pageRouter,
 	}
diff --git a/pages/wallet/transaction.go b/pages/wallet/transaction.go
index bfe3bdd..5b6cb02 100644
--- a/pages/wallet/transaction.go
+++ b/pages/wallet/transaction.go
@@ -1,13 +1,27 @@
 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/text"
 	"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 +31,13 @@ type PageTransaction struct {
 
 	animationEnter *animation.Animation
 	animationLeave *animation.Animation
+	entry          *rpc.Entry
+	txIdEditor     *widget.Editor
+
+	srcImgCoinbase paint.ImageOp
+	srcImgDown     paint.ImageOp
+	srcImgUp       paint.ImageOp
+	txTypeImg      components.Image
 
 	list *widget.List
 }
@@ -32,6 +53,23 @@ func NewPageTransaction() *PageTransaction {
 		gween.New(0, 1, .25, ease.Linear),
 	))
 
+	txIdEditor := new(widget.Editor)
+	txIdEditor.WrapPolicy = text.WrapGraphemes
+	txIdEditor.ReadOnly = true
+
+	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 +77,13 @@ func NewPageTransaction() *PageTransaction {
 		animationEnter: animationEnter,
 		animationLeave: animationLeave,
 
-		list: list,
+		list:       list,
+		txIdEditor: txIdEditor,
+
+		srcImgCoinbase: srcImgCoinbase,
+		srcImgDown:     srcImgDown,
+		srcImgUp:       srcImgUp,
+		txTypeImg:      txTypeImg,
 	}
 }
 
@@ -48,7 +92,30 @@ func (p *PageTransaction) IsActive() bool {
 }
 
 func (p *PageTransaction) Enter() {
+	p.txIdEditor.SetText(p.entry.TXID)
+	fmt.Println(p.entry)
+	if p.entry.Incoming {
+		p.txTypeImg.Src = p.srcImgDown
+	} else {
+		p.txTypeImg.Src = p.srcImgUp
+	}
+
+	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 = "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 +145,130 @@ 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 {
+						lbl := material.Label(th, unit.Sp(16), p.entry.Sender)
+						return lbl.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 {
+						lbl := material.Label(th, unit.Sp(16), p.entry.Destination)
+						return lbl.Layout(gtx)
+					}),
+				)
+			})
+		}
+	}
+
+	widgets = append(widgets, func(gtx layout.Context) layout.Dimensions {
+		amount := globals.FormatMoney(p.entry.Amount)
+		fees := globals.FormatMoney(p.entry.Fees)
+		burn := globals.FormatMoney(p.entry.Burn)
+		date := p.entry.Time.Format("2006-01-02 15:04")
+		timeAgo := lang.TimeAgo(p.entry.Time)
+
+		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
+			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Amount"), amount),
+			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Fees"), fees),
+			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Burn"), burn),
+			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Block Height"), fmt.Sprint(p.entry.Height)),
+			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Date"), date),
+			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Time"), timeAgo),
+		)
+	})
+
+	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"))
+				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 {
+				lbl := material.Label(th, unit.Sp(16), p.entry.BlockHash)
+				return lbl.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 {
+					lbl := material.Label(th, unit.Sp(16), p.entry.Proof)
+					return lbl.Layout(gtx)
+				}),
+			)
+		})
+	}
+
+	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 +279,28 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 		}.Layout(gtx, widgets[index])
 	})
 }
+
+type InfoRowLayout struct {
+	editor *widget.Editor
+}
+
+func (i InfoRowLayout) Layout(gtx layout.Context, th *material.Theme, title string, value string) layout.FlexChild {
+	if i.editor == nil {
+		i.editor = new(widget.Editor)
+		i.editor.SetText(value)
+	}
+
+	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)
+			}),
+		)
+	})
+}

From aba7c1f4736838dee3ed1491ed475a47cd6ec269 Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Tue, 1 Aug 2023 10:29:26 -0400
Subject: [PATCH 04/11] use editors for tx page for text selection

---
 pages/wallet/transaction.go | 97 ++++++++++++++++++++++---------------
 1 file changed, 59 insertions(+), 38 deletions(-)

diff --git a/pages/wallet/transaction.go b/pages/wallet/transaction.go
index 5b6cb02..aecf105 100644
--- a/pages/wallet/transaction.go
+++ b/pages/wallet/transaction.go
@@ -10,7 +10,6 @@ import (
 	"gioui.org/op"
 	"gioui.org/op/clip"
 	"gioui.org/op/paint"
-	"gioui.org/text"
 	"gioui.org/unit"
 	"gioui.org/widget"
 	"gioui.org/widget/material"
@@ -32,13 +31,23 @@ type PageTransaction struct {
 	animationEnter *animation.Animation
 	animationLeave *animation.Animation
 	entry          *rpc.Entry
-	txIdEditor     *widget.Editor
 
 	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
+
 	list *widget.List
 }
 
@@ -53,10 +62,6 @@ func NewPageTransaction() *PageTransaction {
 		gween.New(0, 1, .25, ease.Linear),
 	))
 
-	txIdEditor := new(widget.Editor)
-	txIdEditor.WrapPolicy = text.WrapGraphemes
-	txIdEditor.ReadOnly = true
-
 	imgUp, _ := assets.GetImage("arrow_up_arc.png")
 	srcImgUp := paint.NewImageOp(imgUp)
 
@@ -77,8 +82,17 @@ func NewPageTransaction() *PageTransaction {
 		animationEnter: animationEnter,
 		animationLeave: animationLeave,
 
-		list:       list,
-		txIdEditor: txIdEditor,
+		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,
@@ -93,13 +107,31 @@ func (p *PageTransaction) IsActive() bool {
 
 func (p *PageTransaction) Enter() {
 	p.txIdEditor.SetText(p.entry.TXID)
-	fmt.Println(p.entry)
+
 	if p.entry.Incoming {
+		p.senderDestinationEditor.SetText(p.entry.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)
@@ -193,8 +225,8 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 					}),
 					layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout),
 					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
-						lbl := material.Label(th, unit.Sp(16), p.entry.Sender)
-						return lbl.Layout(gtx)
+						editor := material.Editor(th, p.senderDestinationEditor, "")
+						return editor.Layout(gtx)
 					}),
 				)
 			})
@@ -208,8 +240,8 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 					}),
 					layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout),
 					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
-						lbl := material.Label(th, unit.Sp(16), p.entry.Destination)
-						return lbl.Layout(gtx)
+						editor := material.Editor(th, p.senderDestinationEditor, "")
+						return editor.Layout(gtx)
 					}),
 				)
 			})
@@ -217,33 +249,27 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 	}
 
 	widgets = append(widgets, func(gtx layout.Context) layout.Dimensions {
-		amount := globals.FormatMoney(p.entry.Amount)
-		fees := globals.FormatMoney(p.entry.Fees)
-		burn := globals.FormatMoney(p.entry.Burn)
-		date := p.entry.Time.Format("2006-01-02 15:04")
-		timeAgo := lang.TimeAgo(p.entry.Time)
-
 		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
-			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Amount"), amount),
-			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Fees"), fees),
-			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Burn"), burn),
-			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Block Height"), fmt.Sprint(p.entry.Height)),
-			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Date"), date),
-			InfoRowLayout{}.Layout(gtx, th, lang.Translate("Time"), timeAgo),
+			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"))
+				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 {
-				lbl := material.Label(th, unit.Sp(16), p.entry.BlockHash)
-				return lbl.Layout(gtx)
+				editor := material.Editor(th, p.blockHashEditor, "")
+				return editor.Layout(gtx)
 			}),
 		)
 	})
@@ -258,8 +284,8 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 				}),
 				layout.Rigid(layout.Spacer{Height: unit.Dp(5)}.Layout),
 				layout.Rigid(func(gtx layout.Context) layout.Dimensions {
-					lbl := material.Label(th, unit.Sp(16), p.entry.Proof)
-					return lbl.Layout(gtx)
+					editor := material.Editor(th, p.proofEditor, "")
+					return editor.Layout(gtx)
 				}),
 			)
 		})
@@ -281,15 +307,10 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 }
 
 type InfoRowLayout struct {
-	editor *widget.Editor
+	Editor *widget.Editor
 }
 
-func (i InfoRowLayout) Layout(gtx layout.Context, th *material.Theme, title string, value string) layout.FlexChild {
-	if i.editor == nil {
-		i.editor = new(widget.Editor)
-		i.editor.SetText(value)
-	}
-
+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 {
@@ -298,7 +319,7 @@ func (i InfoRowLayout) Layout(gtx layout.Context, th *material.Theme, title stri
 				return lbl.Layout(gtx)
 			}),
 			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
-				editor := material.Editor(th, i.editor, "")
+				editor := material.Editor(th, i.Editor, "")
 				return editor.Layout(gtx)
 			}),
 		)

From c949d13b3c31d11e717a337603844fdfe5d362ca Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Tue, 1 Aug 2023 11:28:31 -0400
Subject: [PATCH 05/11] tx page display rpc payload e.g comment

---
 pages/wallet/transaction.go | 72 +++++++++++++++++++++++++++++++++++--
 1 file changed, 70 insertions(+), 2 deletions(-)

diff --git a/pages/wallet/transaction.go b/pages/wallet/transaction.go
index aecf105..86cc5f1 100644
--- a/pages/wallet/transaction.go
+++ b/pages/wallet/transaction.go
@@ -48,6 +48,8 @@ type PageTransaction struct {
 	timeAgoEditor           *widget.Editor
 	blockHeightEditor       *widget.Editor
 
+	payloadArgInfoList []PayloadArgInfo
+
 	list *widget.List
 }
 
@@ -93,6 +95,7 @@ func NewPageTransaction() *PageTransaction {
 		dateEditor:              &widget.Editor{ReadOnly: true},
 		timeAgoEditor:           &widget.Editor{ReadOnly: true},
 		blockHeightEditor:       &widget.Editor{ReadOnly: true},
+		payloadArgInfoList:      make([]PayloadArgInfo, 0),
 
 		srcImgCoinbase: srcImgCoinbase,
 		srcImgDown:     srcImgDown,
@@ -108,8 +111,17 @@ func (p *PageTransaction) IsActive() bool {
 func (p *PageTransaction) Enter() {
 	p.txIdEditor.SetText(p.entry.TXID)
 
+	for _, arg := range p.entry.Payload_RPC {
+		p.payloadArgInfoList = append(p.payloadArgInfoList, *NewPayloadArgLayout(arg))
+	}
+
 	if p.entry.Incoming {
-		p.senderDestinationEditor.SetText(p.entry.Sender)
+		sender := p.entry.Sender
+		if sender == "" {
+			sender = "?"
+		}
+
+		p.senderDestinationEditor.SetText(sender)
 		p.txTypeImg.Src = p.srcImgDown
 	} else {
 		p.senderDestinationEditor.SetText(p.entry.Destination)
@@ -136,7 +148,7 @@ func (p *PageTransaction) Enter() {
 	page_instance.header.Subtitle = func(gtx layout.Context, th *material.Theme) layout.Dimensions {
 		txId := utils.ReduceTxId(p.entry.TXID)
 		if txId == "" {
-			txId = "From Coinbase"
+			txId = lang.Translate("From Coinbase")
 		}
 
 		lbl := material.Label(th, unit.Sp(16), txId)
@@ -291,6 +303,13 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 		})
 	}
 
+	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)
 	})
@@ -306,6 +325,55 @@ func (p *PageTransaction) Layout(gtx layout.Context, th *material.Theme) layout.
 	})
 }
 
+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
 }

From b11e6ce3f7089650c363d5ad7e209df1ca74646c Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Tue, 1 Aug 2023 11:50:10 -0400
Subject: [PATCH 06/11] clear tx page on enter

---
 pages/wallet/transaction.go | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/pages/wallet/transaction.go b/pages/wallet/transaction.go
index 86cc5f1..81b1ba0 100644
--- a/pages/wallet/transaction.go
+++ b/pages/wallet/transaction.go
@@ -95,7 +95,6 @@ func NewPageTransaction() *PageTransaction {
 		dateEditor:              &widget.Editor{ReadOnly: true},
 		timeAgoEditor:           &widget.Editor{ReadOnly: true},
 		blockHeightEditor:       &widget.Editor{ReadOnly: true},
-		payloadArgInfoList:      make([]PayloadArgInfo, 0),
 
 		srcImgCoinbase: srcImgCoinbase,
 		srcImgDown:     srcImgDown,
@@ -108,7 +107,22 @@ 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 {

From 9bc139717452c838f16bd2da0d8cb3345d3bb2e2 Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Tue, 1 Aug 2023 14:22:48 -0400
Subject: [PATCH 07/11] fixes

---
 .../recent_txs_modal/recent_txs_modal.go      |  2 +-
 pages/wallet/balance_tokens.go                |  2 +-
 pages/wallet/sc_token.go                      | 34 ++++++++++++-------
 wallet_manager/transfers.go                   |  6 ++++
 4 files changed, 30 insertions(+), 14 deletions(-)

diff --git a/containers/recent_txs_modal/recent_txs_modal.go b/containers/recent_txs_modal/recent_txs_modal.go
index 3b8299e..0029c51 100644
--- a/containers/recent_txs_modal/recent_txs_modal.go
+++ b/containers/recent_txs_modal/recent_txs_modal.go
@@ -296,7 +296,7 @@ func (item *TxItem) Layout(gtx layout.Context, th *material.Theme) layout.Dimens
 						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)
 					}),
diff --git a/pages/wallet/balance_tokens.go b/pages/wallet/balance_tokens.go
index 6677ebd..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)
 				})
 			})
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/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

From 7a5379a093e92ef599bdb7312bc408f067c68fb3 Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Tue, 1 Aug 2023 14:48:00 -0400
Subject: [PATCH 08/11] more timeago lang configs

---
 lang/lang.go     |  14 ---
 lang/time_ago.go | 281 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 281 insertions(+), 14 deletions(-)
 create mode 100644 lang/time_ago.go

diff --git a/lang/lang.go b/lang/lang.go
index 924ddea..8abcd44 100644
--- a/lang/lang.go
+++ b/lang/lang.go
@@ -69,17 +69,3 @@ func Translate(eng string) string {
 
 	return value
 }
-
-func TimeAgo(date time.Time) string {
-	lang := settings.App.Language
-	switch lang {
-	case "pt":
-		return timeago.Portuguese.Format(date)
-	case "es":
-		return timeago.Spanish.Format(date)
-	case "fr":
-		return timeago.French.Format(date)
-	}
-
-	return timeago.English.Format(date)
-}
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)
+}

From 65bcad136dae079f728c63d0643970b2c38b8109 Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Wed, 2 Aug 2023 11:36:21 -0400
Subject: [PATCH 09/11] recent tx remove feature

---
 .../recent_txs_modal/recent_txs_modal.go      | 112 ++++++++++++------
 1 file changed, 77 insertions(+), 35 deletions(-)

diff --git a/containers/recent_txs_modal/recent_txs_modal.go b/containers/recent_txs_modal/recent_txs_modal.go
index 0029c51..e93366c 100644
--- a/containers/recent_txs_modal/recent_txs_modal.go
+++ b/containers/recent_txs_modal/recent_txs_modal.go
@@ -154,6 +154,7 @@ func (r *RecentTxsModal) layout(gtx layout.Context, th *material.Theme) {
 		} 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()
 		}
 	}
 
@@ -214,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 {
@@ -229,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,
 	}
 }
 
@@ -265,47 +276,78 @@ func (item *TxItem) Layout(gtx layout.Context, th *material.Theme) layout.Dimens
 		go open.Run(fmt.Sprintf("https://explorer.dero.io/tx/%s", txId))
 	}
 
+	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 {
+					lbl := material.Label(th, unit.Sp(16), status)
+					return lbl.Layout(gtx)
+				}),
+			)
+		}))
+
+		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 {
+					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), lang.TimeAgo(date))
+					lbl.Alignment = text.End
+					return lbl.Layout(gtx)
+				}),
+			)
+		}))
+
+		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,
-			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 {
-						lbl := material.Label(th, unit.Sp(16), status)
-						return lbl.Layout(gtx)
-					}),
-				)
-			}),
-			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 {
-						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), 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...,
 		)
 	})
 	c := r.Stop()

From fb3a7b9473dee2dc30805999cb157af620ccd11d Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Wed, 2 Aug 2023 14:05:22 -0400
Subject: [PATCH 10/11] refactoring and close wallet bug fix

---
 app_instance/app_instance.go                  |  29 ++++-
 containers/bottom_bar/bottom_bar.go           |  13 +-
 containers/containers.go                      |  17 +++
 containers/node_status_bar/node_status_bar.go |   3 +-
 main.go                                       | 114 +++++++-----------
 pages/pages.go                                |   8 ++
 pages/wallet/page.go                          |   3 +-
 pages/wallet/send_form.go                     |  44 +++----
 pages/wallet/settings.go                      |   3 +-
 pages/wallet_select/page.go                   |   3 +-
 pages/wallet_select/select_wallet.go          |   3 +-
 11 files changed, 129 insertions(+), 111 deletions(-)
 create mode 100644 containers/containers.go
 create mode 100644 pages/pages.go

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/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/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/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/wallet/page.go b/pages/wallet/page.go
index a6791b6..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"
@@ -159,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)
 	}
 }
 
diff --git a/pages/wallet/send_form.go b/pages/wallet/send_form.go
index 1c72042..1bd931f 100644
--- a/pages/wallet/send_form.go
+++ b/pages/wallet/send_form.go
@@ -268,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/settings.go b/pages/wallet/settings.go
index 3167b9c..0c3c002 100644
--- a/pages/wallet/settings.go
+++ b/pages/wallet/settings.go
@@ -17,6 +17,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"
@@ -313,7 +314,7 @@ 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"))
diff --git a/pages/wallet_select/page.go b/pages/wallet_select/page.go
index 3e24323..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,
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()

From 52f89b8d98a6f15755e3a217f3c9eaf40ca56c9a Mon Sep 17 00:00:00 2001
From: g45t345rt <49697006+g45t345rt@users.noreply.github.com>
Date: Wed, 2 Aug 2023 17:06:17 -0400
Subject: [PATCH 11/11] export all wallet transactions in csv format

---
 pages/wallet/settings.go | 87 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 85 insertions(+), 2 deletions(-)

diff --git a/pages/wallet/settings.go b/pages/wallet/settings.go
index 0c3c002..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"
@@ -36,6 +38,7 @@ type PageSettings struct {
 	buttonSave              *components.Button
 	modalWalletPassword     *prefabs.PasswordModal
 	buttonCleanWallet       *components.Button
+	buttonExportTxs         *components.Button
 
 	animationEnter *animation.Animation
 	animationLeave *animation.Animation
@@ -108,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{
@@ -142,6 +165,7 @@ func NewPageSettings() *PageSettings {
 		buttonSave:              buttonSave,
 		buttonInfo:              buttonInfo,
 		buttonCleanWallet:       buttonCleanWallet,
+		buttonExportTxs:         buttonExportTxs,
 	}
 }
 
@@ -190,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
@@ -198,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)
 			}
 		}
 	}
@@ -261,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")
 
@@ -319,6 +351,57 @@ func (p *PageSettings) submitForm(gtx layout.Context, password string) error {
 
 		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 {