diff --git a/main.go b/main.go index 3cdf4a3..82739d4 100644 --- a/main.go +++ b/main.go @@ -224,8 +224,10 @@ type ui struct { toggleSyncMenu widget.Clickable openAdvancedSync widget.Clickable openSecuritySettings widget.Clickable + openRemotePrefsHelp widget.Clickable closeAdvancedSync widget.Clickable closeSecuritySettings widget.Clickable + closeRemotePrefsHelp widget.Clickable runAdvancedSync widget.Clickable saveSecuritySettings widget.Clickable editEntry widget.Clickable @@ -342,6 +344,7 @@ type ui struct { syncDialogOpen bool syncMenuOpen bool securityDialogOpen bool + remotePrefsDialogOpen bool showSyncPassword bool keyboardFocus focusID defaultSaveAsPath string @@ -1425,20 +1428,28 @@ func (u *ui) applyRecentRemoteRecord(record recentRemoteRecord) { u.rememberRemoteAuth.Value = strings.TrimSpace(record.Username) != "" || record.Password != "" } -func (u *ui) remoteAuthStatusMessage() string { +func (u *ui) remotePreferencesCurrentSummary() string { selected, hasSelected := u.selectedRecentRemoteRecord() switch { case !u.rememberRemoteAuth.Value: - return "Only the location will be saved in Recent Connections." + return "Current choice: KeePassGO will remember only the WebDAV location for this connection." case hasSelected && (strings.TrimSpace(selected.Username) != "" || selected.Password != ""): - return "Saved sign-in will be updated for this connection." + return "Current choice: a successful open will update the saved sign-in for this connection on this device." case strings.TrimSpace(u.remoteUsername.Text()) != "" || u.remotePassword.Text() != "": - return "This sign-in will be saved in Recent Connections after a successful open." + return "Current choice: a successful open will save the entered sign-in for this connection on this device." default: - return "Enter a username or password to save sign-in details for this connection." + return "Current choice: sign-in retention is enabled, but no username or password is entered yet." } } +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() { status, ok := u.state.Session.(sessionStatus) if !ok || !status.IsRemote() || status.IsLocked() { @@ -2441,6 +2452,9 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions { u.loadSecuritySettingsFromSession() u.securityDialogOpen = true } + for u.openRemotePrefsHelp.Clicked(gtx) { + u.remotePrefsDialogOpen = true + } for u.closeAdvancedSync.Clicked(gtx) { u.syncDialogOpen = false u.showSyncPassword = false @@ -2448,6 +2462,9 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions { for u.closeSecuritySettings.Clicked(gtx) { u.securityDialogOpen = false } + for u.closeRemotePrefsHelp.Clicked(gtx) { + u.remotePrefsDialogOpen = false + } for u.runAdvancedSync.Clicked(gtx) { u.runAction("advanced synchronize vault", u.advancedSyncAction) } @@ -2853,6 +2870,12 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions { } 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 { if _, ok := u.pendingApproval(); !ok { return layout.Dimensions{} @@ -2935,6 +2958,29 @@ 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 { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { @@ -2967,6 +3013,42 @@ func (u *ui) securityDialogContent(gtx layout.Context) layout.Dimensions { ) } +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 { return layout.Stack{}.Layout(gtx, layout.Expanded(func(gtx layout.Context) layout.Dimensions { diff --git a/main_test.go b/main_test.go index cb7728a..b8199c0 100644 --- a/main_test.go +++ b/main_test.go @@ -3600,25 +3600,25 @@ func TestRecentRemoteStoredAuthSummaryDescribesSavedCredentialState(t *testing.T } } -func TestUIRemoteAuthStatusMessageExplainsWhatWillBeRemembered(t *testing.T) { +func TestUIRemotePreferencesCurrentSummaryExplainsWhatWillBeRemembered(t *testing.T) { t.Parallel() u := newUIWithSession("desktop", &session.Manager{}) u.remoteBaseURL.SetText("https://dav.example.com") u.remotePath.SetText("vaults/home.kdbx") - if got := u.remoteAuthStatusMessage(); got != "Only the location will be saved in Recent Connections." { - t.Fatalf("remoteAuthStatusMessage() = %q, want location-only guidance", got) + if got := u.remotePreferencesCurrentSummary(); got != "Current choice: KeePassGO will remember only the WebDAV location for this connection." { + t.Fatalf("remotePreferencesCurrentSummary() = %q, want location-only guidance", got) } u.rememberRemoteAuth.Value = true - if got := u.remoteAuthStatusMessage(); got != "Enter a username or password to save sign-in details for this connection." { - t.Fatalf("remoteAuthStatusMessage() = %q, want empty-sign-in guidance", got) + if got := u.remotePreferencesCurrentSummary(); got != "Current choice: sign-in retention is enabled, but no username or password is entered yet." { + t.Fatalf("remotePreferencesCurrentSummary() = %q, want empty-sign-in guidance", got) } u.remoteUsername.SetText("alice") - if got := u.remoteAuthStatusMessage(); got != "This sign-in will be saved in Recent Connections after a successful open." { - t.Fatalf("remoteAuthStatusMessage() = %q, want pending-save guidance", got) + if got := u.remotePreferencesCurrentSummary(); got != "Current choice: a successful open will save the entered sign-in for this connection on this device." { + t.Fatalf("remotePreferencesCurrentSummary() = %q, want pending-save guidance", got) } u.recentRemotes = []recentRemoteRecord{{ @@ -3627,8 +3627,44 @@ func TestUIRemoteAuthStatusMessageExplainsWhatWillBeRemembered(t *testing.T) { Username: "alice", Password: "secret-1", }} - if got := u.remoteAuthStatusMessage(); got != "Saved sign-in will be updated for this connection." { - t.Fatalf("remoteAuthStatusMessage() = %q, want saved-sign-in guidance", got) + if got := u.remotePreferencesCurrentSummary(); got != "Current choice: a successful open will update the saved sign-in for this connection on this device." { + t.Fatalf("remotePreferencesCurrentSummary() = %q, want saved-sign-in guidance", got) + } +} + +func TestUIRemotePreferencesHelpExplainsSavedFieldsAndRetention(t *testing.T) { + t.Parallel() + + u := newUIWithSession("desktop", &session.Manager{}) + + if got := u.remotePreferencesAlwaysSavedSummary(); got != "Recent Connections always stores the WebDAV base URL, remote path, and the last group you opened for that connection." { + t.Fatalf("remotePreferencesAlwaysSavedSummary() = %q, want saved-fields guidance", got) + } + if got := u.remotePreferencesRetentionSummary(); got != "KeePassGO keeps up to six recent connections. Turning off Remember sign-in and reopening rewrites that connection without the saved username or password." { + t.Fatalf("remotePreferencesRetentionSummary() = %q, want retention guidance", got) + } +} + +func TestUIRemotePreferencesHelpDialogToggle(t *testing.T) { + t.Parallel() + + u := newUIWithSession("desktop", &session.Manager{}) + gtx := layout.Context{} + + u.openRemotePrefsHelp.Click() + for u.openRemotePrefsHelp.Clicked(gtx) { + u.remotePrefsDialogOpen = true + } + if !u.remotePrefsDialogOpen { + t.Fatal("remotePrefsDialogOpen = false after open click, want true") + } + + u.closeRemotePrefsHelp.Click() + for u.closeRemotePrefsHelp.Clicked(gtx) { + u.remotePrefsDialogOpen = false + } + if u.remotePrefsDialogOpen { + t.Fatal("remotePrefsDialogOpen = true after close click, want false") } } diff --git a/ui_forms.go b/ui_forms.go index 9c261ca..0fb6347 100644 --- a/ui_forms.go +++ b/ui_forms.go @@ -150,11 +150,10 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions { box.Color = accentColor return box.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(11), u.remoteAuthStatusMessage()) - lbl.Color = mutedColor - return lbl.Layout(gtx) + return layout.Inset{Top: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return tonedButton(gtx, u.theme, &u.openRemotePrefsHelp, "Settings & Help") + }) }), layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout), )