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
+101 -108
View File
@@ -76,6 +76,17 @@ const (
autofillNoticeSuppressed autofillNoticeMode = "suppressed" 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 type bannerKind string
const ( 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 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 { func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
panel := card panel := card
spacing := u.sectionSpacing() 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 { return panel(gtx, func(gtx layout.Context) layout.Dimensions {
visibleEntries, entryClicks := u.visibleEntrySnapshot() visibleEntries, entryClicks := u.visibleEntrySnapshot()
rows := make([]layout.Widget, 0, 16+len(visibleEntries)) rows := make([]layout.Widget, 0, 16+len(visibleEntries))
if u.state.Section != appstate.SectionAbout { for _, section := range u.listPanelTopSections() {
rows = append(rows, func(gtx layout.Context) layout.Dimensions { switch section {
gtx.Constraints.Min.X = gtx.Constraints.Max.X case listPanelTopSearch:
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions { rows = append(rows, u.listPanelSearchRow)
editor := material.Editor(u.theme, &u.search, u.searchPlaceholder()) case listPanelTopNavigation:
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) rows = append(rows, u.navigationHeader)
if u.state.Section == appstate.SectionEntries || u.state.Section == appstate.SectionAbout { case listPanelTopPath:
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx)
})
}
}
if !u.isVaultLocked() && (u.state.Section == appstate.SectionEntries || u.state.Section == appstate.SectionRecycleBin) {
rows = append(rows, u.pathBar) rows = append(rows, u.pathBar)
rows = append(rows, func(gtx layout.Context) layout.Dimensions { case listPanelTopGroup:
return layout.Spacer{Height: spacing}.Layout(gtx)
})
}
if !u.isVaultLocked() && u.state.Section == appstate.SectionEntries {
rows = append(rows, u.groupBar) rows = append(rows, u.groupBar)
rows = append(rows, func(gtx layout.Context) layout.Dimensions { case listPanelTopGroupTools:
return layout.Spacer{Height: spacing}.Layout(gtx)
})
rows = append(rows, u.groupControlsSection) rows = append(rows, u.groupControlsSection)
rows = append(rows, func(gtx layout.Context) layout.Dimensions { case listPanelTopPrimary:
return layout.Spacer{Height: spacing}.Layout(gtx) rows = append(rows, u.listPanelPrimaryActionRow)
})
} }
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 { rows = append(rows, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Height: spacing}.Layout(gtx) 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 panel(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children := make([]layout.FlexChild, 0, 16)
layout.Rigid(func(gtx layout.Context) layout.Dimensions { 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() { if u.isVaultLocked() {
return layout.Dimensions{} return layout.Dimensions{}
} }
return u.navigationHeader(gtx) return u.navigationHeader(gtx)
}), }))
layout.Rigid(layout.Spacer{Height: spacing}.Layout), case listPanelTopPath:
layout.Rigid(func(gtx layout.Context) layout.Dimensions { children = append(children, 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) { if u.isVaultLocked() || (u.state.Section != appstate.SectionEntries && u.state.Section != appstate.SectionRecycleBin) {
return layout.Dimensions{} return layout.Dimensions{}
} }
return u.pathBar(gtx) return u.pathBar(gtx)
}), }))
layout.Rigid(layout.Spacer{Height: spacing}.Layout), case listPanelTopGroup:
layout.Rigid(func(gtx layout.Context) layout.Dimensions { children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || u.state.Section != appstate.SectionEntries { if u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
return layout.Dimensions{} return layout.Dimensions{}
} }
return u.groupBar(gtx) return u.groupBar(gtx)
}), }))
layout.Rigid(layout.Spacer{Height: spacing}.Layout), case listPanelTopGroupTools:
layout.Rigid(func(gtx layout.Context) layout.Dimensions { children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if u.isVaultLocked() || u.state.Section != appstate.SectionEntries { if u.isVaultLocked() || u.state.Section != appstate.SectionEntries {
return layout.Dimensions{} return layout.Dimensions{}
} }
return u.groupControlsSection(gtx) return u.groupControlsSection(gtx)
}), }))
layout.Rigid(layout.Spacer{Height: spacing}.Layout), case listPanelTopPrimary:
layout.Rigid(func(gtx layout.Context) layout.Dimensions { children = append(children, layout.Rigid(u.listPanelPrimaryActionRow))
if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{}
} }
if u.mode == "phone" { children = append(children, layout.Rigid(layout.Spacer{Height: spacing}.Layout))
gtx.Constraints.Min.X = gtx.Constraints.Max.X
} }
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions { children = append(children,
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
}
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),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
if u.state.Section == appstate.SectionAPITokens { if u.state.Section == appstate.SectionAPITokens {
return u.apiTokenListPanel(gtx) return u.apiTokenListPanel(gtx)
@@ -6094,7 +6086,7 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
return u.apiAuditListPanel(gtx) return u.apiAuditListPanel(gtx)
} }
if u.state.Section == appstate.SectionAbout { if u.state.Section == appstate.SectionAbout {
return layout.Dimensions{} return emptyStatePanel(gtx, u.theme, u.listEmptyState())
} }
if len(u.visible) == 0 { if len(u.visible) == 0 {
return emptyStatePanel(gtx, u.theme, u.listEmptyState()) 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) { func TestUICurrentVaultSummary(t *testing.T) {
t.Parallel() t.Parallel()