| Index: ios/chrome/browser/ui/bookmarks/bookmark_menu_view.mm
|
| diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_menu_view.mm b/ios/chrome/browser/ui/bookmarks/bookmark_menu_view.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..bee854aeec5f5c32898e07f220afba19e70e08e4
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/bookmarks/bookmark_menu_view.mm
|
| @@ -0,0 +1,429 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import "ios/chrome/browser/ui/bookmarks/bookmark_menu_view.h"
|
| +
|
| +#include <memory>
|
| +
|
| +#include "base/mac/foundation_util.h"
|
| +#include "base/mac/objc_property_releaser.h"
|
| +#include "base/mac/scoped_nsobject.h"
|
| +#include "components/bookmarks/browser/bookmark_model.h"
|
| +#include "components/bookmarks/browser/bookmark_model_observer.h"
|
| +#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
|
| +#include "ios/chrome/browser/bookmarks/bookmarks_utils.h"
|
| +#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
|
| +#include "ios/chrome/browser/experimental_flags.h"
|
| +#import "ios/chrome/browser/ui/bookmarks/bookmark_menu_cell.h"
|
| +#import "ios/chrome/browser/ui/bookmarks/bookmark_menu_item.h"
|
| +#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
|
| +#import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h"
|
| +#include "ios/chrome/browser/ui/ui_util.h"
|
| +#import "ios/chrome/browser/ui/uikit_ui_util.h"
|
| +#include "ios/chrome/grit/ios_strings.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Ink/src/MaterialInk.h"
|
| +#include "ui/base/l10n/l10n_util.h"
|
| +#include "ui/base/models/tree_node_iterator.h"
|
| +
|
| +using bookmarks::BookmarkNode;
|
| +
|
| +@interface BookmarkMenuView ()<BookmarkModelBridgeObserver,
|
| + MDCInkTouchControllerDelegate,
|
| + UITableViewDataSource,
|
| + UITableViewDelegate> {
|
| + // A bridge to receive bookmark model observer callbacks.
|
| + std::unique_ptr<bookmarks::BookmarkModelBridge> _modelBridge;
|
| +
|
| + base::mac::ObjCPropertyReleaser _propertyReleaser_BookmarkMenuView;
|
| +}
|
| +@property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel;
|
| +// This array directly represents the rows that show up in the table.
|
| +@property(nonatomic, retain) NSMutableArray* menuItems;
|
| +// The primary menu item is blue instead of gray.
|
| +@property(nonatomic, retain) BookmarkMenuItem* primaryMenuItem;
|
| +@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
|
| +@property(nonatomic, retain) UITableView* tableView;
|
| +@property(nonatomic, retain) MDCInkTouchController* inkTouchController;
|
| +
|
| +// Updates the data model, and the UI.
|
| +- (void)reloadData;
|
| +
|
| +// Creates the views for this class.
|
| +- (void)createViews;
|
| +
|
| +@end
|
| +
|
| +@implementation BookmarkMenuView
|
| +@synthesize bookmarkModel = _bookmarkModel;
|
| +@synthesize delegate = _delegate;
|
| +@synthesize menuItems = _menuItems;
|
| +@synthesize primaryMenuItem = _primaryMenuItem;
|
| +@synthesize browserState = _browserState;
|
| +@synthesize tableView = _tableView;
|
| +@synthesize inkTouchController = _inkTouchController;
|
| +
|
| +- (id)initWithFrame:(CGRect)frame {
|
| + NOTREACHED();
|
| + return nil;
|
| +}
|
| +
|
| +- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
|
| + frame:(CGRect)frame {
|
| + self = [super initWithFrame:frame];
|
| + if (self) {
|
| + _propertyReleaser_BookmarkMenuView.Init(self, [BookmarkMenuView class]);
|
| +
|
| + _browserState = browserState;
|
| +
|
| + // Set up connection to the BookmarkModel.
|
| + _bookmarkModel =
|
| + ios::BookmarkModelFactory::GetForBrowserState(_browserState);
|
| + // Set up observers.
|
| + _modelBridge.reset(
|
| + new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
|
| +
|
| + self.menuItems = [NSMutableArray array];
|
| +
|
| + [self createViews];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)dealloc {
|
| + self.tableView.delegate = nil;
|
| + self.tableView.dataSource = nil;
|
| + [super dealloc];
|
| +}
|
| +
|
| +- (void)createViews {
|
| + // Make the table view.
|
| + self.tableView = base::scoped_nsobject<UITableView>(
|
| + [[UITableView alloc] initWithFrame:self.bounds]);
|
| + [self addSubview:self.tableView];
|
| + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
| + self.tableView.delegate = self;
|
| + self.tableView.dataSource = self;
|
| + self.tableView.scrollsToTop = NO;
|
| + [self reloadData];
|
| +
|
| + // Set up ink touch controller.
|
| + base::scoped_nsobject<MDCInkTouchController> inkTouchController(
|
| + [[MDCInkTouchController alloc] initWithView:self.tableView]);
|
| + self.inkTouchController = inkTouchController;
|
| + self.inkTouchController.delegate = self;
|
| + self.inkTouchController.delaysInkSpread = YES;
|
| +}
|
| +
|
| +- (void)layoutSubviews {
|
| + [super layoutSubviews];
|
| + self.tableView.frame = self.bounds;
|
| +}
|
| +
|
| +- (void)reloadData {
|
| + if (!self.bookmarkModel->loaded())
|
| + return;
|
| +
|
| + BookmarkMenuItem* primaryItem = [self.primaryMenuItem parentItem];
|
| +
|
| + [self.menuItems removeAllObjects];
|
| +
|
| + const BookmarkNode* mobileBookmarks = self.bookmarkModel->mobile_node();
|
| + const BookmarkNode* bookmarkBar = self.bookmarkModel->bookmark_bar_node();
|
| + const BookmarkNode* otherBookmarks = self.bookmarkModel->other_node();
|
| +
|
| + // The first section is always visible.
|
| + base::scoped_nsobject<NSMutableArray> topSection(
|
| + [[NSMutableArray alloc] init]);
|
| + [self.menuItems addObject:topSection];
|
| +
|
| + if (experimental_flags::IsAllBookmarksEnabled()) {
|
| + // All Items is always visible.
|
| + [topSection addObject:[BookmarkMenuItem allMenuItem]];
|
| + }
|
| + // Bookmarks Bar, Mobile Bookmarks and Other Bookmarks are special folders and
|
| + // are shown at the top if they contain anything.
|
| + if (!mobileBookmarks->empty() ||
|
| + !experimental_flags::IsAllBookmarksEnabled()) {
|
| + [topSection
|
| + addObject:[BookmarkMenuItem folderMenuItemForNode:mobileBookmarks
|
| + rootAncestor:mobileBookmarks]];
|
| + }
|
| + if (!bookmarkBar->empty()) {
|
| + [topSection addObject:[BookmarkMenuItem folderMenuItemForNode:bookmarkBar
|
| + rootAncestor:bookmarkBar]];
|
| + }
|
| + if (!otherBookmarks->empty()) {
|
| + [topSection
|
| + addObject:[BookmarkMenuItem folderMenuItemForNode:otherBookmarks
|
| + rootAncestor:otherBookmarks]];
|
| + }
|
| +
|
| + // The second section contains all the top level folders (except for the
|
| + // permanent nodes).
|
| + base::scoped_nsobject<NSMutableArray> folderSection(
|
| + [[NSMutableArray alloc] init]);
|
| + std::vector<const BookmarkNode*> rootLevelFolders =
|
| + RootLevelFolders(self.bookmarkModel);
|
| + bookmark_utils_ios::SortFolders(&rootLevelFolders);
|
| + for (auto node : rootLevelFolders) {
|
| + [folderSection addObject:[BookmarkMenuItem folderMenuItemForNode:node
|
| + rootAncestor:node]];
|
| + }
|
| + if ([folderSection count]) {
|
| + // Add the title and the divider at the top of the section.
|
| + [folderSection
|
| + insertObject:[BookmarkMenuItem sectionMenuItemWithTitle:
|
| + l10n_util::GetNSString(
|
| + IDS_IOS_BOOKMARK_FOLDERS_LABEL)]
|
| + atIndex:0];
|
| + [folderSection insertObject:[BookmarkMenuItem dividerMenuItem] atIndex:0];
|
| + [self.menuItems addObject:folderSection];
|
| + }
|
| +
|
| + // If the currently selected menuitem is no longer present in the menu, then
|
| + // select the first item in the top section instead.
|
| + if (![topSection containsObject:primaryItem] &&
|
| + ![folderSection containsObject:primaryItem]) {
|
| + self.primaryMenuItem = [topSection firstObject];
|
| + [self.delegate bookmarkMenuView:self selectedMenuItem:self.primaryMenuItem];
|
| + }
|
| +
|
| + [self.tableView reloadData];
|
| +}
|
| +
|
| +- (BookmarkMenuItem*)defaultMenuItem {
|
| + // The first item in the first section.
|
| + DCHECK([[self.menuItems firstObject] firstObject]);
|
| + return [[self.menuItems firstObject] firstObject];
|
| +}
|
| +
|
| +- (BookmarkMenuItem*)menuItemAtIndexPath:(NSIndexPath*)indexPath {
|
| + return self.menuItems[indexPath.section][indexPath.row];
|
| +}
|
| +
|
| +#pragma mark UIView method
|
| +
|
| +- (void)didMoveToSuperview {
|
| + [super didMoveToSuperview];
|
| + // The background color depends on where in the view hierachy the menu is.
|
| + // For example, the menu may be moved to a slide over panel if the
|
| + // horizontal size class changes from regular to compact.
|
| + self.tableView.backgroundColor = bookmark_utils_ios::menuBackgroundColor();
|
| +}
|
| +
|
| +#pragma mark BookmarkModelBridgeObserver
|
| +
|
| +- (void)bookmarkModelLoaded {
|
| + [self reloadData];
|
| +}
|
| +
|
| +- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
|
| + [self reloadData];
|
| +}
|
| +
|
| +- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
|
| + [self reloadData];
|
| +}
|
| +
|
| +- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
|
| + movedFromParent:(const BookmarkNode*)oldParent
|
| + toParent:(const BookmarkNode*)newParent {
|
| + if (self.primaryMenuItem.type == bookmarks::MenuItemFolder &&
|
| + bookmarkNode->is_folder()) {
|
| + // Checking which folder moved and if the current folder was implicated is
|
| + // complicated and not worth the effort. Just rebuild a new primaryMenu item
|
| + // unconditionally, this is simpler.
|
| + const BookmarkNode* currentFolder = self.primaryMenuItem.folder;
|
| + BookmarkMenuItem* menuItem = [BookmarkMenuItem
|
| + folderMenuItemForNode:currentFolder
|
| + rootAncestor:RootLevelFolderForNode(currentFolder,
|
| + self.bookmarkModel)];
|
| + if (menuItem != self.primaryMenuItem) {
|
| + self.primaryMenuItem = menuItem;
|
| + [self.delegate bookmarkMenuView:self
|
| + selectedMenuItem:self.primaryMenuItem];
|
| + }
|
| + }
|
| + [self reloadData];
|
| +}
|
| +
|
| +- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
|
| + fromFolder:(const BookmarkNode*)parentFolder {
|
| + // If the current folder or one of its ancestor has been deleted, the
|
| + // selection needs to move up to a non deleted ancestor. This check is made
|
| + // more complex as by the time this method is called |node| is no longer in
|
| + // the hierarchy : its parent is already set to null.
|
| +
|
| + if (self.primaryMenuItem.type != bookmarks::MenuItemFolder) {
|
| + // If the object currently selected is not a folder, just reload.
|
| + [self reloadData];
|
| + return;
|
| + }
|
| +
|
| + if (parentFolder == self.primaryMenuItem.folder || !node->is_folder()) {
|
| + // A child of the selected folder has been deleted or a url not visible in
|
| + // the UI right now has been deleted. Nothing to do as the menu itself needs
|
| + // no change.
|
| + return;
|
| + }
|
| +
|
| + if (node == self.primaryMenuItem.rootAncestor) {
|
| + // The deleted node is the root node of the current selected folder. Move to
|
| + // all items.
|
| + self.primaryMenuItem = [BookmarkMenuItem allMenuItem];
|
| + [self.delegate bookmarkMenuView:self selectedMenuItem:self.primaryMenuItem];
|
| + [self reloadData];
|
| + return;
|
| + }
|
| +
|
| + const BookmarkNode* root =
|
| + RootLevelFolderForNode(parentFolder, self.bookmarkModel);
|
| +
|
| + if (root != self.primaryMenuItem.rootAncestor) {
|
| + // The deleted folder is not in the same hierarchy as the current selected
|
| + // folder, there is nothing to reload unless the deleted folder is a root
|
| + // node.
|
| + if (!root)
|
| + [self reloadData];
|
| + return;
|
| + }
|
| +
|
| + if (node == self.primaryMenuItem.folder) {
|
| + // The simple case where the deleted folder is the one currently in the UI.
|
| + // At this point the deleted folder is known to not be a root node:
|
| + DCHECK_NE(self.primaryMenuItem.folder, self.primaryMenuItem.rootAncestor);
|
| + // Simply move to the parent.
|
| + self.primaryMenuItem =
|
| + [BookmarkMenuItem folderMenuItemForNode:parentFolder rootAncestor:root];
|
| + [self.delegate bookmarkMenuView:self selectedMenuItem:self.primaryMenuItem];
|
| + [self reloadData];
|
| + }
|
| +
|
| + // The only case left is when the deleted folder used to be an ancestor of the
|
| + // selected folder. This is easy to infer, if the selected folder is no longer
|
| + // present in the common root hierarchy, this means it was deleted as well.
|
| + ui::TreeNodeIterator<const BookmarkNode> iterator(root);
|
| + while (iterator.has_next()) {
|
| + if (self.primaryMenuItem.folder == iterator.Next())
|
| + return; // Nothing to do.
|
| + }
|
| + // The current folder was not found, relocate to the first non deleted
|
| + // ancestor.
|
| + self.primaryMenuItem =
|
| + [BookmarkMenuItem folderMenuItemForNode:parentFolder rootAncestor:root];
|
| + [self.delegate bookmarkMenuView:self selectedMenuItem:self.primaryMenuItem];
|
| + [self reloadData];
|
| +}
|
| +
|
| +- (void)bookmarkModelRemovedAllNodes {
|
| + [self reloadData];
|
| +}
|
| +
|
| +#pragma mark UITableViewDataSource
|
| +
|
| +- (UITableViewCell*)tableView:(UITableView*)tableView
|
| + cellForRowAtIndexPath:(NSIndexPath*)indexPath {
|
| + BookmarkMenuCell* cell = [tableView
|
| + dequeueReusableCellWithIdentifier:[BookmarkMenuCell reuseIdentifier]];
|
| + if (!cell) {
|
| + cell = [[[BookmarkMenuCell alloc]
|
| + initWithStyle:UITableViewCellStyleDefault
|
| + reuseIdentifier:[BookmarkMenuCell reuseIdentifier]] autorelease];
|
| + }
|
| + cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
| + BookmarkMenuItem* menuItem = [self menuItemAtIndexPath:indexPath];
|
| + BOOL primary =
|
| + [[self.primaryMenuItem parentItem] isEqual:[menuItem parentItem]];
|
| + [cell updateWithBookmarkMenuItem:menuItem primary:primary];
|
| + if (primary && bookmark_utils_ios::bookmarkMenuIsInSlideInPanel()) {
|
| + [tableView selectRowAtIndexPath:indexPath
|
| + animated:NO
|
| + scrollPosition:UITableViewScrollPositionNone];
|
| + }
|
| + return cell;
|
| +}
|
| +
|
| +- (NSInteger)tableView:(UITableView*)tableView
|
| + numberOfRowsInSection:(NSInteger)section {
|
| + return [self.menuItems[section] count];
|
| +}
|
| +
|
| +- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
|
| + return [self.menuItems count];
|
| +}
|
| +
|
| +#pragma mark UITableViewDelegate
|
| +
|
| +- (CGFloat)tableView:(UITableView*)tableView
|
| + heightForRowAtIndexPath:(NSIndexPath*)indexPath {
|
| + BookmarkMenuItem* menuItem = [self menuItemAtIndexPath:indexPath];
|
| + return [menuItem height];
|
| +}
|
| +
|
| +- (NSIndexPath*)tableView:(UITableView*)tableView
|
| + willSelectRowAtIndexPath:(NSIndexPath*)indexPath {
|
| + BookmarkMenuItem* menuItem = [self menuItemAtIndexPath:indexPath];
|
| + return [menuItem canBeSelected] ? indexPath : nil;
|
| +}
|
| +
|
| +- (void)tableView:(UITableView*)tableView
|
| + didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
|
| + BookmarkMenuItem* menuItem = [self menuItemAtIndexPath:indexPath];
|
| + [self.delegate bookmarkMenuView:self selectedMenuItem:menuItem];
|
| +}
|
| +
|
| +- (CGFloat)tableView:(UITableView*)tableView
|
| + heightForHeaderInSection:(NSInteger)section {
|
| + return 8.0;
|
| +}
|
| +
|
| +- (CGFloat)tableView:(UITableView*)tableView
|
| + heightForFooterInSection:(NSInteger)section {
|
| + BOOL isLastSection = [tableView numberOfSections] == (section + 1);
|
| + return isLastSection ? 8.0 : 0.0;
|
| +}
|
| +
|
| +- (UIView*)tableView:(UITableView*)tableView
|
| + viewForHeaderInSection:(NSInteger)section {
|
| + return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
|
| +}
|
| +
|
| +- (UIView*)tableView:(UITableView*)tableView
|
| + viewForFooterInSection:(NSInteger)section {
|
| + return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
|
| +}
|
| +
|
| +#pragma mark MDCInkTouchControllerDelegate
|
| +
|
| +- (BOOL)inkTouchController:(MDCInkTouchController*)inkTouchController
|
| + shouldProcessInkTouchesAtTouchLocation:(CGPoint)location {
|
| + NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:location];
|
| + BookmarkMenuItem* menuItem = [self menuItemAtIndexPath:indexPath];
|
| + return menuItem.type == bookmarks::MenuItemAll ||
|
| + menuItem.type == bookmarks::MenuItemFolder;
|
| +}
|
| +
|
| +- (MDCInkView*)inkTouchController:(MDCInkTouchController*)inkTouchController
|
| + inkViewAtTouchLocation:(CGPoint)location {
|
| + NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:location];
|
| + BookmarkMenuCell* cell = base::mac::ObjCCastStrict<BookmarkMenuCell>(
|
| + [self.tableView cellForRowAtIndexPath:indexPath]);
|
| + return cell.inkView;
|
| +}
|
| +
|
| +#pragma mark Public Methods
|
| +
|
| +- (void)updatePrimaryMenuItem:(BookmarkMenuItem*)menuItem {
|
| + if ([self.primaryMenuItem isEqual:menuItem])
|
| + return;
|
| +
|
| + self.primaryMenuItem = menuItem;
|
| + [self.tableView reloadData];
|
| +}
|
| +
|
| +- (void)setScrollsToTop:(BOOL)scrollsToTop {
|
| + self.tableView.scrollsToTop = scrollsToTop;
|
| +}
|
| +
|
| +@end
|
|
|