Refine lifecycle setup screen
This commit is contained in:
@@ -32,8 +32,7 @@ import (
|
|||||||
type entry = vault.Entry
|
type entry = vault.Entry
|
||||||
|
|
||||||
const (
|
const (
|
||||||
productName = "KeePassGO"
|
productName = "KeePassGO"
|
||||||
desktopSubtitle = "KeePass-compatible password management for desktop-first workflows"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxAttachmentBytes = 10 << 20
|
const maxAttachmentBytes = 10 << 20
|
||||||
@@ -124,6 +123,8 @@ type ui struct {
|
|||||||
showEntries widget.Clickable
|
showEntries widget.Clickable
|
||||||
showTemplates widget.Clickable
|
showTemplates widget.Clickable
|
||||||
showRecycle widget.Clickable
|
showRecycle widget.Clickable
|
||||||
|
showLocalLifecycle widget.Clickable
|
||||||
|
showRemoteLifecycle widget.Clickable
|
||||||
masterKeyPasswordOnly widget.Clickable
|
masterKeyPasswordOnly widget.Clickable
|
||||||
masterKeyKeyFileOnly widget.Clickable
|
masterKeyKeyFileOnly widget.Clickable
|
||||||
masterKeyComposite widget.Clickable
|
masterKeyComposite widget.Clickable
|
||||||
@@ -150,6 +151,7 @@ type ui struct {
|
|||||||
copyIcon *widget.Icon
|
copyIcon *widget.Icon
|
||||||
clipboardWriter clipboard.Writer
|
clipboardWriter clipboard.Writer
|
||||||
loadingMessage string
|
loadingMessage string
|
||||||
|
lifecycleMode string
|
||||||
keyboardFocus focusID
|
keyboardFocus focusID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +227,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession) *ui {
|
|||||||
state: appstate.State{},
|
state: appstate.State{},
|
||||||
masterKeyMode: vault.MasterKeyModePasswordOnly,
|
masterKeyMode: vault.MasterKeyModePasswordOnly,
|
||||||
selectedHistoryIndex: -1,
|
selectedHistoryIndex: -1,
|
||||||
|
lifecycleMode: "local",
|
||||||
}
|
}
|
||||||
u.state.Session = sess
|
u.state.Session = sess
|
||||||
u.phoneSplit.Value = 0.46
|
u.phoneSplit.Value = 0.46
|
||||||
@@ -679,6 +682,12 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
for u.showRecycle.Clicked(gtx) {
|
for u.showRecycle.Clicked(gtx) {
|
||||||
u.showRecycleBinSection()
|
u.showRecycleBinSection()
|
||||||
}
|
}
|
||||||
|
for u.showLocalLifecycle.Clicked(gtx) {
|
||||||
|
u.lifecycleMode = "local"
|
||||||
|
}
|
||||||
|
for u.showRemoteLifecycle.Clicked(gtx) {
|
||||||
|
u.lifecycleMode = "remote"
|
||||||
|
}
|
||||||
for u.lockVault.Clicked(gtx) {
|
for u.lockVault.Clicked(gtx) {
|
||||||
u.runAction("lock vault", u.lockAction)
|
u.runAction("lock vault", u.lockAction)
|
||||||
}
|
}
|
||||||
@@ -830,19 +839,9 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
lbl := material.Label(u.theme, unit.Sp(24), productName)
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
lbl.Color = accentColor
|
||||||
lbl := material.Label(u.theme, unit.Sp(24), "Vault")
|
return lbl.Layout(gtx)
|
||||||
lbl.Text = productName
|
|
||||||
lbl.Color = accentColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
lbl := material.Label(u.theme, unit.Sp(13), desktopSubtitle)
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
btn := material.Button(u.theme, &u.lockVault, "Lock")
|
btn := material.Button(u.theme, &u.lockVault, "Lock")
|
||||||
|
|||||||
@@ -1668,9 +1668,6 @@ func TestUIUsesKeePassGOProductCopy(t *testing.T) {
|
|||||||
if productName != "KeePassGO" {
|
if productName != "KeePassGO" {
|
||||||
t.Fatalf("productName = %q, want %q", productName, "KeePassGO")
|
t.Fatalf("productName = %q, want %q", productName, "KeePassGO")
|
||||||
}
|
}
|
||||||
if desktopSubtitle != "KeePass-compatible password management for desktop-first workflows" {
|
|
||||||
t.Fatalf("desktopSubtitle = %q, want updated product subtitle", desktopSubtitle)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUICopyActionsWriteExpectedClipboardContentsAndSanitizedFeedback(t *testing.T) {
|
func TestUICopyActionsWriteExpectedClipboardContentsAndSanitizedFeedback(t *testing.T) {
|
||||||
|
|||||||
+52
-17
@@ -14,6 +14,18 @@ import (
|
|||||||
func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
||||||
surface := u.sessionSurface()
|
surface := u.sessionSurface()
|
||||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.showLocalLifecycle, "Local Vault")
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.showRemoteLifecycle, "Remote Vault")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
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(12), "MASTER KEY MODE")
|
lbl := material.Label(u.theme, unit.Sp(12), "MASTER KEY MODE")
|
||||||
if surface.Locked {
|
if surface.Locked {
|
||||||
@@ -39,21 +51,28 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditor(u.theme, "Master Password", &u.masterPassword, true)),
|
layout.Rigid(labeledEditorHelp(u.theme, "Master Password", "Used alone or together with a key file to unlock the vault.", &u.masterPassword, true)),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditor(u.theme, "Key File", &u.keyFilePath, false)),
|
layout.Rigid(labeledEditorHelp(u.theme, "Key File", "Optional path to a KeePass-compatible key file.", &u.keyFilePath, false)),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditor(u.theme, "Vault Path", &u.vaultPath, false)),
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
if u.lifecycleMode == "remote" {
|
||||||
layout.Rigid(labeledEditor(u.theme, "Save As Path", &u.saveAsPath, false)),
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(labeledEditorHelp(u.theme, "Remote Base URL", "Base WebDAV endpoint, for example https://server/remote.php/webdav.", &u.remoteBaseURL, false)),
|
||||||
layout.Rigid(labeledEditor(u.theme, "Remote Base URL", &u.remoteBaseURL, false)),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(labeledEditorHelp(u.theme, "Remote Path", "Path to the remote .kdbx file under the WebDAV base URL.", &u.remotePath, false)),
|
||||||
layout.Rigid(labeledEditor(u.theme, "Remote Path", &u.remotePath, false)),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(labeledEditorHelp(u.theme, "Remote Username", "Username used to authenticate to the WebDAV server.", &u.remoteUsername, false)),
|
||||||
layout.Rigid(labeledEditor(u.theme, "Remote Username", &u.remoteUsername, false)),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(labeledEditorHelp(u.theme, "Remote Password", "Password or app token used to authenticate to the WebDAV server.", &u.remotePassword, true)),
|
||||||
layout.Rigid(labeledEditor(u.theme, "Remote Password", &u.remotePassword, true)),
|
)
|
||||||
|
}
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(labeledEditorHelp(u.theme, "Vault Path", "Local path to an existing .kdbx file to open.", &u.vaultPath, false)),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(labeledEditorHelp(u.theme, "Save As Path", "Local target path used when creating a new vault or saving a copy.", &u.saveAsPath, false)),
|
||||||
|
)
|
||||||
|
}),
|
||||||
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 {
|
||||||
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
@@ -62,19 +81,21 @@ func (u *ui) lifecycleControls(gtx layout.Context) layout.Dimensions {
|
|||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.lifecycleMode == "remote" {
|
||||||
|
return tonedButton(gtx, u.theme, &u.openRemote, "Open Remote")
|
||||||
|
}
|
||||||
return tonedButton(gtx, u.theme, &u.openVault, "Open Vault")
|
return tonedButton(gtx, u.theme, &u.openVault, "Open Vault")
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.saveVault, "Save") }),
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return tonedButton(gtx, u.theme, &u.saveVault, "Save") }),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.lifecycleMode == "remote" {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
return tonedButton(gtx, u.theme, &u.saveAsVault, "Save As")
|
return tonedButton(gtx, u.theme, &u.saveAsVault, "Save As")
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return tonedButton(gtx, u.theme, &u.openRemote, "Open Remote")
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.changeMasterKey, "Change Master Key")
|
return tonedButton(gtx, u.theme, &u.changeMasterKey, "Change Master Key")
|
||||||
}),
|
}),
|
||||||
@@ -261,6 +282,20 @@ func labeledEditor(th *material.Theme, label string, editor *widget.Editor, sens
|
|||||||
return labeledEditorWithFocus(th, label, editor, sensitive, false)
|
return labeledEditorWithFocus(th, label, editor, sensitive, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func labeledEditorHelp(th *material.Theme, label, help string, editor *widget.Editor, sensitive bool) layout.Widget {
|
||||||
|
return func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(labeledEditor(th, label, editor, sensitive)),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(2)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(th, unit.Sp(11), help)
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func labeledEditorWithFocus(
|
func labeledEditorWithFocus(
|
||||||
th *material.Theme,
|
th *material.Theme,
|
||||||
label string,
|
label string,
|
||||||
|
|||||||
Reference in New Issue
Block a user