Log compact header button bounds
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1136,6 +1140,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 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package appui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"gioui.org/layout"
|
||||
@@ -39,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}
|
||||
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),
|
||||
@@ -71,6 +75,9 @@ func (u *ui) headerActions(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 {
|
||||
@@ -94,6 +101,37 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
||||
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 {
|
||||
if u.isVaultLocked() {
|
||||
return nil
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -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
|
||||
}
|
||||
log.Print(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