Local-first remote sync and cross-platform UI parity #2
@@ -518,6 +518,10 @@ type ui struct {
|
||||
deleteGroupPath []string
|
||||
apiPolicyGroupScope bool
|
||||
apiTokenSecret string
|
||||
phoneSyncMenuAnchor image.Point
|
||||
phoneMainMenuAnchor image.Point
|
||||
phoneSyncMenuVisible bool
|
||||
phoneMainMenuVisible bool
|
||||
selectedAuditIndex int
|
||||
statusExpiresAt 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
|
||||
// unpainted black buffer before nested background widgets run.
|
||||
paint.FillShape(gtx.Ops, bgColor, clip.Rect{Max: gtx.Constraints.Max}.Op())
|
||||
u.phoneSyncMenuVisible = false
|
||||
u.phoneMainMenuVisible = false
|
||||
u.syncHostedAPI()
|
||||
u.filter()
|
||||
u.processShortcuts(gtx)
|
||||
@@ -4164,9 +4170,15 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
for u.toggleSyncMenu.Clicked(gtx) {
|
||||
u.syncMenuOpen = !u.syncMenuOpen
|
||||
if u.syncMenuOpen {
|
||||
u.mainMenuOpen = false
|
||||
}
|
||||
}
|
||||
for u.toggleMainMenu.Clicked(gtx) {
|
||||
u.mainMenuOpen = !u.mainMenuOpen
|
||||
if u.mainMenuOpen {
|
||||
u.syncMenuOpen = false
|
||||
}
|
||||
}
|
||||
for u.openAdvancedSync.Clicked(gtx) {
|
||||
u.openAdvancedSyncDialog()
|
||||
@@ -4773,6 +4785,9 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
return u.approvalDialog(gtx)
|
||||
}),
|
||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
||||
return u.phoneHeaderMenus(gtx)
|
||||
}),
|
||||
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
|
||||
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() {
|
||||
@@ -5575,6 +5613,20 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||
rowCall.Add(gtx.Ops)
|
||||
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 {
|
||||
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
|
||||
if u.mode == "phone" {
|
||||
width = gtx.Constraints.Max.X
|
||||
}
|
||||
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") },
|
||||
}
|
||||
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,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
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 {
|
||||
btn := material.IconButton(u.theme, &u.toggleSyncMenu, u.chevronDownIcon, "More synchronize actions")
|
||||
btn.Background = color.NRGBA{R: 231, G: 236, B: 232, A: 255}
|
||||
btn.Color = accentColor
|
||||
if u.syncMenuOpen {
|
||||
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.Inset = layout.UniformInset(unit.Dp(8))
|
||||
if u.mode == "phone" {
|
||||
@@ -5764,7 +5818,7 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
||||
})
|
||||
}
|
||||
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{
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
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 {
|
||||
if u.mode == "phone" {
|
||||
if u.denseLayout {
|
||||
@@ -7252,8 +7320,13 @@ func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions {
|
||||
icon = u.settingsIcon
|
||||
}
|
||||
btn := material.IconButton(u.theme, &u.toggleMainMenu, icon, "Menu")
|
||||
btn.Background = selectedColor
|
||||
btn.Color = accentColor
|
||||
if u.mainMenuOpen {
|
||||
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.Inset = layout.UniformInset(unit.Dp(8))
|
||||
return btn.Layout(gtx)
|
||||
@@ -7261,6 +7334,42 @@ func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions {
|
||||
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 {
|
||||
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 {
|
||||
if width < gtx.Constraints.Max.X {
|
||||
width = gtx.Constraints.Max.X
|
||||
}
|
||||
if width <= 0 {
|
||||
return child(gtx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user