Fix Android reopen crash and simplify group navigation
This commit is contained in:
@@ -245,6 +245,8 @@ type ui struct {
|
|||||||
detailList widget.List
|
detailList widget.List
|
||||||
apiPolicyList widget.List
|
apiPolicyList widget.List
|
||||||
lifecycleList widget.List
|
lifecycleList widget.List
|
||||||
|
recentVaultListState widget.List
|
||||||
|
recentRemoteListState widget.List
|
||||||
copyUser widget.Clickable
|
copyUser widget.Clickable
|
||||||
copyPass widget.Clickable
|
copyPass widget.Clickable
|
||||||
copyURL widget.Clickable
|
copyURL widget.Clickable
|
||||||
@@ -552,6 +554,12 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
lifecycleList: widget.List{
|
lifecycleList: widget.List{
|
||||||
List: layout.List{Axis: layout.Vertical},
|
List: layout.List{Axis: layout.Vertical},
|
||||||
},
|
},
|
||||||
|
recentVaultListState: widget.List{
|
||||||
|
List: layout.List{Axis: layout.Vertical},
|
||||||
|
},
|
||||||
|
recentRemoteListState: widget.List{
|
||||||
|
List: layout.List{Axis: layout.Vertical},
|
||||||
|
},
|
||||||
state: appstate.State{},
|
state: appstate.State{},
|
||||||
selectedHistoryIndex: -1,
|
selectedHistoryIndex: -1,
|
||||||
selectedAuditIndex: -1,
|
selectedAuditIndex: -1,
|
||||||
@@ -5167,21 +5175,78 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
|||||||
displayPath := u.displayPath()
|
displayPath := u.displayPath()
|
||||||
atRoot := len(displayPath) == 0
|
atRoot := len(displayPath) == 0
|
||||||
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if u.mode == "phone" {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "GROUPS")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if atRoot {
|
||||||
|
return layout.Dimensions{}
|
||||||
|
}
|
||||||
|
return layout.Inset{Top: unit.Dp(8)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
for u.goToRootGroup.Clicked(gtx) {
|
||||||
|
root := u.hiddenVaultRoot()
|
||||||
|
if root == "" {
|
||||||
|
u.setCurrentPath(nil)
|
||||||
|
} else {
|
||||||
|
u.setCurrentPath([]string{root})
|
||||||
|
}
|
||||||
|
u.filter()
|
||||||
|
}
|
||||||
|
return tonedButton(gtx, u.theme, &u.goToRootGroup, "Root")
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
for u.goToParentGroup.Clicked(gtx) {
|
||||||
|
u.setCurrentPath(u.currentPath[:len(u.currentPath)-1])
|
||||||
|
u.filter()
|
||||||
|
}
|
||||||
|
return tonedButton(gtx, u.theme, &u.goToParentGroup, "Up")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
if len(groups) == 0 {
|
||||||
|
lbl := material.Label(u.theme, unit.Sp(12), "No subgroups here.")
|
||||||
|
lbl.Color = mutedColor
|
||||||
|
return lbl.Layout(gtx)
|
||||||
|
}
|
||||||
|
maxY := gtx.Dp(unit.Dp(168))
|
||||||
|
if gtx.Constraints.Max.Y > maxY {
|
||||||
|
gtx.Constraints.Max.Y = maxY
|
||||||
|
}
|
||||||
|
if gtx.Constraints.Min.Y > gtx.Constraints.Max.Y {
|
||||||
|
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
||||||
|
}
|
||||||
|
return material.List(u.theme, &u.groupList).Layout(gtx, len(groups), func(gtx layout.Context, i int) layout.Dimensions {
|
||||||
|
idx := i
|
||||||
|
name := groups[i]
|
||||||
|
return layout.Inset{Bottom: unit.Dp(6)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
for u.groupClicks[idx].Clicked(gtx) {
|
||||||
|
u.state.EnterGroup(name)
|
||||||
|
u.currentPath = append([]string(nil), u.state.CurrentPath...)
|
||||||
|
u.filter()
|
||||||
|
}
|
||||||
|
return tonedButton(gtx, u.theme, &u.groupClicks[idx], name)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
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(12), "BROWSE GROUPS")
|
lbl := material.Label(u.theme, unit.Sp(12), "GROUPS")
|
||||||
lbl.Color = mutedColor
|
lbl.Color = mutedColor
|
||||||
return lbl.Layout(gtx)
|
return lbl.Layout(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(4)}.Layout),
|
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), "Root anchors the vault, current group sets the listing, and child groups open deeper paths.")
|
|
||||||
lbl.Color = mutedColor
|
|
||||||
return lbl.Layout(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
|
||||||
layout.Rigid(detailLine(u.theme, "Root", "Vault root (/)")),
|
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
|
||||||
layout.Rigid(detailLine(u.theme, "Current Group", u.currentGroupDisplayName())),
|
layout.Rigid(detailLine(u.theme, "Current Group", u.currentGroupDisplayName())),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
if atRoot {
|
if atRoot {
|
||||||
@@ -5261,7 +5326,7 @@ func (u *ui) groupBar(gtx layout.Context) layout.Dimensions {
|
|||||||
u.currentPath = append([]string(nil), u.state.CurrentPath...)
|
u.currentPath = append([]string(nil), u.state.CurrentPath...)
|
||||||
u.filter()
|
u.filter()
|
||||||
}
|
}
|
||||||
return tonedButton(gtx, u.theme, &u.groupClicks[idx], "Open "+name)
|
return tonedButton(gtx, u.theme, &u.groupClicks[idx], name)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|||||||
+119
@@ -615,6 +615,125 @@ func TestUIAPITokenDetailPanelResizesPolicyRemoveClickablesAcrossTokenSelection(
|
|||||||
_ = u.apiTokenDetailPanel(gtx)
|
_ = u.apiTokenDetailPanel(gtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUILifecycleScreenWithSelectedRecentVaultDoesNotPanic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
paths := statePaths{
|
||||||
|
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||||
|
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
|
||||||
|
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
|
||||||
|
SettingsPath: filepath.Join(dir, "settings.json"),
|
||||||
|
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
|
||||||
|
AutofillCachePath: filepath.Join(dir, "autofill-cache.json"),
|
||||||
|
}
|
||||||
|
|
||||||
|
first := newUIWithSession("phone", &session.Manager{}, paths)
|
||||||
|
first.noteRecentVault("/sdcard/Download/sample-vault.kdbx")
|
||||||
|
|
||||||
|
u := newUIWithSession("phone", &session.Manager{}, paths)
|
||||||
|
ops := new(op.Ops)
|
||||||
|
gtx := layout.Context{
|
||||||
|
Ops: ops,
|
||||||
|
Constraints: layout.Exact(image.Pt(1080, 2400)),
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Fatalf("layout() panicked with selected startup vault: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = u.layout(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUILifecycleControlsWithSelectedRecentVaultDoesNotPanic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
paths := statePaths{
|
||||||
|
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||||
|
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
|
||||||
|
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
|
||||||
|
SettingsPath: filepath.Join(dir, "settings.json"),
|
||||||
|
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
|
||||||
|
AutofillCachePath: filepath.Join(dir, "autofill-cache.json"),
|
||||||
|
}
|
||||||
|
|
||||||
|
first := newUIWithSession("phone", &session.Manager{}, paths)
|
||||||
|
first.noteRecentVault("/sdcard/Download/sample-vault.kdbx")
|
||||||
|
|
||||||
|
u := newUIWithSession("phone", &session.Manager{}, paths)
|
||||||
|
ops := new(op.Ops)
|
||||||
|
gtx := layout.Context{
|
||||||
|
Ops: ops,
|
||||||
|
Constraints: layout.Exact(image.Pt(1080, 2000)),
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Fatalf("lifecycleControls() panicked with selected startup vault: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = u.lifecycleControls(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUIRecentVaultListWithSelectedRecentVaultDoesNotPanic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
paths := statePaths{
|
||||||
|
DefaultSaveAsPath: filepath.Join(dir, "vault.kdbx"),
|
||||||
|
RecentVaultsPath: filepath.Join(dir, "recent-vaults.json"),
|
||||||
|
RecentRemotesPath: filepath.Join(dir, "recent-remotes.json"),
|
||||||
|
SettingsPath: filepath.Join(dir, "settings.json"),
|
||||||
|
UIPreferencesPath: filepath.Join(dir, "ui-prefs.json"),
|
||||||
|
AutofillCachePath: filepath.Join(dir, "autofill-cache.json"),
|
||||||
|
}
|
||||||
|
|
||||||
|
first := newUIWithSession("phone", &session.Manager{}, paths)
|
||||||
|
first.noteRecentVault("/sdcard/Download/sample-vault.kdbx")
|
||||||
|
|
||||||
|
u := newUIWithSession("phone", &session.Manager{}, paths)
|
||||||
|
ops := new(op.Ops)
|
||||||
|
gtx := layout.Context{
|
||||||
|
Ops: ops,
|
||||||
|
Constraints: layout.Exact(image.Pt(1080, 800)),
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Fatalf("recentVaultList() panicked with selected startup vault: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = u.recentVaultList(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUIPhoneGroupBarWithChildGroupsDoesNotPanic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
u := newUIWithModel("phone", vault.Model{
|
||||||
|
Groups: [][]string{
|
||||||
|
{"Crew"},
|
||||||
|
{"Crew", "Internet"},
|
||||||
|
{"Crew", "eMail"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
u.setCurrentPath([]string{"Crew"})
|
||||||
|
|
||||||
|
ops := new(op.Ops)
|
||||||
|
gtx := layout.Context{
|
||||||
|
Ops: ops,
|
||||||
|
Constraints: layout.Exact(image.Pt(1080, 700)),
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Fatalf("groupBar() panicked on phone with child groups: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = u.groupBar(gtx)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUIAPIAuditSectionShowsRecordedEvents(t *testing.T) {
|
func TestUIAPIAuditSectionShowsRecordedEvents(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -386,7 +386,7 @@ func (u *ui) recentVaultList(gtx layout.Context) layout.Dimensions {
|
|||||||
if gtx.Constraints.Min.Y > gtx.Constraints.Max.Y {
|
if gtx.Constraints.Min.Y > gtx.Constraints.Max.Y {
|
||||||
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
||||||
}
|
}
|
||||||
return material.List(u.theme, &u.lifecycleList).Layout(gtx, len(u.recentVaults), func(gtx layout.Context, i int) layout.Dimensions {
|
return material.List(u.theme, &u.recentVaultListState).Layout(gtx, len(u.recentVaults), func(gtx layout.Context, i int) layout.Dimensions {
|
||||||
path := u.recentVaults[i]
|
path := u.recentVaults[i]
|
||||||
label := path
|
label := path
|
||||||
if friendly := friendlyRecentVaultLabel(path); friendly != "" {
|
if friendly := friendlyRecentVaultLabel(path); friendly != "" {
|
||||||
@@ -467,7 +467,7 @@ func (u *ui) recentRemoteList(gtx layout.Context) layout.Dimensions {
|
|||||||
if gtx.Constraints.Min.Y > gtx.Constraints.Max.Y {
|
if gtx.Constraints.Min.Y > gtx.Constraints.Max.Y {
|
||||||
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
|
||||||
}
|
}
|
||||||
return material.List(u.theme, &u.lifecycleList).Layout(gtx, len(u.recentRemotes), func(gtx layout.Context, i int) layout.Dimensions {
|
return material.List(u.theme, &u.recentRemoteListState).Layout(gtx, len(u.recentRemotes), func(gtx layout.Context, i int) layout.Dimensions {
|
||||||
record := u.recentRemotes[i]
|
record := u.recentRemotes[i]
|
||||||
label := friendlyRecentRemoteLabel(record)
|
label := friendlyRecentRemoteLabel(record)
|
||||||
selected := strings.TrimSpace(u.remoteBaseURL.Text()) == record.BaseURL && strings.TrimSpace(u.remotePath.Text()) == record.Path
|
selected := strings.TrimSpace(u.remoteBaseURL.Text()) == record.BaseURL && strings.TrimSpace(u.remotePath.Text()) == record.Path
|
||||||
|
|||||||
Reference in New Issue
Block a user