OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/bookmarks/bookmark_home_tablet_ntp_controller.h" |
| 6 |
| 7 #include <memory> |
| 8 |
| 9 #include "base/ios/block_types.h" |
| 10 #import "base/ios/weak_nsobject.h" |
| 11 #include "base/logging.h" |
| 12 #include "base/mac/objc_property_releaser.h" |
| 13 #include "base/mac/scoped_nsobject.h" |
| 14 #include "base/metrics/user_metrics.h" |
| 15 #include "base/metrics/user_metrics_action.h" |
| 16 #include "base/strings/sys_string_conversions.h" |
| 17 #include "components/bookmarks/browser/bookmark_model.h" |
| 18 #include "components/strings/grit/components_strings.h" |
| 19 #include "google_apis/gaia/google_service_auth_error.h" |
| 20 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" |
| 21 #include "ios/chrome/browser/bookmarks/bookmarks_utils.h" |
| 22 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 23 #import "ios/chrome/browser/metrics/new_tab_page_uma.h" |
| 24 #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h" |
| 25 #import "ios/chrome/browser/ui/bookmarks/bars/bookmark_editing_bar.h" |
| 26 #import "ios/chrome/browser/ui/bookmarks/bars/bookmark_navigation_bar.h" |
| 27 #import "ios/chrome/browser/ui/bookmarks/bookmark_all_collection_view.h" |
| 28 #import "ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.h" |
| 29 #import "ios/chrome/browser/ui/bookmarks/bookmark_folder_collection_view.h" |
| 30 #import "ios/chrome/browser/ui/bookmarks/bookmark_folder_editor_view_controller.
h" |
| 31 #import "ios/chrome/browser/ui/bookmarks/bookmark_folder_view_controller.h" |
| 32 #import "ios/chrome/browser/ui/bookmarks/bookmark_home_primary_view.h" |
| 33 #import "ios/chrome/browser/ui/bookmarks/bookmark_home_waiting_view.h" |
| 34 #import "ios/chrome/browser/ui/bookmarks/bookmark_menu_item.h" |
| 35 #import "ios/chrome/browser/ui/bookmarks/bookmark_menu_view.h" |
| 36 #include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h" |
| 37 #import "ios/chrome/browser/ui/bookmarks/bookmark_navigation_controller.h" |
| 38 #import "ios/chrome/browser/ui/bookmarks/bookmark_panel_view.h" |
| 39 #import "ios/chrome/browser/ui/bookmarks/bookmark_promo_controller.h" |
| 40 #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h" |
| 41 #import "ios/chrome/browser/ui/rtl_geometry.h" |
| 42 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 43 #import "ios/chrome/browser/ui/url_loader.h" |
| 44 #include "ios/chrome/grit/ios_strings.h" |
| 45 #include "ios/web/public/referrer.h" |
| 46 #include "ui/base/l10n/l10n_util.h" |
| 47 #include "ui/base/l10n/l10n_util_mac.h" |
| 48 #include "ui/base/page_transition_types.h" |
| 49 |
| 50 using bookmarks::BookmarkNode; |
| 51 |
| 52 namespace { |
| 53 // The width of the bookmark menu, displaying the different sections. |
| 54 const CGFloat kMenuWidth = 264.0; |
| 55 // The margin on top to the navigation bar. |
| 56 const CGFloat kNavigationBarTopMargin = 8.0; |
| 57 } // namespace |
| 58 |
| 59 // A simple UIView subclass to pass on relayout information to its delegate. |
| 60 @protocol ContentViewDelegate<NSObject> |
| 61 - (void)willLayoutSubviews; |
| 62 @end |
| 63 |
| 64 @interface ContentView : UIView |
| 65 @property(nonatomic, assign) id<ContentViewDelegate> delegate; |
| 66 @end |
| 67 |
| 68 @implementation ContentView |
| 69 @synthesize delegate = _delegate; |
| 70 |
| 71 - (void)layoutSubviews { |
| 72 [self.delegate willLayoutSubviews]; |
| 73 [super layoutSubviews]; |
| 74 } |
| 75 @end |
| 76 |
| 77 @interface BookmarkHomeTabletNTPController ()< |
| 78 BookmarkEditViewControllerDelegate, |
| 79 BookmarkFolderCollectionViewDelegate, |
| 80 BookmarkFolderEditorViewControllerDelegate, |
| 81 BookmarkFolderViewControllerDelegate, |
| 82 BookmarkMenuViewDelegate, |
| 83 BookmarkModelBridgeObserver, |
| 84 BookmarkPromoControllerDelegate, |
| 85 ContentViewDelegate> { |
| 86 // Bridge to register for bookmark changes. |
| 87 std::unique_ptr<bookmarks::BookmarkModelBridge> _bridge; |
| 88 ios::ChromeBrowserState* _browserState; // Weak. |
| 89 id<UrlLoader> _loader; // Weak. |
| 90 |
| 91 // The following 2 ivars both represent the set of nodes being edited. |
| 92 // The set is for fast lookup. |
| 93 // The vector maintains the order that edit nodes were added. |
| 94 // Use the relevant instance methods to modify these two ivars in tandem. |
| 95 // DO NOT modify these two ivars directly. |
| 96 std::set<const BookmarkNode*> _editNodes; |
| 97 std::vector<const BookmarkNode*> _editNodesOrdered; |
| 98 |
| 99 base::mac::ObjCPropertyReleaser |
| 100 _propertyReleaser_BookmarkHomeTabletNTPController; |
| 101 } |
| 102 |
| 103 @property(nonatomic, retain) BookmarkPanelView* panelView; |
| 104 |
| 105 #pragma mark - Properties and methods akin to BookmarkHomeViewController |
| 106 |
| 107 // Whether the view controller is in editing mode. |
| 108 @property(nonatomic, assign) BOOL editing; |
| 109 // The set of edited index paths. |
| 110 @property(nonatomic, retain) NSMutableArray* editIndexPaths; |
| 111 // The bookmark model used. |
| 112 @property(nonatomic, assign, readonly) bookmarks::BookmarkModel* bookmarks; |
| 113 // The user's browser state model used. |
| 114 @property(nonatomic, assign, readonly) |
| 115 ios::ChromeBrowserState* browserState; // from superclass. |
| 116 |
| 117 // Replaces |_editNodes| and |_editNodesOrdered| with new container objects. |
| 118 - (void)resetEditNodes; |
| 119 // Adds |node| corresponding to a |cell| if it isn't already present. |
| 120 - (void)insertEditNode:(const BookmarkNode*)node |
| 121 atIndexPath:(NSIndexPath*)indexPath; |
| 122 // Removes |node| corresponding to a |cell| if it's present. |
| 123 - (void)removeEditNode:(const BookmarkNode*)node |
| 124 atIndexPath:(NSIndexPath*)indexPath; |
| 125 // This method updates the property, and resets the edit nodes. |
| 126 - (void)setEditing:(BOOL)editing animated:(BOOL)animated; |
| 127 |
| 128 #pragma mark - Properties and methods akin to BookmarkHomeHandsetViewController |
| 129 |
| 130 // This views holds the primary content of this controller. At any point in |
| 131 // time, it contains exactly one of the BookmarkCollectionView subclasses. |
| 132 @property(nonatomic, retain) ContentView* contentView; |
| 133 // The possible views that can be shown from the menu. |
| 134 @property(nonatomic, retain) BookmarkAllCollectionView* allItemsView; |
| 135 @property(nonatomic, retain) BookmarkFolderCollectionView* folderView; |
| 136 // This view is created and used if the model is not fully loaded yet by the |
| 137 // time this controller starts. |
| 138 @property(nonatomic, retain) BookmarkHomeWaitingView* waitForModelView; |
| 139 |
| 140 // The menu with all the folders and special entries. |
| 141 @property(nonatomic, retain) BookmarkMenuView* menuView; |
| 142 // At any point in time, there is exactly one collection view whose view is part |
| 143 // of the view hierarchy. This property determines which collection view is |
| 144 // visible. Not by accident, this property also reflects the selected menu item |
| 145 // in the BookmarkMenuView. |
| 146 @property(nonatomic, retain) BookmarkMenuItem* primaryMenuItem; |
| 147 // When the view is first shown on the screen, this property represents the |
| 148 // cached value of the y of the content offset of the primary view. This |
| 149 // property is set to nil after it is used. |
| 150 @property(nonatomic, retain) |
| 151 NSNumber* cachedContentPosition; // FIXME: INACTIVE |
| 152 |
| 153 // The navigation bar sits on top of the main content. |
| 154 @property(nonatomic, retain) BookmarkNavigationBar* navigationBar; |
| 155 // The editing bar present when items are selected. |
| 156 @property(nonatomic, retain) BookmarkEditingBar* editingBar; |
| 157 |
| 158 // The action sheet coordinator used when trying to edit a single bookmark. |
| 159 @property(nonatomic, retain) ActionSheetCoordinator* actionSheetCoordinator; |
| 160 // The view controller used to view and edit a single bookmark. |
| 161 @property(nonatomic, retain) BookmarkEditViewController* editViewController; |
| 162 // The view controller used to pick a folder in which to move the selected |
| 163 // bookmarks. |
| 164 @property(nonatomic, retain) BookmarkFolderViewController* folderSelector; |
| 165 // The view controller to present when editing the current folder. |
| 166 @property(nonatomic, retain) |
| 167 BookmarkFolderEditorViewController* folderEditor; // FIX |
| 168 // The controller managing the display of the promo cell and the promo view |
| 169 // controller. |
| 170 @property(nonatomic, retain) BookmarkPromoController* bookmarkPromoController; |
| 171 |
| 172 #pragma mark Specific to this class. |
| 173 |
| 174 // Either the menu or the primaryView can scrollToTop. |
| 175 @property(nonatomic, assign) BOOL scrollToTop; |
| 176 |
| 177 // Opens the url. |
| 178 - (void)loadURL:(const GURL&)url; |
| 179 #pragma mark View loading, laying out, and switching. |
| 180 // This method is called if the view needs to be loaded and the model is not |
| 181 // ready yet. |
| 182 - (void)loadWaitingView; |
| 183 // This method should be called at most once in the life-cycle of the class. |
| 184 // It should be called at the soonest possible time after the view has been |
| 185 // loaded, and the bookmark model is loaded. |
| 186 - (void)loadBookmarkViews; |
| 187 // If the view doesn't exist, create it. |
| 188 - (void)ensureAllViewExists; |
| 189 - (void)ensureFolderViewExists; |
| 190 // Updates the property 'primaryMenuItem'. |
| 191 // Updates the UI to reflect the new state of 'primaryMenuItem'. |
| 192 - (void)updatePrimaryMenuItem:(BookmarkMenuItem*)menuItem |
| 193 animated:(BOOL)animated; |
| 194 // The active collection view that corresponds to primaryMenuItem. |
| 195 - (UIView<BookmarkHomePrimaryView>*)primaryView; |
| 196 // Returns whether the menu should be in a side panel that slides in. |
| 197 - (BOOL)shouldPresentMenuInSlideInPanel; |
| 198 // Returns the width of the menu. |
| 199 - (CGFloat)menuWidth; |
| 200 // Returns the leading margin of the primary view. |
| 201 - (CGFloat)primaryViewLeadingMargin; |
| 202 // Moves the menu and primary view to their correct parent views depending on |
| 203 // the layout. |
| 204 - (void)moveMenuAndPrimaryViewToAdequateParent; |
| 205 // Updates the frame of the primary view. |
| 206 - (void)refreshFrameOfPrimaryView; |
| 207 // Returns the frame of the primary view. |
| 208 - (CGRect)frameForPrimaryView; |
| 209 |
| 210 #pragma mark Editing related methods |
| 211 // This method statelessly updates the editing top bar from |_editNodes| and |
| 212 // |editing|. |
| 213 - (void)updateEditingStateAnimated:(BOOL)animated; |
| 214 // Shows or hides the editing bar. |
| 215 - (void)showEditingBarAnimated:(BOOL)animated; |
| 216 - (void)hideEditingBarAnimated:(BOOL)animated; |
| 217 // Instaneously updates the shadow of the edit bar. |
| 218 // This method should be called anytime: |
| 219 // (1)|editing| property changes. |
| 220 // (2)The primary view changes. |
| 221 // (3)The primary view's collection view is scrolled. |
| 222 // When |editing| is NO, the shadow is never shown. |
| 223 - (void)updateEditBarShadow; |
| 224 |
| 225 #pragma mark Editing bar callbacks |
| 226 // The cancel button was tapped on the editing bar. |
| 227 - (void)editingBarCancel; |
| 228 // The move button was tapped on the editing bar. |
| 229 - (void)editingBarMove; |
| 230 // The delete button was tapped on the editing bar. |
| 231 - (void)editingBarDelete; |
| 232 // The edit button was tapped on the editing bar. |
| 233 - (void)editingBarEdit; |
| 234 // The menu button is pressed on the editing bar. |
| 235 - (void)toggleMenuAnimated; |
| 236 |
| 237 #pragma mark Action sheet callbacks |
| 238 // Enters into edit mode by selecting the given node corresponding to the |
| 239 // given cell. |
| 240 - (void)selectFirstNode:(const BookmarkNode*)node |
| 241 withCell:(UICollectionViewCell*)cell; |
| 242 // Opens the editor on the given node and cell. |
| 243 - (void)editNode:(const BookmarkNode*)node withCell:(UICollectionViewCell*)cell; |
| 244 // Opens the folder move editor for the given node. |
| 245 - (void)moveNodes:(const std::set<const BookmarkNode*>&)nodes; |
| 246 // Deletes the current node. |
| 247 - (void)deleteNodes:(const std::set<const BookmarkNode*>&)nodes; |
| 248 |
| 249 #pragma mark private utility methods |
| 250 // Deletes the nodes, and presents a toast with an undo button. |
| 251 - (void)deleteSelectedNodes; |
| 252 |
| 253 #pragma mark Navigation bar |
| 254 // Updates the UI of the navigation bar with the primaryMenuItem. |
| 255 // This method should be called anytime: |
| 256 // (1)The primary view changes. |
| 257 // (2)The primary view has type folder, and the relevant folder has changed. |
| 258 // (3)The interface orientation changes. |
| 259 // (4)viewWillAppear, as the interface orientation may have changed. |
| 260 - (void)updateNavigationBarAnimated:(BOOL)animated |
| 261 orientation:(UIInterfaceOrientation)orientation; |
| 262 - (void)updateNavigationBarWithDuration:(CGFloat)duration |
| 263 orientation:(UIInterfaceOrientation)orientation; |
| 264 // Whether the edit button on the navigation bar should be shown. |
| 265 - (BOOL)shouldShowEditButton; |
| 266 // Whether the back button on the navigation bar should be shown. |
| 267 - (BOOL)shouldShowBackButton; |
| 268 // Called when the edit button is pressed on the navigation bar. |
| 269 - (void)navigationBarWantsEditing:(id)sender; |
| 270 // Called when the back button is pressed on the navigation bar. |
| 271 - (void)navigationBarBack:(id)sender; |
| 272 |
| 273 // TODO(crbug.com/450646): This should not be needed but require refactoring of |
| 274 // the BookmarkCollectionViewDelegate. |
| 275 - (NSIndexPath*)indexPathForCell:(UICollectionViewCell*)cell; |
| 276 |
| 277 @end |
| 278 |
| 279 @implementation BookmarkHomeTabletNTPController |
| 280 @synthesize editing = _editing; |
| 281 @synthesize editIndexPaths = _editIndexPaths; |
| 282 @synthesize bookmarks = _bookmarks; |
| 283 |
| 284 @synthesize contentView = _contentView; |
| 285 @synthesize allItemsView = _allItemsView; |
| 286 @synthesize folderView = _folderView; |
| 287 @synthesize waitForModelView = _waitForModelView; |
| 288 |
| 289 @synthesize menuView = _menuView; |
| 290 @synthesize primaryMenuItem = _primaryMenuItem; |
| 291 @synthesize cachedContentPosition = _cachedContentPosition; |
| 292 @synthesize navigationBar = _navigationBar; |
| 293 @synthesize editingBar = _editingBar; |
| 294 @synthesize panelView = _panelView; |
| 295 |
| 296 @synthesize actionSheetCoordinator = _actionSheetCoordinator; |
| 297 @synthesize editViewController = _editViewController; |
| 298 @synthesize folderSelector = _folderSelector; |
| 299 @synthesize folderEditor = _folderEditor; |
| 300 @synthesize bookmarkPromoController = _bookmarkPromoController; |
| 301 |
| 302 @synthesize scrollToTop = _scrollToTop; |
| 303 |
| 304 // Property declared in NewTabPagePanelProtocol. |
| 305 @synthesize delegate = _delegate; |
| 306 |
| 307 - (id)initWithLoader:(id<UrlLoader>)loader |
| 308 browserState:(ios::ChromeBrowserState*)browserState { |
| 309 self = [super init]; |
| 310 if (self) { |
| 311 DCHECK(browserState); |
| 312 _browserState = browserState->GetOriginalChromeBrowserState(); |
| 313 _loader = loader; |
| 314 _propertyReleaser_BookmarkHomeTabletNTPController.Init( |
| 315 self, [BookmarkHomeTabletNTPController class]); |
| 316 |
| 317 _bookmarks = ios::BookmarkModelFactory::GetForBrowserState(_browserState); |
| 318 _bridge.reset(new bookmarks::BookmarkModelBridge(self, _bookmarks)); |
| 319 _editIndexPaths = [[NSMutableArray alloc] init]; |
| 320 // It is important to initialize the promo controller with the browser state |
| 321 // passed in, as it could be incognito. |
| 322 _bookmarkPromoController = |
| 323 [[BookmarkPromoController alloc] initWithBrowserState:browserState |
| 324 delegate:self]; |
| 325 } |
| 326 return self; |
| 327 } |
| 328 |
| 329 - (void)dealloc { |
| 330 _contentView.delegate = nil; |
| 331 |
| 332 _allItemsView.delegate = nil; |
| 333 _folderView.delegate = nil; |
| 334 |
| 335 _menuView.delegate = nil; |
| 336 |
| 337 _editViewController.delegate = nil; |
| 338 _folderSelector.delegate = nil; |
| 339 |
| 340 [super dealloc]; |
| 341 } |
| 342 |
| 343 - (ios::ChromeBrowserState*)browserState { |
| 344 return _browserState; |
| 345 } |
| 346 |
| 347 #pragma mark - ContentViewDelegate method. |
| 348 |
| 349 - (void)willLayoutSubviews { |
| 350 if (![self primaryView] && ![self primaryMenuItem] && |
| 351 self.bookmarks->loaded()) { |
| 352 BookmarkMenuItem* item = nil; |
| 353 CGFloat position = 0; |
| 354 BOOL found = |
| 355 bookmark_utils_ios::GetPositionCache(self.bookmarks, &item, &position); |
| 356 if (!found) |
| 357 item = [self.menuView defaultMenuItem]; |
| 358 |
| 359 [self updatePrimaryMenuItem:item animated:NO]; |
| 360 } |
| 361 |
| 362 [self moveMenuAndPrimaryViewToAdequateParent]; |
| 363 CGFloat leadingMargin = [self primaryViewLeadingMargin]; |
| 364 |
| 365 // Prevent the panelView from hijacking the gestures so that the |
| 366 // NTPController's scrollview can still scroll with the gestures. |
| 367 [self.panelView enableSideSwiping:NO]; |
| 368 |
| 369 CGFloat width = self.contentView.bounds.size.width; |
| 370 LayoutRect navBarLayout = |
| 371 LayoutRectMake(leadingMargin, width, 0, width - leadingMargin, |
| 372 CGRectGetHeight([self navigationBarFrame])); |
| 373 self.navigationBar.frame = LayoutRectGetRect(navBarLayout); |
| 374 [self.editingBar setFrame:[self editingBarFrame]]; |
| 375 |
| 376 UIInterfaceOrientation orient = GetInterfaceOrientation(); |
| 377 [self refreshFrameOfPrimaryView]; |
| 378 [[self primaryView] changeOrientation:orient]; |
| 379 [self updateNavigationBarWithDuration:0 orientation:orient]; |
| 380 if (![self shouldPresentMenuInSlideInPanel]) |
| 381 [self updateMenuViewLayout]; |
| 382 } |
| 383 |
| 384 #pragma mark HomeViewController simili methods. |
| 385 |
| 386 - (void)resetEditNodes { |
| 387 _editNodes = std::set<const BookmarkNode*>(); |
| 388 _editNodesOrdered = std::vector<const BookmarkNode*>(); |
| 389 [self.editIndexPaths removeAllObjects]; |
| 390 } |
| 391 |
| 392 - (void)insertEditNode:(const BookmarkNode*)node |
| 393 atIndexPath:(NSIndexPath*)indexPath { |
| 394 if (_editNodes.find(node) != _editNodes.end()) |
| 395 return; |
| 396 _editNodes.insert(node); |
| 397 _editNodesOrdered.push_back(node); |
| 398 if (indexPath) { |
| 399 [self.editIndexPaths addObject:indexPath]; |
| 400 } else { |
| 401 // Insert null to keep the index valid. |
| 402 [self.editIndexPaths addObject:[NSNull null]]; |
| 403 } |
| 404 } |
| 405 |
| 406 - (void)removeEditNode:(const BookmarkNode*)node |
| 407 atIndexPath:(NSIndexPath*)indexPath { |
| 408 if (_editNodes.find(node) == _editNodes.end()) |
| 409 return; |
| 410 _editNodes.erase(node); |
| 411 std::vector<const BookmarkNode*>::iterator it = |
| 412 std::find(_editNodesOrdered.begin(), _editNodesOrdered.end(), node); |
| 413 DCHECK(it != _editNodesOrdered.end()); |
| 414 _editNodesOrdered.erase(it); |
| 415 if (indexPath) { |
| 416 [self.editIndexPaths removeObject:indexPath]; |
| 417 } else { |
| 418 // If we don't have the cell, we remove it by using its index. |
| 419 const NSUInteger index = std::distance(_editNodesOrdered.begin(), it); |
| 420 if (index < self.editIndexPaths.count) { |
| 421 [self.editIndexPaths removeObjectAtIndex:index]; |
| 422 } |
| 423 } |
| 424 |
| 425 if (_editNodes.size() == 0) |
| 426 [self setEditing:NO animated:YES]; |
| 427 else |
| 428 [self updateEditingStateAnimated:YES]; |
| 429 } |
| 430 |
| 431 #pragma mark - private methods |
| 432 |
| 433 - (void)loadURL:(const GURL&)url { |
| 434 if (url == GURL() || url.SchemeIs(url::kJavaScriptScheme)) |
| 435 return; |
| 436 |
| 437 new_tab_page_uma::RecordAction(self.browserState, |
| 438 new_tab_page_uma::ACTION_OPENED_BOOKMARK); |
| 439 base::RecordAction(base::UserMetricsAction("MobileNTPBookmark")); |
| 440 [_loader loadURL:url |
| 441 referrer:web::Referrer() |
| 442 transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK |
| 443 rendererInitiated:NO]; |
| 444 } |
| 445 |
| 446 #pragma mark - Views |
| 447 |
| 448 - (void)loadWaitingView { |
| 449 DCHECK(!self.waitForModelView); |
| 450 DCHECK(self.contentView); |
| 451 |
| 452 // Present a waiting view. |
| 453 base::scoped_nsobject<BookmarkHomeWaitingView> waitingView( |
| 454 [[BookmarkHomeWaitingView alloc] initWithFrame:self.view.bounds]); |
| 455 self.waitForModelView = waitingView; |
| 456 [self.view addSubview:self.waitForModelView]; |
| 457 [self.waitForModelView startWaiting]; |
| 458 } |
| 459 |
| 460 - (void)updateMenuViewLayout { |
| 461 LayoutRect menuLayout = |
| 462 LayoutRectMake(0, self.view.bounds.size.width, 0, self.menuWidth, |
| 463 self.view.bounds.size.height); |
| 464 self.menuView.frame = LayoutRectGetRect(menuLayout); |
| 465 } |
| 466 |
| 467 - (void)loadBookmarkViews { |
| 468 DCHECK(self.bookmarks->loaded()); |
| 469 |
| 470 // Create the menu. |
| 471 LayoutRect menuLayout = |
| 472 LayoutRectMake(0, self.view.bounds.size.width, 0, self.menuWidth, |
| 473 self.view.bounds.size.height); |
| 474 self.menuView = |
| 475 base::scoped_nsobject<BookmarkMenuView>([[BookmarkMenuView alloc] |
| 476 initWithBrowserState:self.browserState |
| 477 frame:LayoutRectGetRect(menuLayout)]); |
| 478 self.menuView.delegate = self; |
| 479 self.menuView.autoresizingMask = UIViewAutoresizingFlexibleHeight; |
| 480 |
| 481 [self moveMenuAndPrimaryViewToAdequateParent]; |
| 482 |
| 483 // Load the last primary menu item which the user had active. |
| 484 BookmarkMenuItem* item = nil; |
| 485 CGFloat position = 0; |
| 486 BOOL found = |
| 487 bookmark_utils_ios::GetPositionCache(self.bookmarks, &item, &position); |
| 488 if (!found) |
| 489 item = [self.menuView defaultMenuItem]; |
| 490 |
| 491 [self updatePrimaryMenuItem:item animated:NO]; |
| 492 |
| 493 [[self primaryView] applyContentPosition:position]; |
| 494 |
| 495 if (found) { |
| 496 // If the view has already been laid out, then immediately apply the content |
| 497 // position. |
| 498 if (self.view.window) { |
| 499 [[self primaryView] applyContentPosition:position]; |
| 500 } else { |
| 501 // Otherwise, save the position to be applied once the view has been laid |
| 502 // out. |
| 503 self.cachedContentPosition = [NSNumber numberWithFloat:position]; |
| 504 } |
| 505 } |
| 506 } |
| 507 |
| 508 - (void)ensureAllViewExists { |
| 509 if (self.allItemsView) |
| 510 return; |
| 511 |
| 512 base::scoped_nsobject<BookmarkAllCollectionView> view( |
| 513 [[BookmarkAllCollectionView alloc] initWithBrowserState:self.browserState |
| 514 frame:CGRectZero]); |
| 515 self.allItemsView = view; |
| 516 self.allItemsView.delegate = self; |
| 517 [self.allItemsView setEditing:self.editing animated:NO]; |
| 518 self.allItemsView.autoresizingMask = |
| 519 UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; |
| 520 } |
| 521 |
| 522 - (void)ensureFolderViewExists { |
| 523 if (self.folderView) |
| 524 return; |
| 525 |
| 526 base::scoped_nsobject<BookmarkFolderCollectionView> view( |
| 527 [[BookmarkFolderCollectionView alloc] |
| 528 initWithBrowserState:self.browserState |
| 529 frame:CGRectZero]); |
| 530 self.folderView = view; |
| 531 self.folderView.delegate = self; |
| 532 [self.folderView setEditing:self.editing animated:NO]; |
| 533 self.folderView.autoresizingMask = |
| 534 UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; |
| 535 } |
| 536 |
| 537 - (void)updatePrimaryMenuItem:(BookmarkMenuItem*)menuItem |
| 538 animated:(BOOL)animated { |
| 539 if ([self.primaryMenuItem isEqual:menuItem]) |
| 540 return; |
| 541 |
| 542 if (![self.contentView superview]) |
| 543 return; |
| 544 |
| 545 [[self primaryView] removeFromSuperview]; |
| 546 self.primaryMenuItem = menuItem; |
| 547 |
| 548 switch (self.primaryMenuItem.type) { |
| 549 case bookmarks::MenuItemAll: |
| 550 [self ensureAllViewExists]; |
| 551 break; |
| 552 case bookmarks::MenuItemFolder: |
| 553 [self ensureFolderViewExists]; |
| 554 [self.folderView resetFolder:self.primaryMenuItem.folder]; |
| 555 [self.folderView promoStateChangedAnimated:NO]; |
| 556 break; |
| 557 case bookmarks::MenuItemDivider: |
| 558 case bookmarks::MenuItemSectionHeader: |
| 559 NOTREACHED(); |
| 560 break; |
| 561 } |
| 562 |
| 563 [[self primaryView] changeOrientation:GetInterfaceOrientation()]; |
| 564 [[self primaryView] setScrollsToTop:self.scrollToTop]; |
| 565 |
| 566 [self moveMenuAndPrimaryViewToAdequateParent]; |
| 567 |
| 568 // [self.contentView sendSubviewToBack:primaryView]; |
| 569 [self refreshFrameOfPrimaryView]; |
| 570 |
| 571 self.navigationBar.hidden = NO; |
| 572 [self updateNavigationBarAnimated:animated |
| 573 orientation:GetInterfaceOrientation()]; |
| 574 |
| 575 [self.menuView updatePrimaryMenuItem:self.primaryMenuItem]; |
| 576 [self updateEditBarShadow]; |
| 577 } |
| 578 |
| 579 - (UIView<BookmarkHomePrimaryView>*)primaryView { |
| 580 if (!self.primaryMenuItem) |
| 581 return nil; |
| 582 DCHECK([self contentView]); |
| 583 |
| 584 switch (self.primaryMenuItem.type) { |
| 585 case bookmarks::MenuItemAll: |
| 586 return self.allItemsView; |
| 587 case bookmarks::MenuItemFolder: |
| 588 return self.folderView; |
| 589 case bookmarks::MenuItemDivider: |
| 590 case bookmarks::MenuItemSectionHeader: |
| 591 NOTREACHED(); |
| 592 return nil; |
| 593 } |
| 594 } |
| 595 |
| 596 - (BOOL)shouldPresentMenuInSlideInPanel { |
| 597 return IsCompactTablet(); |
| 598 } |
| 599 |
| 600 - (CGFloat)menuWidth { |
| 601 return kMenuWidth; |
| 602 } |
| 603 |
| 604 - (CGFloat)primaryViewLeadingMargin { |
| 605 if ([self shouldPresentMenuInSlideInPanel]) |
| 606 return 0; |
| 607 return [self menuWidth]; |
| 608 } |
| 609 |
| 610 - (void)moveMenuAndPrimaryViewToAdequateParent { |
| 611 // Remove the menuView, panelView, and primaryView from the view hierarchy. |
| 612 if ([self.menuView superview]) |
| 613 [self.menuView removeFromSuperview]; |
| 614 if ([self.panelView superview]) |
| 615 [self.panelView removeFromSuperview]; |
| 616 UIView* primaryView = [self primaryView]; |
| 617 if ([primaryView superview]) |
| 618 [primaryView removeFromSuperview]; |
| 619 |
| 620 if ([self shouldPresentMenuInSlideInPanel]) { |
| 621 // Create (if needed), and add the panelView to the view hierarchy. |
| 622 if (!self.panelView) { |
| 623 self.panelView = base::scoped_nsobject<BookmarkPanelView>( |
| 624 [[BookmarkPanelView alloc] initWithFrame:CGRectZero |
| 625 menuViewWidth:[self menuWidth]]); |
| 626 } |
| 627 [self.view addSubview:self.panelView]; |
| 628 CGSize size = self.contentView.bounds.size; |
| 629 CGFloat navBarHeight = CGRectGetHeight([self navigationBarFrame]); |
| 630 LayoutRect panelLayout = LayoutRectMake( |
| 631 0, size.width, navBarHeight, size.width, size.height - navBarHeight); |
| 632 |
| 633 // Initialize the panelView with the menuView and the primaryView. |
| 634 [self.panelView setFrame:LayoutRectGetRect(panelLayout)]; |
| 635 [self.panelView.menuView addSubview:self.menuView]; |
| 636 self.menuView.frame = self.panelView.menuView.bounds; |
| 637 [self.panelView.contentView addSubview:primaryView]; |
| 638 } else { |
| 639 [self.view addSubview:self.menuView]; |
| 640 [self.view addSubview:primaryView]; |
| 641 } |
| 642 |
| 643 // Make sure the navigation bar is the frontmost subview. |
| 644 [self.view bringSubviewToFront:self.navigationBar]; |
| 645 } |
| 646 |
| 647 - (void)refreshFrameOfPrimaryView { |
| 648 [self primaryView].frame = [self frameForPrimaryView]; |
| 649 } |
| 650 |
| 651 - (CGRect)frameForPrimaryView { |
| 652 CGFloat topInset = 0; |
| 653 if (!IsCompactTablet()) |
| 654 topInset = CGRectGetHeight([self navigationBarFrame]); |
| 655 |
| 656 CGFloat leadingMargin = [self primaryViewLeadingMargin]; |
| 657 CGSize size = self.view.bounds.size; |
| 658 LayoutRect primaryViewLayout = |
| 659 LayoutRectMake(leadingMargin, size.width, topInset, |
| 660 size.width - leadingMargin, size.height - topInset); |
| 661 return LayoutRectGetRect(primaryViewLayout); |
| 662 } |
| 663 |
| 664 #pragma mark - Editing bar methods. |
| 665 |
| 666 - (CGRect)editingBarFrame { |
| 667 return CGRectInset(self.navigationBar.frame, 24.0, 0); |
| 668 } |
| 669 |
| 670 - (void)updateEditingStateAnimated:(BOOL)animated { |
| 671 if (!self.editing) { |
| 672 [self hideEditingBarAnimated:animated]; |
| 673 [self updateEditBarShadow]; |
| 674 return; |
| 675 } |
| 676 |
| 677 if (!self.editingBar) { |
| 678 self.editingBar = base::scoped_nsobject<BookmarkEditingBar>( |
| 679 [[BookmarkEditingBar alloc] initWithFrame:[self editingBarFrame]]); |
| 680 [self.editingBar setCancelTarget:self action:@selector(editingBarCancel)]; |
| 681 [self.editingBar setDeleteTarget:self action:@selector(editingBarDelete)]; |
| 682 [self.editingBar setMoveTarget:self action:@selector(editingBarMove)]; |
| 683 [self.editingBar setEditTarget:self action:@selector(editingBarEdit)]; |
| 684 |
| 685 [self.view addSubview:self.editingBar]; |
| 686 self.editingBar.hidden = YES; |
| 687 } |
| 688 |
| 689 int bookmarkCount = 0; |
| 690 int folderCount = 0; |
| 691 for (auto node : _editNodes) { |
| 692 if (node->is_url()) |
| 693 ++bookmarkCount; |
| 694 else |
| 695 ++folderCount; |
| 696 } |
| 697 [self.editingBar updateUIWithBookmarkCount:bookmarkCount |
| 698 folderCount:folderCount]; |
| 699 |
| 700 [self showEditingBarAnimated:animated]; |
| 701 [self updateEditBarShadow]; |
| 702 } |
| 703 |
| 704 - (void)setEditing:(BOOL)editing animated:(BOOL)animated { |
| 705 if (_editing == editing) |
| 706 return; |
| 707 |
| 708 _editing = editing; |
| 709 |
| 710 if (editing) { |
| 711 self.bookmarkPromoController.promoState = NO; |
| 712 } else { |
| 713 // Only reset the editing state when leaving edit mode. This allows |
| 714 // subclasses to add nodes for editing before entering edit mode. |
| 715 [self resetEditNodes]; |
| 716 [self.bookmarkPromoController updatePromoState]; |
| 717 } |
| 718 |
| 719 [self updateEditingStateAnimated:animated]; |
| 720 if ([[self primaryMenuItem] supportsEditing]) |
| 721 [[self primaryView] setEditing:editing animated:animated]; |
| 722 } |
| 723 |
| 724 - (void)showEditingBarAnimated:(BOOL)animated { |
| 725 CGRect endFrame = [self editingBarFrame]; |
| 726 if (self.editingBar.hidden) { |
| 727 CGRect startFrame = endFrame; |
| 728 startFrame.origin.y = -CGRectGetHeight(startFrame); |
| 729 self.editingBar.frame = startFrame; |
| 730 } |
| 731 self.editingBar.hidden = NO; |
| 732 [UIView animateWithDuration:animated ? 0.2 : 0 |
| 733 delay:0 |
| 734 options:UIViewAnimationOptionBeginFromCurrentState |
| 735 animations:^{ |
| 736 self.editingBar.frame = endFrame; |
| 737 } |
| 738 completion:^(BOOL finished) { |
| 739 if (finished) |
| 740 self.navigationBar.hidden = YES; |
| 741 }]; |
| 742 } |
| 743 |
| 744 - (void)hideEditingBarAnimated:(BOOL)animated { |
| 745 CGRect frame = [self editingBarFrame]; |
| 746 if (!self.editingBar.hidden) { |
| 747 frame.origin.y = -CGRectGetHeight(frame); |
| 748 } |
| 749 self.navigationBar.hidden = NO; |
| 750 [UIView animateWithDuration:animated ? 0.2 : 0 |
| 751 delay:0 |
| 752 options:UIViewAnimationOptionBeginFromCurrentState |
| 753 animations:^{ |
| 754 self.editingBar.frame = frame; |
| 755 } |
| 756 completion:^(BOOL finished) { |
| 757 if (finished) |
| 758 self.editingBar.hidden = YES; |
| 759 }]; |
| 760 } |
| 761 |
| 762 - (void)updateEditBarShadow { |
| 763 [self.editingBar showShadow:self.editing]; |
| 764 } |
| 765 |
| 766 #pragma mark Editing Bar Callbacks |
| 767 |
| 768 - (void)editingBarCancel { |
| 769 [self setEditing:NO animated:YES]; |
| 770 } |
| 771 |
| 772 - (void)editingBarMove { |
| 773 [self moveNodes:_editNodes]; |
| 774 } |
| 775 |
| 776 - (void)editingBarDelete { |
| 777 [self deleteSelectedNodes]; |
| 778 [self setEditing:NO animated:YES]; |
| 779 } |
| 780 |
| 781 - (void)editingBarEdit { |
| 782 DCHECK_EQ(_editNodes.size(), 1u); |
| 783 const BookmarkNode* node = *(_editNodes.begin()); |
| 784 BookmarkItemCell* itemCell = nil; |
| 785 if (self.editIndexPaths.count > 0) { |
| 786 NSIndexPath* indexPath = [self.editIndexPaths firstObject]; |
| 787 if (indexPath && ![indexPath isKindOfClass:[NSNull class]]) { |
| 788 UICollectionViewCell* cell = |
| 789 [[self primaryView].collectionView cellForItemAtIndexPath:indexPath]; |
| 790 if ([cell isKindOfClass:[BookmarkItemCell class]]) { |
| 791 itemCell = static_cast<BookmarkItemCell*>(cell); |
| 792 } |
| 793 } |
| 794 } |
| 795 [self editNode:node withCell:itemCell]; |
| 796 } |
| 797 |
| 798 - (void)toggleMenuAnimated { |
| 799 if ([self.panelView userDrivenAnimationInProgress]) |
| 800 return; |
| 801 |
| 802 if (self.panelView.showingMenu) { |
| 803 [self.panelView hideMenuAnimated:YES]; |
| 804 } else { |
| 805 [self.panelView showMenuAnimated:YES]; |
| 806 } |
| 807 } |
| 808 |
| 809 #pragma mark - BookmarkMenuViewDelegate |
| 810 |
| 811 - (void)bookmarkMenuView:(BookmarkMenuView*)view |
| 812 selectedMenuItem:(BookmarkMenuItem*)menuItem { |
| 813 BOOL menuItemChanged = ![[self primaryMenuItem] isEqual:menuItem]; |
| 814 [self toggleMenuAnimated]; |
| 815 if (menuItemChanged) { |
| 816 [self setEditing:NO animated:YES]; |
| 817 [self updatePrimaryMenuItem:menuItem animated:YES]; |
| 818 } |
| 819 } |
| 820 |
| 821 #pragma mark - BookmarkCollectionViewDelegate |
| 822 // This class owns multiple views that have a delegate that conforms to |
| 823 // BookmarkCollectionViewDelegate, or a subprotocol of |
| 824 // BookmarkCollectionViewDelegate. |
| 825 - (void)bookmarkCollectionView:(BookmarkCollectionView*)view |
| 826 cell:(UICollectionViewCell*)cell |
| 827 addNodeForEditing:(const BookmarkNode*)node { |
| 828 [self insertEditNode:node atIndexPath:[self indexPathForCell:cell]]; |
| 829 [self updateEditingStateAnimated:YES]; |
| 830 } |
| 831 |
| 832 - (void)bookmarkCollectionView:(BookmarkCollectionView*)view |
| 833 cell:(UICollectionViewCell*)cell |
| 834 removeNodeForEditing:(const BookmarkNode*)node { |
| 835 [self removeEditNode:node atIndexPath:[self indexPathForCell:cell]]; |
| 836 } |
| 837 |
| 838 - (const std::set<const BookmarkNode*>&)nodesBeingEdited { |
| 839 return _editNodes; |
| 840 } |
| 841 |
| 842 - (void)bookmarkCollectionViewDidScroll:(BookmarkCollectionView*)view { |
| 843 [self updateEditBarShadow]; |
| 844 } |
| 845 |
| 846 - (void)bookmarkCollectionView:(BookmarkCollectionView*)view |
| 847 selectedUrlForNavigation:(const GURL&)url { |
| 848 [self cachePosition]; |
| 849 // Before passing the URL to the block, make sure the block has a copy of the |
| 850 // URL and not just a reference. |
| 851 const GURL localUrl(url); |
| 852 dispatch_async(dispatch_get_main_queue(), ^{ |
| 853 [self loadURL:localUrl]; |
| 854 }); |
| 855 } |
| 856 |
| 857 - (void)bookmarkCollectionView:(BookmarkCollectionView*)collectionView |
| 858 wantsMenuForBookmark:(const BookmarkNode*)node |
| 859 onView:(UIView*)view |
| 860 forCell:(BookmarkItemCell*)cell { |
| 861 DCHECK(!self.editViewController); |
| 862 DCHECK(!self.actionSheetCoordinator); |
| 863 self.actionSheetCoordinator = [[[ActionSheetCoordinator alloc] |
| 864 initWithBaseViewController:self.view.window.rootViewController |
| 865 title:nil |
| 866 message:nil |
| 867 rect:view.bounds |
| 868 view:view] autorelease]; |
| 869 base::WeakNSObject<BookmarkHomeTabletNTPController> weakSelf(self); |
| 870 |
| 871 // Select action. |
| 872 [self.actionSheetCoordinator |
| 873 addItemWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_ACTION_SELECT) |
| 874 action:^{ |
| 875 [weakSelf selectFirstNode:node withCell:cell]; |
| 876 weakSelf.get().actionSheetCoordinator = nil; |
| 877 } |
| 878 style:UIAlertActionStyleDefault]; |
| 879 |
| 880 // Edit action. |
| 881 [self.actionSheetCoordinator |
| 882 addItemWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_ACTION_EDIT) |
| 883 action:^{ |
| 884 BookmarkItemCell* itemCell = nil; |
| 885 if ([cell isKindOfClass:[BookmarkItemCell class]]) |
| 886 itemCell = static_cast<BookmarkItemCell*>(cell); |
| 887 [weakSelf editNode:node withCell:itemCell]; |
| 888 weakSelf.get().actionSheetCoordinator = nil; |
| 889 } |
| 890 style:UIAlertActionStyleDefault]; |
| 891 |
| 892 // Move action. |
| 893 [self.actionSheetCoordinator |
| 894 addItemWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_ACTION_MOVE) |
| 895 action:^{ |
| 896 std::set<const BookmarkNode*> nodes; |
| 897 nodes.insert(node); |
| 898 [weakSelf moveNodes:nodes]; |
| 899 weakSelf.get().actionSheetCoordinator = nil; |
| 900 } |
| 901 style:UIAlertActionStyleDefault]; |
| 902 |
| 903 // Delete action. |
| 904 [self.actionSheetCoordinator |
| 905 addItemWithTitle:l10n_util::GetNSString(IDS_IOS_BOOKMARK_ACTION_DELETE) |
| 906 action:^{ |
| 907 std::set<const BookmarkNode*> nodes; |
| 908 nodes.insert(node); |
| 909 [weakSelf deleteNodes:nodes]; |
| 910 weakSelf.get().actionSheetCoordinator = nil; |
| 911 } |
| 912 style:UIAlertActionStyleDestructive]; |
| 913 |
| 914 // Cancel action. |
| 915 [self.actionSheetCoordinator |
| 916 addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL) |
| 917 action:^{ |
| 918 weakSelf.get().actionSheetCoordinator = nil; |
| 919 } |
| 920 style:UIAlertActionStyleCancel]; |
| 921 |
| 922 [self.actionSheetCoordinator start]; |
| 923 } |
| 924 |
| 925 - (void)bookmarkCollectionView:(BookmarkCollectionView*)view |
| 926 didLongPressCell:(UICollectionViewCell*)cell |
| 927 forBookmark:(const BookmarkNode*)node { |
| 928 DCHECK(!self.editing); |
| 929 [self selectFirstNode:node withCell:cell]; |
| 930 } |
| 931 |
| 932 - (BOOL)bookmarkCollectionViewShouldShowPromoCell: |
| 933 (BookmarkCollectionView*)collectionView { |
| 934 return self.bookmarkPromoController.promoState; |
| 935 } |
| 936 |
| 937 - (void)bookmarkCollectionViewShowSignIn:(BookmarkCollectionView*)view { |
| 938 [self.bookmarkPromoController showSignIn]; |
| 939 } |
| 940 |
| 941 - (void)bookmarkCollectionViewDismissPromo:(BookmarkCollectionView*)view { |
| 942 [self.bookmarkPromoController hidePromoCell]; |
| 943 } |
| 944 |
| 945 #pragma mark Action Sheet Callbacks |
| 946 |
| 947 - (void)selectFirstNode:(const BookmarkNode*)node |
| 948 withCell:(UICollectionViewCell*)cell { |
| 949 DCHECK(!self.editing); |
| 950 [self insertEditNode:node atIndexPath:[self indexPathForCell:cell]]; |
| 951 [self setEditing:YES animated:YES]; |
| 952 } |
| 953 |
| 954 - (void)editNode:(const BookmarkNode*)node withCell:(BookmarkItemCell*)cell { |
| 955 DCHECK(!self.editViewController); |
| 956 DCHECK(!self.folderEditor); |
| 957 UIViewController* editorController = nil; |
| 958 if (node->is_folder()) { |
| 959 BookmarkFolderEditorViewController* folderEditor = |
| 960 [BookmarkFolderEditorViewController |
| 961 folderEditorWithBookmarkModel:self.bookmarks |
| 962 folder:node |
| 963 browserState:self.browserState]; |
| 964 folderEditor.delegate = self; |
| 965 self.folderEditor = folderEditor; |
| 966 editorController = folderEditor; |
| 967 } else { |
| 968 base::scoped_nsobject<BookmarkEditViewController> controller([ |
| 969 [BookmarkEditViewController alloc] initWithBookmark:node |
| 970 browserState:self.browserState]); |
| 971 self.editViewController = controller; |
| 972 self.editViewController.delegate = self; |
| 973 editorController = self.editViewController; |
| 974 } |
| 975 DCHECK(editorController); |
| 976 base::scoped_nsobject<UINavigationController> navController( |
| 977 [[BookmarkNavigationController alloc] |
| 978 initWithRootViewController:editorController]); |
| 979 navController.get().modalPresentationStyle = UIModalPresentationFormSheet; |
| 980 [self.view.window.rootViewController presentViewController:navController |
| 981 animated:YES |
| 982 completion:NULL]; |
| 983 } |
| 984 |
| 985 - (void)moveNodes:(const std::set<const BookmarkNode*>&)nodes { |
| 986 DCHECK(!self.folderSelector); |
| 987 DCHECK(nodes.size() > 0); |
| 988 const BookmarkNode* editedNode = *(nodes.begin()); |
| 989 const BookmarkNode* selectedFolder = editedNode->parent(); |
| 990 self.folderSelector = base::scoped_nsobject<BookmarkFolderViewController>( |
| 991 [[BookmarkFolderViewController alloc] |
| 992 initWithBookmarkModel:self.bookmarks |
| 993 allowsNewFolders:YES |
| 994 editedNodes:nodes |
| 995 allowsCancel:YES |
| 996 selectedFolder:selectedFolder]); |
| 997 self.folderSelector.delegate = self; |
| 998 base::scoped_nsobject<UINavigationController> controller( |
| 999 [[BookmarkNavigationController alloc] |
| 1000 initWithRootViewController:self.folderSelector]); |
| 1001 controller.get().modalPresentationStyle = UIModalPresentationFormSheet; |
| 1002 [self.view.window.rootViewController presentViewController:controller |
| 1003 animated:YES |
| 1004 completion:NULL]; |
| 1005 } |
| 1006 |
| 1007 - (void)deleteNodes:(const std::set<const BookmarkNode*>&)nodes { |
| 1008 DCHECK_GE(nodes.size(), 1u); |
| 1009 bookmark_utils_ios::DeleteBookmarksWithUndoToast(nodes, self.bookmarks, |
| 1010 self.browserState); |
| 1011 } |
| 1012 |
| 1013 #pragma mark - BookmarkFolderCollectionViewDelegate |
| 1014 |
| 1015 - (void)bookmarkFolderCollectionView:(BookmarkFolderCollectionView*)view |
| 1016 selectedFolderForNavigation:(const BookmarkNode*)folder { |
| 1017 BookmarkMenuItem* menuItem = nil; |
| 1018 if (view == self.folderView) { |
| 1019 const BookmarkNode* parent = RootLevelFolderForNode(folder, self.bookmarks); |
| 1020 menuItem = |
| 1021 [BookmarkMenuItem folderMenuItemForNode:folder rootAncestor:parent]; |
| 1022 } else { |
| 1023 NOTREACHED(); |
| 1024 return; |
| 1025 } |
| 1026 [self updatePrimaryMenuItem:menuItem animated:YES]; |
| 1027 } |
| 1028 |
| 1029 #pragma mark - BookmarkEditViewControllerDelegate |
| 1030 |
| 1031 - (BOOL)bookmarkEditor:(BookmarkEditViewController*)controller |
| 1032 shoudDeleteAllOccurencesOfBookmark:(const BookmarkNode*)bookmark { |
| 1033 return NO; |
| 1034 } |
| 1035 |
| 1036 - (void)bookmarkEditorWantsDismissal:(BookmarkEditViewController*)controller { |
| 1037 self.editViewController.delegate = nil; |
| 1038 self.editViewController = nil; |
| 1039 [controller dismissViewControllerAnimated:YES completion:NULL]; |
| 1040 |
| 1041 // The editViewController can be displayed from the menu button, or from the |
| 1042 // edit button in edit mode. Either way, after it's dismissed, edit mode |
| 1043 // should be off. |
| 1044 [self setEditing:NO animated:NO]; |
| 1045 } |
| 1046 |
| 1047 #pragma mark - BookmarkFolderViewControllerDelegate |
| 1048 |
| 1049 - (void)folderPicker:(BookmarkFolderViewController*)folderPicker |
| 1050 didFinishWithFolder:(const BookmarkNode*)folder { |
| 1051 DCHECK(folder); |
| 1052 DCHECK(!folder->is_url()); |
| 1053 DCHECK_GE(folderPicker.editedNodes.size(), 1u); |
| 1054 |
| 1055 bookmark_utils_ios::MoveBookmarksWithUndoToast( |
| 1056 folderPicker.editedNodes, self.bookmarks, folder, self.browserState); |
| 1057 |
| 1058 [self setEditing:NO animated:NO]; |
| 1059 [[folderPicker presentingViewController] dismissViewControllerAnimated:YES |
| 1060 completion:nil]; |
| 1061 self.folderSelector.delegate = nil; |
| 1062 self.folderSelector = nil; |
| 1063 } |
| 1064 |
| 1065 - (void)folderPickerDidCancel:(BookmarkFolderViewController*)folderPicker { |
| 1066 [self setEditing:NO animated:NO]; |
| 1067 [[folderPicker presentingViewController] dismissViewControllerAnimated:YES |
| 1068 completion:nil]; |
| 1069 self.folderSelector.delegate = nil; |
| 1070 self.folderSelector = nil; |
| 1071 } |
| 1072 |
| 1073 #pragma mark - BookmarkFolderEditorViewControllerDelegate |
| 1074 |
| 1075 - (void)bookmarkFolderEditor:(BookmarkFolderEditorViewController*)folderEditor |
| 1076 didFinishEditingFolder:(const BookmarkNode*)folder { |
| 1077 DCHECK(folder); |
| 1078 [[folderEditor presentingViewController] dismissViewControllerAnimated:YES |
| 1079 completion:nil]; |
| 1080 self.folderEditor.delegate = nil; |
| 1081 self.folderEditor = nil; |
| 1082 } |
| 1083 |
| 1084 - (void)bookmarkFolderEditorDidDeleteEditedFolder: |
| 1085 (BookmarkFolderEditorViewController*)folderEditor { |
| 1086 [[folderEditor presentingViewController] dismissViewControllerAnimated:YES |
| 1087 completion:nil]; |
| 1088 self.folderEditor.delegate = nil; |
| 1089 self.folderEditor = nil; |
| 1090 } |
| 1091 |
| 1092 - (void)bookmarkFolderEditorDidCancel: |
| 1093 (BookmarkFolderEditorViewController*)folderEditor { |
| 1094 [[folderEditor presentingViewController] dismissViewControllerAnimated:YES |
| 1095 completion:nil]; |
| 1096 self.folderEditor.delegate = nil; |
| 1097 self.folderEditor = nil; |
| 1098 } |
| 1099 |
| 1100 #pragma mark - Internal Utility Methods |
| 1101 |
| 1102 - (void)deleteSelectedNodes { |
| 1103 [self deleteNodes:_editNodes]; |
| 1104 } |
| 1105 |
| 1106 - (void)moveEditingNodesToFolder:(const BookmarkNode*)folder { |
| 1107 // The UI only supports moving nodes when there are at least one selected. |
| 1108 DCHECK_GE(_editNodes.size(), 1u); |
| 1109 |
| 1110 bookmark_utils_ios::MoveBookmarksWithUndoToast(_editNodes, self.bookmarks, |
| 1111 folder, self.browserState); |
| 1112 } |
| 1113 |
| 1114 - (void)cachePosition { |
| 1115 if ([self primaryView]) { |
| 1116 bookmark_utils_ios::CachePosition( |
| 1117 [[self primaryView] contentPositionInPortraitOrientation], |
| 1118 [self primaryMenuItem]); |
| 1119 } |
| 1120 } |
| 1121 |
| 1122 #pragma mark - Navigation bar |
| 1123 |
| 1124 - (CGRect)navigationBarFrame { |
| 1125 return CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), |
| 1126 [BookmarkNavigationBar expectedContentViewHeight] + |
| 1127 kNavigationBarTopMargin); |
| 1128 } |
| 1129 |
| 1130 - (void)updateNavigationBarAnimated:(BOOL)animated |
| 1131 orientation:(UIInterfaceOrientation)orientation { |
| 1132 CGFloat duration = animated ? bookmark_utils_ios::menuAnimationDuration : 0; |
| 1133 [self updateNavigationBarWithDuration:duration orientation:orientation]; |
| 1134 } |
| 1135 |
| 1136 - (void)updateNavigationBarWithDuration:(CGFloat)duration |
| 1137 orientation:(UIInterfaceOrientation)orientation { |
| 1138 [self.navigationBar setTitle:[self.primaryMenuItem titleForNavigationBar]]; |
| 1139 if ([self shouldShowEditButton]) |
| 1140 [self.navigationBar showEditButtonWithAnimationDuration:duration]; |
| 1141 else |
| 1142 [self.navigationBar hideEditButtonWithAnimationDuration:duration]; |
| 1143 |
| 1144 if ([self shouldShowBackButton]) |
| 1145 [self.navigationBar showBackButtonInsteadOfMenuButton:duration]; |
| 1146 else |
| 1147 [self.navigationBar showMenuButtonInsteadOfBackButton:duration]; |
| 1148 } |
| 1149 |
| 1150 - (BOOL)shouldShowEditButton { |
| 1151 if (self.primaryMenuItem.type != bookmarks::MenuItemFolder) |
| 1152 return NO; |
| 1153 // The type is MenuItemFolder, so it is safe to access |folder|. |
| 1154 return !self.bookmarks->is_permanent_node(self.primaryMenuItem.folder); |
| 1155 } |
| 1156 |
| 1157 - (BOOL)shouldShowBackButton { |
| 1158 if (self.primaryMenuItem.type != bookmarks::MenuItemFolder) |
| 1159 return NO; |
| 1160 // The type is MenuItemFolder, so it is safe to access |folder|. |
| 1161 const BookmarkNode* folder = self.primaryMenuItem.folder; |
| 1162 // Show the back button iff the folder or its immediate parent is a permanent |
| 1163 // primary folder. |
| 1164 BOOL isTopFolder = IsPrimaryPermanentNode(folder, self.bookmarks) || |
| 1165 IsPrimaryPermanentNode(folder->parent(), self.bookmarks); |
| 1166 return !isTopFolder; |
| 1167 } |
| 1168 |
| 1169 #pragma mark Navigation Bar Callbacks |
| 1170 |
| 1171 - (void)navigationBarWantsEditing:(id)sender { |
| 1172 DCHECK(self.primaryMenuItem.type == bookmarks::MenuItemFolder); |
| 1173 const BookmarkNode* folder = self.primaryMenuItem.folder; |
| 1174 BookmarkFolderEditorViewController* folderEditor = |
| 1175 [BookmarkFolderEditorViewController |
| 1176 folderEditorWithBookmarkModel:self.bookmarks |
| 1177 folder:folder |
| 1178 browserState:self.browserState]; |
| 1179 folderEditor.delegate = self; |
| 1180 self.folderEditor = folderEditor; |
| 1181 |
| 1182 base::scoped_nsobject<BookmarkNavigationController> navController( |
| 1183 [[BookmarkNavigationController alloc] |
| 1184 initWithRootViewController:self.folderEditor]); |
| 1185 navController.get().modalPresentationStyle = UIModalPresentationFormSheet; |
| 1186 [self.view.window.rootViewController presentViewController:navController |
| 1187 animated:YES |
| 1188 completion:NULL]; |
| 1189 } |
| 1190 |
| 1191 - (void)navigationBarBack:(id)sender { |
| 1192 DCHECK([self shouldShowBackButton]); |
| 1193 |
| 1194 // Go to the parent folder. |
| 1195 DCHECK(self.primaryMenuItem.type == bookmarks::MenuItemFolder); |
| 1196 const BookmarkNode* parentFolder = self.primaryMenuItem.folder->parent(); |
| 1197 const BookmarkNode* rootAncestor = |
| 1198 RootLevelFolderForNode(parentFolder, self.bookmarks); |
| 1199 BookmarkMenuItem* menuItem = |
| 1200 [BookmarkMenuItem folderMenuItemForNode:parentFolder |
| 1201 rootAncestor:rootAncestor]; |
| 1202 [self updatePrimaryMenuItem:menuItem animated:YES]; |
| 1203 } |
| 1204 |
| 1205 #pragma mark - NewTabPagePanelProtocol |
| 1206 |
| 1207 - (void)reload { |
| 1208 } |
| 1209 |
| 1210 - (void)wasShown { |
| 1211 } |
| 1212 |
| 1213 - (void)wasHidden { |
| 1214 [self cachePosition]; |
| 1215 } |
| 1216 |
| 1217 - (void)dismissModals { |
| 1218 [self.actionSheetCoordinator stop]; |
| 1219 self.actionSheetCoordinator = nil; |
| 1220 [self.editViewController dismiss]; |
| 1221 } |
| 1222 |
| 1223 - (void)dismissKeyboard { |
| 1224 // Uses self.contentView directly instead of going throught self.view to |
| 1225 // avoid creating the view hierarchy unnecessarily. |
| 1226 [self.contentView endEditing:YES]; |
| 1227 } |
| 1228 |
| 1229 - (void)setScrollsToTop:(BOOL)enabled { |
| 1230 self.scrollToTop = enabled; |
| 1231 [[self primaryView] setScrollsToTop:self.scrollToTop]; |
| 1232 } |
| 1233 |
| 1234 - (UIView*)view { |
| 1235 if (!self.contentView) { |
| 1236 _contentView = [[ContentView alloc] initWithFrame:CGRectZero]; |
| 1237 _contentView.delegate = self; |
| 1238 self.contentView.backgroundColor = |
| 1239 bookmark_utils_ios::mainBackgroundColor(); |
| 1240 base::scoped_nsobject<BookmarkNavigationBar> bar( |
| 1241 [[BookmarkNavigationBar alloc] initWithFrame:CGRectZero]); |
| 1242 self.navigationBar = bar; |
| 1243 self.navigationBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; |
| 1244 |
| 1245 [self.navigationBar setEditTarget:self |
| 1246 action:@selector(navigationBarWantsEditing:)]; |
| 1247 [self.navigationBar setBackTarget:self |
| 1248 action:@selector(navigationBarBack:)]; |
| 1249 |
| 1250 [self.navigationBar setMenuTarget:self |
| 1251 action:@selector(toggleMenuAnimated)]; |
| 1252 |
| 1253 [self.view addSubview:self.navigationBar]; |
| 1254 |
| 1255 if (self.bookmarks->loaded()) |
| 1256 [self loadBookmarkViews]; |
| 1257 else |
| 1258 [self loadWaitingView]; |
| 1259 } |
| 1260 return self.contentView; |
| 1261 } |
| 1262 |
| 1263 - (CGFloat)alphaForBottomShadow { |
| 1264 return 0; |
| 1265 } |
| 1266 |
| 1267 #pragma mark - BookmarkModelBridgeObserver |
| 1268 |
| 1269 - (void)bookmarkModelLoaded { |
| 1270 if (!self.contentView) |
| 1271 return; |
| 1272 |
| 1273 DCHECK(self.waitForModelView); |
| 1274 base::WeakNSObject<BookmarkHomeTabletNTPController> weakSelf(self); |
| 1275 [self.waitForModelView stopWaitingWithCompletion:^{ |
| 1276 base::scoped_nsobject<BookmarkHomeTabletNTPController> strongSelf( |
| 1277 [weakSelf retain]); |
| 1278 // Early return if the controller has been deallocated. |
| 1279 if (!strongSelf) |
| 1280 return; |
| 1281 [UIView animateWithDuration:0.2 |
| 1282 animations:^{ |
| 1283 strongSelf.get().waitForModelView.alpha = 0.0; |
| 1284 } |
| 1285 completion:^(BOOL finished) { |
| 1286 [strongSelf.get().waitForModelView removeFromSuperview]; |
| 1287 strongSelf.get().waitForModelView = nil; |
| 1288 }]; |
| 1289 [strongSelf loadBookmarkViews]; |
| 1290 }]; |
| 1291 } |
| 1292 |
| 1293 - (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode { |
| 1294 // The title of the folder may have changed. |
| 1295 if (self.primaryMenuItem.type == bookmarks::MenuItemFolder && |
| 1296 self.primaryMenuItem.folder == bookmarkNode) { |
| 1297 UIInterfaceOrientation orient = GetInterfaceOrientation(); |
| 1298 [self updateNavigationBarAnimated:NO orientation:orient]; |
| 1299 } |
| 1300 } |
| 1301 |
| 1302 - (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode { |
| 1303 // The node has not changed, but the ordering and existence of its children |
| 1304 // have changed. |
| 1305 } |
| 1306 |
| 1307 - (void)bookmarkNode:(const BookmarkNode*)bookmarkNode |
| 1308 movedFromParent:(const BookmarkNode*)oldParent |
| 1309 toParent:(const BookmarkNode*)newParent { |
| 1310 // The node has moved to a new parent folder. |
| 1311 } |
| 1312 |
| 1313 - (void)bookmarkNodeDeleted:(const BookmarkNode*)node |
| 1314 fromFolder:(const BookmarkNode*)folder { |
| 1315 [self removeEditNode:node atIndexPath:nil]; |
| 1316 } |
| 1317 |
| 1318 - (void)bookmarkModelRemovedAllNodes { |
| 1319 // All non-permanent nodes have been removed. |
| 1320 [self setEditing:NO animated:YES]; |
| 1321 } |
| 1322 |
| 1323 #pragma mark - BookmarkPromoControllerDelegate |
| 1324 |
| 1325 - (void)promoStateChanged:(BOOL)promoEnabled { |
| 1326 [self.allItemsView.collectionView reloadData]; |
| 1327 // This is required to workaround a crash seen once on iOS 7.1 when |
| 1328 // the collection gets two reloadData without getting a call to layout |
| 1329 // subviews, the collection view will reuse some cached data for the perfoming |
| 1330 // the layout which are invalid after the second call to reloadData. |
| 1331 // Forcing the layout invalidation after each reloadData seems to fix the |
| 1332 // issue. |
| 1333 [self.allItemsView.collectionView.collectionViewLayout invalidateLayout]; |
| 1334 [self.folderView |
| 1335 promoStateChangedAnimated:self.folderView == [self primaryView]]; |
| 1336 } |
| 1337 |
| 1338 - (NSIndexPath*)indexPathForCell:(UICollectionViewCell*)cell { |
| 1339 DCHECK([self primaryView].collectionView); |
| 1340 NSIndexPath* indexPath = |
| 1341 [[self primaryView].collectionView indexPathForCell:cell]; |
| 1342 return indexPath; |
| 1343 } |
| 1344 |
| 1345 @end |
OLD | NEW |