Align desktop entries pane order with phone

This commit is contained in:
Joe Julian
2026-04-07 21:05:45 -07:00
parent e88d1fd875
commit 288cb34f1a
2 changed files with 146 additions and 129 deletions
+122 -129
View File
@@ -76,6 +76,17 @@ const (
autofillNoticeSuppressed autofillNoticeMode = "suppressed"
)
type listPanelTopSection string
const (
listPanelTopSearch listPanelTopSection = "search"
listPanelTopNavigation listPanelTopSection = "navigation"
listPanelTopPath listPanelTopSection = "path"
listPanelTopGroup listPanelTopSection = "group"
listPanelTopGroupTools listPanelTopSection = "group_tools"
listPanelTopPrimary listPanelTopSection = "primary"
)
type bannerKind string
const (
@@ -5917,6 +5928,63 @@ func (u *ui) entryRowMetrics() (unit.Dp, unit.Sp, unit.Sp, unit.Sp, unit.Sp, uni
return inset, titleSize, metaSize, urlSize, pathSize, dividerGap
}
func (u *ui) listPanelTopSections() []listPanelTopSection {
sections := make([]listPanelTopSection, 0, 6)
if u.state.Section != appstate.SectionAbout {
sections = append(sections, listPanelTopSearch)
}
if !u.isVaultLocked() {
sections = append(sections, listPanelTopNavigation)
}
if !u.isVaultLocked() && (u.state.Section == appstate.SectionEntries || u.state.Section == appstate.SectionRecycleBin) {
sections = append(sections, listPanelTopPath)
}
if !u.isVaultLocked() && u.state.Section == appstate.SectionEntries {
sections = append(sections, listPanelTopGroup, listPanelTopGroupTools)
}
if !u.isVaultLocked() {
sections = append(sections, listPanelTopPrimary)
}
return sections
}
func (u *ui) listPanelSearchRow(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{}
}
if u.mode == "phone" {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
}
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions {
editor := material.Editor(u.theme, &u.search, u.searchPlaceholder())
editor.Color = u.theme.Palette.Fg
editor.HintColor = mutedColor
return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout)
})
}
func (u *ui) listPanelPrimaryActionRow(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{}
}
if u.isVaultLocked() {
return layout.Dimensions{}
}
switch u.state.Section {
case appstate.SectionEntries:
label := "Add Entry"
if u.mode == "phone" {
label = "+ " + label
}
btn := material.Button(u.theme, &u.addEntry, label)
return btn.Layout(gtx)
case appstate.SectionAPITokens:
return tonedButton(gtx, u.theme, &u.issueAPIToken, "Issue API Token")
default:
return layout.Dimensions{}
}
}
func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
panel := card
spacing := u.sectionSpacing()
@@ -5928,58 +5996,21 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
return panel(gtx, func(gtx layout.Context) layout.Dimensions {
visibleEntries, entryClicks := u.visibleEntrySnapshot()
rows := make([]layout.Widget, 0, 16+len(visibleEntries))
if u.state.Section != appstate.SectionAbout {
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions {
editor := material.Editor(u.theme, &u.search, u.searchPlaceholder())
editor.Color = u.theme.Palette.Fg
editor.HintColor = mutedColor
return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout)
})
})
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx)
})
}
if !u.isVaultLocked() {
rows = append(rows, u.navigationHeader)
if u.state.Section == appstate.SectionEntries || u.state.Section == appstate.SectionAbout {
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx)
})
for _, section := range u.listPanelTopSections() {
switch section {
case listPanelTopSearch:
rows = append(rows, u.listPanelSearchRow)
case listPanelTopNavigation:
rows = append(rows, u.navigationHeader)
case listPanelTopPath:
rows = append(rows, u.pathBar)
case listPanelTopGroup:
rows = append(rows, u.groupBar)
case listPanelTopGroupTools:
rows = append(rows, u.groupControlsSection)
case listPanelTopPrimary:
rows = append(rows, u.listPanelPrimaryActionRow)
}
}
if !u.isVaultLocked() && (u.state.Section == appstate.SectionEntries || u.state.Section == appstate.SectionRecycleBin) {
rows = append(rows, u.pathBar)
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx)
})
}
if !u.isVaultLocked() && u.state.Section == appstate.SectionEntries {
rows = append(rows, u.groupBar)
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx)
})
rows = append(rows, u.groupControlsSection)
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx)
})
}
if !u.isVaultLocked() {
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
switch u.state.Section {
case appstate.SectionEntries:
btn := material.Button(u.theme, &u.addEntry, "+ Add Entry")
return btn.Layout(gtx)
case appstate.SectionAPITokens:
return tonedButton(gtx, u.theme, &u.issueAPIToken, "Issue API Token")
case appstate.SectionAbout:
return emptyStatePanel(gtx, u.theme, u.listEmptyState())
default:
return layout.Dimensions{}
}
})
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx)
})
@@ -6008,84 +6039,45 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
})
}
return panel(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() {
return layout.Dimensions{}
}
return u.navigationHeader(gtx)
}),
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAbout {
return emptyStatePanel(gtx, u.theme, u.listEmptyState())
}
return layout.Dimensions{}
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{}
}
return layout.Spacer{Height: spacing}.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || (u.state.Section != appstate.SectionEntries && u.state.Section != appstate.SectionRecycleBin) {
return layout.Dimensions{}
}
return u.pathBar(gtx)
}),
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
return layout.Dimensions{}
}
return u.groupBar(gtx)
}),
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
return layout.Dimensions{}
}
return u.groupControlsSection(gtx)
}),
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{}
}
if u.mode == "phone" {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
}
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions {
editor := material.Editor(u.theme, &u.search, u.searchPlaceholder())
editor.Color = u.theme.Palette.Fg
editor.HintColor = mutedColor
return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout)
})
}),
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{}
}
if u.isVaultLocked() {
return layout.Dimensions{}
}
switch u.state.Section {
case appstate.SectionEntries:
label := "Add Entry"
if u.mode == "phone" {
label = "+ " + label
children := make([]layout.FlexChild, 0, 16)
for _, section := range u.listPanelTopSections() {
switch section {
case listPanelTopSearch:
children = append(children, layout.Rigid(u.listPanelSearchRow))
case listPanelTopNavigation:
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() {
return layout.Dimensions{}
}
btn := material.Button(u.theme, &u.addEntry, label)
return btn.Layout(gtx)
case appstate.SectionAPITokens:
return tonedButton(gtx, u.theme, &u.issueAPIToken, "Issue API Token")
default:
return layout.Dimensions{}
}
}),
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
return u.navigationHeader(gtx)
}))
case listPanelTopPath:
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || (u.state.Section != appstate.SectionEntries && u.state.Section != appstate.SectionRecycleBin) {
return layout.Dimensions{}
}
return u.pathBar(gtx)
}))
case listPanelTopGroup:
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
return layout.Dimensions{}
}
return u.groupBar(gtx)
}))
case listPanelTopGroupTools:
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
return layout.Dimensions{}
}
return u.groupControlsSection(gtx)
}))
case listPanelTopPrimary:
children = append(children, layout.Rigid(u.listPanelPrimaryActionRow))
}
children = append(children, layout.Rigid(layout.Spacer{Height: spacing}.Layout))
}
children = append(children,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAPITokens {
return u.apiTokenListPanel(gtx)
@@ -6094,7 +6086,7 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
return u.apiAuditListPanel(gtx)
}
if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{}
return emptyStatePanel(gtx, u.theme, u.listEmptyState())
}
if len(u.visible) == 0 {
return emptyStatePanel(gtx, u.theme, u.listEmptyState())
@@ -6106,6 +6098,7 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
})
}),
)
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
})
}
+24
View File
@@ -251,6 +251,30 @@ func TestUISearchBehaviorIsConsistentAcrossDesktopAndPhoneLayouts(t *testing.T)
}
}
func TestUIListPanelTopSectionsMatchAcrossDesktopAndPhoneForEntries(t *testing.T) {
t.Parallel()
desktop := newUIWithModel("desktop", vault.Model{})
desktop.state.Section = appstate.SectionEntries
phone := newUIWithModel("phone", vault.Model{})
phone.state.Section = appstate.SectionEntries
want := []listPanelTopSection{
listPanelTopSearch,
listPanelTopNavigation,
listPanelTopPath,
listPanelTopGroup,
listPanelTopGroupTools,
listPanelTopPrimary,
}
if got := desktop.listPanelTopSections(); !slices.Equal(got, want) {
t.Fatalf("desktop.listPanelTopSections() = %v, want %v", got, want)
}
if got := phone.listPanelTopSections(); !slices.Equal(got, want) {
t.Fatalf("phone.listPanelTopSections() = %v, want %v", got, want)
}
}
func TestUICurrentVaultSummary(t *testing.T) {
t.Parallel()