| // Copyright 2013 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_interaction_controller.h" |
| |
| #include <stdint.h> |
| |
| #include "base/logging.h" |
| #include "base/mac/bind_objc_block.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/browser/bookmark_utils.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #import "ios/chrome/browser/metrics/new_tab_page_uma.h" |
| #include "ios/chrome/browser/pref_names.h" |
| #import "ios/chrome/browser/tabs/tab.h" |
| #import "ios/chrome/browser/ui/bookmarks/bookmark_controller_factory.h" |
| #import "ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.h" |
| #import "ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.h" |
| #import "ios/chrome/browser/ui/bookmarks/bookmark_navigation_controller.h" |
| #import "ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h" |
| #include "ios/chrome/browser/ui/uikit_ui_util.h" |
| #include "ios/chrome/browser/ui/url_loader.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h" |
| #include "ios/web/public/referrer.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using bookmarks::BookmarkModel; |
| using bookmarks::BookmarkNode; |
| |
| namespace { |
| const int64_t kLastUsedFolderNone = -1; |
| } // namespace |
| |
| @interface BookmarkInteractionController ()< |
| BookmarkEditViewControllerDelegate, |
| BookmarkHomeViewControllerDelegate> { |
| // The browser state of the current user. |
| ios::ChromeBrowserState* _currentBrowserState; // weak |
| |
| // The browser state to use, might be different from _currentBrowserState if |
| // it is incognito. |
| ios::ChromeBrowserState* _browserState; // weak |
| |
| // The designated url loader. |
| __weak id<UrlLoader> _loader; |
| |
| // The parent controller on top of which the UI needs to be presented. |
| __weak UIViewController* _parentController; |
| } |
| |
| // The bookmark model in use. |
| @property(nonatomic, assign) BookmarkModel* bookmarkModel; |
| |
| // A reference to the potentially presented bookmark browser. |
| @property(nonatomic, strong) BookmarkHomeViewController* bookmarkBrowser; |
| |
| // A reference to the potentially presented single bookmark editor. |
| @property(nonatomic, strong) BookmarkEditViewController* bookmarkEditor; |
| |
| // The user wants to bookmark the current tab. |
| - (void)addBookmarkForTab:(Tab*)tab; |
| |
| // Builds a controller and brings it on screen. |
| - (void)presentBookmarkForTab:(Tab*)tab; |
| |
| // Dismisses the bookmark browser. |
| - (void)dismissBookmarkBrowserAnimated:(BOOL)animated; |
| |
| // Dismisses the bookmark editor. |
| - (void)dismissBookmarkEditorAnimated:(BOOL)animated; |
| |
| @end |
| |
| @implementation BookmarkInteractionController |
| @synthesize bookmarkBrowser = _bookmarkBrowser; |
| @synthesize bookmarkEditor = _bookmarkEditor; |
| @synthesize bookmarkModel = _bookmarkModel; |
| |
| + (void)registerBrowserStatePrefs:(user_prefs::PrefRegistrySyncable*)registry { |
| registry->RegisterInt64Pref(prefs::kIosBookmarkFolderDefault, |
| kLastUsedFolderNone); |
| } |
| |
| + (const BookmarkNode*)folderForNewBookmarksInBrowserState: |
| (ios::ChromeBrowserState*)browserState { |
| bookmarks::BookmarkModel* bookmarks = |
| ios::BookmarkModelFactory::GetForBrowserState(browserState); |
| const BookmarkNode* defaultFolder = bookmarks->mobile_node(); |
| |
| PrefService* prefs = browserState->GetPrefs(); |
| int64_t node_id = prefs->GetInt64(prefs::kIosBookmarkFolderDefault); |
| if (node_id == kLastUsedFolderNone) |
| node_id = defaultFolder->id(); |
| const BookmarkNode* result = |
| bookmarks::GetBookmarkNodeByID(bookmarks, node_id); |
| |
| if (result) |
| return result; |
| |
| return defaultFolder; |
| } |
| |
| + (void)setFolderForNewBookmarks:(const BookmarkNode*)folder |
| inBrowserState:(ios::ChromeBrowserState*)browserState { |
| DCHECK(folder && folder->is_folder()); |
| browserState->GetPrefs()->SetInt64(prefs::kIosBookmarkFolderDefault, |
| folder->id()); |
| } |
| |
| - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
| loader:(id<UrlLoader>)loader |
| parentController:(UIViewController*)parentController { |
| self = [super init]; |
| if (self) { |
| // Bookmarks are always opened with the main browser state, even in |
| // incognito mode. |
| _currentBrowserState = browserState; |
| _browserState = browserState->GetOriginalChromeBrowserState(); |
| _loader = loader; |
| _parentController = parentController; |
| _bookmarkModel = |
| ios::BookmarkModelFactory::GetForBrowserState(_browserState); |
| DCHECK(_bookmarkModel); |
| DCHECK(_parentController); |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| _bookmarkBrowser.delegate = nil; |
| _bookmarkEditor.delegate = nil; |
| } |
| |
| - (void)addBookmarkForTab:(Tab*)tab { |
| base::RecordAction(base::UserMetricsAction("BookmarkAdded")); |
| const BookmarkNode* defaultFolder = |
| [[self class] folderForNewBookmarksInBrowserState:_browserState]; |
| self.bookmarkModel->AddURL(defaultFolder, defaultFolder->child_count(), |
| base::SysNSStringToUTF16(tab.title), tab.url); |
| |
| MDCSnackbarMessageAction* action = [[MDCSnackbarMessageAction alloc] init]; |
| __weak BookmarkInteractionController* weakSelf = self; |
| __weak Tab* weakTab = tab; |
| action.handler = ^{ |
| BookmarkInteractionController* strongSelf = weakSelf; |
| if (!strongSelf || !weakTab) |
| return; |
| [strongSelf presentBookmarkForTab:weakTab]; |
| }; |
| action.title = l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_EDIT_BUTTON); |
| action.accessibilityIdentifier = @"Edit"; |
| |
| NSString* folderTitle = |
| bookmark_utils_ios::TitleForBookmarkNode(defaultFolder); |
| NSString* text = |
| _browserState->GetPrefs()->GetInt64(prefs::kIosBookmarkFolderDefault) != |
| kLastUsedFolderNone |
| ? l10n_util::GetNSStringF(IDS_IOS_BOOKMARK_PAGE_SAVED_FOLDER, |
| base::SysNSStringToUTF16(folderTitle)) |
| : l10n_util::GetNSString(IDS_IOS_BOOKMARK_PAGE_SAVED); |
| TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess); |
| MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text]; |
| message.action = action; |
| message.category = bookmark_utils_ios::kBookmarksSnackbarCategory; |
| [MDCSnackbarManager showMessage:message]; |
| } |
| |
| - (void)presentBookmarkForTab:(Tab*)tab { |
| DCHECK(!self.bookmarkBrowser && !self.bookmarkEditor); |
| DCHECK(tab); |
| |
| const BookmarkNode* bookmark = |
| self.bookmarkModel->GetMostRecentlyAddedUserNodeForURL(tab.url); |
| if (!bookmark) |
| return; |
| |
| [self dismissSnackbar]; |
| |
| BookmarkEditViewController* bookmarkEditor = |
| [[BookmarkEditViewController alloc] initWithBookmark:bookmark |
| browserState:_browserState]; |
| self.bookmarkEditor = bookmarkEditor; |
| self.bookmarkEditor.delegate = self; |
| UINavigationController* navController = [[BookmarkNavigationController alloc] |
| initWithRootViewController:self.bookmarkEditor]; |
| navController.modalPresentationStyle = UIModalPresentationFormSheet; |
| [_parentController presentViewController:navController |
| animated:YES |
| completion:nil]; |
| } |
| |
| - (void)presentBookmarkForTab:(Tab*)tab |
| currentlyBookmarked:(BOOL)bookmarked |
| inView:(UIView*)parentView |
| originRect:(CGRect)origin { |
| if (!self.bookmarkModel->loaded()) |
| return; |
| if (!tab) |
| return; |
| |
| if (bookmarked) |
| [self presentBookmarkForTab:tab]; |
| else |
| [self addBookmarkForTab:tab]; |
| } |
| |
| - (void)presentBookmarks { |
| DCHECK(!self.bookmarkBrowser && !self.bookmarkEditor); |
| BookmarkControllerFactory* bookmarkControllerFactory = |
| [[BookmarkControllerFactory alloc] init]; |
| self.bookmarkBrowser = [bookmarkControllerFactory |
| bookmarkControllerWithBrowserState:_currentBrowserState |
| loader:_loader]; |
| self.bookmarkBrowser.delegate = self; |
| self.bookmarkBrowser.modalPresentationStyle = UIModalPresentationFormSheet; |
| [_parentController presentViewController:self.bookmarkBrowser |
| animated:YES |
| completion:nil]; |
| } |
| |
| - (void)dismissBookmarkBrowserAnimated:(BOOL)animated { |
| if (!self.bookmarkBrowser) |
| return; |
| |
| [self.bookmarkBrowser dismissModals:animated]; |
| [_parentController dismissViewControllerAnimated:animated |
| completion:^{ |
| self.bookmarkBrowser.delegate = nil; |
| self.bookmarkBrowser = nil; |
| }]; |
| } |
| |
| - (void)dismissBookmarkEditorAnimated:(BOOL)animated { |
| if (!self.bookmarkEditor) |
| return; |
| |
| [_parentController dismissViewControllerAnimated:animated |
| completion:^{ |
| self.bookmarkEditor.delegate = nil; |
| self.bookmarkEditor = nil; |
| }]; |
| } |
| |
| - (void)dismissBookmarkModalControllerAnimated:(BOOL)animated { |
| [self dismissBookmarkBrowserAnimated:animated]; |
| [self dismissBookmarkEditorAnimated:animated]; |
| } |
| |
| - (void)dismissSnackbar { |
| // Dismiss any bookmark related snackbar this controller could have presented. |
| [MDCSnackbarManager dismissAndCallCompletionBlocksWithCategory: |
| bookmark_utils_ios::kBookmarksSnackbarCategory]; |
| } |
| |
| #pragma mark - BookmarkEditViewControllerDelegate |
| |
| - (BOOL)bookmarkEditor:(BookmarkEditViewController*)controller |
| shoudDeleteAllOccurencesOfBookmark:(const BookmarkNode*)bookmark { |
| return YES; |
| } |
| |
| - (void)bookmarkEditorWantsDismissal:(BookmarkEditViewController*)controller { |
| [self dismissBookmarkEditorAnimated:YES]; |
| } |
| |
| #pragma mark - BookmarkHomeViewControllerDelegate |
| |
| - (void)bookmarkHomeViewControllerWantsDismissal: |
| (BookmarkHomeViewController*)controller |
| navigationToUrl:(const GURL&)url { |
| [self dismissBookmarkBrowserAnimated:YES]; |
| |
| if (url != GURL()) { |
| new_tab_page_uma::RecordAction(_browserState, |
| new_tab_page_uma::ACTION_OPENED_BOOKMARK); |
| base::RecordAction( |
| base::UserMetricsAction("MobileBookmarkManagerEntryOpened")); |
| |
| if (url.SchemeIs(url::kJavaScriptScheme)) { // bookmarklet |
| NSString* jsToEval = [base::SysUTF8ToNSString(url.GetContent()) |
| stringByRemovingPercentEncoding]; |
| [_loader loadJavaScriptFromLocationBar:jsToEval]; |
| } else { |
| [_loader loadURL:url |
| referrer:web::Referrer() |
| transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK |
| rendererInitiated:NO]; |
| } |
| } |
| } |
| |
| @end |