Compare commits
4 Commits
2f1cd7876c
...
44da1e6599
| Author | SHA1 | Date | |
|---|---|---|---|
| 44da1e6599 | |||
| b59cf8044b | |||
| 5838588fc5 | |||
| 0e9fd478e5 |
@@ -377,6 +377,7 @@ type ui struct {
|
||||
settingsLifecycleAdvanced widget.Bool
|
||||
settingsHistory widget.Bool
|
||||
settingsDenseLayout widget.Bool
|
||||
settingsDebugHeaderBounds widget.Bool
|
||||
entryClicks []widget.Clickable
|
||||
apiTokenClicks []widget.Clickable
|
||||
apiPolicyRemoves []widget.Clickable
|
||||
@@ -477,6 +478,7 @@ type ui struct {
|
||||
autofillNoticePreference autofillNoticeMode
|
||||
autofillFirstFillApprovalMode autofillFirstFillApprovalMode
|
||||
accessibilityPrefs accessibilityPreferences
|
||||
debugLogHeaderBounds bool
|
||||
settingsDraft settingsDraft
|
||||
recentVaults []string
|
||||
recentRemotes []recentRemoteRecord
|
||||
@@ -502,6 +504,8 @@ type ui struct {
|
||||
lastLifecycleAction string
|
||||
pendingLifecycleOpenIntent lifecycleOpenIntent
|
||||
requestMasterPassFocus bool
|
||||
lastHeaderBoundsLog string
|
||||
frameInsetPx int
|
||||
invalidate func()
|
||||
}
|
||||
|
||||
@@ -688,6 +692,9 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
||||
u.requestMasterPassFocus = u.hasSelectedLifecycleTarget()
|
||||
u.loadUIPreferences()
|
||||
u.loadSettings()
|
||||
if u.debugLogHeaderBounds {
|
||||
platform.LogInfo("KeePassGO", "keepassgo header-bounds logging enabled")
|
||||
}
|
||||
u.loadSettingsFormFromPreferences()
|
||||
u.loadSettingsDraft()
|
||||
u.requestMasterPassFocus = u.hasSelectedLifecycleTarget()
|
||||
@@ -1136,6 +1143,16 @@ func (u *ui) securityDialogContent(gtx layout.Context) layout.Dimensions {
|
||||
check := material.CheckBox(u.theme, &u.settingsHistory, "Keep entry history collapsed")
|
||||
return check.Layout(gtx)
|
||||
},
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
check := material.CheckBox(u.theme, &u.settingsDebugHeaderBounds, "Log compact header button bounds")
|
||||
return check.Layout(gtx)
|
||||
},
|
||||
layout.Spacer{Height: unit.Dp(4)}.Layout,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "Write compact Android header button screen coordinates to the app log so emulator taps can read exact bounds from logcat.")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
},
|
||||
layout.Spacer{Height: unit.Dp(14)}.Layout,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(16), "Vault Security")
|
||||
|
||||
@@ -603,6 +603,7 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
||||
u.handleGroupClicks(gtx)
|
||||
u.handleInputUpdates(gtx)
|
||||
u.updateViewportLayoutMode(gtx)
|
||||
u.frameInsetPx = gtx.Dp(unit.Dp(16))
|
||||
inset := layout.UniformInset(unit.Dp(16))
|
||||
return layout.Stack{}.Layout(gtx,
|
||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
||||
|
||||
+94
-15
@@ -1,6 +1,7 @@
|
||||
package appui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"gioui.org/layout"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
headerview "git.julianfamily.org/keepassgo/internal/appui/header"
|
||||
headerlayout "git.julianfamily.org/keepassgo/internal/appui/header/layout"
|
||||
)
|
||||
|
||||
@@ -25,10 +27,10 @@ func (u *ui) header(gtx layout.Context) 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 {
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return u.brandMark(gtx, 196, 56)
|
||||
}),
|
||||
layout.Rigid(u.headerActions),
|
||||
layout.Flexed(1, u.headerActions),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -38,11 +40,14 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
spacing := gtx.Dp(unit.Dp(8))
|
||||
metrics := headerlayout.ActionMetrics{Spacing: spacing}
|
||||
row := func(gtx layout.Context) layout.Dimensions {
|
||||
metrics := headerlayout.ActionMetrics{Spacing: spacing, SyncInnerSpacing: gtx.Dp(unit.Dp(3))}
|
||||
if !u.usesCompactViewport() {
|
||||
metrics.SyncInnerSpacing = gtx.Dp(unit.Dp(4))
|
||||
}
|
||||
actionCluster := 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)
|
||||
metrics.SyncDims, metrics.SyncPrimaryDims, metrics.SyncToggleDims = u.syncButtonGroupWithMetrics(gtx)
|
||||
return metrics.SyncDims
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||
@@ -60,18 +65,19 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
|
||||
rowOps := op.Record(gtx.Ops)
|
||||
metrics.RowDims = row(gtx)
|
||||
metrics.RowDims = actionCluster(gtx)
|
||||
rowCall := rowOps.Stop()
|
||||
|
||||
if u.usesCompactViewport() {
|
||||
metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X)
|
||||
}
|
||||
metrics.RowOriginX = max(0, gtx.Constraints.Max.X-metrics.RowDims.Size.X)
|
||||
|
||||
surface := headerlayout.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()
|
||||
rowDims := layout.E.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
rowCall.Add(gtx.Ops)
|
||||
return metrics.RowDims
|
||||
})
|
||||
if u.usesCompactViewport() {
|
||||
u.maybeLogHeaderBounds(newHeaderButtonBounds(image.Pt(u.frameInsetPx, u.frameInsetPx), metrics.Bounds()))
|
||||
}
|
||||
|
||||
if u.usesCompactViewport() {
|
||||
if u.syncMenuOpen {
|
||||
@@ -82,7 +88,7 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||
u.phoneMainMenuVisible = true
|
||||
u.phoneMainMenuAnchor = metrics.MainAnchor().Point()
|
||||
}
|
||||
return layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, metrics.RowDims.Size.Y)}
|
||||
return layout.Dimensions{Size: image.Pt(gtx.Constraints.Max.X, rowDims.Size.Y)}
|
||||
}
|
||||
|
||||
if u.syncMenuOpen {
|
||||
@@ -92,7 +98,38 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||
surface.Draw(gtx, metrics.MainAnchor(), u.mainMenu)
|
||||
}
|
||||
|
||||
return layout.Dimensions{Size: image.Pt(metrics.RowDims.Size.X, metrics.RowDims.Size.Y)}
|
||||
return rowDims
|
||||
}
|
||||
|
||||
type headerButtonBounds struct {
|
||||
SyncPrimary image.Rectangle
|
||||
SyncToggle image.Rectangle
|
||||
Lock image.Rectangle
|
||||
MainMenu image.Rectangle
|
||||
}
|
||||
|
||||
func newHeaderButtonBounds(origin image.Point, bounds headerlayout.ActionBounds) headerButtonBounds {
|
||||
return headerButtonBounds{
|
||||
SyncPrimary: bounds.SyncPrimary.Add(origin),
|
||||
SyncToggle: bounds.SyncToggle.Add(origin),
|
||||
Lock: bounds.Lock.Add(origin),
|
||||
MainMenu: bounds.MainMenu.Add(origin),
|
||||
}
|
||||
}
|
||||
|
||||
func (b headerButtonBounds) logLine(mode string) string {
|
||||
return fmt.Sprintf(
|
||||
"keepassgo header-bounds mode=%s sync=%s sync_toggle=%s lock=%s menu=%s",
|
||||
mode,
|
||||
formatHeaderRect(b.SyncPrimary),
|
||||
formatHeaderRect(b.SyncToggle),
|
||||
formatHeaderRect(b.Lock),
|
||||
formatHeaderRect(b.MainMenu),
|
||||
)
|
||||
}
|
||||
|
||||
func formatHeaderRect(rect image.Rectangle) string {
|
||||
return fmt.Sprintf("%d,%d-%d,%d", rect.Min.X, rect.Min.Y, rect.Max.X, rect.Max.Y)
|
||||
}
|
||||
|
||||
func (u *ui) topRightActionOrder() []string {
|
||||
@@ -174,3 +211,45 @@ func (u *ui) brandImage(gtx layout.Context, src paint.ImageOp, widthDP, heightDP
|
||||
gtx.Constraints.Max = image.Pt(width, height)
|
||||
return img.Layout(gtx)
|
||||
}
|
||||
|
||||
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")
|
||||
},
|
||||
}
|
||||
return headerview.MainMenu(gtx, u.theme, rows, compactCard)
|
||||
}
|
||||
|
||||
func (u *ui) mainMenuButtonGroup(gtx layout.Context) layout.Dimensions {
|
||||
icon := u.menuIcon
|
||||
if icon == nil {
|
||||
icon = u.settingsIcon
|
||||
}
|
||||
return headerview.MainMenuButtonGroup(gtx, u.theme, &u.toggleMainMenu, icon, u.mainMenuOpen, selectedColor, accentColor)
|
||||
}
|
||||
|
||||
func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions {
|
||||
return headerlayout.IntrinsicCompactCard(gtx, w, compactCard)
|
||||
}
|
||||
|
||||
func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
||||
return headerlayout.MenuActionWidth(gtx, rows)
|
||||
}
|
||||
|
||||
func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions {
|
||||
return headerlayout.RightAlignedAction(gtx, width, child)
|
||||
}
|
||||
|
||||
@@ -64,12 +64,15 @@ func (s DropdownSurface) Draw(gtx layout.Context, anchor DropdownAnchor, menu la
|
||||
}
|
||||
|
||||
type ActionMetrics struct {
|
||||
RowOriginX int
|
||||
Spacing int
|
||||
RowDims layout.Dimensions
|
||||
SyncDims layout.Dimensions
|
||||
LockDims layout.Dimensions
|
||||
MainDims layout.Dimensions
|
||||
RowOriginX int
|
||||
Spacing int
|
||||
SyncInnerSpacing int
|
||||
RowDims layout.Dimensions
|
||||
SyncDims layout.Dimensions
|
||||
SyncPrimaryDims layout.Dimensions
|
||||
SyncToggleDims layout.Dimensions
|
||||
LockDims layout.Dimensions
|
||||
MainDims layout.Dimensions
|
||||
}
|
||||
|
||||
func (m ActionMetrics) SyncAnchor() DropdownAnchor {
|
||||
@@ -86,3 +89,28 @@ func (m ActionMetrics) MainAnchor() DropdownAnchor {
|
||||
TriggerBottomY: m.RowDims.Size.Y,
|
||||
}
|
||||
}
|
||||
|
||||
type ActionBounds struct {
|
||||
SyncPrimary image.Rectangle
|
||||
SyncToggle image.Rectangle
|
||||
Lock image.Rectangle
|
||||
MainMenu image.Rectangle
|
||||
}
|
||||
|
||||
func (m ActionMetrics) Bounds() ActionBounds {
|
||||
top := 0
|
||||
syncLeft := m.RowOriginX
|
||||
syncPrimary := image.Rect(syncLeft, top, syncLeft+m.SyncPrimaryDims.Size.X, top+m.SyncPrimaryDims.Size.Y)
|
||||
syncToggleLeft := syncPrimary.Max.X + m.SyncInnerSpacing
|
||||
syncToggle := image.Rect(syncToggleLeft, top, syncToggleLeft+m.SyncToggleDims.Size.X, top+m.SyncToggleDims.Size.Y)
|
||||
lockLeft := syncLeft + m.SyncDims.Size.X + m.Spacing
|
||||
lock := image.Rect(lockLeft, top, lockLeft+m.LockDims.Size.X, top+m.LockDims.Size.Y)
|
||||
mainLeft := lock.Max.X + m.Spacing
|
||||
mainMenu := image.Rect(mainLeft, top, mainLeft+m.MainDims.Size.X, top+m.MainDims.Size.Y)
|
||||
return ActionBounds{
|
||||
SyncPrimary: syncPrimary,
|
||||
SyncToggle: syncToggle,
|
||||
Lock: lock,
|
||||
MainMenu: mainMenu,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package appui
|
||||
package layout
|
||||
|
||||
import (
|
||||
"image"
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions {
|
||||
func IntrinsicCompactCard(gtx layout.Context, w layout.Widget, card func(layout.Context, layout.Widget) layout.Dimensions) layout.Dimensions {
|
||||
measureGTX := gtx
|
||||
measureGTX.Constraints.Min = image.Point{}
|
||||
measureGTX.Constraints.Max.X = gtx.Constraints.Max.X
|
||||
@@ -24,10 +24,10 @@ func intrinsicCompactCard(gtx layout.Context, w layout.Widget) layout.Dimensions
|
||||
gtx.Constraints.Min.X = width
|
||||
gtx.Constraints.Max.X = width
|
||||
}
|
||||
return compactCard(gtx, w)
|
||||
return card(gtx, w)
|
||||
}
|
||||
|
||||
func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
||||
func MenuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
||||
width := 0
|
||||
for _, row := range rows {
|
||||
measureGTX := gtx
|
||||
@@ -42,7 +42,7 @@ func menuActionWidth(gtx layout.Context, rows []layout.Widget) int {
|
||||
return width
|
||||
}
|
||||
|
||||
func rightAlignedMenuAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions {
|
||||
func RightAlignedAction(gtx layout.Context, width int, child layout.Widget) layout.Dimensions {
|
||||
if width <= 0 {
|
||||
return child(gtx)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package header
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
headerlayout "git.julianfamily.org/keepassgo/internal/appui/header/layout"
|
||||
)
|
||||
|
||||
func MainMenu(gtx layout.Context, theme *material.Theme, rows []layout.Widget, card func(layout.Context, layout.Widget) layout.Dimensions) layout.Dimensions {
|
||||
rowWidth := headerlayout.MenuActionWidth(gtx, rows)
|
||||
return headerlayout.IntrinsicCompactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
children := make([]layout.FlexChild, 0, (len(rows)*2)-1)
|
||||
for i, row := range rows {
|
||||
if i > 0 {
|
||||
children = append(children, layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout))
|
||||
}
|
||||
current := row
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return headerlayout.RightAlignedAction(gtx, rowWidth, current)
|
||||
}))
|
||||
}
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...)
|
||||
}, card)
|
||||
}
|
||||
|
||||
func MainMenuButtonGroup(gtx layout.Context, theme *material.Theme, click *widget.Clickable, icon *widget.Icon, open bool, selectedColor, accentColor color.NRGBA) layout.Dimensions {
|
||||
btn := material.IconButton(theme, click, icon, "Menu")
|
||||
if open {
|
||||
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)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package appui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
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) mainMenuButtonGroup(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)
|
||||
}
|
||||
@@ -15,19 +15,29 @@ import (
|
||||
)
|
||||
|
||||
func (u *ui) syncButtonGroup(gtx layout.Context) layout.Dimensions {
|
||||
group, _, _ := u.syncButtonGroupWithMetrics(gtx)
|
||||
return group
|
||||
}
|
||||
|
||||
func (u *ui) syncButtonGroupWithMetrics(gtx layout.Context) (layout.Dimensions, layout.Dimensions, layout.Dimensions) {
|
||||
spacing := unit.Dp(4)
|
||||
if u.usesCompactViewport() {
|
||||
spacing = unit.Dp(3)
|
||||
}
|
||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||
var primaryDims layout.Dimensions
|
||||
var toggleDims layout.Dimensions
|
||||
groupDims := layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return syncPrimaryButton(gtx, u.theme, &u.synchronizeVault, "Sync", u.usesCompactViewport())
|
||||
primaryDims = syncPrimaryButton(gtx, u.theme, &u.synchronizeVault, "Sync", u.usesCompactViewport())
|
||||
return primaryDims
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: spacing}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return u.syncMenuToggle(gtx)
|
||||
toggleDims = u.syncMenuToggle(gtx)
|
||||
return toggleDims
|
||||
}),
|
||||
)
|
||||
return groupDims, primaryDims, toggleDims
|
||||
}
|
||||
|
||||
func (u *ui) syncMenuToggle(gtx layout.Context) layout.Dimensions {
|
||||
|
||||
@@ -5460,6 +5460,28 @@ func TestUISyncDefaultsPersistInSettings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIDebugHeaderBoundsPersistInSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "settings.json")
|
||||
|
||||
first := newUIWithSession("phone", &session.Manager{}, statePaths{
|
||||
SettingsPath: configPath,
|
||||
})
|
||||
first.debugLogHeaderBounds = true
|
||||
first.saveSettings()
|
||||
|
||||
second := newUIWithSession("phone", &session.Manager{}, statePaths{
|
||||
SettingsPath: configPath,
|
||||
})
|
||||
second.debugLogHeaderBounds = false
|
||||
second.loadSettings()
|
||||
|
||||
if !second.debugLogHeaderBounds {
|
||||
t.Fatal("debugLogHeaderBounds = false, want true after reload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUILoadSettingsFallsBackToLegacySyncDefaultsInUIPreferences(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -5552,6 +5574,39 @@ func TestUISaveSecuritySettingsPersistsSyncDefaults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUISaveSecuritySettingsPersistsDebugHeaderBounds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := &session.Manager{}
|
||||
dir := t.TempDir()
|
||||
u := newUIWithSession("phone", manager, statePaths{
|
||||
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||
SettingsPath: filepath.Join(dir, "settings.json"),
|
||||
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
|
||||
})
|
||||
u.masterPassword.SetText("correct horse battery staple")
|
||||
if err := u.createVaultAction(); err != nil {
|
||||
t.Fatalf("createVaultAction() error = %v", err)
|
||||
}
|
||||
u.securityCipher.SetText(vault.CipherAES256)
|
||||
u.securityKDF.SetText(vault.KDFAES)
|
||||
u.loadSettingsDraft()
|
||||
u.settingsDebugHeaderBounds.Value = true
|
||||
|
||||
if err := u.saveSecuritySettingsAction(); err != nil {
|
||||
t.Fatalf("saveSecuritySettingsAction() error = %v", err)
|
||||
}
|
||||
|
||||
reloaded := newUIWithSession("phone", &session.Manager{}, statePaths{
|
||||
SettingsPath: u.settingsPath,
|
||||
})
|
||||
reloaded.loadSettings()
|
||||
|
||||
if !reloaded.debugLogHeaderBounds {
|
||||
t.Fatal("reloaded debugLogHeaderBounds = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIAccessibilityPreferencesPersist(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//go:build android
|
||||
|
||||
package platform
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror
|
||||
#cgo LDFLAGS: -landroid
|
||||
|
||||
#include <android/log.h>
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func LogInfo(tag, msg string) {
|
||||
ctag := C.CString(tag)
|
||||
defer C.free(unsafe.Pointer(ctag))
|
||||
cmsg := C.CString(msg)
|
||||
defer C.free(unsafe.Pointer(cmsg))
|
||||
C.__android_log_write(C.ANDROID_LOG_INFO, ctag, cmsg)
|
||||
}
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
package platform
|
||||
|
||||
import "log"
|
||||
|
||||
func NewVaultSharer(goos string) VaultSharer {
|
||||
return nil
|
||||
}
|
||||
|
||||
func LogInfo(tag, msg string) {
|
||||
log.Printf("%s: %s", tag, msg)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
editormodel "git.julianfamily.org/keepassgo/internal/appui/editor"
|
||||
"git.julianfamily.org/keepassgo/internal/appui/platform"
|
||||
settingsmodel "git.julianfamily.org/keepassgo/internal/appui/settings"
|
||||
"git.julianfamily.org/keepassgo/internal/vault"
|
||||
)
|
||||
@@ -35,7 +36,8 @@ const (
|
||||
type accessibilityPreferences = settingsmodel.AccessibilityPreferences
|
||||
|
||||
type settingsFile struct {
|
||||
Sync syncSettings `json:"sync,omitempty"`
|
||||
Sync syncSettings `json:"sync,omitempty"`
|
||||
Debug debugSettings `json:"debug,omitempty"`
|
||||
}
|
||||
|
||||
type syncSettings struct {
|
||||
@@ -43,6 +45,10 @@ type syncSettings struct {
|
||||
DirectionDefault string `json:"directionDefault,omitempty"`
|
||||
}
|
||||
|
||||
type debugSettings struct {
|
||||
LogHeaderBounds bool `json:"logHeaderBounds,omitempty"`
|
||||
}
|
||||
|
||||
type syncSettingsDraft struct {
|
||||
SourceDefault syncSourceMode
|
||||
DirectionDefault syncDirection
|
||||
@@ -51,6 +57,7 @@ type syncSettingsDraft struct {
|
||||
type settingsDraft struct {
|
||||
Accessibility accessibilityPreferences
|
||||
Sync syncSettingsDraft
|
||||
Debug debugSettings
|
||||
}
|
||||
|
||||
type legacySyncPreferences struct {
|
||||
@@ -191,7 +198,11 @@ func (u *ui) loadSettingsDraft() {
|
||||
SourceDefault: u.syncDefaultSourceMode,
|
||||
DirectionDefault: u.syncDefaultDirection,
|
||||
},
|
||||
Debug: debugSettings{
|
||||
LogHeaderBounds: u.debugLogHeaderBounds,
|
||||
},
|
||||
}
|
||||
u.settingsDebugHeaderBounds.Value = u.settingsDraft.Debug.LogHeaderBounds
|
||||
}
|
||||
|
||||
func (u *ui) saveSecuritySettingsAction() error {
|
||||
@@ -213,9 +224,14 @@ func (u *ui) applySecuritySettingsLive() error {
|
||||
if u.settingsDraft.Accessibility.DisplayDensity == displayDensityForDenseLayout(u.denseLayout) {
|
||||
u.settingsDraft.Accessibility.DisplayDensity = displayDensityForDenseLayout(u.settingsDenseLayout.Value)
|
||||
}
|
||||
u.settingsDraft.Debug.LogHeaderBounds = u.settingsDebugHeaderBounds.Value
|
||||
u.settingsDenseLayout.Value = u.settingsDraft.Accessibility.DisplayDensity == displayDensityDense
|
||||
u.syncDefaultSourceMode = sanitizeSyncSourceMode(u.settingsDraft.Sync.SourceDefault)
|
||||
u.syncDefaultDirection = sanitizeSyncDirection(u.settingsDraft.Sync.DirectionDefault)
|
||||
u.debugLogHeaderBounds = u.settingsDraft.Debug.LogHeaderBounds
|
||||
if !u.debugLogHeaderBounds {
|
||||
u.lastHeaderBoundsLog = ""
|
||||
}
|
||||
u.applySettingsFormToPreferences()
|
||||
u.applyAccessibilityPreferences(u.settingsDraft.Accessibility)
|
||||
u.saveSettings()
|
||||
@@ -234,6 +250,7 @@ func (u *ui) loadSettings() {
|
||||
if json.Unmarshal(content, &settings) == nil {
|
||||
u.syncDefaultSourceMode = sanitizeSyncSourceMode(syncSourceMode(settings.Sync.SourceDefault))
|
||||
u.syncDefaultDirection = sanitizeSyncDirection(syncDirection(settings.Sync.DirectionDefault))
|
||||
u.debugLogHeaderBounds = settings.Debug.LogHeaderBounds
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -270,6 +287,9 @@ func (u *ui) saveSettings() {
|
||||
SourceDefault: string(u.syncDefaultSourceMode),
|
||||
DirectionDefault: string(u.syncDefaultDirection),
|
||||
},
|
||||
Debug: debugSettings{
|
||||
LogHeaderBounds: u.debugLogHeaderBounds,
|
||||
},
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
return
|
||||
@@ -277,6 +297,18 @@ func (u *ui) saveSettings() {
|
||||
_ = os.WriteFile(u.settingsPath, content, 0o600)
|
||||
}
|
||||
|
||||
func (u *ui) maybeLogHeaderBounds(bounds headerButtonBounds) {
|
||||
if !u.debugLogHeaderBounds {
|
||||
return
|
||||
}
|
||||
line := bounds.logLine(u.mode)
|
||||
if line == u.lastHeaderBoundsLog {
|
||||
return
|
||||
}
|
||||
platform.LogInfo("KeePassGO", line)
|
||||
u.lastHeaderBoundsLog = line
|
||||
}
|
||||
|
||||
func (u *ui) showStatusMessage(message string) {
|
||||
u.state.StatusMessage = message
|
||||
if u.accessibilityPrefs.ReducedMotion {
|
||||
|
||||
Reference in New Issue
Block a user