Add persisted UI settings preferences
This commit is contained in:
@@ -141,6 +141,7 @@ type uiPreferences struct {
|
|||||||
GroupControlsHidden bool `json:"groupControlsHidden"`
|
GroupControlsHidden bool `json:"groupControlsHidden"`
|
||||||
LifecycleAdvancedHidden bool `json:"lifecycleAdvancedHidden"`
|
LifecycleAdvancedHidden bool `json:"lifecycleAdvancedHidden"`
|
||||||
HistoryHidden bool `json:"historyHidden"`
|
HistoryHidden bool `json:"historyHidden"`
|
||||||
|
DenseLayout bool `json:"denseLayout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type entriesSectionState struct {
|
type entriesSectionState struct {
|
||||||
@@ -165,217 +166,219 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ui struct {
|
type ui struct {
|
||||||
mode string
|
mode string
|
||||||
theme *material.Theme
|
theme *material.Theme
|
||||||
logoHorizontal paint.ImageOp
|
logoHorizontal paint.ImageOp
|
||||||
splashSquare paint.ImageOp
|
splashSquare paint.ImageOp
|
||||||
search widget.Editor
|
search widget.Editor
|
||||||
vaultPath widget.Editor
|
vaultPath widget.Editor
|
||||||
saveAsPath widget.Editor
|
saveAsPath widget.Editor
|
||||||
remoteBaseURL widget.Editor
|
remoteBaseURL widget.Editor
|
||||||
remotePath widget.Editor
|
remotePath widget.Editor
|
||||||
remoteUsername widget.Editor
|
remoteUsername widget.Editor
|
||||||
remotePassword widget.Editor
|
remotePassword widget.Editor
|
||||||
masterPassword widget.Editor
|
masterPassword widget.Editor
|
||||||
keyFilePath widget.Editor
|
keyFilePath widget.Editor
|
||||||
apiTokenName widget.Editor
|
apiTokenName widget.Editor
|
||||||
apiTokenClientName widget.Editor
|
apiTokenClientName widget.Editor
|
||||||
apiTokenExpiresAt widget.Editor
|
apiTokenExpiresAt widget.Editor
|
||||||
apiPolicyOperation widget.Editor
|
apiPolicyOperation widget.Editor
|
||||||
apiPolicyPath widget.Editor
|
apiPolicyPath widget.Editor
|
||||||
apiPolicyEntryID widget.Editor
|
apiPolicyEntryID widget.Editor
|
||||||
securityCipher widget.Editor
|
securityCipher widget.Editor
|
||||||
securityKDF widget.Editor
|
securityKDF widget.Editor
|
||||||
entryID widget.Editor
|
entryID widget.Editor
|
||||||
entryTitle widget.Editor
|
entryTitle widget.Editor
|
||||||
entryUsername widget.Editor
|
entryUsername widget.Editor
|
||||||
entryPassword widget.Editor
|
entryPassword widget.Editor
|
||||||
entryURL widget.Editor
|
entryURL widget.Editor
|
||||||
entryNotes widget.Editor
|
entryNotes widget.Editor
|
||||||
entryTags widget.Editor
|
entryTags widget.Editor
|
||||||
entryPath widget.Editor
|
entryPath widget.Editor
|
||||||
entryFields widget.Editor
|
entryFields widget.Editor
|
||||||
customFieldKeys []widget.Editor
|
customFieldKeys []widget.Editor
|
||||||
customFieldValues []widget.Editor
|
customFieldValues []widget.Editor
|
||||||
historyIndex widget.Editor
|
historyIndex widget.Editor
|
||||||
groupName widget.Editor
|
groupName widget.Editor
|
||||||
groupParentPath widget.Editor
|
groupParentPath widget.Editor
|
||||||
passwordProfile widget.Editor
|
passwordProfile widget.Editor
|
||||||
attachmentName widget.Editor
|
attachmentName widget.Editor
|
||||||
attachmentPath widget.Editor
|
attachmentPath widget.Editor
|
||||||
exportAttachmentPath widget.Editor
|
exportAttachmentPath widget.Editor
|
||||||
list widget.List
|
list widget.List
|
||||||
groupList widget.List
|
groupList widget.List
|
||||||
detailList widget.List
|
detailList widget.List
|
||||||
apiPolicyList widget.List
|
apiPolicyList widget.List
|
||||||
lifecycleList widget.List
|
lifecycleList widget.List
|
||||||
copyUser widget.Clickable
|
copyUser widget.Clickable
|
||||||
copyPass widget.Clickable
|
copyPass widget.Clickable
|
||||||
copyURL widget.Clickable
|
copyURL widget.Clickable
|
||||||
lockVault widget.Clickable
|
lockVault widget.Clickable
|
||||||
unlockVault widget.Clickable
|
unlockVault widget.Clickable
|
||||||
createVault widget.Clickable
|
createVault widget.Clickable
|
||||||
openVault widget.Clickable
|
openVault widget.Clickable
|
||||||
saveVault widget.Clickable
|
saveVault widget.Clickable
|
||||||
saveAsVault widget.Clickable
|
saveAsVault widget.Clickable
|
||||||
openRemote widget.Clickable
|
openRemote widget.Clickable
|
||||||
changeMasterKey widget.Clickable
|
changeMasterKey widget.Clickable
|
||||||
synchronizeVault widget.Clickable
|
synchronizeVault widget.Clickable
|
||||||
toggleSyncMenu widget.Clickable
|
toggleSyncMenu widget.Clickable
|
||||||
openAdvancedSync widget.Clickable
|
openAdvancedSync widget.Clickable
|
||||||
openSecuritySettings widget.Clickable
|
openSecuritySettings widget.Clickable
|
||||||
openRemotePrefsHelp widget.Clickable
|
closeAdvancedSync widget.Clickable
|
||||||
closeAdvancedSync widget.Clickable
|
closeSecuritySettings widget.Clickable
|
||||||
closeSecuritySettings widget.Clickable
|
runAdvancedSync widget.Clickable
|
||||||
closeRemotePrefsHelp widget.Clickable
|
saveSecuritySettings widget.Clickable
|
||||||
runAdvancedSync widget.Clickable
|
editEntry widget.Clickable
|
||||||
saveSecuritySettings widget.Clickable
|
cancelEdit widget.Clickable
|
||||||
editEntry widget.Clickable
|
pickVaultPath widget.Clickable
|
||||||
cancelEdit widget.Clickable
|
pickKeyFile widget.Clickable
|
||||||
pickVaultPath widget.Clickable
|
pickSyncLocalPath widget.Clickable
|
||||||
pickKeyFile widget.Clickable
|
clearVaultSelection widget.Clickable
|
||||||
pickSyncLocalPath widget.Clickable
|
clearRemoteSelection widget.Clickable
|
||||||
clearVaultSelection widget.Clickable
|
dismissBanner widget.Clickable
|
||||||
clearRemoteSelection widget.Clickable
|
addEntry widget.Clickable
|
||||||
dismissBanner widget.Clickable
|
saveEntry widget.Clickable
|
||||||
addEntry widget.Clickable
|
duplicateEntry widget.Clickable
|
||||||
saveEntry widget.Clickable
|
deleteEntry widget.Clickable
|
||||||
duplicateEntry widget.Clickable
|
restoreEntry widget.Clickable
|
||||||
deleteEntry widget.Clickable
|
saveTemplate widget.Clickable
|
||||||
restoreEntry widget.Clickable
|
deleteTemplate widget.Clickable
|
||||||
saveTemplate widget.Clickable
|
instantiateTemplate widget.Clickable
|
||||||
deleteTemplate widget.Clickable
|
addAttachment widget.Clickable
|
||||||
instantiateTemplate widget.Clickable
|
replaceAttachment widget.Clickable
|
||||||
addAttachment widget.Clickable
|
removeAttachment widget.Clickable
|
||||||
replaceAttachment widget.Clickable
|
exportAttachment widget.Clickable
|
||||||
removeAttachment widget.Clickable
|
restoreHistory widget.Clickable
|
||||||
exportAttachment widget.Clickable
|
generatePassword widget.Clickable
|
||||||
restoreHistory widget.Clickable
|
goToRootGroup widget.Clickable
|
||||||
generatePassword widget.Clickable
|
goToParentGroup widget.Clickable
|
||||||
goToRootGroup widget.Clickable
|
createGroup widget.Clickable
|
||||||
goToParentGroup widget.Clickable
|
moveGroup widget.Clickable
|
||||||
createGroup widget.Clickable
|
renameGroup widget.Clickable
|
||||||
moveGroup widget.Clickable
|
deleteGroup widget.Clickable
|
||||||
renameGroup widget.Clickable
|
confirmDeleteGroup widget.Clickable
|
||||||
deleteGroup widget.Clickable
|
cancelDeleteGroup widget.Clickable
|
||||||
confirmDeleteGroup widget.Clickable
|
addCustomField widget.Clickable
|
||||||
cancelDeleteGroup widget.Clickable
|
toggleGroupControls widget.Clickable
|
||||||
addCustomField widget.Clickable
|
toggleLifecycleAdvanced widget.Clickable
|
||||||
toggleGroupControls widget.Clickable
|
toggleHistory widget.Clickable
|
||||||
toggleLifecycleAdvanced widget.Clickable
|
togglePasswordInline widget.Clickable
|
||||||
toggleHistory widget.Clickable
|
toggleSyncPassword widget.Clickable
|
||||||
togglePasswordInline widget.Clickable
|
showEntries widget.Clickable
|
||||||
toggleSyncPassword widget.Clickable
|
showTemplates widget.Clickable
|
||||||
showEntries widget.Clickable
|
showRecycle widget.Clickable
|
||||||
showTemplates widget.Clickable
|
showAPITokens widget.Clickable
|
||||||
showRecycle widget.Clickable
|
showAPIAudit widget.Clickable
|
||||||
showAPITokens widget.Clickable
|
showLocalLifecycle widget.Clickable
|
||||||
showAPIAudit widget.Clickable
|
showRemoteLifecycle widget.Clickable
|
||||||
showLocalLifecycle widget.Clickable
|
showSyncLocal widget.Clickable
|
||||||
showRemoteLifecycle widget.Clickable
|
showSyncRemote widget.Clickable
|
||||||
showSyncLocal widget.Clickable
|
showSyncPull widget.Clickable
|
||||||
showSyncRemote widget.Clickable
|
showSyncPush widget.Clickable
|
||||||
showSyncPull widget.Clickable
|
allowApproval widget.Clickable
|
||||||
showSyncPush widget.Clickable
|
denyApproval widget.Clickable
|
||||||
allowApproval widget.Clickable
|
cancelApproval widget.Clickable
|
||||||
denyApproval widget.Clickable
|
cancelLifecycleProgress widget.Clickable
|
||||||
cancelApproval widget.Clickable
|
retryLifecycleOpen widget.Clickable
|
||||||
cancelLifecycleProgress widget.Clickable
|
approvalPermanent widget.Bool
|
||||||
retryLifecycleOpen widget.Clickable
|
rememberRemoteAuth widget.Bool
|
||||||
approvalPermanent widget.Bool
|
apiPolicyAllow widget.Bool
|
||||||
rememberRemoteAuth widget.Bool
|
apiPolicyGroupScopeW widget.Bool
|
||||||
apiPolicyAllow widget.Bool
|
apiTokenDisabled widget.Bool
|
||||||
apiPolicyGroupScopeW widget.Bool
|
settingsGroupControls widget.Bool
|
||||||
apiTokenDisabled widget.Bool
|
settingsLifecycleAdvanced widget.Bool
|
||||||
entryClicks []widget.Clickable
|
settingsHistory widget.Bool
|
||||||
apiTokenClicks []widget.Clickable
|
settingsDenseLayout widget.Bool
|
||||||
apiPolicyRemoves []widget.Clickable
|
entryClicks []widget.Clickable
|
||||||
apiAuditClicks []widget.Clickable
|
apiTokenClicks []widget.Clickable
|
||||||
apiAuditTokenFilters []widget.Clickable
|
apiPolicyRemoves []widget.Clickable
|
||||||
apiAuditDecisionFilters []widget.Clickable
|
apiAuditClicks []widget.Clickable
|
||||||
apiAuditOperationFilters []widget.Clickable
|
apiAuditTokenFilters []widget.Clickable
|
||||||
clearAPIAuditFilters widget.Clickable
|
apiAuditDecisionFilters []widget.Clickable
|
||||||
historyClicks []widget.Clickable
|
apiAuditOperationFilters []widget.Clickable
|
||||||
attachmentClicks []widget.Clickable
|
clearAPIAuditFilters widget.Clickable
|
||||||
breadcrumbs []widget.Clickable
|
historyClicks []widget.Clickable
|
||||||
groupClicks []widget.Clickable
|
attachmentClicks []widget.Clickable
|
||||||
recentVaultClicks []widget.Clickable
|
breadcrumbs []widget.Clickable
|
||||||
recentRemoteClicks []widget.Clickable
|
groupClicks []widget.Clickable
|
||||||
removeCustomFields []widget.Clickable
|
recentVaultClicks []widget.Clickable
|
||||||
state appstate.State
|
recentRemoteClicks []widget.Clickable
|
||||||
visible []entry
|
removeCustomFields []widget.Clickable
|
||||||
currentPath []string
|
state appstate.State
|
||||||
syncedPath []string
|
visible []entry
|
||||||
selectedHistoryIndex int
|
currentPath []string
|
||||||
showPassword bool
|
syncedPath []string
|
||||||
generatedPasswordDraft bool
|
selectedHistoryIndex int
|
||||||
togglePassword widget.Clickable
|
showPassword bool
|
||||||
copyAPITokenSecret widget.Clickable
|
generatedPasswordDraft bool
|
||||||
issueAPIToken widget.Clickable
|
togglePassword widget.Clickable
|
||||||
saveAPIToken widget.Clickable
|
copyAPITokenSecret widget.Clickable
|
||||||
rotateAPIToken widget.Clickable
|
issueAPIToken widget.Clickable
|
||||||
disableAPIToken widget.Clickable
|
saveAPIToken widget.Clickable
|
||||||
revokeAPIToken widget.Clickable
|
rotateAPIToken widget.Clickable
|
||||||
deleteAPIToken widget.Clickable
|
disableAPIToken widget.Clickable
|
||||||
addAPIPolicyRule widget.Clickable
|
revokeAPIToken widget.Clickable
|
||||||
phoneSplit widget.Float
|
deleteAPIToken widget.Clickable
|
||||||
splitDrag gesture.Drag
|
addAPIPolicyRule widget.Clickable
|
||||||
splitBase float32
|
phoneSplit widget.Float
|
||||||
splitStartY float32
|
splitDrag gesture.Drag
|
||||||
phoneSpan int
|
splitBase float32
|
||||||
eyeIcon *widget.Icon
|
splitStartY float32
|
||||||
eyeOffIcon *widget.Icon
|
phoneSpan int
|
||||||
copyIcon *widget.Icon
|
eyeIcon *widget.Icon
|
||||||
expandMoreIcon *widget.Icon
|
eyeOffIcon *widget.Icon
|
||||||
expandLessIcon *widget.Icon
|
copyIcon *widget.Icon
|
||||||
chevronDownIcon *widget.Icon
|
expandMoreIcon *widget.Icon
|
||||||
settingsIcon *widget.Icon
|
expandLessIcon *widget.Icon
|
||||||
clipboardWriter clipboard.Writer
|
chevronDownIcon *widget.Icon
|
||||||
loadingMessage string
|
settingsIcon *widget.Icon
|
||||||
loadingActionLabel string
|
clipboardWriter clipboard.Writer
|
||||||
lifecycleMode string
|
loadingMessage string
|
||||||
syncSourceMode syncSourceMode
|
loadingActionLabel string
|
||||||
syncDirection syncDirection
|
lifecycleMode string
|
||||||
syncLocalPath widget.Editor
|
syncSourceMode syncSourceMode
|
||||||
syncRemoteBaseURL widget.Editor
|
syncDirection syncDirection
|
||||||
syncRemotePath widget.Editor
|
syncLocalPath widget.Editor
|
||||||
syncRemoteUsername widget.Editor
|
syncRemoteBaseURL widget.Editor
|
||||||
syncRemotePassword widget.Editor
|
syncRemotePath widget.Editor
|
||||||
syncDialogOpen bool
|
syncRemoteUsername widget.Editor
|
||||||
syncMenuOpen bool
|
syncRemotePassword widget.Editor
|
||||||
securityDialogOpen bool
|
syncDialogOpen bool
|
||||||
remotePrefsDialogOpen bool
|
syncMenuOpen bool
|
||||||
showSyncPassword bool
|
securityDialogOpen bool
|
||||||
keyboardFocus focusID
|
showSyncPassword bool
|
||||||
defaultSaveAsPath string
|
keyboardFocus focusID
|
||||||
recentVaultsPath string
|
defaultSaveAsPath string
|
||||||
uiPreferencesPath string
|
recentVaultsPath string
|
||||||
recentRemotesPath string
|
uiPreferencesPath string
|
||||||
autofillCachePath string
|
recentRemotesPath string
|
||||||
editingEntry bool
|
autofillCachePath string
|
||||||
groupControlsHidden bool
|
editingEntry bool
|
||||||
lifecycleAdvancedHidden bool
|
groupControlsHidden bool
|
||||||
historyHidden bool
|
lifecycleAdvancedHidden bool
|
||||||
recentVaults []string
|
historyHidden bool
|
||||||
recentRemotes []recentRemoteRecord
|
denseLayout bool
|
||||||
recentVaultGroups map[string][]string
|
recentVaults []string
|
||||||
recentVaultUsedAt map[string]time.Time
|
recentRemotes []recentRemoteRecord
|
||||||
entriesState entriesSectionState
|
recentVaultGroups map[string][]string
|
||||||
deleteGroupPath []string
|
recentVaultUsedAt map[string]time.Time
|
||||||
apiPolicyGroupScope bool
|
entriesState entriesSectionState
|
||||||
apiTokenSecret string
|
deleteGroupPath []string
|
||||||
selectedAuditIndex int
|
apiPolicyGroupScope bool
|
||||||
statusExpiresAt time.Time
|
apiTokenSecret string
|
||||||
now func() time.Time
|
selectedAuditIndex int
|
||||||
apiHost *api.Host
|
statusExpiresAt time.Time
|
||||||
auditLog *apiaudit.Log
|
now func() time.Time
|
||||||
grpcAddress string
|
apiHost *api.Host
|
||||||
backgroundResults chan backgroundActionResult
|
auditLog *apiaudit.Log
|
||||||
backgroundActionSerial int
|
grpcAddress string
|
||||||
activeBackgroundAction int
|
backgroundResults chan backgroundActionResult
|
||||||
lastLifecycleAction string
|
backgroundActionSerial int
|
||||||
requestMasterPassFocus bool
|
activeBackgroundAction int
|
||||||
invalidate func()
|
lastLifecycleAction string
|
||||||
|
requestMasterPassFocus bool
|
||||||
|
invalidate func()
|
||||||
}
|
}
|
||||||
|
|
||||||
type backgroundActionResult struct {
|
type backgroundActionResult struct {
|
||||||
@@ -519,6 +522,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
u.loadRecentRemotes()
|
u.loadRecentRemotes()
|
||||||
u.restoreStartupLifecycleTarget()
|
u.restoreStartupLifecycleTarget()
|
||||||
u.loadUIPreferences()
|
u.loadUIPreferences()
|
||||||
|
u.loadSettingsFormFromPreferences()
|
||||||
u.filter()
|
u.filter()
|
||||||
u.syncAutofillCache()
|
u.syncAutofillCache()
|
||||||
return u
|
return u
|
||||||
@@ -1016,6 +1020,8 @@ func (u *ui) saveSecuritySettingsAction() error {
|
|||||||
if err := u.state.ConfigureSecurity(settings); err != nil {
|
if err := u.state.ConfigureSecurity(settings); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
u.applySettingsFormToPreferences()
|
||||||
|
u.saveUIPreferences()
|
||||||
u.securityDialogOpen = false
|
u.securityDialogOpen = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1292,6 +1298,7 @@ func (u *ui) loadUIPreferences() {
|
|||||||
u.groupControlsHidden = prefs.GroupControlsHidden
|
u.groupControlsHidden = prefs.GroupControlsHidden
|
||||||
u.lifecycleAdvancedHidden = prefs.LifecycleAdvancedHidden
|
u.lifecycleAdvancedHidden = prefs.LifecycleAdvancedHidden
|
||||||
u.historyHidden = prefs.HistoryHidden
|
u.historyHidden = prefs.HistoryHidden
|
||||||
|
u.denseLayout = prefs.DenseLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) saveUIPreferences() {
|
func (u *ui) saveUIPreferences() {
|
||||||
@@ -1305,6 +1312,7 @@ func (u *ui) saveUIPreferences() {
|
|||||||
GroupControlsHidden: u.groupControlsHidden,
|
GroupControlsHidden: u.groupControlsHidden,
|
||||||
LifecycleAdvancedHidden: u.lifecycleAdvancedHidden,
|
LifecycleAdvancedHidden: u.lifecycleAdvancedHidden,
|
||||||
HistoryHidden: u.historyHidden,
|
HistoryHidden: u.historyHidden,
|
||||||
|
DenseLayout: u.denseLayout,
|
||||||
}, "", " ")
|
}, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -1312,6 +1320,20 @@ func (u *ui) saveUIPreferences() {
|
|||||||
_ = os.WriteFile(u.uiPreferencesPath, content, 0o600)
|
_ = os.WriteFile(u.uiPreferencesPath, content, 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) loadSettingsFormFromPreferences() {
|
||||||
|
u.settingsGroupControls.Value = u.groupControlsHidden
|
||||||
|
u.settingsLifecycleAdvanced.Value = u.lifecycleAdvancedHidden
|
||||||
|
u.settingsHistory.Value = u.historyHidden
|
||||||
|
u.settingsDenseLayout.Value = u.denseLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) applySettingsFormToPreferences() {
|
||||||
|
u.groupControlsHidden = u.settingsGroupControls.Value
|
||||||
|
u.lifecycleAdvancedHidden = u.settingsLifecycleAdvanced.Value
|
||||||
|
u.historyHidden = u.settingsHistory.Value
|
||||||
|
u.denseLayout = u.settingsDenseLayout.Value
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) noteRecentRemote(baseURL, path, username, password string, rememberAuth bool) {
|
func (u *ui) noteRecentRemote(baseURL, path, username, password string, rememberAuth bool) {
|
||||||
baseURL = strings.TrimSpace(baseURL)
|
baseURL = strings.TrimSpace(baseURL)
|
||||||
path = strings.TrimSpace(path)
|
path = strings.TrimSpace(path)
|
||||||
@@ -1428,28 +1450,20 @@ func (u *ui) applyRecentRemoteRecord(record recentRemoteRecord) {
|
|||||||
u.rememberRemoteAuth.Value = strings.TrimSpace(record.Username) != "" || record.Password != ""
|
u.rememberRemoteAuth.Value = strings.TrimSpace(record.Username) != "" || record.Password != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) remotePreferencesCurrentSummary() string {
|
func (u *ui) remoteAuthStatusMessage() string {
|
||||||
selected, hasSelected := u.selectedRecentRemoteRecord()
|
selected, hasSelected := u.selectedRecentRemoteRecord()
|
||||||
switch {
|
switch {
|
||||||
case !u.rememberRemoteAuth.Value:
|
case !u.rememberRemoteAuth.Value:
|
||||||
return "Current choice: KeePassGO will remember only the WebDAV location for this connection."
|
return "Only the location will be saved in Recent Connections."
|
||||||
case hasSelected && (strings.TrimSpace(selected.Username) != "" || selected.Password != ""):
|
case hasSelected && (strings.TrimSpace(selected.Username) != "" || selected.Password != ""):
|
||||||
return "Current choice: a successful open will update the saved sign-in for this connection on this device."
|
return "Saved sign-in will be updated for this connection."
|
||||||
case strings.TrimSpace(u.remoteUsername.Text()) != "" || u.remotePassword.Text() != "":
|
case strings.TrimSpace(u.remoteUsername.Text()) != "" || u.remotePassword.Text() != "":
|
||||||
return "Current choice: a successful open will save the entered sign-in for this connection on this device."
|
return "This sign-in will be saved in Recent Connections after a successful open."
|
||||||
default:
|
default:
|
||||||
return "Current choice: sign-in retention is enabled, but no username or password is entered yet."
|
return "Enter a username or password to save sign-in details for this connection."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) remotePreferencesAlwaysSavedSummary() string {
|
|
||||||
return "Recent Connections always stores the WebDAV base URL, remote path, and the last group you opened for that connection."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) remotePreferencesRetentionSummary() string {
|
|
||||||
return "KeePassGO keeps up to six recent connections. Turning off Remember sign-in and reopening rewrites that connection without the saved username or password."
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) noteCurrentRemotePath() {
|
func (u *ui) noteCurrentRemotePath() {
|
||||||
status, ok := u.state.Session.(sessionStatus)
|
status, ok := u.state.Session.(sessionStatus)
|
||||||
if !ok || !status.IsRemote() || status.IsLocked() {
|
if !ok || !status.IsRemote() || status.IsLocked() {
|
||||||
@@ -2450,11 +2464,9 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
for u.openSecuritySettings.Clicked(gtx) {
|
for u.openSecuritySettings.Clicked(gtx) {
|
||||||
u.loadSecuritySettingsFromSession()
|
u.loadSecuritySettingsFromSession()
|
||||||
|
u.loadSettingsFormFromPreferences()
|
||||||
u.securityDialogOpen = true
|
u.securityDialogOpen = true
|
||||||
}
|
}
|
||||||
for u.openRemotePrefsHelp.Clicked(gtx) {
|
|
||||||
u.remotePrefsDialogOpen = true
|
|
||||||
}
|
|
||||||
for u.closeAdvancedSync.Clicked(gtx) {
|
for u.closeAdvancedSync.Clicked(gtx) {
|
||||||
u.syncDialogOpen = false
|
u.syncDialogOpen = false
|
||||||
u.showSyncPassword = false
|
u.showSyncPassword = false
|
||||||
@@ -2462,9 +2474,6 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
for u.closeSecuritySettings.Clicked(gtx) {
|
for u.closeSecuritySettings.Clicked(gtx) {
|
||||||
u.securityDialogOpen = false
|
u.securityDialogOpen = false
|
||||||
}
|
}
|
||||||
for u.closeRemotePrefsHelp.Clicked(gtx) {
|
|
||||||
u.remotePrefsDialogOpen = false
|
|
||||||
}
|
|
||||||
for u.runAdvancedSync.Clicked(gtx) {
|
for u.runAdvancedSync.Clicked(gtx) {
|
||||||
u.runAction("advanced synchronize vault", u.advancedSyncAction)
|
u.runAction("advanced synchronize vault", u.advancedSyncAction)
|
||||||
}
|
}
|
||||||
@@ -2870,12 +2879,6 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
}
|
}
|
||||||
return u.securityDialog(gtx)
|
return u.securityDialog(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
if !u.remotePrefsDialogOpen {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
return u.remotePrefsDialog(gtx)
|
|
||||||
}),
|
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
||||||
if _, ok := u.pendingApproval(); !ok {
|
if _, ok := u.pendingApproval(); !ok {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
@@ -2958,43 +2961,55 @@ func (u *ui) securityDialog(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) remotePrefsDialog(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.Stack{}.Layout(gtx,
|
|
||||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
paint.FillShape(gtx.Ops, color.NRGBA{A: 90}, clip.Rect{Max: gtx.Constraints.Max}.Op())
|
|
||||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
|
||||||
}),
|
|
||||||
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
width := gtx.Dp(unit.Dp(660))
|
|
||||||
if width > gtx.Constraints.Max.X {
|
|
||||||
width = gtx.Constraints.Max.X - gtx.Dp(unit.Dp(24))
|
|
||||||
}
|
|
||||||
if width < 1 {
|
|
||||||
width = gtx.Constraints.Max.X
|
|
||||||
}
|
|
||||||
gtx.Constraints.Min.X = width
|
|
||||||
gtx.Constraints.Max.X = width
|
|
||||||
return card(gtx, u.remotePrefsDialogContent)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) securityDialogContent(gtx layout.Context) layout.Dimensions {
|
func (u *ui) securityDialogContent(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.Label(u.theme, unit.Sp(20), "Security Settings")
|
lbl := material.Label(u.theme, unit.Sp(20), "Settings")
|
||||||
lbl.Color = accentColor
|
lbl.Color = accentColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.Label(u.theme, unit.Sp(14), "Choose the KDBX cipher and KDF family KeePassGO should use for new or future saves.")
|
lbl := material.Label(u.theme, unit.Sp(14), "Choose how KeePassGO remembers UI layout behavior and which KDBX security settings it should use for new or future saves.")
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(16), "UI Preferences")
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
check := material.CheckBox(u.theme, &u.settingsGroupControls, "Keep Group Tools collapsed")
|
||||||
|
return check.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
check := material.CheckBox(u.theme, &u.settingsLifecycleAdvanced, "Keep advanced lifecycle controls collapsed")
|
||||||
|
return check.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
check := material.CheckBox(u.theme, &u.settingsHistory, "Keep entry history collapsed")
|
||||||
|
return check.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
check := material.CheckBox(u.theme, &u.settingsDenseLayout, "Use dense entry layout")
|
||||||
|
return check.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "Dense layout reduces list and detail spacing. Leave it unchecked for the default comfortable spacing.")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(14)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(16), "Vault Security")
|
||||||
|
lbl.Color = accentColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "Cipher", "Supported values: "+strings.Join([]string{vault.CipherAES256, vault.CipherChaCha20}, ", "), &u.securityCipher, false)),
|
layout.Rigid(labeledEditorHelp(u.theme, "Cipher", "Supported values: "+strings.Join([]string{vault.CipherAES256, vault.CipherChaCha20}, ", "), &u.securityCipher, false)),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "KDF", "Supported values: "+strings.Join([]string{vault.KDFAES, vault.KDFArgon2}, ", "), &u.securityKDF, false)),
|
layout.Rigid(labeledEditorHelp(u.theme, "KDF", "Supported values: "+strings.Join([]string{vault.KDFAES, vault.KDFArgon2}, ", "), &u.securityKDF, false)),
|
||||||
@@ -3006,49 +3021,13 @@ func (u *ui) securityDialogContent(gtx layout.Context) layout.Dimensions {
|
|||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.saveSecuritySettings, "Save Security Settings")
|
return tonedButton(gtx, u.theme, &u.saveSecuritySettings, "Save Settings")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) remotePrefsDialogContent(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(20), "Remote Connection Settings & Help")
|
|
||||||
lbl.Color = accentColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(14), "Use Recent Connections to reopen WebDAV-backed vaults quickly without cluttering the main open flow.")
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return approvalFact(u.theme, "Current", u.remotePreferencesCurrentSummary(), "")(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return approvalFact(u.theme, "Always Saved", u.remotePreferencesAlwaysSavedSummary(), "")(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return approvalFact(u.theme, "Retention", u.remotePreferencesRetentionSummary(), "")(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return approvalFact(u.theme, "When Sign-in Saves", "Username and password or app token are only stored after a successful remote open when Remember sign-in is enabled.", "")(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(14)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.closeRemotePrefsHelp, "Done")
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ui) approvalDialog(gtx layout.Context) layout.Dimensions {
|
func (u *ui) approvalDialog(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Stack{}.Layout(gtx,
|
return layout.Stack{}.Layout(gtx,
|
||||||
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
layout.Expanded(func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -3355,7 +3334,7 @@ func (u *ui) headerActions(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(u.syncButtonGroup),
|
layout.Rigid(u.syncButtonGroup),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
btn := material.IconButton(u.theme, &u.openSecuritySettings, u.settingsIcon, "Vault settings")
|
btn := material.IconButton(u.theme, &u.openSecuritySettings, u.settingsIcon, "Settings")
|
||||||
btn.Background = selectedColor
|
btn.Background = selectedColor
|
||||||
btn.Color = accentColor
|
btn.Color = accentColor
|
||||||
btn.Size = unit.Dp(18)
|
btn.Size = unit.Dp(18)
|
||||||
@@ -3422,12 +3401,58 @@ func (u *ui) syncMenu(gtx layout.Context) layout.Dimensions {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) sectionSpacing() unit.Dp {
|
||||||
|
if u.mode == "phone" {
|
||||||
|
if u.denseLayout {
|
||||||
|
return unit.Dp(4)
|
||||||
|
}
|
||||||
|
return unit.Dp(6)
|
||||||
|
}
|
||||||
|
if u.denseLayout {
|
||||||
|
return unit.Dp(8)
|
||||||
|
}
|
||||||
|
return unit.Dp(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) entryRowMetrics() (unit.Dp, unit.Sp, unit.Sp, unit.Sp, unit.Sp, unit.Dp) {
|
||||||
|
inset := unit.Dp(12)
|
||||||
|
titleSize := unit.Sp(17)
|
||||||
|
metaSize := unit.Sp(14)
|
||||||
|
urlSize := unit.Sp(12)
|
||||||
|
pathSize := unit.Sp(11)
|
||||||
|
dividerGap := unit.Dp(7)
|
||||||
|
if u.denseLayout {
|
||||||
|
inset = unit.Dp(9)
|
||||||
|
titleSize = unit.Sp(15)
|
||||||
|
metaSize = unit.Sp(12)
|
||||||
|
urlSize = unit.Sp(11)
|
||||||
|
pathSize = unit.Sp(10)
|
||||||
|
dividerGap = unit.Dp(5)
|
||||||
|
}
|
||||||
|
if u.mode == "phone" {
|
||||||
|
inset = unit.Dp(9)
|
||||||
|
titleSize = unit.Sp(15)
|
||||||
|
metaSize = unit.Sp(12)
|
||||||
|
urlSize = unit.Sp(11)
|
||||||
|
pathSize = unit.Sp(10)
|
||||||
|
dividerGap = unit.Dp(5)
|
||||||
|
if u.denseLayout {
|
||||||
|
inset = unit.Dp(8)
|
||||||
|
titleSize = unit.Sp(14)
|
||||||
|
metaSize = unit.Sp(11)
|
||||||
|
urlSize = unit.Sp(10)
|
||||||
|
pathSize = unit.Sp(9)
|
||||||
|
dividerGap = unit.Dp(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inset, titleSize, metaSize, urlSize, pathSize, dividerGap
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
||||||
panel := card
|
panel := card
|
||||||
spacing := unit.Dp(12)
|
spacing := u.sectionSpacing()
|
||||||
if u.mode == "phone" {
|
if u.mode == "phone" {
|
||||||
panel = compactCard
|
panel = compactCard
|
||||||
spacing = unit.Dp(6)
|
|
||||||
}
|
}
|
||||||
u.ensureNavClickables()
|
u.ensureNavClickables()
|
||||||
return panel(gtx, func(gtx layout.Context) layout.Dimensions {
|
return panel(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -3614,18 +3639,7 @@ func (u *ui) entryRow(gtx layout.Context, click *widget.Clickable, idx int, item
|
|||||||
u.loadSelectedEntryIntoEditor()
|
u.loadSelectedEntryIntoEditor()
|
||||||
}
|
}
|
||||||
return click.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return click.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
inset := unit.Dp(12)
|
inset, titleSize, metaSize, urlSize, pathSize, dividerGap := u.entryRowMetrics()
|
||||||
titleSize := unit.Sp(17)
|
|
||||||
metaSize := unit.Sp(14)
|
|
||||||
urlSize := unit.Sp(12)
|
|
||||||
pathSize := unit.Sp(11)
|
|
||||||
if u.mode == "phone" {
|
|
||||||
inset = unit.Dp(9)
|
|
||||||
titleSize = unit.Sp(15)
|
|
||||||
metaSize = unit.Sp(12)
|
|
||||||
urlSize = unit.Sp(11)
|
|
||||||
pathSize = unit.Sp(10)
|
|
||||||
}
|
|
||||||
selected := item.ID == u.state.SelectedEntryID
|
selected := item.ID == u.state.SelectedEntryID
|
||||||
focused := u.isFocused(listFocusID(idx))
|
focused := u.isFocused(listFocusID(idx))
|
||||||
titleColor := accentColor
|
titleColor := accentColor
|
||||||
@@ -3696,7 +3710,7 @@ func (u *ui) entryRow(gtx layout.Context, click *widget.Clickable, idx int, item
|
|||||||
lbl.Color = secondaryColor
|
lbl.Color = secondaryColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(7)}.Layout),
|
layout.Rigid(layout.Spacer{Height: dividerGap}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
w := gtx.Constraints.Max.X
|
w := gtx.Constraints.Max.X
|
||||||
if w < 1 {
|
if w < 1 {
|
||||||
@@ -3808,7 +3822,7 @@ func (u *ui) detailPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(u.syncButtonGroup),
|
layout.Rigid(u.syncButtonGroup),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
btn := material.IconButton(u.theme, &u.openSecuritySettings, u.settingsIcon, "Vault settings")
|
btn := material.IconButton(u.theme, &u.openSecuritySettings, u.settingsIcon, "Settings")
|
||||||
btn.Background = selectedColor
|
btn.Background = selectedColor
|
||||||
btn.Color = accentColor
|
btn.Color = accentColor
|
||||||
btn.Size = unit.Dp(18)
|
btn.Size = unit.Dp(18)
|
||||||
@@ -3947,10 +3961,22 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
|||||||
titleSize := unit.Sp(26)
|
titleSize := unit.Sp(26)
|
||||||
titlePad := unit.Dp(10)
|
titlePad := unit.Dp(10)
|
||||||
sectionGap := unit.Dp(6)
|
sectionGap := unit.Dp(6)
|
||||||
|
cardGap := unit.Dp(8)
|
||||||
|
if u.denseLayout {
|
||||||
|
titlePad = unit.Dp(6)
|
||||||
|
sectionGap = unit.Dp(4)
|
||||||
|
cardGap = unit.Dp(6)
|
||||||
|
}
|
||||||
if u.mode == "phone" {
|
if u.mode == "phone" {
|
||||||
titleSize = unit.Sp(18)
|
titleSize = unit.Sp(18)
|
||||||
titlePad = unit.Dp(4)
|
titlePad = unit.Dp(4)
|
||||||
sectionGap = unit.Dp(4)
|
sectionGap = unit.Dp(4)
|
||||||
|
cardGap = unit.Dp(6)
|
||||||
|
if u.denseLayout {
|
||||||
|
titlePad = unit.Dp(3)
|
||||||
|
sectionGap = unit.Dp(3)
|
||||||
|
cardGap = unit.Dp(4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rows := []layout.Widget{
|
rows := []layout.Widget{
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -4068,11 +4094,11 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
layout.Spacer{Height: unit.Dp(8)}.Layout,
|
layout.Spacer{Height: cardGap}.Layout,
|
||||||
u.attachmentSummaryPanel,
|
u.attachmentSummaryPanel,
|
||||||
layout.Spacer{Height: unit.Dp(8)}.Layout,
|
layout.Spacer{Height: cardGap}.Layout,
|
||||||
u.historyPanel,
|
u.historyPanel,
|
||||||
layout.Spacer{Height: unit.Dp(8)}.Layout,
|
layout.Spacer{Height: cardGap}.Layout,
|
||||||
func(gtx layout.Context) layout.Dimensions {
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
switch u.state.Section {
|
switch u.state.Section {
|
||||||
case appstate.SectionTemplates:
|
case appstate.SectionTemplates:
|
||||||
|
|||||||
@@ -865,6 +865,48 @@ func TestUISaveSecuritySettingsUpdatesExistingVault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUISaveSettingsPersistsUIPreferences(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
configPath := filepath.Join(dir, "ui-prefs.json")
|
||||||
|
|
||||||
|
u := newUIWithSession("desktop", &session.Manager{}, statePaths{
|
||||||
|
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||||
|
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
|
||||||
|
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
|
||||||
|
UIPreferencesPath: configPath,
|
||||||
|
})
|
||||||
|
u.settingsGroupControls.Value = true
|
||||||
|
u.settingsLifecycleAdvanced.Value = false
|
||||||
|
u.settingsHistory.Value = false
|
||||||
|
u.settingsDenseLayout.Value = true
|
||||||
|
|
||||||
|
if err := u.saveSecuritySettingsAction(); err != nil {
|
||||||
|
t.Fatalf("saveSecuritySettingsAction() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reloaded := newUIWithSession("desktop", &session.Manager{}, statePaths{
|
||||||
|
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||||
|
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
|
||||||
|
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
|
||||||
|
UIPreferencesPath: configPath,
|
||||||
|
})
|
||||||
|
|
||||||
|
if !reloaded.groupControlsHidden {
|
||||||
|
t.Fatal("groupControlsHidden after reload = false, want true")
|
||||||
|
}
|
||||||
|
if reloaded.lifecycleAdvancedHidden {
|
||||||
|
t.Fatal("lifecycleAdvancedHidden after reload = true, want false")
|
||||||
|
}
|
||||||
|
if reloaded.historyHidden {
|
||||||
|
t.Fatal("historyHidden after reload = true, want false")
|
||||||
|
}
|
||||||
|
if !reloaded.denseLayout {
|
||||||
|
t.Fatal("denseLayout after reload = false, want true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUILockAndUnlockClearMasterPasswordField(t *testing.T) {
|
func TestUILockAndUnlockClearMasterPasswordField(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -3420,6 +3462,46 @@ func TestUIGroupToolsDisclosureStatePersists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUIDenseLayoutPreferencePersists(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
configPath := filepath.Join(t.TempDir(), "ui-prefs.json")
|
||||||
|
|
||||||
|
first := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
first.uiPreferencesPath = configPath
|
||||||
|
first.denseLayout = true
|
||||||
|
first.saveUIPreferences()
|
||||||
|
|
||||||
|
second := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
second.uiPreferencesPath = configPath
|
||||||
|
second.denseLayout = false
|
||||||
|
second.loadUIPreferences()
|
||||||
|
|
||||||
|
if !second.denseLayout {
|
||||||
|
t.Fatal("denseLayout = false after reload, want true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUIEntryRowMetricsUseDenseLayout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
u := newUIWithSession("desktop", &session.Manager{})
|
||||||
|
|
||||||
|
comfortableInset, comfortableTitle, _, _, _, comfortableGap := u.entryRowMetrics()
|
||||||
|
u.denseLayout = true
|
||||||
|
denseInset, denseTitle, _, _, _, denseGap := u.entryRowMetrics()
|
||||||
|
|
||||||
|
if denseInset >= comfortableInset {
|
||||||
|
t.Fatalf("dense inset = %v, want smaller than comfortable inset %v", denseInset, comfortableInset)
|
||||||
|
}
|
||||||
|
if denseTitle >= comfortableTitle {
|
||||||
|
t.Fatalf("dense title size = %v, want smaller than comfortable title size %v", denseTitle, comfortableTitle)
|
||||||
|
}
|
||||||
|
if denseGap >= comfortableGap {
|
||||||
|
t.Fatalf("dense divider gap = %v, want smaller than comfortable divider gap %v", denseGap, comfortableGap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSelectingRecentRemoteConnectionKeepsPasswordMasked(t *testing.T) {
|
func TestSelectingRecentRemoteConnectionKeepsPasswordMasked(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user