Simplify lifecycle and section UI

This commit is contained in:
Joe Julian
2026-04-01 14:58:56 -07:00
parent 1ec373821d
commit 1148eb819f
2 changed files with 308 additions and 230 deletions
+30 -25
View File
@@ -29,9 +29,9 @@ import (
"git.julianfamily.org/keepassgo/apiapproval" "git.julianfamily.org/keepassgo/apiapproval"
"git.julianfamily.org/keepassgo/apiaudit" "git.julianfamily.org/keepassgo/apiaudit"
"git.julianfamily.org/keepassgo/apitokens" "git.julianfamily.org/keepassgo/apitokens"
"git.julianfamily.org/keepassgo/appstate"
keepassassets "git.julianfamily.org/keepassgo/assets" keepassassets "git.julianfamily.org/keepassgo/assets"
"git.julianfamily.org/keepassgo/autofillcache" "git.julianfamily.org/keepassgo/autofillcache"
"git.julianfamily.org/keepassgo/appstate"
"git.julianfamily.org/keepassgo/clipboard" "git.julianfamily.org/keepassgo/clipboard"
"git.julianfamily.org/keepassgo/passwords" "git.julianfamily.org/keepassgo/passwords"
"git.julianfamily.org/keepassgo/session" "git.julianfamily.org/keepassgo/session"
@@ -107,6 +107,7 @@ type recentRemoteRecord struct {
type uiPreferences struct { type uiPreferences struct {
GroupControlsHidden bool `json:"groupControlsHidden"` GroupControlsHidden bool `json:"groupControlsHidden"`
LifecycleAdvancedHidden bool `json:"lifecycleAdvancedHidden"`
} }
type entriesSectionState struct { type entriesSectionState struct {
@@ -220,6 +221,7 @@ type ui struct {
cancelDeleteGroup widget.Clickable cancelDeleteGroup widget.Clickable
addCustomField widget.Clickable addCustomField widget.Clickable
toggleGroupControls widget.Clickable toggleGroupControls widget.Clickable
toggleLifecycleAdvanced widget.Clickable
togglePasswordInline widget.Clickable togglePasswordInline widget.Clickable
toggleSyncPassword widget.Clickable toggleSyncPassword widget.Clickable
showEntries widget.Clickable showEntries widget.Clickable
@@ -300,6 +302,7 @@ type ui struct {
autofillCachePath string autofillCachePath string
editingEntry bool editingEntry bool
groupControlsHidden bool groupControlsHidden bool
lifecycleAdvancedHidden bool
recentVaults []string recentVaults []string
recentRemotes []recentRemoteRecord recentRemotes []recentRemoteRecord
recentVaultGroups map[string][]string recentVaultGroups map[string][]string
@@ -418,6 +421,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
autofillCachePath: paths.AutofillCachePath, autofillCachePath: paths.AutofillCachePath,
recentVaultGroups: map[string][]string{}, recentVaultGroups: map[string][]string{},
recentVaultUsedAt: map[string]time.Time{}, recentVaultUsedAt: map[string]time.Time{},
lifecycleAdvancedHidden: true,
now: time.Now, now: time.Now,
syncSourceMode: syncSourceLocal, syncSourceMode: syncSourceLocal,
syncDirection: syncDirectionPull, syncDirection: syncDirectionPull,
@@ -1098,6 +1102,7 @@ func (u *ui) loadUIPreferences() {
return return
} }
u.groupControlsHidden = prefs.GroupControlsHidden u.groupControlsHidden = prefs.GroupControlsHidden
u.lifecycleAdvancedHidden = prefs.LifecycleAdvancedHidden
} }
func (u *ui) saveUIPreferences() { func (u *ui) saveUIPreferences() {
@@ -1109,6 +1114,7 @@ func (u *ui) saveUIPreferences() {
} }
content, err := json.MarshalIndent(uiPreferences{ content, err := json.MarshalIndent(uiPreferences{
GroupControlsHidden: u.groupControlsHidden, GroupControlsHidden: u.groupControlsHidden,
LifecycleAdvancedHidden: u.lifecycleAdvancedHidden,
}, "", " ") }, "", " ")
if err != nil { if err != nil {
return return
@@ -1761,6 +1767,10 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
for u.showRemoteLifecycle.Clicked(gtx) { for u.showRemoteLifecycle.Clicked(gtx) {
u.lifecycleMode = "remote" u.lifecycleMode = "remote"
} }
for u.toggleLifecycleAdvanced.Clicked(gtx) {
u.lifecycleAdvancedHidden = !u.lifecycleAdvancedHidden
u.saveUIPreferences()
}
for u.showSyncLocal.Clicked(gtx) { for u.showSyncLocal.Clicked(gtx) {
u.syncSourceMode = syncSourceLocal u.syncSourceMode = syncSourceLocal
} }
@@ -2596,39 +2606,19 @@ func (u *ui) navigationHeader(gtx layout.Context) layout.Dimensions {
func (u *ui) sectionBar(gtx layout.Context) layout.Dimensions { func (u *ui) sectionBar(gtx layout.Context) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
btn := material.Button(u.theme, &u.showEntries, "Entries") return sectionTabButton(gtx, u.theme, &u.showEntries, "Entries", u.state.Section == appstate.SectionEntries)
btn.Background = accentColor
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
btn.TextSize = unit.Sp(11)
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
return btn.Layout(gtx)
}), }),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
btn := material.Button(u.theme, &u.showRecycle, "Recycle Bin") return sectionTabButton(gtx, u.theme, &u.showRecycle, "Recycle Bin", u.state.Section == appstate.SectionRecycleBin)
btn.Background = accentColor
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
btn.TextSize = unit.Sp(11)
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
return btn.Layout(gtx)
}), }),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
btn := material.Button(u.theme, &u.showAPITokens, "API Tokens") return sectionTabButton(gtx, u.theme, &u.showAPITokens, "API Tokens", u.state.Section == appstate.SectionAPITokens)
btn.Background = accentColor
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
btn.TextSize = unit.Sp(11)
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
return btn.Layout(gtx)
}), }),
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
btn := material.Button(u.theme, &u.showAPIAudit, "API Audit") return sectionTabButton(gtx, u.theme, &u.showAPIAudit, "API Audit", u.state.Section == appstate.SectionAPIAudit)
btn.Background = accentColor
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
btn.TextSize = unit.Sp(11)
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
return btn.Layout(gtx)
}), }),
) )
} }
@@ -3345,6 +3335,21 @@ func tonedButton(gtx layout.Context, th *material.Theme, click *widget.Clickable
return btn.Layout(gtx) return btn.Layout(gtx)
} }
func sectionTabButton(gtx layout.Context, th *material.Theme, click *widget.Clickable, label string, active bool) layout.Dimensions {
btn := material.Button(th, click, label)
btn.CornerRadius = unit.Dp(10)
btn.TextSize = unit.Sp(11)
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
if active {
btn.Background = accentColor
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
} else {
btn.Background = selectedColor
btn.Color = accentColor
}
return btn.Layout(gtx)
}
func fill(c color.NRGBA) layout.Widget { func fill(c color.NRGBA) layout.Widget {
return func(gtx layout.Context) layout.Dimensions { return func(gtx layout.Context) layout.Dimensions {
paint.FillShape(gtx.Ops, c, clip.Rect{Max: gtx.Constraints.Min}.Op()) paint.FillShape(gtx.Ops, c, clip.Rect{Max: gtx.Constraints.Min}.Op())
+78 -5
View File
@@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"image/color" "image/color"
"path/filepath"
"strings" "strings"
"gioui.org/layout" "gioui.org/layout"
@@ -17,11 +18,11 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.showLocalLifecycle, "Local Vault") return sectionTabButton(gtx, u.theme, &u.showLocalLifecycle, "Local Vault", u.lifecycleMode == "local")
}), }),
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout), layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.showRemoteLifecycle, "Remote Vault") return sectionTabButton(gtx, u.theme, &u.showRemoteLifecycle, "Remote Vault", u.lifecycleMode == "remote")
}), }),
) )
}), }),
@@ -61,9 +62,18 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
) )
}), }),
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
layout.Rigid(labeledEditorHelp(u.theme, "Cipher", "Supported values: aes256, chacha20", &u.securityCipher, false)), layout.Rigid(u.lifecycleAdvancedDisclosure),
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout), layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
layout.Rigid(labeledEditorHelp(u.theme, "KDF", "Supported values: aes-kdf, argon2", &u.securityKDF, false)), layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.lifecycleAdvancedHidden {
return layout.Dimensions{}
}
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(labeledEditorHelp(u.theme, "Cipher", "Used for new vaults and future saves. Supported values: aes256, chacha20.", &u.securityCipher, false)),
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
layout.Rigid(labeledEditorHelp(u.theme, "KDF", "Used for new vaults and future saves. Supported values: aes-kdf, argon2.", &u.securityKDF, false)),
)
}),
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.lifecycleMode == "remote" { if u.lifecycleMode == "remote" {
@@ -82,6 +92,36 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
) )
} }
func (u *ui) lifecycleAdvancedDisclosure(gtx layout.Context) layout.Dimensions {
return u.toggleLifecycleAdvanced.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
icon := u.expandLessIcon
if u.lifecycleAdvancedHidden {
icon = u.expandMoreIcon
}
if icon != nil {
return icon.Layout(gtx, accentColor)
}
lbl := material.Label(u.theme, unit.Sp(16), ">")
if !u.lifecycleAdvancedHidden {
lbl.Text = "v"
}
lbl.Color = accentColor
return lbl.Layout(gtx)
}),
layout.Rigid(layout.Spacer{Width: unit.Dp(4)}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(12), "Advanced Vault Settings")
lbl.Color = mutedColor
return lbl.Layout(gtx)
}),
)
})
})
}
func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions { func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
if len(u.recentVaults) == 0 { if len(u.recentVaults) == 0 {
return layout.Dimensions{} return layout.Dimensions{}
@@ -102,6 +142,9 @@ func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
for i, path := range u.recentVaults { for i, path := range u.recentVaults {
index := i index := i
label := path label := path
if friendly := friendlyRecentVaultLabel(path); friendly != "" {
label = friendly
}
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions { children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.recentVaultClicks[index], label) return tonedButton(gtx, u.theme, &u.recentVaultClicks[index], label)
})) }))
@@ -134,7 +177,7 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions {
children := make([]layout.FlexChild, 0, len(u.recentRemotes)*2) children := make([]layout.FlexChild, 0, len(u.recentRemotes)*2)
for i, record := range u.recentRemotes { for i, record := range u.recentRemotes {
index := i index := i
label := record.BaseURL + " / " + record.Path label := friendlyRecentRemoteLabel(record)
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions { children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.recentRemoteClicks[index], label) return tonedButton(gtx, u.theme, &u.recentRemoteClicks[index], label)
})) }))
@@ -148,6 +191,36 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions {
) )
} }
func friendlyRecentVaultLabel(path string) string {
value := strings.TrimSpace(path)
if value == "" {
return ""
}
base := filepath.Base(value)
if base == "." || base == string(filepath.Separator) || base == "" {
return value
}
return base
}
func friendlyRecentRemoteLabel(record recentRemoteRecord) string {
baseURL := strings.TrimSpace(record.BaseURL)
path := strings.TrimSpace(record.Path)
if baseURL == "" && path == "" {
return ""
}
host := strings.TrimSpace(strings.TrimPrefix(strings.TrimPrefix(baseURL, "https://"), "http://"))
host = strings.TrimSuffix(host, "/")
switch {
case host == "":
return path
case path == "":
return host
default:
return host + " · " + path
}
}
func (u *ui) attachmentList(gtx layout.Context) layout.Dimensions { func (u *ui) attachmentList(gtx layout.Context) layout.Dimensions {
items := u.selectedAttachmentItems() items := u.selectedAttachmentItems()
if len(items) == 0 { if len(items) == 0 {