Fix Android header menus

This commit is contained in:
Joe Julian
2026-04-08 16:27:46 -07:00
parent 5a98fe1a75
commit d7741d14f5
+121 -9
View File
@@ -518,6 +518,10 @@ type ui struct {
deleteGroupPath []string deleteGroupPath []string
apiPolicyGroupScope bool apiPolicyGroupScope bool
apiTokenSecret string apiTokenSecret string
phoneSyncMenuAnchor image.Point
phoneMainMenuAnchor image.Point
phoneSyncMenuVisible bool
phoneMainMenuVisible bool
selectedAuditIndex int selectedAuditIndex int
statusExpiresAt time.Time statusExpiresAt time.Time
now func() time.Time now func() time.Time
@@ -4132,6 +4136,8 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
// Clear the full frame explicitly so mobile surfaces don't start from an // Clear the full frame explicitly so mobile surfaces don't start from an
// unpainted black buffer before nested background widgets run. // unpainted black buffer before nested background widgets run.
paint.FillShape(gtx.Ops, bgColor, clip.Rect{Max: gtx.Constraints.Max}.Op()) paint.FillShape(gtx.Ops, bgColor, clip.Rect{Max: gtx.Constraints.Max}.Op())
u.phoneSyncMenuVisible = false
u.phoneMainMenuVisible = false
u.syncHostedAPI() u.syncHostedAPI()
u.filter() u.filter()
u.processShortcuts(gtx) u.processShortcuts(gtx)
@@ -4164,9 +4170,15 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
} }
for u.toggleSyncMenu.Clicked(gtx) { for u.toggleSyncMenu.Clicked(gtx) {
u.syncMenuOpen = !u.syncMenuOpen u.syncMenuOpen = !u.syncMenuOpen
if u.syncMenuOpen {
u.mainMenuOpen = false
}
} }
for u.toggleMainMenu.Clicked(gtx) { for u.toggleMainMenu.Clicked(gtx) {
u.mainMenuOpen = !u.mainMenuOpen u.mainMenuOpen = !u.mainMenuOpen
if u.mainMenuOpen {
u.syncMenuOpen = false
}
} }
for u.openAdvancedSync.Clicked(gtx) { for u.openAdvancedSync.Clicked(gtx) {
u.openAdvancedSyncDialog() u.openAdvancedSyncDialog()
@@ -4773,6 +4785,9 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
} }
return u.approvalDialog(gtx) return u.approvalDialog(gtx)
}), }),
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
return u.phoneHeaderMenus(gtx)
}),
layout.Stacked(u.statusToast), layout.Stacked(u.statusToast),
) )
} }
@@ -5502,6 +5517,29 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min.X = gtx.Constraints.Max.X gtx.Constraints.Min.X = gtx.Constraints.Max.X
return u.headerActions(gtx) return u.headerActions(gtx)
}), }),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if !u.syncMenuOpen && !u.mainMenuOpen {
return layout.Dimensions{}
}
return layout.Inset{Top: unit.Dp(6)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
var menu layout.Widget
if u.syncMenuOpen {
menu = u.syncMenu
} else {
menu = u.mainMenu
}
measureGTX := gtx
measureGTX.Constraints.Min = image.Point{}
macro := op.Record(gtx.Ops)
dims := menu(measureGTX)
_ = macro.Stop()
gtx.Constraints.Min.X = dims.Size.X
gtx.Constraints.Max.X = dims.Size.X
return menu(gtx)
})
})
}),
) )
} }
if u.shouldShowDesktopWorkingHeader() { if u.shouldShowDesktopWorkingHeader() {
@@ -5575,6 +5613,20 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
rowCall.Add(gtx.Ops) rowCall.Add(gtx.Ops)
rowStack.Pop() rowStack.Pop()
if u.mode == "phone" {
if u.syncMenuOpen {
u.phoneSyncMenuVisible = true
u.phoneSyncMenuAnchor = image.Pt(rowOriginX+syncDims.Size.X, rowDims.Size.Y)
}
if u.mainMenuOpen {
triggerRightX := syncDims.Size.X + spacing + lockDims.Size.X + spacing + mainDims.Size.X
u.phoneMainMenuVisible = true
u.phoneMainMenuAnchor = image.Pt(rowOriginX+triggerRightX, rowDims.Size.Y)
}
width := gtx.Constraints.Max.X
return layout.Dimensions{Size: image.Pt(width, rowDims.Size.Y)}
}
if u.syncMenuOpen { if u.syncMenuOpen {
drawMenu(u.syncMenu, syncDims.Size.X, rowDims.Size.Y) drawMenu(u.syncMenu, syncDims.Size.X, rowDims.Size.Y)
} }
@@ -5584,9 +5636,6 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
} }
width := rowDims.Size.X width := rowDims.Size.X
if u.mode == "phone" {
width = gtx.Constraints.Max.X
}
return layout.Dimensions{Size: image.Pt(width, rowDims.Size.Y)} return layout.Dimensions{Size: image.Pt(width, rowDims.Size.Y)}
} }
@@ -5600,7 +5649,7 @@ func (u *ui) mainMenu(gtx layout.Context) layout.Dimensions {
func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Settings") }, func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Settings") },
} }
rowWidth := menuActionWidth(gtx, rows) rowWidth := menuActionWidth(gtx, rows)
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { return intrinsicCompactCard(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return rightAlignedMenuAction(gtx, rowWidth, rows[0]) return rightAlignedMenuAction(gtx, rowWidth, rows[0])
@@ -5715,8 +5764,13 @@ func (u *ui) syncButtonGroup(gtx layout.Context) layout.Dimensions {
func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions { func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions {
btn := material.IconButton(u.theme, &u.toggleSyncMenu, u.chevronDownIcon, "More synchronize actions") btn := material.IconButton(u.theme, &u.toggleSyncMenu, u.chevronDownIcon, "More synchronize actions")
btn.Background = color.NRGBA{R: 231, G: 236, B: 232, A: 255} if u.syncMenuOpen {
btn.Color = accentColor btn.Background = accentColor
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
} else {
btn.Background = color.NRGBA{R: 231, G: 236, B: 232, A: 255}
btn.Color = accentColor
}
btn.Size = unit.Dp(18) btn.Size = unit.Dp(18)
btn.Inset = layout.UniformInset(unit.Dp(8)) btn.Inset = layout.UniformInset(unit.Dp(8))
if u.mode == "phone" { if u.mode == "phone" {
@@ -5764,7 +5818,7 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
}) })
} }
actionWidth := menuActionWidth(gtx, actionRows) actionWidth := menuActionWidth(gtx, actionRows)
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions { return intrinsicCompactCard(gtx, func(gtx layout.Context) layout.Dimensions {
rows := []layout.FlexChild{ rows := []layout.FlexChild{
layout.Rigid(func(gtx layout.Context) layout.Dimensions { layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?") lbl := material.Label(u.theme, unit.Sp(11), "Need another source or direction?")
@@ -5951,6 +6005,20 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
}) })
} }
func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions {
measureGTX := gtx
measureGTX.Constraints.Min = image.Point{}
macro := op.Record(gtx.Ops)
contentDims := w(measureGTX)
_ = macro.Stop()
width := contentDims.Size.X + gtx.Dp(unit.Dp(20))
if width > 0 {
gtx.Constraints.Min.X = width
gtx.Constraints.Max.X = width
}
return compactCard(gtx, w)
}
func (u *ui) sectionSpacing() unit.Dp { func (u *ui) sectionSpacing() unit.Dp {
if u.mode == "phone" { if u.mode == "phone" {
if u.denseLayout { if u.denseLayout {
@@ -7252,8 +7320,13 @@ func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions {
icon = u.settingsIcon icon = u.settingsIcon
} }
btn := material.IconButton(u.theme, &u.toggleMainMenu, icon, "Menu") btn := material.IconButton(u.theme, &u.toggleMainMenu, icon, "Menu")
btn.Background = selectedColor if u.mainMenuOpen {
btn.Color = accentColor btn.Background = accentColor
btn.Color = color.NRGBA{R: 255, G: 252, B: 247, A: 255}
} else {
btn.Background = selectedColor
btn.Color = accentColor
}
btn.Size = unit.Dp(18) btn.Size = unit.Dp(18)
btn.Inset = layout.UniformInset(unit.Dp(8)) btn.Inset = layout.UniformInset(unit.Dp(8))
return btn.Layout(gtx) return btn.Layout(gtx)
@@ -7261,6 +7334,42 @@ func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions {
return button(gtx) return button(gtx)
} }
func (u *ui) phoneHeaderMenus(gtx layout.Context) layout.Dimensions {
if u.mode != "phone" {
return layout.Dimensions{}
}
if !u.syncMenuVisibleOnPhone() && !u.mainMenuVisibleOnPhone() {
return layout.Dimensions{}
}
gtx.Constraints.Min = gtx.Constraints.Max
drawMenu := func(anchor image.Point, menu layout.Widget) layout.Dimensions {
_ = anchor
return layout.NE.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Inset{
Top: unit.Dp(56),
Right: unit.Dp(16),
}.Layout(gtx, menu)
})
}
if u.syncMenuVisibleOnPhone() {
drawMenu(u.phoneSyncMenuAnchor, u.syncMenu)
}
if u.mainMenuVisibleOnPhone() {
drawMenu(u.phoneMainMenuAnchor, u.mainMenu)
}
return layout.Dimensions{Size: gtx.Constraints.Max}
}
func (u *ui) syncMenuVisibleOnPhone() bool {
return u.mode == "phone" && u.phoneSyncMenuVisible && u.syncMenuOpen
}
func (u *ui) mainMenuVisibleOnPhone() bool {
return u.mode == "phone" && u.phoneMainMenuVisible && u.mainMenuOpen
}
func (u *ui) syncMenuDropsBelowTrigger() bool { func (u *ui) syncMenuDropsBelowTrigger() bool {
return true return true
} }
@@ -7510,6 +7619,9 @@ func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
} }
func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions { func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions {
if width < gtx.Constraints.Max.X {
width = gtx.Constraints.Max.X
}
if width <= 0 { if width <= 0 {
return child(gtx) return child(gtx)
} }