Tighten mobile locked vault hierarchy
This commit is contained in:
@@ -96,6 +96,12 @@ type emptyState struct {
|
||||
Body string
|
||||
}
|
||||
|
||||
type vaultSummary struct {
|
||||
Title string
|
||||
Detail string
|
||||
Context string
|
||||
}
|
||||
|
||||
type sessionStatus interface {
|
||||
HasVault() bool
|
||||
IsLocked() bool
|
||||
@@ -2000,6 +2006,47 @@ func (u *ui) loadingDetailMessage() string {
|
||||
return "Target: " + path
|
||||
}
|
||||
|
||||
func (u *ui) currentVaultSummary() vaultSummary {
|
||||
status, ok := u.state.Session.(sessionStatus)
|
||||
if !ok || !status.HasVault() {
|
||||
return vaultSummary{}
|
||||
}
|
||||
if status.IsRemote() {
|
||||
baseURL := strings.TrimSpace(u.remoteBaseURL.Text())
|
||||
path := strings.TrimSpace(u.remotePath.Text())
|
||||
summary := vaultSummary{
|
||||
Title: friendlyRecentRemoteLabel(recentRemoteRecord{BaseURL: baseURL, Path: path}),
|
||||
Detail: baseURL,
|
||||
}
|
||||
if strings.TrimSpace(summary.Title) == "" {
|
||||
summary.Title = "Remote vault"
|
||||
}
|
||||
summary.Context = u.vaultResumeContext(u.recentRemoteGroup(baseURL, path))
|
||||
return summary
|
||||
}
|
||||
path := strings.TrimSpace(u.vaultPath.Text())
|
||||
summary := vaultSummary{
|
||||
Title: friendlyRecentVaultLabel(path),
|
||||
Detail: path,
|
||||
}
|
||||
if strings.TrimSpace(summary.Title) == "" {
|
||||
summary.Title = "Local vault"
|
||||
}
|
||||
summary.Context = u.vaultResumeContext(u.recentVaultGroup(path))
|
||||
return summary
|
||||
}
|
||||
|
||||
func (u *ui) vaultResumeContext(path []string) string {
|
||||
if len(path) == 0 {
|
||||
return ""
|
||||
}
|
||||
displayPath := append([]string(nil), path...)
|
||||
if len(displayPath) == 0 {
|
||||
return ""
|
||||
}
|
||||
return "Resume in: " + strings.Join(displayPath, " / ")
|
||||
}
|
||||
|
||||
func (u *ui) sessionSurface() uiSurface {
|
||||
if u.state.Session == nil {
|
||||
return uiSurface{}
|
||||
@@ -3027,7 +3074,28 @@ func (u *ui) header(gtx layout.Context) layout.Dimensions {
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
||||
return u.brandMark(gtx, 132, 42)
|
||||
summary := u.currentVaultSummary()
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(11), "VAULT")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(1)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(15), summary.Title)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if strings.TrimSpace(summary.Detail) == "" || summary.Detail == summary.Title {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
lbl := material.Label(u.theme, unit.Sp(11), summary.Detail)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(u.headerActions),
|
||||
)
|
||||
@@ -3132,7 +3200,7 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
||||
spacing := unit.Dp(12)
|
||||
if u.mode == "phone" {
|
||||
panel = compactCard
|
||||
spacing = unit.Dp(8)
|
||||
spacing = unit.Dp(6)
|
||||
}
|
||||
u.ensureNavClickables()
|
||||
return panel(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
@@ -3542,6 +3610,7 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
||||
_ = panel
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, func() []layout.FlexChild {
|
||||
if u.isVaultLocked() {
|
||||
summary := u.currentVaultSummary()
|
||||
return []layout.FlexChild{
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(18), "Unlock Vault")
|
||||
@@ -3554,6 +3623,47 @@ func (u *ui) detailPanelContent(gtx layout.Context) layout.Dimensions {
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(10)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if strings.TrimSpace(summary.Title) == "" {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return compactCard(gtx, func(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(11), "UNLOCK TARGET")
|
||||
lbl.Color = mutedColor
|
||||
return lbl.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(15), summary.Title)
|
||||
lbl.Color = accentColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if strings.TrimSpace(summary.Detail) == "" || summary.Detail == summary.Title {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Inset{Top: unit.Dp(2)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), summary.Detail)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
if strings.TrimSpace(summary.Context) == "" {
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
return layout.Inset{Top: unit.Dp(2)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), summary.Context)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
||||
layout.Rigid(u.unlockPanel),
|
||||
}
|
||||
@@ -4124,7 +4234,7 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
||||
btn.Background, btn.Color = buttonFocusColors(u.isFocused(breadcrumbFocusID(index)))
|
||||
btn.TextSize = unit.Sp(11)
|
||||
if u.mode == "phone" {
|
||||
btn.TextSize = unit.Sp(10)
|
||||
btn.TextSize = unit.Sp(9)
|
||||
btn.Inset = layout.Inset{Top: 3, Bottom: 3, Left: 6, Right: 6}
|
||||
} else {
|
||||
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
|
||||
@@ -4135,7 +4245,11 @@ func (u *ui) pathBar(gtx layout.Context) layout.Dimensions {
|
||||
children = append(children, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "/")
|
||||
lbl.Color = mutedColor
|
||||
return layout.UniformInset(unit.Dp(6)).Layout(gtx, lbl.Layout)
|
||||
inset := unit.Dp(6)
|
||||
if u.mode == "phone" {
|
||||
inset = unit.Dp(4)
|
||||
}
|
||||
return layout.UniformInset(inset).Layout(gtx, lbl.Layout)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -4255,7 +4369,7 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
||||
}
|
||||
maxGroupListHeight := 200
|
||||
if u.mode == "phone" {
|
||||
maxGroupListHeight = 112
|
||||
maxGroupListHeight = 96
|
||||
}
|
||||
maxY := gtx.Dp(unit.Dp(maxGroupListHeight))
|
||||
if gtx.Constraints.Max.Y > maxY {
|
||||
@@ -4563,8 +4677,8 @@ func sectionTabButton(gtx layout.Context, th *material.Theme, click *widget.Clic
|
||||
btn.TextSize = unit.Sp(11)
|
||||
btn.Inset = layout.Inset{Top: 5, Bottom: 5, Left: 9, Right: 9}
|
||||
if gtx.Constraints.Max.X <= gtx.Dp(unit.Dp(460)) {
|
||||
btn.TextSize = unit.Sp(10)
|
||||
btn.Inset = layout.Inset{Top: 4, Bottom: 4, Left: 8, Right: 8}
|
||||
btn.TextSize = unit.Sp(9)
|
||||
btn.Inset = layout.Inset{Top: 3, Bottom: 3, Left: 7, Right: 7}
|
||||
}
|
||||
if gtx.Constraints.Max.X <= gtx.Dp(unit.Dp(220)) {
|
||||
btn.TextSize = unit.Sp(9)
|
||||
|
||||
@@ -55,6 +55,32 @@ func waitForBackgroundResult(t *testing.T, u *ui) backgroundActionResult {
|
||||
}
|
||||
}
|
||||
|
||||
type summarySession struct {
|
||||
model vault.Model
|
||||
hasVault bool
|
||||
locked bool
|
||||
remote bool
|
||||
}
|
||||
|
||||
func (s summarySession) Current() (vault.Model, error) {
|
||||
if s.locked {
|
||||
return vault.Model{}, session.ErrLocked
|
||||
}
|
||||
return s.model, nil
|
||||
}
|
||||
|
||||
func (s summarySession) Save(vault.Model) error { return nil }
|
||||
func (s summarySession) SaveAs(string, vault.MasterKey) error { return nil }
|
||||
func (s summarySession) Create(vault.MasterKey) error { return nil }
|
||||
func (s summarySession) Open(string, vault.MasterKey) error { return nil }
|
||||
func (s summarySession) OpenRemote(*webdav.Client, string, vault.MasterKey) error { return nil }
|
||||
func (s summarySession) ChangeMasterKey(vault.MasterKey) error { return nil }
|
||||
func (s summarySession) Lock() error { return nil }
|
||||
func (s summarySession) Unlock(vault.MasterKey) error { return nil }
|
||||
func (s summarySession) HasVault() bool { return s.hasVault }
|
||||
func (s summarySession) IsLocked() bool { return s.locked }
|
||||
func (s summarySession) IsRemote() bool { return s.remote }
|
||||
|
||||
func TestUIFiltersUsingVaultModelPathsAndSearch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -135,6 +161,51 @@ func TestUISearchBehaviorIsConsistentAcrossDesktopAndPhoneLayouts(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUICurrentVaultSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("local", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("phone", summarySession{hasVault: true})
|
||||
u.vaultPath.SetText("/vaults/family.kdbx")
|
||||
u.recentVaultGroups["/vaults/family.kdbx"] = []string{"Root", "Internet"}
|
||||
|
||||
got := u.currentVaultSummary()
|
||||
want := vaultSummary{
|
||||
Title: "family.kdbx",
|
||||
Detail: "/vaults/family.kdbx",
|
||||
Context: "Resume in: Root / Internet",
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("currentVaultSummary() = %#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("remote", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithSession("phone", summarySession{hasVault: true, remote: true})
|
||||
u.remoteBaseURL.SetText("https://dav.example.com")
|
||||
u.remotePath.SetText("vaults/home.kdbx")
|
||||
u.recentRemotes = []recentRemoteRecord{{
|
||||
BaseURL: "https://dav.example.com",
|
||||
Path: "vaults/home.kdbx",
|
||||
LastGroup: []string{"Root", "Shared"},
|
||||
}}
|
||||
|
||||
got := u.currentVaultSummary()
|
||||
want := vaultSummary{
|
||||
Title: "home.kdbx · dav.example.com",
|
||||
Detail: "https://dav.example.com",
|
||||
Context: "Resume in: Root / Shared",
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("currentVaultSummary() = %#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUIClearingSearchResetsToCurrentSectionListing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+14
-4
@@ -784,8 +784,8 @@ func (u *ui) groupControlsSection(gtx layout.Context) layout.Dimensions {
|
||||
|
||||
func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions {
|
||||
return u.toggleGroupControls.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
content := func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(2)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
icon := u.expandLessIcon
|
||||
@@ -804,13 +804,23 @@ func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions {
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Width: unit.Dp(4)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.Label(u.theme, unit.Sp(12), "Group management")
|
||||
label := "Group management"
|
||||
size := unit.Sp(12)
|
||||
if u.mode == "phone" {
|
||||
label = "Tools"
|
||||
size = unit.Sp(11)
|
||||
}
|
||||
lbl := material.Label(u.theme, size, label)
|
||||
lbl.Color = mutedColor
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
if u.mode == "phone" {
|
||||
return content(gtx)
|
||||
}
|
||||
return compactCard(gtx, content)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user