diff --git a/src/App/Controls/VaultGroupingViewCell.cs b/src/App/Controls/VaultGroupingViewCell.cs index 98ec8c2a9..cc71dbacc 100644 --- a/src/App/Controls/VaultGroupingViewCell.cs +++ b/src/App/Controls/VaultGroupingViewCell.cs @@ -24,7 +24,7 @@ namespace Bit.App.Controls FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)), HorizontalOptions = LayoutOptions.StartAndExpand }; - Label.SetBinding(Label.TextProperty, string.Format("{0}.{1}", + Label.SetBinding(Label.TextProperty, string.Format("{0}.Node.{1}", nameof(VaultListPageModel.GroupingOrCipher.Grouping), nameof(VaultListPageModel.Grouping.Name))); CountLabel = new Label @@ -34,7 +34,7 @@ namespace Bit.App.Controls Style = (Style)Application.Current.Resources["text-muted"], HorizontalOptions = LayoutOptions.End }; - CountLabel.SetBinding(Label.TextProperty, string.Format("{0}.{1}", + CountLabel.SetBinding(Label.TextProperty, string.Format("{0}.Node.{1}", nameof(VaultListPageModel.GroupingOrCipher.Grouping), nameof(VaultListPageModel.Grouping.Count))); var stackLayout = new StackLayout @@ -62,8 +62,8 @@ namespace Bit.App.Controls { if(BindingContext is VaultListPageModel.GroupingOrCipher model) { - Icon.Source = model.Grouping.Folder ? - $"folder{(model.Grouping.Id == null ? "_o" : string.Empty)}.png" : "cube.png"; + Icon.Source = model.Grouping.Node.Folder ? + $"folder{(model.Grouping.Node.Id == null ? "_o" : string.Empty)}.png" : "cube.png"; } base.OnBindingContextChanged(); diff --git a/src/App/Models/Page/VaultListPageModel.cs b/src/App/Models/Page/VaultListPageModel.cs index c36f4d834..c0edc9335 100644 --- a/src/App/Models/Page/VaultListPageModel.cs +++ b/src/App/Models/Page/VaultListPageModel.cs @@ -191,7 +191,7 @@ namespace Bit.App.Models.Page public class GroupingOrCipher { - public GroupingOrCipher(Grouping grouping) + public GroupingOrCipher(TreeNode grouping) { Grouping = grouping; Cipher = null; @@ -203,11 +203,11 @@ namespace Bit.App.Models.Page Grouping = null; } - public Grouping Grouping { get; set; } + public TreeNode Grouping { get; set; } public Cipher Cipher { get; set; } } - public class Grouping + public class Grouping : ITreeNodeObject { public Grouping(string name, int count) { diff --git a/src/App/Pages/Vault/VaultListGroupingsPage.cs b/src/App/Pages/Vault/VaultListGroupingsPage.cs index 87efa41a3..75ca19919 100644 --- a/src/App/Pages/Vault/VaultListGroupingsPage.cs +++ b/src/App/Pages/Vault/VaultListGroupingsPage.cs @@ -235,15 +235,18 @@ namespace Bit.App.Pages var folders = await _folderService.GetAllAsync(); var collections = await _collectionService.GetAllAsync(); - var folderGroupings = folders - .Select(f => new GroupingOrCipher( - new Grouping(f, folderCounts.ContainsKey(f.Id) ? folderCounts[f.Id] : 0))) - .OrderBy(g => g.Grouping.Name).ToList(); + var fGroupings = folders + .Select(f => new Grouping(f, folderCounts.ContainsKey(f.Id) ? folderCounts[f.Id] : 0)) + .OrderBy(g => g.Name); + var folderGroupings = Helpers.GetAllNested(fGroupings) + .Select(n => new GroupingOrCipher(n)).ToList(); if(collections.Any() || noFolderCipherGroupings.Count >= 100) { - folderGroupings.Add(new GroupingOrCipher(new Grouping(AppResources.FolderNone, - noFolderCipherGroupings.Count))); + var noneFolderGrouping = new Grouping(AppResources.FolderNone, noFolderCipherGroupings.Count); + var noneFolderNode = new Bit.App.Models.TreeNode(noneFolderGrouping, + noneFolderGrouping.Name, null); + folderGroupings.Add(new GroupingOrCipher(noneFolderNode)); } if(folderGroupings.Any()) @@ -251,10 +254,12 @@ namespace Bit.App.Pages sections.Add(new Section(folderGroupings, AppResources.Folders)); } - var collectionGroupings = collections.Select(c => - new GroupingOrCipher(new Grouping( - c, collectionsDict.ContainsKey(c.Id) ? collectionsDict[c.Id].Count() : 0))) - .OrderBy(g => g.Grouping.Name).ToList(); + var cGroupings = collections + .Select(c => new Grouping(c, collectionsDict.ContainsKey(c.Id) ? collectionsDict[c.Id].Count() : 0)) + .OrderBy(g => g.Name); + var collectionGroupings = Helpers.GetAllNested(cGroupings) + .Select(n => new GroupingOrCipher(n)).ToList(); + if(collectionGroupings.Any()) { sections.Add(new Section(collectionGroupings, AppResources.Collections)); @@ -299,15 +304,15 @@ namespace Bit.App.Pages if(groupingOrCipher.Grouping != null) { Page page; - if(groupingOrCipher.Grouping.Folder) + if(groupingOrCipher.Grouping.Node.Folder) { page = new VaultListCiphersPage(folder: true, - folderId: groupingOrCipher.Grouping.Id, groupingName: groupingOrCipher.Grouping.Name); + folderId: groupingOrCipher.Grouping.Node.Id, groupingName: groupingOrCipher.Grouping.Node.Name); } else { - page = new VaultListCiphersPage(collectionId: groupingOrCipher.Grouping.Id, - groupingName: groupingOrCipher.Grouping.Name); + page = new VaultListCiphersPage(collectionId: groupingOrCipher.Grouping.Node.Id, + groupingName: groupingOrCipher.Grouping.Node.Name); } await Navigation.PushAsync(page); diff --git a/src/App/Utilities/Helpers.cs b/src/App/Utilities/Helpers.cs index c659034a3..cfa68b165 100644 --- a/src/App/Utilities/Helpers.cs +++ b/src/App/Utilities/Helpers.cs @@ -570,8 +570,8 @@ namespace Bit.App.Utilities return appSettingsService?.OrganizationGivesPremium ?? false; } - public static void NestedTraverse(List> nodeTree, int partIndex, string[] parts, - ITreeNodeObject obj, ITreeNodeObject parent, string delimiter) + public static void NestedTraverse(List> nodeTree, int partIndex, string[] parts, + T obj, T parent, string delimiter) where T : ITreeNodeObject { if(parts.Length <= partIndex) { @@ -590,7 +590,7 @@ namespace Bit.App.Utilities if(end && n.Node.Id != obj.Id) { // Another node with the same name. - nodeTree.Add(new TreeNode(obj, partName, parent)); + nodeTree.Add(new TreeNode(obj, partName, parent)); return; } NestedTraverse(n.Children, partIndex + 1, parts, obj, n.Node, delimiter); @@ -601,7 +601,7 @@ namespace Bit.App.Utilities { if(end) { - nodeTree.Add(new TreeNode(obj, partName, parent)); + nodeTree.Add(new TreeNode(obj, partName, parent)); return; } var newPartName = string.Concat(parts[partIndex], delimiter, parts[partIndex + 1]); @@ -612,7 +612,7 @@ namespace Bit.App.Utilities } } - public static TreeNode GetTreeNodeObject(List> nodeTree, string id) + public static TreeNode GetTreeNodeObject(List> nodeTree, string id) where T : ITreeNodeObject { foreach(var n in nodeTree) { @@ -631,5 +631,15 @@ namespace Bit.App.Utilities } return null; } + + public static List> GetAllNested(IEnumerable objs) where T : ITreeNodeObject + { + var nodes = new List>(); + foreach(var o in objs) + { + NestedTraverse(nodes, 0, o.Name.Split('/'), o, default(T), "/"); + } + return nodes; + } } }