From a8bb5bf6b9abfd8ee7ffadcf1afc6895b5c6e40e Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Fri, 3 Apr 2026 07:10:25 -0700 Subject: [PATCH] Tighten phone group and detail layout --- main.go | 130 ++++++++++++++++----------------------------------- main_test.go | 9 ++++ ui_forms.go | 3 +- 3 files changed, 51 insertions(+), 91 deletions(-) diff --git a/main.go b/main.go index 85ee630..68fedd0 100644 --- a/main.go +++ b/main.go @@ -316,7 +316,6 @@ type ui struct { cancelDeleteGroup widget.Clickable addCustomField widget.Clickable toggleGroupControls widget.Clickable - togglePhoneGroupBrowser widget.Clickable toggleLifecycleAdvanced widget.Clickable toggleHistory widget.Clickable togglePasswordInline widget.Clickable @@ -626,6 +625,11 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths) u.restoreStartupLifecycleTarget() u.requestMasterPassFocus = u.hasSelectedLifecycleTarget() u.loadUIPreferences() + if u.mode == "phone" { + if _, err := os.Stat(u.uiPreferencesPath); err != nil { + u.groupControlsHidden = true + } + } u.loadSettings() u.loadSettingsFormFromPreferences() u.loadSettingsDraft() @@ -3946,6 +3950,11 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(u.syncButtonGroup), layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + btn := material.Button(u.theme, &u.lockVault, "Lock") + return btn.Layout(gtx) + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { icon := u.menuIcon if icon == nil { @@ -3958,11 +3967,6 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions { btn.Inset = layout.UniformInset(unit.Dp(8)) 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.lockVault, "Lock") - return btn.Layout(gtx) - }), ) } @@ -4483,17 +4487,17 @@ func (u *ui) phoneSlider(gtx layout.Context) layout.Dimensions { } } } - gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(18)) - gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(18)) + gtx.Constraints.Min.Y = gtx.Dp(unit.Dp(24)) + gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(24)) return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions { defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop() u.splitDrag.Add(gtx.Ops) pointer.CursorRowResize.Add(gtx.Ops) - handleW := gtx.Dp(unit.Dp(84)) - handleH := gtx.Dp(unit.Dp(4)) + handleW := gtx.Dp(unit.Dp(108)) + handleH := gtx.Dp(unit.Dp(6)) x := (gtx.Constraints.Min.X - handleW) / 2 y := (gtx.Constraints.Min.Y - handleH) / 2 - paint.FillShape(gtx.Ops, color.NRGBA{R: 214, G: 208, B: 197, A: 255}, clip.Rect{Min: image.Pt(0, y+1), Max: image.Pt(gtx.Constraints.Min.X, y+2)}.Op()) + paint.FillShape(gtx.Ops, color.NRGBA{R: 214, G: 208, B: 197, A: 255}, clip.Rect{Min: image.Pt(0, y+2), Max: image.Pt(gtx.Constraints.Min.X, y+3)}.Op()) paint.FillShape(gtx.Ops, accentColor, clip.RRect{ Rect: image.Rectangle{Min: image.Pt(x, y), Max: image.Pt(x+handleW, y+handleH)}, NE: 2, NW: 2, SE: 2, SW: 2, @@ -4697,8 +4701,18 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions { layout.Spacer{Height: titlePad}.Layout, func(gtx layout.Context) layout.Dimensions { if u.state.Section != appstate.SectionRecycleBin { - lbl := material.Label(u.theme, unit.Sp(16), item.Title) - return lbl.Layout(gtx) + if u.mode == "phone" { + return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, + layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions { + return compactTonedButton(gtx, u.theme, &u.copyUser, "Copy Username") + }), + layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout), + layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions { + return compactTonedButton(gtx, u.theme, &u.copyPass, "Copy Password") + }), + ) + } + return layout.Dimensions{} } return recycleDetailTitle(gtx, u.theme, item.Title) }, @@ -4741,29 +4755,11 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions { 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), "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 { 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 compactTonedButton(gtx, u.theme, &u.copyURL, "Copy URL") } return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { @@ -5344,72 +5340,20 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions { displayPath := u.displayPath() atRoot := len(displayPath) == 0 return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { - if u.mode == "phone" { + if u.mode == "phone" { if atRoot { u.phoneGroupBrowserExpanded = true } - toggleLabel := "Browse" - if u.phoneGroupBrowserExpanded { - toggleLabel = "Hide" - } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Alignment: layout.Middle}.Layout(gtx, - layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { - lbl := material.Label(u.theme, unit.Sp(12), "GROUPS") - lbl.Color = mutedColor - return lbl.Layout(gtx) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - if len(groups) == 0 && atRoot { - return layout.Dimensions{} - } - return tonedButton(gtx, u.theme, &u.togglePhoneGroupBrowser, toggleLabel) - }), - ) + lbl := material.Label(u.theme, unit.Sp(12), "GROUPS") + lbl.Color = mutedColor + return lbl.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - if atRoot { - return layout.Dimensions{} - } - return layout.Inset{Top: unit.Dp(8)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { - return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx, - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - for u.goToRootGroup.Clicked(gtx) { - root := u.hiddenVaultRoot() - if root == "" { - u.setCurrentPath(nil) - } else { - u.setCurrentPath([]string{root}) - } - u.filter() - } - return tonedButton(gtx, u.theme, &u.goToRootGroup, "Root") - }), - layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - for u.goToParentGroup.Clicked(gtx) { - u.setCurrentPath(u.currentPath[:len(u.currentPath)-1]) - u.filter() - } - return tonedButton(gtx, u.theme, &u.goToParentGroup, "Up") - }), - ) - }) - }), - layout.Rigid(func(gtx layout.Context) layout.Dimensions { - for u.togglePhoneGroupBrowser.Clicked(gtx) { - u.phoneGroupBrowserExpanded = !u.phoneGroupBrowserExpanded - } - if !u.phoneGroupBrowserExpanded { - return layout.Dimensions{} - } return layout.Spacer{Height: unit.Dp(8)}.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - if !u.phoneGroupBrowserExpanded { - return layout.Dimensions{} - } if len(groups) == 0 { lbl := material.Label(u.theme, unit.Sp(12), "No subgroups here.") lbl.Color = mutedColor @@ -5429,7 +5373,6 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions { for u.groupClicks[idx].Clicked(gtx) { u.state.EnterGroup(name) u.currentPath = append([]string(nil), u.state.CurrentPath...) - u.syncPhoneGroupBrowser(u.currentPath) u.filter() } return tonedButton(gtx, u.theme, &u.groupClicks[idx], name) @@ -5673,6 +5616,15 @@ func tonedButton(gtx layout.Context, th *material.Theme, click *widget.Clickable return btn.Layout(gtx) } +func compactTonedButton(gtx layout.Context, th *material.Theme, click *widget.Clickable, label string) layout.Dimensions { + btn := material.Button(th, click, label) + btn.Background, btn.Color = buttonFocusColors(defaultAccessibilityPreferences(), false) + btn.CornerRadius = unit.Dp(10) + btn.TextSize = unit.Sp(13) + btn.Inset = layout.Inset{Top: 6, Bottom: 6, Left: 8, Right: 8} + return btn.Layout(gtx) +} + func syncPrimaryButton(gtx layout.Context, th *material.Theme, click *widget.Clickable, label string, compact bool) layout.Dimensions { btn := material.Button(th, click, label) btn.Background = color.NRGBA{R: 231, G: 236, B: 232, A: 255} diff --git a/main_test.go b/main_test.go index 79adeee..8917ce7 100644 --- a/main_test.go +++ b/main_test.go @@ -775,6 +775,15 @@ func TestUIPhoneGroupBrowserToggleDoesNotChangeCurrentGroupToolsState(t *testing } } +func TestUIPhoneStartsWithGroupToolsCollapsed(t *testing.T) { + t.Parallel() + + u := newUIWithModel("phone", vault.Model{}) + if !u.groupControlsHidden { + t.Fatal("groupControlsHidden = false, want phone Group Tools collapsed by default") + } +} + func TestUIPhoneListPanelWithExpandedGroupControlsAndEntriesDoesNotPanic(t *testing.T) { t.Parallel() diff --git a/ui_forms.go b/ui_forms.go index dc6e81f..45df640 100644 --- a/ui_forms.go +++ b/ui_forms.go @@ -923,10 +923,9 @@ func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions { }), layout.Rigid(layout.Spacer{Width: unit.Dp(4)}.Layout), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - label := "Group management" + label := "Group Tools" size := unit.Sp(12) if u.mode == "phone" { - label = "Tools" size = unit.Sp(11) } lbl := material.Label(u.theme, size, label)