Extract header and menu rendering
This commit is contained in:
@@ -5279,146 +5279,6 @@ func approvalFact(theme *material.Theme, title, primary, secondary string) layou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
|
||||||
if u.mode == "phone" {
|
|
||||||
if u.shouldShowLifecycleSetup() || u.isVaultLocked() {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
|
||||||
return u.headerActions(gtx)
|
|
||||||
}
|
|
||||||
if u.shouldShowDesktopWorkingHeader() {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
return card(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
|
||||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return u.brandMark(gtx, 196, 56)
|
|
||||||
}),
|
|
||||||
layout.Rigid(u.headerActions),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
|
||||||
if u.shouldShowLifecycleSetup() {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
if u.isVaultLocked() {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
if u.shouldShowDesktopWorkingHeader() {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
spacing := gtx.Dp(unit.Dp(8))
|
|
||||||
metrics := headerActionMetrics{Spacing: spacing}
|
|
||||||
row := func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
metrics.SyncDims = u.syncButtonGroup(gtx)
|
|
||||||
return metrics.SyncDims
|
|
||||||
}),
|
|
||||||
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")
|
|
||||||
metrics.LockDims = btn.Layout(gtx)
|
|
||||||
return metrics.LockDims
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
metrics.MainDims = u.mainMenuButtonGroup(gtx)
|
|
||||||
return metrics.MainDims
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowOps := op.Record(gtx.Ops)
|
|
||||||
metrics.RowDims = row(gtx)
|
|
||||||
rowCall := rowOps.Stop()
|
|
||||||
|
|
||||||
if u.mode == "phone" {
|
|
||||||
metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X)
|
|
||||||
}
|
|
||||||
|
|
||||||
surface := dropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0}
|
|
||||||
|
|
||||||
rowStack := op.Offset(image.Pt(metrics.RowOriginX, 0)).Push(gtx.Ops)
|
|
||||||
rowCall.Add(gtx.Ops)
|
|
||||||
rowStack.Pop()
|
|
||||||
|
|
||||||
if u.mode == "phone" {
|
|
||||||
if u.syncMenuOpen {
|
|
||||||
u.phoneSyncMenuVisible = true
|
|
||||||
u.phoneSyncMenuAnchor = metrics.syncAnchor().point()
|
|
||||||
}
|
|
||||||
if u.mainMenuOpen {
|
|
||||||
u.phoneMainMenuVisible = true
|
|
||||||
u.phoneMainMenuAnchor = metrics.mainAnchor().point()
|
|
||||||
}
|
|
||||||
width := gtx.Constraints.Max.X
|
|
||||||
return layout.Dimensions{Size: image.Pt(width, metrics.RowDims.Size.Y)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.syncMenuOpen {
|
|
||||||
surface.draw(gtx, metrics.syncAnchor(), u.syncMenu)
|
|
||||||
}
|
|
||||||
if u.mainMenuOpen {
|
|
||||||
surface.draw(gtx, metrics.mainAnchor(), u.mainMenu)
|
|
||||||
}
|
|
||||||
|
|
||||||
width := metrics.RowDims.Size.X
|
|
||||||
return layout.Dimensions{Size: image.Pt(width, metrics.RowDims.Size.Y)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) mainMenu(gtx layout.Context) layout.Dimensions {
|
|
||||||
rows := []layout.Widget{
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.showEntries, "Entries")
|
|
||||||
},
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.showRecycle, "Recycle Bin")
|
|
||||||
},
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.showAPITokens, "API Tokens")
|
|
||||||
},
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.showAPIAudit, "API Audit")
|
|
||||||
},
|
|
||||||
func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.showAbout, "About") },
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Settings")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
rowWidth := menuActionWidth(gtx, rows)
|
|
||||||
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])
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, rowWidth, rows[1])
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, rowWidth, rows[2])
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, rowWidth, rows[3])
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, rowWidth, rows[4])
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, rowWidth, rows[5])
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) aboutDetailPanel(gtx layout.Context) layout.Dimensions {
|
func (u *ui) aboutDetailPanel(gtx layout.Context) layout.Dimensions {
|
||||||
rows := []layout.Widget{
|
rows := []layout.Widget{
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -5483,290 +5343,6 @@ func aboutFact(theme *material.Theme, title, primary, secondary string) layout.W
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) syncButtonGroup(gtx layout.Context) layout.Dimensions {
|
|
||||||
label := "Sync"
|
|
||||||
spacing := unit.Dp(4)
|
|
||||||
if u.mode == "phone" {
|
|
||||||
spacing = unit.Dp(3)
|
|
||||||
}
|
|
||||||
row := func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return syncPrimaryButton(gtx, u.theme, &u.synchronizeVault, label, u.mode == "phone")
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Width: spacing}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return u.syncMenuToggle(gtx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return row(gtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions {
|
|
||||||
btn := material.IconButton(u.theme, &u.toggleSyncMenu, u.chevronDownIcon, "More synchronize actions")
|
|
||||||
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" {
|
|
||||||
btn.Size = unit.Dp(16)
|
|
||||||
btn.Inset = layout.UniformInset(unit.Dp(7))
|
|
||||||
}
|
|
||||||
return btn.Layout(gtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|
||||||
profiles := u.availableRemoteProfiles()
|
|
||||||
credentials := u.availableRemoteCredentialEntries()
|
|
||||||
if len(u.vaultRemoteProfileClicks) < len(profiles) {
|
|
||||||
u.vaultRemoteProfileClicks = make([]widget.Clickable, len(profiles))
|
|
||||||
}
|
|
||||||
if len(u.vaultRemoteCredentialClicks) < len(credentials) {
|
|
||||||
u.vaultRemoteCredentialClicks = make([]widget.Clickable, len(credentials))
|
|
||||||
}
|
|
||||||
actionRows := []layout.Widget{
|
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if supportsVaultShare(runtime.GOOS) && u.vaultSharer != nil && strings.TrimSpace(u.currentShareableVaultPath()) != "" {
|
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.shareCurrentVault, "Share Vault")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if u.shouldShowRemoteSyncSetupShortcut() {
|
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSetupShortcutLabel())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if u.shouldShowDirectRemoteSyncShortcut() {
|
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.directRemoteSyncShortcutLabel())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if u.shouldShowRemoteSyncSettingsShortcut() {
|
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSettingsShortcutLabel())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if u.shouldShowRemoveRemoteSyncShortcut() {
|
|
||||||
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, u.removeRemoteSyncShortcutLabel())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
actionWidth := menuActionWidth(gtx, actionRows)
|
|
||||||
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?")
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
if !supportsVaultShare(runtime.GOOS) || u.vaultSharer == nil || strings.TrimSpace(u.currentShareableVaultPath()) == "" {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.shareCurrentVault, "Share Vault")
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
if u.shouldShowRemoteSyncSetupShortcut() {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSetupShortcutLabel())
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if u.shouldShowDirectRemoteSyncShortcut() {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.directRemoteSyncShortcutLabel())
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if u.shouldShowRemoteSyncSettingsShortcut() {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSettingsShortcutLabel())
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if u.shouldShowRemoveRemoteSyncShortcut() {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, u.removeRemoteSyncShortcutLabel())
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if u.hasOpenVault() && len(profiles) > 0 && len(credentials) > 0 {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(11), u.savedRemoteBindingHeading())
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
)
|
|
||||||
if !u.shouldShowSavedRemoteBindingSelectors() {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
profileLabel, credentialLabel, syncLabel, ok := u.savedRemoteBindingSummary()
|
|
||||||
if !ok {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
return layout.Background{}.Layout(gtx, fill(color.NRGBA{R: 242, G: 245, B: 240, A: 255}), func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.UniformInset(unit.Dp(10)).Layout(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(13), profileLabel)
|
|
||||||
lbl.Color = accentColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(12), "Credential: "+credentialLabel)
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(12), syncLabel)
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
for i, profile := range profiles {
|
|
||||||
i := i
|
|
||||||
profile := profile
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
selected := strings.TrimSpace(u.selectedVaultRemoteProfileID) == profile.ID
|
|
||||||
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return u.vaultRemoteProfileClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(13), profile.Name)
|
|
||||||
lbl.Color = accentColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
rows = append(rows, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
|
||||||
for i, entry := range credentials {
|
|
||||||
i := i
|
|
||||||
entry := entry
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
selected := strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == entry.ID
|
|
||||||
label := entry.Title
|
|
||||||
if strings.TrimSpace(entry.Username) != "" {
|
|
||||||
label += " · " + strings.TrimSpace(entry.Username)
|
|
||||||
}
|
|
||||||
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return u.vaultRemoteCredentialClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(13), label)
|
|
||||||
lbl.Color = accentColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := u.selectedVaultRemoteProfile(); ok {
|
|
||||||
if _, ok := u.selectedVaultRemoteCredentialEntry(); ok {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.openSelectedVaultRemoteButtonLabel())
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if u.hasOpenVault() {
|
|
||||||
baseURL := strings.TrimSpace(u.remoteBaseURL.Text())
|
|
||||||
remotePath := strings.TrimSpace(u.remotePath.Text())
|
|
||||||
username := strings.TrimSpace(u.remoteUsername.Text())
|
|
||||||
password := u.remotePassword.Text()
|
|
||||||
if baseURL != "" && remotePath != "" && username != "" && password != "" {
|
|
||||||
rows = append(rows,
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(11), u.saveCurrentRemoteBindingHeading())
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.saveCurrentRemoteBinding, u.saveCurrentRemoteBindingButtonLabel())
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, rows...)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions {
|
|
||||||
measureGTX := gtx
|
|
||||||
measureGTX.Constraints.Min = image.Point{}
|
|
||||||
measureGTX.Constraints.Max.X = gtx.Constraints.Max.X
|
|
||||||
macro := op.Record(gtx.Ops)
|
|
||||||
contentDims := w(measureGTX)
|
|
||||||
_ = macro.Stop()
|
|
||||||
width := contentDims.Size.X + gtx.Dp(unit.Dp(20))
|
|
||||||
maxWidth := gtx.Constraints.Max.X
|
|
||||||
if maxWidth > 0 && width > maxWidth {
|
|
||||||
width = maxWidth
|
|
||||||
}
|
|
||||||
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 {
|
||||||
@@ -7054,101 +6630,6 @@ func (u *ui) groupBarShowsExplicitNavigationButtons() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) topRightActionOrder() []string {
|
|
||||||
if u.isVaultLocked() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []string{"Sync", "Lock", "Menu"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions {
|
|
||||||
button := func(gtx layout.Context) layout.Dimensions {
|
|
||||||
icon := u.menuIcon
|
|
||||||
if icon == nil {
|
|
||||||
icon = u.settingsIcon
|
|
||||||
}
|
|
||||||
btn := material.IconButton(u.theme, &u.toggleMainMenu, icon, "Menu")
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
contentInsetPx := gtx.Dp(unit.Dp(16))
|
|
||||||
surface := dropdownSurface{
|
|
||||||
ContainerWidth: max(0, gtx.Constraints.Max.X-(contentInsetPx*2)),
|
|
||||||
LeftInset: contentInsetPx,
|
|
||||||
TopInset: contentInsetPx,
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.syncMenuVisibleOnPhone() {
|
|
||||||
surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu)
|
|
||||||
}
|
|
||||||
if u.mainMenuVisibleOnPhone() {
|
|
||||||
surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) syncMenuRightAlignsToTrigger() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) headerMenusUseOverlayModel() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) mainMenuDropsBelowTrigger() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) mainMenuRightAlignsToTrigger() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func anchoredMenuX(triggerWidth, menuWidth int) int {
|
|
||||||
return triggerWidth - menuWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
func anchoredMenuOriginX(containerWidth, rowOriginX, triggerRightX, menuWidth int) int {
|
|
||||||
x := rowOriginX + triggerRightX - menuWidth
|
|
||||||
if x < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if x+menuWidth > containerWidth {
|
|
||||||
return max(0, containerWidth-menuWidth)
|
|
||||||
}
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func detailLine(th *material.Theme, label, value string) layout.Widget {
|
func detailLine(th *material.Theme, label, value string) layout.Widget {
|
||||||
return func(gtx layout.Context) layout.Dimensions {
|
return func(gtx layout.Context) layout.Dimensions {
|
||||||
valueSize := unit.Sp(16)
|
valueSize := unit.Sp(16)
|
||||||
@@ -7351,30 +6832,6 @@ func syncChoiceButton(gtx layout.Context, th *material.Theme, click *widget.Clic
|
|||||||
return btn.Layout(gtx)
|
return btn.Layout(gtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
|
||||||
width := 0
|
|
||||||
for _, row := range rows {
|
|
||||||
measureGTX := gtx
|
|
||||||
measureGTX.Constraints.Min = image.Point{}
|
|
||||||
macro := op.Record(gtx.Ops)
|
|
||||||
dims := row(measureGTX)
|
|
||||||
_ = macro.Stop()
|
|
||||||
if dims.Size.X > width {
|
|
||||||
width = dims.Size.X
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return width
|
|
||||||
}
|
|
||||||
|
|
||||||
func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions {
|
|
||||||
if width <= 0 {
|
|
||||||
return child(gtx)
|
|
||||||
}
|
|
||||||
gtx.Constraints.Min.X = width
|
|
||||||
gtx.Constraints.Max.X = width
|
|
||||||
return layout.E.Layout(gtx, child)
|
|
||||||
}
|
|
||||||
|
|
||||||
func syncDialogSectionLabel(th *material.Theme, text string) layout.Widget {
|
func syncDialogSectionLabel(th *material.Theme, text string) layout.Widget {
|
||||||
return func(gtx layout.Context) layout.Dimensions {
|
return func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.Label(th, unit.Sp(12), strings.ToUpper(text))
|
lbl := material.Label(th, unit.Sp(12), strings.ToUpper(text))
|
||||||
|
|||||||
@@ -0,0 +1,557 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/op"
|
||||||
|
"gioui.org/unit"
|
||||||
|
"gioui.org/widget"
|
||||||
|
"gioui.org/widget/material"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.mode == "phone" {
|
||||||
|
if u.shouldShowLifecycleSetup() || u.isVaultLocked() {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||||
|
return u.headerActions(gtx)
|
||||||
|
}
|
||||||
|
if u.shouldShowDesktopWorkingHeader() {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
return card(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.brandMark(gtx, 196, 56)
|
||||||
|
}),
|
||||||
|
layout.Rigid(u.headerActions),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.shouldShowLifecycleSetup() {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
if u.isVaultLocked() {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
if u.shouldShowDesktopWorkingHeader() {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
spacing := gtx.Dp(unit.Dp(8))
|
||||||
|
metrics := headerActionMetrics{Spacing: spacing}
|
||||||
|
row := func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
metrics.SyncDims = u.syncButtonGroup(gtx)
|
||||||
|
return metrics.SyncDims
|
||||||
|
}),
|
||||||
|
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")
|
||||||
|
metrics.LockDims = btn.Layout(gtx)
|
||||||
|
return metrics.LockDims
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
metrics.MainDims = u.mainMenuButtonGroup(gtx)
|
||||||
|
return metrics.MainDims
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowOps := op.Record(gtx.Ops)
|
||||||
|
metrics.RowDims = row(gtx)
|
||||||
|
rowCall := rowOps.Stop()
|
||||||
|
|
||||||
|
if u.mode == "phone" {
|
||||||
|
metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
surface := dropdownSurface{ContainerWidth: gtx.Constraints.Max.X, LeftInset: 0, TopInset: 0}
|
||||||
|
|
||||||
|
rowStack := op.Offset(image.Pt(metrics.RowOriginX, 0)).Push(gtx.Ops)
|
||||||
|
rowCall.Add(gtx.Ops)
|
||||||
|
rowStack.Pop()
|
||||||
|
|
||||||
|
if u.mode == "phone" {
|
||||||
|
if u.syncMenuOpen {
|
||||||
|
u.phoneSyncMenuVisible = true
|
||||||
|
u.phoneSyncMenuAnchor = metrics.syncAnchor().point()
|
||||||
|
}
|
||||||
|
if u.mainMenuOpen {
|
||||||
|
u.phoneMainMenuVisible = true
|
||||||
|
u.phoneMainMenuAnchor = metrics.mainAnchor().point()
|
||||||
|
}
|
||||||
|
width := gtx.Constraints.Max.X
|
||||||
|
return layout.Dimensions{Size: image.Pt(width, metrics.RowDims.Size.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.syncMenuOpen {
|
||||||
|
surface.draw(gtx, metrics.syncAnchor(), u.syncMenu)
|
||||||
|
}
|
||||||
|
if u.mainMenuOpen {
|
||||||
|
surface.draw(gtx, metrics.mainAnchor(), u.mainMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
width := metrics.RowDims.Size.X
|
||||||
|
return layout.Dimensions{Size: image.Pt(width, metrics.RowDims.Size.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) mainMenu(gtx layout.Context) layout.Dimensions {
|
||||||
|
rows := []layout.Widget{
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.showEntries, "Entries")
|
||||||
|
},
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.showRecycle, "Recycle Bin")
|
||||||
|
},
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.showAPITokens, "API Tokens")
|
||||||
|
},
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.showAPIAudit, "API Audit")
|
||||||
|
},
|
||||||
|
func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.showAbout, "About") },
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.openSecuritySettings, "Settings")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rowWidth := menuActionWidth(gtx, rows)
|
||||||
|
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])
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, rowWidth, rows[1])
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, rowWidth, rows[2])
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, rowWidth, rows[3])
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, rowWidth, rows[4])
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, rowWidth, rows[5])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) syncButtonGroup(gtx layout.Context) layout.Dimensions {
|
||||||
|
label := "Sync"
|
||||||
|
spacing := unit.Dp(4)
|
||||||
|
if u.mode == "phone" {
|
||||||
|
spacing = unit.Dp(3)
|
||||||
|
}
|
||||||
|
row := func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return syncPrimaryButton(gtx, u.theme, &u.synchronizeVault, label, u.mode == "phone")
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: spacing}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.syncMenuToggle(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return row(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions {
|
||||||
|
btn := material.IconButton(u.theme, &u.toggleSyncMenu, u.chevronDownIcon, "More synchronize actions")
|
||||||
|
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" {
|
||||||
|
btn.Size = unit.Dp(16)
|
||||||
|
btn.Inset = layout.UniformInset(unit.Dp(7))
|
||||||
|
}
|
||||||
|
return btn.Layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
||||||
|
profiles := u.availableRemoteProfiles()
|
||||||
|
credentials := u.availableRemoteCredentialEntries()
|
||||||
|
if len(u.vaultRemoteProfileClicks) < len(profiles) {
|
||||||
|
u.vaultRemoteProfileClicks = make([]widget.Clickable, len(profiles))
|
||||||
|
}
|
||||||
|
if len(u.vaultRemoteCredentialClicks) < len(credentials) {
|
||||||
|
u.vaultRemoteCredentialClicks = make([]widget.Clickable, len(credentials))
|
||||||
|
}
|
||||||
|
actionRows := []layout.Widget{
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if supportsVaultShare(runtime.GOOS) && u.vaultSharer != nil && strings.TrimSpace(u.currentShareableVaultPath()) != "" {
|
||||||
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.shareCurrentVault, "Share Vault")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if u.shouldShowRemoteSyncSetupShortcut() {
|
||||||
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSetupShortcutLabel())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if u.shouldShowDirectRemoteSyncShortcut() {
|
||||||
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.directRemoteSyncShortcutLabel())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if u.shouldShowRemoteSyncSettingsShortcut() {
|
||||||
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSettingsShortcutLabel())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if u.shouldShowRemoveRemoteSyncShortcut() {
|
||||||
|
actionRows = append(actionRows, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, u.removeRemoteSyncShortcutLabel())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
actionWidth := menuActionWidth(gtx, actionRows)
|
||||||
|
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?")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if !supportsVaultShare(runtime.GOOS) || u.vaultSharer == nil || strings.TrimSpace(u.currentShareableVaultPath()) == "" {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.shareCurrentVault, "Share Vault")
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.openAdvancedSync, "Open Advanced Sync")
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
if u.shouldShowRemoteSyncSetupShortcut() {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSetupShortcutLabel())
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if u.shouldShowDirectRemoteSyncShortcut() {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.directRemoteSyncShortcutLabel())
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if u.shouldShowRemoteSyncSettingsShortcut() {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.useSavedAdvancedSyncRemote, u.remoteSyncSettingsShortcutLabel())
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if u.shouldShowRemoveRemoteSyncShortcut() {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return rightAlignedMenuAction(gtx, actionWidth, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.removeSelectedRemoteBinding, u.removeRemoteSyncShortcutLabel())
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if u.hasOpenVault() && len(profiles) > 0 && len(credentials) > 0 {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(11), u.savedRemoteBindingHeading())
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
)
|
||||||
|
if !u.shouldShowSavedRemoteBindingSelectors() {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
profileLabel, credentialLabel, syncLabel, ok := u.savedRemoteBindingSummary()
|
||||||
|
if !ok {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
return layout.Background{}.Layout(gtx, fill(color.NRGBA{R: 242, G: 245, B: 240, A: 255}), func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.UniformInset(unit.Dp(10)).Layout(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(13), profileLabel)
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "Credential: "+credentialLabel)
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), syncLabel)
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
for i, profile := range profiles {
|
||||||
|
i := i
|
||||||
|
profile := profile
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
selected := strings.TrimSpace(u.selectedVaultRemoteProfileID) == profile.ID
|
||||||
|
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.vaultRemoteProfileClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(13), profile.Name)
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rows = append(rows, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||||
|
for i, entry := range credentials {
|
||||||
|
i := i
|
||||||
|
entry := entry
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
selected := strings.TrimSpace(u.selectedVaultRemoteCredentialEntryID) == entry.ID
|
||||||
|
label := entry.Title
|
||||||
|
if strings.TrimSpace(entry.Username) != "" {
|
||||||
|
label += " · " + strings.TrimSpace(entry.Username)
|
||||||
|
}
|
||||||
|
return layout.Inset{Bottom: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return recentSelectionCard(gtx, selected, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return u.vaultRemoteCredentialClicks[i].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(13), label)
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := u.selectedVaultRemoteProfile(); ok {
|
||||||
|
if _, ok := u.selectedVaultRemoteCredentialEntry(); ok {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.openSelectedVaultRemote, u.openSelectedVaultRemoteButtonLabel())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if u.hasOpenVault() {
|
||||||
|
baseURL := strings.TrimSpace(u.remoteBaseURL.Text())
|
||||||
|
remotePath := strings.TrimSpace(u.remotePath.Text())
|
||||||
|
username := strings.TrimSpace(u.remoteUsername.Text())
|
||||||
|
password := u.remotePassword.Text()
|
||||||
|
if baseURL != "" && remotePath != "" && username != "" && password != "" {
|
||||||
|
rows = append(rows,
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(11), u.saveCurrentRemoteBindingHeading())
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.saveCurrentRemoteBinding, u.saveCurrentRemoteBindingButtonLabel())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, rows...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions {
|
||||||
|
measureGTX := gtx
|
||||||
|
measureGTX.Constraints.Min = image.Point{}
|
||||||
|
measureGTX.Constraints.Max.X = gtx.Constraints.Max.X
|
||||||
|
macro := op.Record(gtx.Ops)
|
||||||
|
contentDims := w(measureGTX)
|
||||||
|
_ = macro.Stop()
|
||||||
|
width := contentDims.Size.X + gtx.Dp(unit.Dp(20))
|
||||||
|
maxWidth := gtx.Constraints.Max.X
|
||||||
|
if maxWidth > 0 && width > maxWidth {
|
||||||
|
width = maxWidth
|
||||||
|
}
|
||||||
|
if width > 0 {
|
||||||
|
gtx.Constraints.Min.X = width
|
||||||
|
gtx.Constraints.Max.X = width
|
||||||
|
}
|
||||||
|
return compactCard(gtx, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) topRightActionOrder() []string {
|
||||||
|
if u.isVaultLocked() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{"Sync", "Lock", "Menu"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions {
|
||||||
|
button := func(gtx layout.Context) layout.Dimensions {
|
||||||
|
icon := u.menuIcon
|
||||||
|
if icon == nil {
|
||||||
|
icon = u.settingsIcon
|
||||||
|
}
|
||||||
|
btn := material.IconButton(u.theme, &u.toggleMainMenu, icon, "Menu")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
contentInsetPx := gtx.Dp(unit.Dp(16))
|
||||||
|
surface := dropdownSurface{
|
||||||
|
ContainerWidth: max(0, gtx.Constraints.Max.X-(contentInsetPx*2)),
|
||||||
|
LeftInset: contentInsetPx,
|
||||||
|
TopInset: contentInsetPx,
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.syncMenuVisibleOnPhone() {
|
||||||
|
surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneSyncMenuAnchor.X, TriggerBottomY: u.phoneSyncMenuAnchor.Y}, u.syncMenu)
|
||||||
|
}
|
||||||
|
if u.mainMenuVisibleOnPhone() {
|
||||||
|
surface.draw(gtx, dropdownAnchor{TriggerRightX: u.phoneMainMenuAnchor.X, TriggerBottomY: u.phoneMainMenuAnchor.Y}, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) syncMenuRightAlignsToTrigger() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) headerMenusUseOverlayModel() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) mainMenuDropsBelowTrigger() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) mainMenuRightAlignsToTrigger() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func anchoredMenuX(triggerWidth, menuWidth int) int {
|
||||||
|
return triggerWidth - menuWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func anchoredMenuOriginX(containerWidth, rowOriginX, triggerRightX, menuWidth int) int {
|
||||||
|
x := rowOriginX + triggerRightX - menuWidth
|
||||||
|
if x < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if x+menuWidth > containerWidth {
|
||||||
|
return max(0, containerWidth-menuWidth)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
||||||
|
width := 0
|
||||||
|
for _, row := range rows {
|
||||||
|
measureGTX := gtx
|
||||||
|
measureGTX.Constraints.Min = image.Point{}
|
||||||
|
macro := op.Record(gtx.Ops)
|
||||||
|
dims := row(measureGTX)
|
||||||
|
_ = macro.Stop()
|
||||||
|
if dims.Size.X > width {
|
||||||
|
width = dims.Size.X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions {
|
||||||
|
if width <= 0 {
|
||||||
|
return child(gtx)
|
||||||
|
}
|
||||||
|
gtx.Constraints.Min.X = width
|
||||||
|
gtx.Constraints.Max.X = width
|
||||||
|
return layout.E.Layout(gtx, child)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user