Refine entry detail and edit panels
This commit is contained in:
@@ -299,6 +299,7 @@ type ui struct {
|
||||
syncedPath []string
|
||||
selectedHistoryIndex int
|
||||
showPassword bool
|
||||
generatedPasswordDraft bool
|
||||
togglePassword widget.Clickable
|
||||
copyAPITokenSecret widget.Clickable
|
||||
issueAPIToken widget.Clickable
|
||||
@@ -3719,11 +3720,11 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
||||
password := u.detailPasswordValue()
|
||||
titleSize := unit.Sp(26)
|
||||
titlePad := unit.Dp(10)
|
||||
sectionGap := unit.Dp(8)
|
||||
sectionGap := unit.Dp(6)
|
||||
if u.mode == "phone" {
|
||||
titleSize = unit.Sp(18)
|
||||
titlePad = unit.Dp(6)
|
||||
sectionGap = unit.Dp(6)
|
||||
titlePad = unit.Dp(4)
|
||||
sectionGap = unit.Dp(4)
|
||||
}
|
||||
rows := []layout.Widget{
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
@@ -3765,72 +3766,87 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
return 0
|
||||
}()}.Layout,
|
||||
detailLine(u.theme, "Path", strings.Join(u.displayEntryPath(item.Path), " / ")),
|
||||
layout.Spacer{Height: sectionGap}.Layout,
|
||||
detailLine(u.theme, "Username", item.Username),
|
||||
layout.Spacer{Height: sectionGap}.Layout,
|
||||
u.passwordLine("Password", password),
|
||||
layout.Spacer{Height: sectionGap}.Layout,
|
||||
detailLine(u.theme, "URL", item.URL),
|
||||
layout.Spacer{Height: sectionGap}.Layout,
|
||||
detailLine(u.theme, "Tags", strings.Join(item.Tags, ", ")),
|
||||
layout.Spacer{Height: unit.Dp(10)}.Layout,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "Quick actions")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(detailLine(u.theme, "Path", strings.Join(u.displayEntryPath(item.Path), " / "))),
|
||||
layout.Rigid(layout.Spacer{Height: sectionGap}.Layout),
|
||||
layout.Rigid(detailLine(u.theme, "Username", item.Username)),
|
||||
layout.Rigid(layout.Spacer{Height: sectionGap}.Layout),
|
||||
layout.Rigid(detailLine(u.theme, "URL", item.URL)),
|
||||
layout.Rigid(layout.Spacer{Height: sectionGap}.Layout),
|
||||
layout.Rigid(detailLine(u.theme, "Tags", strings.Join(item.Tags, ", "))),
|
||||
)
|
||||
})
|
||||
},
|
||||
layout.Spacer{Height: unit.Dp(6)}.Layout,
|
||||
layout.Spacer{Height: sectionGap}.Layout,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
if u.mode == "phone" {
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyUser, "Copy User")
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "PASSWORD")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(u.passwordLine("Password", password)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyPass, "Copy Password")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL")
|
||||
if u.mode == "phone" {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyPass, "Copy Password")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyUser, "Copy Username")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL")
|
||||
}),
|
||||
)
|
||||
}
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyPass, "Copy Password")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyUser, "Copy Username")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL")
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
return layout.Flex{}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
btn := material.Button(u.theme, &u.copyUser, "Copy User")
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
btn := material.Button(u.theme, &u.copyPass, "Copy Password")
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
btn := material.Button(u.theme, &u.copyURL, "Copy URL")
|
||||
return btn.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||
layout.Spacer{Height: unit.Dp(8)}.Layout,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "Notes")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
return compactCard(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(u.theme, unit.Sp(12), "NOTES")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Body1(u.theme, item.Notes)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
layout.Spacer{Height: unit.Dp(4)}.Layout,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Body1(u.theme, item.Notes)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
},
|
||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||
layout.Spacer{Height: unit.Dp(8)}.Layout,
|
||||
u.attachmentSummaryPanel,
|
||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||
layout.Spacer{Height: unit.Dp(8)}.Layout,
|
||||
u.historyPanel,
|
||||
layout.Spacer{Height: unit.Dp(12)}.Layout,
|
||||
layout.Spacer{Height: unit.Dp(8)}.Layout,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
switch u.state.Section {
|
||||
case appstate.SectionTemplates:
|
||||
@@ -4105,36 +4121,89 @@ func (u *ui) historyPanel(gtx layout.Context) layout.Dimensions {
|
||||
|
||||
func (u *ui) attachmentSummaryPanel(gtx layout.Context) layout.Dimensions {
|
||||
items := u.selectedAttachmentItems()
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "Attachments")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if len(items) == 0 {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "No attachments on this entry.")
|
||||
return compactCard(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(u.theme, unit.Sp(12), "ATTACHMENTS")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||
children := make([]layout.FlexChild, 0, len(items)*2)
|
||||
for i, item := range items {
|
||||
label := fmt.Sprintf("%s (%d B)", item.Name, item.Size)
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), label)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}))
|
||||
if i < len(items)-1 {
|
||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout))
|
||||
}
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), u.attachmentActionSummary())
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if len(items) == 0 {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return children
|
||||
}()...)
|
||||
}),
|
||||
)
|
||||
return layout.Inset{Top: unit.Dp(8)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||
children := make([]layout.FlexChild, 0, len(items)*2)
|
||||
selectedName := strings.TrimSpace(u.attachmentName.Text())
|
||||
for i, item := range items {
|
||||
index := i
|
||||
name := item.Name
|
||||
selected := selectedName == name
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
for u.attachmentClicks[index].Clicked(gtx) {
|
||||
u.attachmentName.SetText(name)
|
||||
}
|
||||
return u.attachmentClicks[index].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Stack{}.Layout(gtx,
|
||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
||||
size := gtx.Constraints.Min
|
||||
if size.X == 0 {
|
||||
size.X = gtx.Constraints.Max.X
|
||||
}
|
||||
if size.Y == 0 {
|
||||
size.Y = gtx.Dp(unit.Dp(58))
|
||||
}
|
||||
bg := panelColor
|
||||
if selected {
|
||||
bg = selectedColor
|
||||
}
|
||||
paint.FillShape(gtx.Ops, bg, clip.Rect{Max: size}.Op())
|
||||
if selected {
|
||||
paint.FillShape(gtx.Ops, selectedEdge, clip.Rect{Max: image.Pt(4, size.Y)}.Op())
|
||||
}
|
||||
return layout.Dimensions{Size: size}
|
||||
}),
|
||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(10)).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(u.theme, unit.Sp(12), name)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
text := fmt.Sprintf("%d B", item.Size)
|
||||
if selected {
|
||||
text += " · selected"
|
||||
}
|
||||
lbl := material.Label(u.theme, unit.Sp(11), text)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}))
|
||||
if i < len(items)-1 {
|
||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||
}
|
||||
}
|
||||
return children
|
||||
}()...)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ui) historyRow(gtx layout.Context, click *widget.Clickable, index int, item entry) layout.Dimensions {
|
||||
|
||||
+87
-1
@@ -2187,6 +2187,40 @@ func TestUIAttachmentActionsRejectDuplicateMissingAndOversizeCases(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIAttachmentActionSummaryReflectsSelectionState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
Entries: []vault.Entry{
|
||||
{
|
||||
ID: "vault-console",
|
||||
Title: "Vault Console",
|
||||
Attachments: map[string][]byte{"token.txt": []byte("original")},
|
||||
Path: []string{"Root", "Internet"},
|
||||
},
|
||||
},
|
||||
})
|
||||
u.showEntriesSection()
|
||||
u.state.NavigateToPath([]string{"Root", "Internet"})
|
||||
u.filter()
|
||||
u.state.SelectedEntryID = "vault-console"
|
||||
u.loadSelectedEntryIntoEditor()
|
||||
|
||||
if got := u.attachmentActionSummary(); !strings.Contains(got, "Select an attachment above") {
|
||||
t.Fatalf("attachmentActionSummary() = %q, want prompt to select an attachment", got)
|
||||
}
|
||||
|
||||
u.attachmentName.SetText("token.txt")
|
||||
if got := u.attachmentActionSummary(); !strings.Contains(got, "Selected attachment") {
|
||||
t.Fatalf("attachmentActionSummary() = %q, want selected attachment guidance", got)
|
||||
}
|
||||
|
||||
u.attachmentName.SetText("missing.txt")
|
||||
if got := u.attachmentActionSummary(); !strings.Contains(got, "is not on this entry yet") {
|
||||
t.Fatalf("attachmentActionSummary() = %q, want missing attachment guidance", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIRestoresSelectedEntryHistoryVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -2584,7 +2618,59 @@ func TestUIGeneratedPasswordFlowsIntoCreateEntryForm(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIBannerSurfacePrefersLoadingThenError(t *testing.T) {
|
||||
func TestUIGeneratedPasswordDraftStateClearsOnReloadAndSave(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
Entries: []vault.Entry{
|
||||
{
|
||||
ID: "vault-console",
|
||||
Title: "Vault Console",
|
||||
Username: "dannyocean",
|
||||
Password: "token-1",
|
||||
Path: []string{"Root", "Internet"},
|
||||
},
|
||||
},
|
||||
})
|
||||
u.showEntriesSection()
|
||||
u.state.NavigateToPath([]string{"Root", "Internet"})
|
||||
u.filter()
|
||||
u.state.SelectedEntryID = "vault-console"
|
||||
u.loadSelectedEntryIntoEditor()
|
||||
|
||||
if u.generatedPasswordDraft {
|
||||
t.Fatal("generatedPasswordDraft = true, want false before generating")
|
||||
}
|
||||
|
||||
u.passwordProfile.SetText("strong")
|
||||
if err := u.generatePasswordAction(); err != nil {
|
||||
t.Fatalf("generatePasswordAction() error = %v", err)
|
||||
}
|
||||
if !u.generatedPasswordDraft {
|
||||
t.Fatal("generatedPasswordDraft = false, want true after generating")
|
||||
}
|
||||
|
||||
u.loadSelectedEntryIntoEditor()
|
||||
if u.generatedPasswordDraft {
|
||||
t.Fatal("generatedPasswordDraft = true, want false after reloading entry into editor")
|
||||
}
|
||||
|
||||
u.passwordProfile.SetText("strong")
|
||||
if err := u.generatePasswordAction(); err != nil {
|
||||
t.Fatalf("generatePasswordAction() second call error = %v", err)
|
||||
}
|
||||
if !u.generatedPasswordDraft {
|
||||
t.Fatal("generatedPasswordDraft = false, want true after generating the second time")
|
||||
}
|
||||
if err := u.saveEntryAction(); err != nil {
|
||||
t.Fatalf("saveEntryAction() error = %v", err)
|
||||
}
|
||||
if u.generatedPasswordDraft {
|
||||
t.Fatal("generatedPasswordDraft = true, want false after saving")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIBannerSurfacePrefersLoadingThenErrorThenStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{})
|
||||
|
||||
@@ -41,6 +41,7 @@ func (u *ui) attachmentInput() (string, []byte, error) {
|
||||
|
||||
func (u *ui) loadSelectedEntryIntoEditor() {
|
||||
u.resetPasswordPeek()
|
||||
u.clearGeneratedPasswordDraft()
|
||||
u.selectedHistoryIndex = -1
|
||||
u.historyIndex.SetText("")
|
||||
|
||||
@@ -175,6 +176,7 @@ func (u *ui) saveEntryAction() error {
|
||||
return err
|
||||
}
|
||||
u.editingEntry = false
|
||||
u.clearGeneratedPasswordDraft()
|
||||
u.filter()
|
||||
return nil
|
||||
}
|
||||
@@ -229,6 +231,7 @@ func (u *ui) saveTemplateAction() error {
|
||||
return err
|
||||
}
|
||||
u.editingEntry = false
|
||||
u.clearGeneratedPasswordDraft()
|
||||
u.filter()
|
||||
return nil
|
||||
}
|
||||
@@ -408,6 +411,7 @@ func (u *ui) generatePasswordAction() error {
|
||||
return err
|
||||
}
|
||||
u.entryPassword.SetText(password)
|
||||
u.generatedPasswordDraft = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -489,3 +493,27 @@ func marshalFields(fields map[string]string) string {
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func (u *ui) clearGeneratedPasswordDraft() {
|
||||
u.generatedPasswordDraft = false
|
||||
}
|
||||
|
||||
func (u *ui) attachmentActionSummary() string {
|
||||
items := u.selectedAttachmentItems()
|
||||
if len(items) == 0 {
|
||||
return "No attachments yet. Add one below to store a file with this entry."
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(u.attachmentName.Text())
|
||||
if name == "" {
|
||||
return "Select an attachment above, then replace it, export it, or remove it below."
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
if item.Name == name {
|
||||
return fmt.Sprintf("Selected attachment %q. Replace it, export it, or remove it below.", name)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Attachment %q is not on this entry yet. Add it as new, or select an existing attachment above.", name)
|
||||
}
|
||||
|
||||
+301
-184
@@ -475,6 +475,42 @@ func passiveTonedButton(gtx layout.Context, th *material.Theme, label string) la
|
||||
return tonedButton(gtx, th, click, label)
|
||||
}
|
||||
|
||||
func sectionTitle(theme *material.Theme, title string) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(theme, unit.Sp(12), title)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}
|
||||
}
|
||||
|
||||
func sectionCard(gtx layout.Context, theme *material.Theme, title, detail string, body layout.Widget) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(sectionTitle(theme, title)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if strings.TrimSpace(detail) == "" {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
lbl := material.Label(theme, unit.Sp(11), detail)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if strings.TrimSpace(detail) == "" {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Spacer{Height: unit.Dp(8)}.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(body),
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func friendlyRecentVaultLabel(path string) string {
|
||||
value := strings.TrimSpace(path)
|
||||
if value == "" {
|
||||
@@ -544,29 +580,62 @@ func (u *ui) attachmentList(gtx layout.Context) layout.Dimensions {
|
||||
for i, item := range items {
|
||||
index := i
|
||||
itemName := item.Name
|
||||
label := fmt.Sprintf("%s (%d B)", itemName, item.Size)
|
||||
selected := strings.TrimSpace(u.attachmentName.Text()) == itemName
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
for u.attachmentClicks[index].Clicked(gtx) {
|
||||
u.attachmentName.SetText(itemName)
|
||||
}
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return u.attachmentClicks[index].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(8)).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(u.theme, unit.Sp(13), itemName)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), label)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
return u.attachmentClicks[index].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Stack{}.Layout(gtx,
|
||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
||||
size := gtx.Constraints.Min
|
||||
if size.X == 0 {
|
||||
size.X = gtx.Constraints.Max.X
|
||||
}
|
||||
if size.Y == 0 {
|
||||
size.Y = gtx.Dp(unit.Dp(72))
|
||||
}
|
||||
bg := panelColor
|
||||
if selected {
|
||||
bg = selectedColor
|
||||
}
|
||||
paint.FillShape(gtx.Ops, bg, clip.Rect{Max: size}.Op())
|
||||
if selected {
|
||||
paint.FillShape(gtx.Ops, selectedEdge, clip.Rect{Max: image.Pt(4, size.Y)}.Op())
|
||||
}
|
||||
return layout.Dimensions{Size: size}
|
||||
}),
|
||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(10)).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(u.theme, unit.Sp(13), itemName)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), fmt.Sprintf("%d B", item.Size))
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
text := "Select for replace, export, or remove."
|
||||
if selected {
|
||||
text = "Selected for replace, export, or remove."
|
||||
}
|
||||
lbl := material.Label(u.theme, unit.Sp(11), text)
|
||||
lbl.Color = mutedColor
|
||||
if selected {
|
||||
lbl.Color = accentColor
|
||||
}
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}))
|
||||
if i < len(items)-1 {
|
||||
@@ -581,60 +650,59 @@ func (u *ui) customFieldEditorPanel(gtx layout.Context) layout.Dimensions {
|
||||
if len(u.customFieldKeys) == 0 {
|
||||
u.setCustomFieldRows(nil)
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "CUSTOM FIELDS")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), "Add key/value pairs. Changes are only saved when you save the entry.")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||
children := make([]layout.FlexChild, 0, len(u.customFieldKeys)*2)
|
||||
for i := range u.customFieldKeys {
|
||||
index := i
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
for u.removeCustomFields[index].Clicked(gtx) {
|
||||
u.removeCustomFieldRow(index)
|
||||
return sectionCard(gtx, u.theme, "CUSTOM FIELDS", "Add key/value pairs. Changes are only saved when you save the entry.", func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||
children := make([]layout.FlexChild, 0, len(u.customFieldKeys)*2)
|
||||
for i := range u.customFieldKeys {
|
||||
index := i
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
for u.removeCustomFields[index].Clicked(gtx) {
|
||||
u.removeCustomFieldRow(index)
|
||||
}
|
||||
return compactCard(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(u.theme, unit.Sp(11), fmt.Sprintf("Field %d", index+1))
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return labeledEditor(u.theme, "Name", &u.customFieldKeys[index], false)(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return labeledEditor(u.theme, "Value", &u.customFieldValues[index], false)(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if len(u.customFieldKeys) == 1 && (strings.TrimSpace(u.customFieldKeys[index].Text()) != "" || strings.TrimSpace(u.customFieldValues[index].Text()) != "") {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Inset{Top: unit.Dp(6)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.removeCustomFields[index], "Remove Field")
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}))
|
||||
if i < len(u.customFieldKeys)-1 {
|
||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||
}
|
||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Flexed(0.38, func(gtx layout.Context) layout.Dimensions {
|
||||
return labeledEditor(u.theme, "Name", &u.customFieldKeys[index], false)(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Flexed(0.52, func(gtx layout.Context) layout.Dimensions {
|
||||
return labeledEditor(u.theme, "Value", &u.customFieldValues[index], false)(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if len(u.customFieldKeys) == 1 && (strings.TrimSpace(u.customFieldKeys[index].Text()) != "" || strings.TrimSpace(u.customFieldValues[index].Text()) != "") {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return tonedButton(gtx, u.theme, &u.removeCustomFields[index], "Remove")
|
||||
}),
|
||||
)
|
||||
}))
|
||||
if i < len(u.customFieldKeys)-1 {
|
||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||
}
|
||||
return children
|
||||
}()...)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
for u.addCustomField.Clicked(gtx) {
|
||||
u.appendCustomFieldRow("", "")
|
||||
}
|
||||
return children
|
||||
}()...)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
for u.addCustomField.Clicked(gtx) {
|
||||
u.appendCustomFieldRow("", "")
|
||||
}
|
||||
return tonedButton(gtx, u.theme, &u.addCustomField, "Add Custom Field")
|
||||
}),
|
||||
)
|
||||
return tonedButton(gtx, u.theme, &u.addCustomField, "Add Another Field")
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ui) groupControls(gtx layout.Context) layout.Dimensions {
|
||||
@@ -827,127 +895,176 @@ func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions {
|
||||
func (u *ui) entryEditorPanel(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "BASICS")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Title", &u.entryTitle, false, u.isFocused(detailFocusID(detailFieldTitle)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Username", &u.entryUsername, false, u.isFocused(detailFocusID(detailFieldUsername)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Password", &u.entryPassword, true, u.isFocused(detailFocusID(detailFieldPassword)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "URL", &u.entryURL, false, u.isFocused(detailFocusID(detailFieldURL)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Path", &u.entryPath, false, u.isFocused(detailFocusID(detailFieldPath)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Tags", &u.entryTags, false, u.isFocused(detailFocusID(detailFieldTags)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Password Profile", &u.passwordProfile, false, u.isFocused(detailFocusID(detailFieldPasswordProfile)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), u.passwordProfileOptionsText())
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "NOTES")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledMultilineEditorWithFocus(u.theme, "Notes", &u.entryNotes, false, u.isFocused(detailFocusID(detailFieldNotes)), unit.Dp(120))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(u.customFieldEditorPanel),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "HISTORY")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "History Index", &u.historyIndex, false, u.isFocused(detailFocusID(detailFieldHistoryIndex)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), "Generate Password only updates the form. Nothing is persisted until you save.")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
return sectionCard(gtx, u.theme, "BASICS", "Core entry identity and navigation fields.", func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Title", &u.entryTitle, false, u.isFocused(detailFocusID(detailFieldTitle)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Username", &u.entryUsername, false, u.isFocused(detailFocusID(detailFieldUsername)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "URL", &u.entryURL, false, u.isFocused(detailFocusID(detailFieldURL)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Path", &u.entryPath, false, u.isFocused(detailFocusID(detailFieldPath)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Tags", &u.entryTags, false, u.isFocused(detailFocusID(detailFieldTags)))),
|
||||
)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.generatePassword, "Generate Password")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.cancelEdit, "Cancel")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if u.state.Section == appstate.SectionTemplates {
|
||||
return tonedButton(gtx, u.theme, &u.saveTemplate, "Save Template")
|
||||
}
|
||||
return tonedButton(gtx, u.theme, &u.saveEntry, "Save Entry")
|
||||
}),
|
||||
)
|
||||
return sectionCard(gtx, u.theme, "PASSWORD", "Generate, review, and keep track of password changes before you save.", func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Password", &u.entryPassword, true, u.isFocused(detailFocusID(detailFieldPassword)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditorWithFocus(u.theme, "Password Profile", &u.passwordProfile, false, u.isFocused(detailFocusID(detailFieldPasswordProfile)))),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), u.passwordProfileOptionsText())
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if !u.generatedPasswordDraft {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Inset{Top: unit.Dp(8)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return compactCard(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(u.theme, unit.Sp(12), "Generated password draft")
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), "This generated password is only in the editor. Save the entry or template to persist it.")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.generatePassword, "Generate Password Draft")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if u.mode == "phone" {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyPass, "Copy Password")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyUser, "Copy Username")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL")
|
||||
}),
|
||||
)
|
||||
}
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyPass, "Copy Password")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyUser, "Copy Username")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL")
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "COPY")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.copyUser, "Copy User") }),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.copyPass, "Copy Password")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.copyURL, "Copy URL") }),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "ATTACHMENTS")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(u.attachmentList),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(labeledEditor(u.theme, "Attachment Name", &u.attachmentName, false)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditor(u.theme, "Attachment Path", &u.attachmentPath, false)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditor(u.theme, "Export Attachment Path", &u.exportAttachmentPath, false)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.addAttachment, "Add Attachment")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.replaceAttachment, "Replace Attachment")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.removeAttachment, "Remove Attachment")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.exportAttachment, "Export Attachment")
|
||||
}),
|
||||
)
|
||||
return sectionCard(gtx, u.theme, "NOTES", "Long-form context for this entry.", func(gtx layout.Context) layout.Dimensions {
|
||||
return labeledMultilineEditorWithFocus(u.theme, "Notes", &u.entryNotes, false, u.isFocused(detailFocusID(detailFieldNotes)), unit.Dp(108))(gtx)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(u.customFieldEditorPanel),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return sectionCard(gtx, u.theme, "HISTORY", "Pick a saved version index to restore into the current entry.", func(gtx layout.Context) layout.Dimensions {
|
||||
return labeledEditorWithFocus(u.theme, "History Index", &u.historyIndex, false, u.isFocused(detailFocusID(detailFieldHistoryIndex)))(gtx)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return sectionCard(gtx, u.theme, "ATTACHMENTS", u.attachmentActionSummary(), func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(u.attachmentList),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(labeledEditor(u.theme, "Attachment Name", &u.attachmentName, false)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditor(u.theme, "Attachment Path", &u.attachmentPath, false)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(labeledEditor(u.theme, "Export Attachment Path", &u.exportAttachmentPath, false)),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if u.mode == "phone" {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.addAttachment, "Add Attachment")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.replaceAttachment, "Replace Selected")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.exportAttachment, "Export Selected")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.removeAttachment, "Remove Selected")
|
||||
}),
|
||||
)
|
||||
}
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.addAttachment, "Add Attachment")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.replaceAttachment, "Replace Selected")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.exportAttachment, "Export Selected")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.removeAttachment, "Remove Selected")
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return sectionCard(gtx, u.theme, "SAVE", "Entry changes only persist after you save.", func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return tonedButton(gtx, u.theme, &u.cancelEdit, "Cancel")
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if u.state.Section == appstate.SectionTemplates {
|
||||
return tonedButton(gtx, u.theme, &u.saveTemplate, "Save Template")
|
||||
}
|
||||
return tonedButton(gtx, u.theme, &u.saveEntry, "Save Entry")
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user