~whereswaldon/rosebud

8df6a7866b9e1c43ea555817d99a0c42b728f51e — Chris Waldon 1 year, 2 months ago fd484cf
appwidget{,/apptheme}: implement simple tx validation

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
M appwidget/apptheme/amount-editor.go => appwidget/apptheme/amount-editor.go +1 -1
@@ 22,7 22,7 @@ func Editor(th *Theme, state *widget.Editor, hint string) EditorStyle {
			BorderColor: th.Surface3.Contrast,
			BorderWidth: unit.Dp(1),
			Padding:     layout.UniformInset(8),
			Margin:      layout.UniformInset(2),
			Margin:      layout.UniformInset(1),
			Rounding:    unit.Dp(8),
			Bg:          th.Surface3.Base,
		},

M appwidget/apptheme/tx-editor.go => appwidget/apptheme/tx-editor.go +1 -0
@@ 32,6 32,7 @@ func TxEditor(th *Theme, state *appwidget.TxEditor, hint string) TxEditorStyle {
			BorderColor: th.Surface3.Contrast,
			BorderWidth: unit.Dp(1),
			Padding:     layout.UniformInset(unit.Dp(8)),
			Margin:      layout.UniformInset(1),
			Rounding:    unit.Dp(8),
			Bg:          th.Surface3.Base,
		},

M appwidget/apptheme/tx-form.go => appwidget/apptheme/tx-form.go +17 -6
@@ 75,10 75,11 @@ func (t TxHeaderStyle) Layout(gtx C) D {
	return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
		layout.Rigid(func(gtx C) D {
			return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
				layout.Flexed(.2, func(gtx C) D {
				layout.Rigid(func(gtx C) D {
					gtx.Constraints.Min.X = gtx.Dp(100)
					return t.Date.Layout(gtx)
				}),
				layout.Flexed(.8, func(gtx C) D {
				layout.Flexed(1, func(gtx C) D {
					return t.Payee.Layout(gtx)
				}),
			)


@@ 88,14 89,16 @@ func (t TxHeaderStyle) Layout(gtx C) D {
}

type TxFormStyle struct {
	State *appwidget.TxForm
	th    *Theme
	State     *appwidget.TxForm
	th        *Theme
	SubmitBtn material.ButtonStyle
}

func TxForm(th *Theme, state *appwidget.TxForm) TxFormStyle {
	return TxFormStyle{
		th:    th,
		State: state,
		th:        th,
		State:     state,
		SubmitBtn: material.Button(th.Th, &state.SubmitBtn, "Submit"),
	}
}



@@ 112,6 115,14 @@ func (t TxFormStyle) Layout(gtx C) D {
				return Row(t.th, row).Layout(gtx)
			})
		}
		children = append(children, layout.Rigid(func(gtx C) D {
			if !t.State.IsValid() {
				gtx = gtx.Disabled()
			}
			return layout.E.Layout(gtx, func(gtx C) D {
				return layout.UniformInset(1).Layout(gtx, t.SubmitBtn.Layout)
			})
		}))
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
	})
}

M appwidget/tx-form.go => appwidget/tx-form.go +32 -1
@@ 30,6 30,10 @@ func (t *TxRow) Populated() bool {
	return t.Account.Editor.Len() > 0 || t.Amount.Len() > 0
}

func (t *TxRow) Valid() (account, amount bool) {
	return t.Account.Editor.Len() > 0, t.Amount.Len() > 0
}

func (t *TxRow) Value() ledger.Account {
	bal, _ := decimal.NewFromString(t.Amount.Text())



@@ 39,13 43,19 @@ func (t *TxRow) Value() ledger.Account {
	}
}

type TransactionSubmittedEvent struct {
	value ledger.Transaction
}

type TxForm struct {
	DateEditor  widget.Editor
	PayeeEditor TxEditor
	Rows        []*TxRow
	Hints       []string
	SubmitBtn   widget.Clickable
	events      []any
	value       ledger.Transaction
	valid       bool
}

func (t *TxForm) Events() (events []any) {


@@ 59,6 69,10 @@ func (t *TxForm) SetAccountSuggestions(suggestions []string) {
	}
}

func (t *TxForm) IsValid() bool {
	return t.valid
}

// balance returns whether the transaction is currently balanced. If it is
// balanced, but has an empty amount with an inferred value, the index of
// the empty accound and its inferred value are returned. If there is no


@@ 88,6 102,7 @@ func balance(tx *ledger.Transaction) (emptyIdx int, value decimal.Decimal, ok bo
func (t *TxForm) Layout(gtx C, w layout.Widget) D {
	populated := 0
	t.value.AccountChanges = t.value.AccountChanges[:0]
	accounts, amounts := 0, 0
	for i := range t.Rows {
		if t.Rows[i].Populated() {
			populated++


@@ 98,19 113,32 @@ func (t *TxForm) Layout(gtx C, w layout.Widget) D {
			} else {
				t.Rows[i].Message = ""
			}
			accountValid, amountValid := t.Rows[i].Valid()
			if accountValid {
				accounts++
			}
			if amountValid {
				amounts++
			}
		}
	}
	t.value.Payee = t.PayeeEditor.Editor.Text()
	payeeValid := len(t.value.Payee) > 0
	dateValid := false
	if t.DateEditor.Text() != "" {
		t.value.Date, _ = time.Parse("2006-01-02", t.DateEditor.Text())
		var dateErr error
		t.value.Date, dateErr = time.Parse("2006-01-02", t.DateEditor.Text())
		dateValid = dateErr == nil
	} else {
		t.DateEditor.SetText(time.Now().Format("2006-01-02"))
		dateValid = true
	}
	t.Rows = ds.EnsureFilledSize(t.Rows, max(populated+1, 2))
	emptyIdx, emptyValue, isBalanced := balance(&t.value)
	if isBalanced && emptyIdx >= 0 {
		t.Rows[emptyIdx].Hint = emptyValue.StringFixedBank()
	}
	t.valid = dateValid && payeeValid && isBalanced && populated >= 2 && accounts == populated && amounts >= (populated-1)
	dims := w(gtx)
	for i := range t.Rows {
		t.events = append(t.events, t.Rows[i].Account.Events()...)


@@ 118,6 146,9 @@ func (t *TxForm) Layout(gtx C, w layout.Widget) D {
			t.events = append(t.events, e)
		}
	}
	if t.SubmitBtn.Clicked() && t.IsValid() {
		t.events = append(t.events, TransactionSubmittedEvent{t.value})
	}

	return dims
}