Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(357)

Unified Diff: ios/chrome/browser/ui/history/history_collection_view_controller.mm

Issue 2590473002: Upstream Chrome on iOS source code [5/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/history/history_collection_view_controller.mm
diff --git a/ios/chrome/browser/ui/history/history_collection_view_controller.mm b/ios/chrome/browser/ui/history/history_collection_view_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..c2c010360832627a6b280f0861faedca1db39484
--- /dev/null
+++ b/ios/chrome/browser/ui/history/history_collection_view_controller.mm
@@ -0,0 +1,791 @@
+// Copyright 2016 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.
+
+#include "ios/chrome/browser/ui/history/history_collection_view_controller.h"
+
+#import <MobileCoreServices/MobileCoreServices.h>
+
+#include <memory>
+
+#import "base/ios/weak_nsobject.h"
+#include "base/mac/foundation_util.h"
+#import "base/mac/objc_property_releaser.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/browsing_data/core/history_notice_utils.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/url_formatter.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/chrome_url_constants.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#include "ios/chrome/browser/signin/authentication_service_factory.h"
+#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
+#import "ios/chrome/browser/ui/collection_view/cells/activity_indicator_cell.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h"
+#import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
+#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
+#include "ios/chrome/browser/ui/history/history_entries_status_item.h"
+#include "ios/chrome/browser/ui/history/history_entry.h"
+#include "ios/chrome/browser/ui/history/history_entry_inserter.h"
+#import "ios/chrome/browser/ui/history/history_entry_item.h"
+#include "ios/chrome/browser/ui/history/history_service_facade.h"
+#include "ios/chrome/browser/ui/history/history_service_facade_delegate.h"
+#include "ios/chrome/browser/ui/history/history_util.h"
+#import "ios/chrome/browser/ui/url_loader.h"
+#include "ios/chrome/grit/ios_strings.h"
+#import "ios/third_party/material_components_ios/src/components/Collections/src/MaterialCollections.h"
+#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
+#import "ios/web/public/referrer.h"
+#import "ios/web/public/web_state/context_menu_params.h"
+#import "net/base/mac/url_conversions.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+namespace {
+// Section identifier for the header (sync information) section.
+const NSInteger kEntriesStatusSectionIdentifier = kSectionIdentifierEnumZero;
+// Maximum number of entries to retrieve in a single query to history service.
+const int kMaxFetchCount = 100;
+// Horizontal inset for item separators.
+const CGFloat kSeparatorInset = 10;
+}
+
+@interface HistoryCollectionViewController ()<HistoryEntriesStatusItemDelegate,
+ HistoryEntryInserterDelegate,
+ HistoryEntryItemDelegate,
+ HistoryServiceFacadeDelegate> {
+ base::mac::ObjCPropertyReleaser
+ _propertyReleaser_HistoryCollectionViewController;
+ // Facade for communicating with HistoryService and WebHistoryService.
+ std::unique_ptr<HistoryServiceFacade> _historyServiceFacade;
+ // The main browser state. Not owned by HistoryCollectionViewController.
+ ios::ChromeBrowserState* _browserState;
+ // Backing ivar for delegate property.
+ base::WeakNSProtocol<id<HistoryCollectionViewControllerDelegate>> _delegate;
+ // Backing ivar for URLLoader property.
+ base::WeakNSProtocol<id<UrlLoader>> _URLLoader;
+}
+
+// Object to manage insertion of history entries into the collection view model.
+@property(nonatomic, retain) HistoryEntryInserter* entryInserter;
+// Delegate for the history collection view.
+@property(nonatomic, assign, readonly)
+ id<HistoryCollectionViewControllerDelegate>
+ delegate;
+// UrlLoader for navigating to history entries.
+@property(nonatomic, assign, readonly) id<UrlLoader> URLLoader;
+// The current query for visible history entries.
+@property(nonatomic, copy) NSString* currentQuery;
+// Coordinator for displaying context menus for history entries.
+@property(nonatomic, assign) ContextMenuCoordinator* contextMenuCoordinator;
+// Type of displayed history entries. Entries can be synced or local, or there
+// may be no history entries.
+@property(nonatomic, assign) HistoryEntriesStatus entriesType;
+// YES if the history panel should show a notice about additional forms of
+// browsing history.
+@property(nonatomic, assign)
+ BOOL shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
+// YES if there is an outstanding history query.
+@property(nonatomic, assign, getter=isLoading) BOOL loading;
+// YES if there are no more history entries to load.
+@property(nonatomic, assign, getter=hasFinishedLoading) BOOL finishedLoading;
+// YES if the collection should be filtered by the next received query result.
+@property(nonatomic, assign) BOOL filterForNextQueryResult;
+
+// Fetches history prior to |time| for search text |query|. If |query| is nil or
+// the empty string, all history is fetched.
+- (void)fetchHistoryForQuery:(NSString*)query
+ priorToTime:(const base::Time&)time;
+// Updates header section to provide relevant information about the currently
+// displayed history entries.
+- (void)updateEntriesStatusMessage;
+// Removes selected items from the visible collection, but does not delete them
+// from browser history.
+- (void)removeSelectedItemsFromCollection;
+// Removes all items in the collection that are not included in entries.
+- (void)filterForHistoryEntries:(NSArray*)entries;
+// Displays context menu on cell pressed with gestureRecognizer.
+- (void)displayContextMenuInvokedByGestureRecognizer:
+ (UILongPressGestureRecognizer*)gestureRecognizer;
+// Opens URL in the current tab and dismisses the history view.
+- (void)openURL:(const GURL&)URL;
+// Opens URL in a new non-incognito tab and dismisses the history view.
+- (void)openURLInNewTab:(const GURL&)URL;
+// Opens URL in a new incognito tab and dismisses the history view.
+- (void)openURLInNewIncognitoTab:(const GURL&)URL;
+// Copies URL to the clipboard.
+- (void)copyURL:(const GURL&)URL;
+@end
+
+@implementation HistoryCollectionViewController
+
+@synthesize searching = _searching;
+@synthesize entryInserter = _entryInserter;
+@synthesize currentQuery = _currentQuery;
+@synthesize contextMenuCoordinator = _contextMenuCoordinator;
+@synthesize entriesType = _entriesType;
+@synthesize shouldShowNoticeAboutOtherFormsOfBrowsingHistory =
+ _shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
+@synthesize loading = _loading;
+@synthesize finishedLoading = _finishedLoading;
+@synthesize filterForNextQueryResult = _filterForNextQueryResult;
+
+- (instancetype)initWithLoader:(id<UrlLoader>)loader
+ browserState:(ios::ChromeBrowserState*)browserState
+ delegate:(id<HistoryCollectionViewControllerDelegate>)
+ delegate {
+ self = [super initWithStyle:CollectionViewControllerStyleDefault];
+ if (self) {
+ _propertyReleaser_HistoryCollectionViewController.Init(
+ self, [HistoryCollectionViewController class]);
+ _historyServiceFacade.reset(new HistoryServiceFacade(browserState, self));
+ _browserState = browserState;
+ _delegate.reset(delegate);
+ _URLLoader.reset(loader);
+ [self loadModel];
+ // Add initial info section as header.
+ [self.collectionViewModel
+ addSectionWithIdentifier:kEntriesStatusSectionIdentifier];
+ _entryInserter =
+ [[HistoryEntryInserter alloc] initWithModel:self.collectionViewModel];
+ _entryInserter.delegate = self;
+ _entriesType = NO_ENTRIES;
+ [self showHistoryMatchingQuery:nil];
+ }
+ return self;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ self.styler.cellLayoutType = MDCCollectionViewCellLayoutTypeList;
+ self.styler.separatorInset =
+ UIEdgeInsetsMake(0, kSeparatorInset, 0, kSeparatorInset);
+ self.styler.allowsItemInlay = NO;
+
+ self.clearsSelectionOnViewWillAppear = NO;
+ self.collectionView.keyboardDismissMode =
+ UIScrollViewKeyboardDismissModeOnDrag;
+
+ base::scoped_nsobject<UILongPressGestureRecognizer> longPressRecognizer([
+ [UILongPressGestureRecognizer alloc]
+ initWithTarget:self
+ action:@selector(displayContextMenuInvokedByGestureRecognizer:)]);
+ [self.collectionView addGestureRecognizer:longPressRecognizer];
+}
+
+- (BOOL)isEditing {
+ return self.editor.isEditing;
+}
+
+- (void)setEditing:(BOOL)editing {
+ [self.editor setEditing:editing animated:YES];
+}
+
+- (void)setSearching:(BOOL)searching {
+ _searching = searching;
+ [self updateEntriesStatusMessage];
+}
+
+- (BOOL)hasHistoryEntries {
+ return self.entriesType != NO_ENTRIES;
+}
+
+- (BOOL)hasSelectedEntries {
+ return self.collectionView.indexPathsForSelectedItems.count;
+}
+
+- (void)showHistoryMatchingQuery:(NSString*)query {
+ self.finishedLoading = NO;
+ self.currentQuery = query;
+ [self fetchHistoryForQuery:query priorToTime:base::Time::Now()];
+}
+
+- (void)deleteSelectedItemsFromHistory {
+ NSArray* deletedIndexPaths = self.collectionView.indexPathsForSelectedItems;
+ std::vector<HistoryServiceFacade::RemovedEntry> entries;
+ for (NSIndexPath* indexPath in deletedIndexPaths) {
+ HistoryEntryItem* object = base::mac::ObjCCastStrict<HistoryEntryItem>(
+ [self.collectionViewModel itemAtIndexPath:indexPath]);
+ entries.push_back(
+ HistoryServiceFacade::RemovedEntry(object.URL, object.timestamp));
+ }
+ _historyServiceFacade->RemoveHistoryEntries(entries);
+ [self removeSelectedItemsFromCollection];
+}
+
+- (id<HistoryCollectionViewControllerDelegate>)delegate {
+ return _delegate;
+}
+
+- (id<UrlLoader>)URLLoader {
+ return _URLLoader;
+}
+
+#pragma mark - MDCollectionViewController
+
+// TODO(crbug.com/653547): Remove this once the MDC adds an option for
+// preventing the infobar from showing.
+- (void)updateFooterInfoBarIfNecessary {
+ // No-op. This prevents the default infobar from showing.
+}
+
+#pragma mark - HistoryEntriesStatusItemDelegate
+
+- (void)historyEntriesStatusItem:(HistoryEntriesStatusItem*)item
+ didRequestOpenURL:(const GURL&)URL {
+ [self openURL:URL];
+}
+
+#pragma mark - HistoryEntryInserterDelegate
+
+- (void)historyEntryInserter:(HistoryEntryInserter*)inserter
+ didInsertItemAtIndexPath:(NSIndexPath*)indexPath {
+ [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
+}
+
+- (void)historyEntryInserter:(HistoryEntryInserter*)inserter
+ didInsertSectionAtIndex:(NSInteger)sectionIndex {
+ [self.collectionView
+ insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
+}
+
+- (void)historyEntryInserter:(HistoryEntryInserter*)inserter
+ didRemoveSectionAtIndex:(NSInteger)sectionIndex {
+ [self.collectionView
+ deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
+}
+
+#pragma mark - HistoryEntryItemDelegate
+
+- (void)historyEntryItemDidRequestOpen:(HistoryEntryItem*)item {
+ [self openURL:item.URL];
+}
+
+- (void)historyEntryItemDidRequestDelete:(HistoryEntryItem*)item {
+ NSInteger sectionIdentifier =
+ [self.entryInserter sectionIdentifierForTimestamp:item.timestamp];
+ if ([self.collectionViewModel
+ hasSectionForSectionIdentifier:sectionIdentifier] &&
+ [self.collectionViewModel hasItem:item
+ inSectionWithIdentifier:sectionIdentifier]) {
+ NSIndexPath* indexPath =
+ [self.collectionViewModel indexPathForItem:item
+ inSectionWithIdentifier:sectionIdentifier];
+ [self.collectionView
+ selectItemAtIndexPath:indexPath
+ animated:NO
+ scrollPosition:UICollectionViewScrollPositionNone];
+ [self deleteSelectedItemsFromHistory];
+ }
+}
+
+- (void)historyEntryItemDidRequestCopy:(HistoryEntryItem*)item {
+ [self copyURL:item.URL];
+}
+
+- (void)historyEntryItemDidRequestOpenInNewTab:(HistoryEntryItem*)item {
+ [self openURLInNewTab:item.URL];
+}
+
+- (void)historyEntryItemDidRequestOpenInNewIncognitoTab:
+ (HistoryEntryItem*)item {
+ [self openURLInNewIncognitoTab:item.URL];
+}
+
+- (void)historyEntryItemShouldUpdateView:(HistoryEntryItem*)item {
+ NSInteger sectionIdentifier =
+ [self.entryInserter sectionIdentifierForTimestamp:item.timestamp];
+ // If the item is still in the model, reconfigure it.
+ if ([self.collectionViewModel
+ hasSectionForSectionIdentifier:sectionIdentifier] &&
+ [self.collectionViewModel hasItem:item
+ inSectionWithIdentifier:sectionIdentifier]) {
+ [self
+ reconfigureCellsForItems:@[ item ]
+ inSectionWithIdentifier:
+ [self.entryInserter sectionIdentifierForTimestamp:item.timestamp]];
+ }
+}
+
+#pragma mark - HistoryServiceFacadeDelegate
+
+- (void)historyServiceFacade:(HistoryServiceFacade*)facade
+ didReceiveQueryResult:(HistoryServiceFacade::QueryResult)result {
+ self.loading = NO;
+ // Remove loading indicator.
+ CollectionViewItem* headerItem = [self.collectionViewModel
+ itemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
+ if ([headerItem.cellClass isSubclassOfClass:[ActivityIndicatorCell class]]) {
+ [self.collectionViewModel removeItemWithType:kItemTypeEnumZero
+ fromSectionWithIdentifier:kSectionIdentifierEnumZero];
+ [self.collectionView
+ deleteItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0
+ inSection:0] ]];
+ }
+
+ // If there are no results and no URLs have been loaded, report that no
+ // history entries were found.
+ if (result.entries.empty() && !self.hasHistoryEntries) {
+ DCHECK(self.entriesType == NO_ENTRIES);
+ [self updateEntriesStatusMessage];
+ [self.delegate historyCollectionViewControllerDidChangeEntries:self];
+ return;
+ }
+
+ self.finishedLoading = result.has_synced_results
+ ? result.finished && result.sync_finished
+ : result.finished;
+ self.entriesType = result.has_synced_results ? SYNCED_ENTRIES : LOCAL_ENTRIES;
+ std::vector<history::HistoryEntry> entries = result.entries;
+
+ // Header section should be updated outside of batch updates, otherwise
+ // loading indicator removal will not be observed.
+ [self updateEntriesStatusMessage];
+
+ __block base::scoped_nsobject<NSMutableArray> searchResults(
+ [[NSMutableArray array] retain]);
+ __block base::scoped_nsobject<NSString> searchQuery(
+ [base::SysUTF16ToNSString(result.query) copy]);
+ [self.collectionView performBatchUpdates:^{
+ // There should always be at least a header section present.
+ DCHECK([[self collectionViewModel] numberOfSections]);
+ for (const history::HistoryEntry& entry : entries) {
+ HistoryEntryItem* item =
+ [[[HistoryEntryItem alloc] initWithType:kItemTypeEnumZero
+ historyEntry:entry
+ browserState:_browserState
+ delegate:self] autorelease];
+ [self.entryInserter insertHistoryEntryItem:item];
+ if ([self isSearching]) {
+ [searchResults addObject:item];
+ }
+ }
+ [self.delegate historyCollectionViewControllerDidChangeEntries:self];
+ }
+ completion:^(BOOL) {
+ if (([self isSearching] && [searchQuery length] > 0 &&
+ [self.currentQuery isEqualToString:searchQuery]) ||
+ self.filterForNextQueryResult) {
+ // If in search mode, filter out entries that are not
+ // part of the search result.
+ [self filterForHistoryEntries:searchResults];
+ self.filterForNextQueryResult = NO;
+ }
+ }];
+}
+
+- (void)historyServiceFacade:(HistoryServiceFacade*)facade
+ shouldShowNoticeAboutOtherFormsOfBrowsingHistory:(BOOL)shouldShowNotice {
+ self.shouldShowNoticeAboutOtherFormsOfBrowsingHistory = shouldShowNotice;
+}
+
+- (void)historyServiceFacadeDidObserveHistoryDeletion:
+ (HistoryServiceFacade*)facade {
+ // If history has been deleted, reload history filtering for the current
+ // results. This only observes local changes to history, i.e. removing
+ // history via the clear browsing data page.
+ self.filterForNextQueryResult = YES;
+ [self showHistoryMatchingQuery:nil];
+}
+
+#pragma mark - MDCCollectionViewEditingDelegate
+
+- (BOOL)collectionViewAllowsEditing:(UICollectionView*)collectionView {
+ return YES;
+}
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ canEditItemAtIndexPath:(NSIndexPath*)indexPath {
+ // All items except those in the header section may be edited.
+ return indexPath.section;
+}
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ canSelectItemDuringEditingAtIndexPath:(NSIndexPath*)indexPath {
+ // All items except those in the header section may be edited.
+ return indexPath.section;
+}
+
+#pragma mark - MDCCollectionViewStylingDelegate
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath {
+ // Display the entries status section (always the first section) without any
+ // background image or shadowing.
+ return !indexPath.section;
+}
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
+ return [indexPath isEqual:[NSIndexPath indexPathForItem:0 inSection:0]];
+}
+
+- (CGFloat)collectionView:(UICollectionView*)collectionView
+ cellHeightAtIndexPath:(NSIndexPath*)indexPath {
+ if (indexPath.section) {
+ return MDCCellDefaultTwoLineHeight;
+ } else {
+ DCHECK([indexPath isEqual:[NSIndexPath indexPathForItem:0 inSection:0]]);
+ // Configure size for loading indicator and entries status cells.
+ CollectionViewItem* item =
+ [self.collectionViewModel itemAtIndexPath:indexPath];
+ if ([item isKindOfClass:[CollectionViewTextItem class]]) {
+ return MDCCellDefaultOneLineHeight;
+ }
+ CGFloat height = [[item cellClass]
+ cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
+ forItem:item];
+ return height;
+ }
+}
+
+- (MDCCollectionViewCellStyle)collectionView:(UICollectionView*)collectionView
+ cellStyleForSection:(NSInteger)section {
+ return section ? MDCCollectionViewCellStyleCard
+ : MDCCollectionViewCellStyleDefault;
+}
+
+#pragma mark - UICollectionViewDelegate
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ shouldSelectItemAtIndexPath:(NSIndexPath*)indexPath {
+ // The first section is not selectable.
+ return indexPath.section && [super collectionView:collectionView
+ shouldSelectItemAtIndexPath:indexPath];
+}
+
+- (void)collectionView:(UICollectionView*)collectionView
+ didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
+ [super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
+
+ if (self.isEditing) {
+ [self.delegate historyCollectionViewControllerDidChangeEntrySelection:self];
+ } else {
+ HistoryEntryItem* item = base::mac::ObjCCastStrict<HistoryEntryItem>(
+ [self.collectionViewModel itemAtIndexPath:indexPath]);
+ [self openURL:item.URL];
+ }
+}
+
+- (void)collectionView:(UICollectionView*)collectionView
+ didDeselectItemAtIndexPath:(NSIndexPath*)indexPath {
+ [super collectionView:collectionView didDeselectItemAtIndexPath:indexPath];
+ [self.delegate historyCollectionViewControllerDidChangeEntrySelection:self];
+}
+
+- (void)collectionView:(UICollectionView*)collectionView
+ didEndDisplayingCell:(UICollectionViewCell*)cell
+ forItemAtIndexPath:(NSIndexPath*)indexPath {
+ if ([cell isKindOfClass:[ActivityIndicatorCell class]]) {
+ [[base::mac::ObjCCast<ActivityIndicatorCell>(cell) activityIndicator]
+ stopAnimating];
+ }
+}
+#pragma mark - UIScrollViewDelegate
+
+- (void)scrollViewDidScroll:(UIScrollView*)scrollView {
+ [super scrollViewDidScroll:scrollView];
+ // Adjust header shadow.
+ [self.delegate historyCollectionViewController:self
+ didScrollToOffset:scrollView.contentOffset];
+
+ if (self.hasFinishedLoading)
+ return;
+
+ CGFloat insetHeight =
+ scrollView.contentInset.top + scrollView.contentInset.bottom;
+ CGFloat contentViewHeight = scrollView.bounds.size.height - insetHeight;
+ CGFloat contentHeight = scrollView.contentSize.height;
+ CGFloat contentOffset = scrollView.contentOffset.y;
+ CGFloat buffer = contentViewHeight;
+ // If the scroll view is approaching the end of loaded history, try to fetch
+ // more history. Do so when the content offset is greater than the content
+ // height minus the view height, minus a buffer to start the fetch early.
+ if (contentOffset > (contentHeight - contentViewHeight) - buffer &&
+ !self.isLoading) {
+ // If at end, try to grab more history.
+ NSInteger lastSection = [self.collectionViewModel numberOfSections] - 1;
+ NSInteger lastItemIndex =
+ [self.collectionViewModel numberOfItemsInSection:lastSection] - 1;
+ if (lastSection == 0 || lastItemIndex < 0) {
+ return;
+ }
+ NSIndexPath* indexPath =
+ [NSIndexPath indexPathForItem:lastItemIndex inSection:lastSection];
+ HistoryEntryItem* lastItem = base::mac::ObjCCastStrict<HistoryEntryItem>(
+ [self.collectionViewModel itemAtIndexPath:indexPath]);
+ [self fetchHistoryForQuery:_currentQuery priorToTime:lastItem.timestamp];
+ }
+}
+
+#pragma mark - Private methods
+
+- (void)fetchHistoryForQuery:(NSString*)query
+ priorToTime:(const base::Time&)time {
+ self.loading = YES;
+ // Add loading indicator if nothing else is shown.
+ if (!self.hasHistoryEntries && !self.isSearching) {
+ [self.collectionView performBatchUpdates:^{
+ NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
+ if ([self.collectionViewModel hasItemAtIndexPath:indexPath]) {
+ [self.collectionViewModel
+ removeItemWithType:kItemTypeEnumZero
+ fromSectionWithIdentifier:kSectionIdentifierEnumZero];
+ [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]];
+ }
+ CollectionViewItem* loadingIndicatorItem = [[[CollectionViewItem alloc]
+ initWithType:kItemTypeEnumZero] autorelease];
+ loadingIndicatorItem.cellClass = [ActivityIndicatorCell class];
+ [self.collectionViewModel addItem:loadingIndicatorItem
+ toSectionWithIdentifier:kEntriesStatusSectionIdentifier];
+ [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
+ }
+ completion:nil];
+ }
+
+ BOOL fetchAllHistory = !query || [query isEqualToString:@""];
+ base::string16 queryString =
+ fetchAllHistory ? base::string16() : base::SysNSStringToUTF16(query);
+ history::QueryOptions options;
+ options.end_time = time;
+ options.duplicate_policy =
+ fetchAllHistory ? history::QueryOptions::REMOVE_DUPLICATES_PER_DAY
+ : history::QueryOptions::REMOVE_ALL_DUPLICATES;
+ options.max_count = kMaxFetchCount;
+ options.matching_algorithm =
+ query_parser::MatchingAlgorithm::ALWAYS_PREFIX_SEARCH;
+ _historyServiceFacade->QueryOtherFormsOfBrowsingHistory();
+ _historyServiceFacade->QueryHistory(queryString, options);
+ // Also determine whether notice regarding other forms of browsing history
+ // should be shown.
+ _historyServiceFacade->QueryOtherFormsOfBrowsingHistory();
+}
+
+- (void)updateEntriesStatusMessage {
+ CollectionViewItem* entriesStatusItem = nil;
+ if (!self.hasHistoryEntries) {
+ CollectionViewTextItem* noResultsItem = [[[CollectionViewTextItem alloc]
+ initWithType:kItemTypeEnumZero] autorelease];
+ noResultsItem.text =
+ self.isSearching ? l10n_util::GetNSString(IDS_HISTORY_NO_SEARCH_RESULTS)
+ : l10n_util::GetNSString(IDS_HISTORY_NO_RESULTS);
+ entriesStatusItem = noResultsItem;
+ } else {
+ HistoryEntriesStatusItem* historyEntriesStatusItem =
+ [[[HistoryEntriesStatusItem alloc] initWithType:kItemTypeEnumZero]
+ autorelease];
+ historyEntriesStatusItem.delegate = self;
+ AuthenticationService* authService =
+ AuthenticationServiceFactory::GetForBrowserState(_browserState);
+ BOOL signedIn = authService->IsAuthenticated();
+
+ historyEntriesStatusItem.hidden =
+ self.isSearching || (!signedIn && self.hasHistoryEntries);
+ historyEntriesStatusItem.entriesStatus = self.entriesType;
+ historyEntriesStatusItem.showsOtherBrowsingDataNotice =
+ _shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
+ entriesStatusItem = historyEntriesStatusItem;
+ }
+ // Replace the item in the first section, which is always present.
+ NSArray* items = [self.collectionViewModel
+ itemsInSectionWithIdentifier:kEntriesStatusSectionIdentifier];
+ if ([items count]) {
+ // There should only ever be one item in this section.
+ DCHECK([items count] == 1);
+ // Only update if the item has changed.
+ if ([items[0] isEqual:entriesStatusItem]) {
+ return;
+ }
+ }
+ [self.collectionView performBatchUpdates:^{
+ NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
+ if ([items count]) {
+ [self.collectionViewModel
+ removeItemWithType:kItemTypeEnumZero
+ fromSectionWithIdentifier:kEntriesStatusSectionIdentifier];
+ [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]];
+ }
+ [self.collectionViewModel addItem:entriesStatusItem
+ toSectionWithIdentifier:kEntriesStatusSectionIdentifier];
+ [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
+ }
+ completion:nil];
+}
+
+- (void)removeSelectedItemsFromCollection {
+ NSArray* deletedIndexPaths = self.collectionView.indexPathsForSelectedItems;
+ [self.collectionView performBatchUpdates:^{
+ [self collectionView:self.collectionView
+ willDeleteItemsAtIndexPaths:deletedIndexPaths];
+ [self.collectionView deleteItemsAtIndexPaths:deletedIndexPaths];
+
+ // Remove any empty sections, except the header section.
+ for (int section = self.collectionView.numberOfSections - 1; section > 0;
+ --section) {
+ if (![self.collectionViewModel numberOfItemsInSection:section]) {
+ [self.entryInserter removeSection:section];
+ }
+ }
+ }
+ completion:^(BOOL) {
+ // If only the header section remains, there are no history entries.
+ if ([self.collectionViewModel numberOfSections] == 1) {
+ self.entriesType = NO_ENTRIES;
+ }
+ [self updateEntriesStatusMessage];
+ }];
+}
+
+- (void)filterForHistoryEntries:(NSArray*)entries {
+ self.collectionView.allowsMultipleSelection = YES;
+ for (int section = 1; section < [self.collectionViewModel numberOfSections];
+ ++section) {
+ NSInteger sectionIdentifier =
+ [self.collectionViewModel sectionIdentifierForSection:section];
+ if ([self.collectionViewModel
+ hasSectionForSectionIdentifier:sectionIdentifier]) {
+ NSArray* items = [self.collectionViewModel
+ itemsInSectionWithIdentifier:sectionIdentifier];
+ for (id item in items) {
+ HistoryEntryItem* historyItem =
+ base::mac::ObjCCastStrict<HistoryEntryItem>(item);
+ if (![entries containsObject:historyItem]) {
+ NSIndexPath* indexPath =
+ [self.collectionViewModel indexPathForItem:historyItem
+ inSectionWithIdentifier:sectionIdentifier];
+ [self.collectionView
+ selectItemAtIndexPath:indexPath
+ animated:NO
+ scrollPosition:UICollectionViewScrollPositionNone];
+ }
+ }
+ }
+ }
+ [self removeSelectedItemsFromCollection];
+}
+
+#pragma mark Context Menu
+
+- (void)displayContextMenuInvokedByGestureRecognizer:
+ (UILongPressGestureRecognizer*)gestureRecognizer {
+ if (gestureRecognizer.numberOfTouches != 1 || self.editing ||
+ gestureRecognizer.state != UIGestureRecognizerStateBegan) {
+ return;
+ }
+
+ CGPoint touchLocation =
+ [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
+ NSIndexPath* touchedItemIndexPath =
+ [self.collectionView indexPathForItemAtPoint:touchLocation];
+ // If there's no index path, or the index path is for the header item, do not
+ // display a contextual menu.
+ if (!touchedItemIndexPath ||
+ [touchedItemIndexPath
+ isEqual:[NSIndexPath indexPathForItem:0 inSection:0]])
+ return;
+
+ HistoryEntryItem* entry = base::mac::ObjCCastStrict<HistoryEntryItem>(
+ [self.collectionViewModel itemAtIndexPath:touchedItemIndexPath]);
+
+ base::WeakNSObject<HistoryCollectionViewController> weakSelf(self);
+ web::ContextMenuParams params;
+ params.location = touchLocation;
+ params.view.reset([self.collectionView retain]);
+ NSString* menuTitle =
+ base::SysUTF16ToNSString(url_formatter::FormatUrl(entry.URL));
+ params.menu_title.reset([menuTitle copy]);
+
+ // Present sheet/popover using controller that is added to view hierarchy.
+ UIViewController* topController = [params.view window].rootViewController;
+ while (topController.presentedViewController)
+ topController = topController.presentedViewController;
+
+ self.contextMenuCoordinator =
+ [[ContextMenuCoordinator alloc] initWithBaseViewController:topController
+ params:params];
+
+ // TODO(crbug.com/606503): Refactor context menu creation code to be shared
+ // with BrowserViewController.
+ // Add "Open in New Tab" option.
+ NSString* openInNewTabTitle =
+ l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
+ ProceduralBlock openInNewTabAction = ^{
+ [weakSelf openURLInNewTab:entry.URL];
+ };
+ [self.contextMenuCoordinator addItemWithTitle:openInNewTabTitle
+ action:openInNewTabAction];
+
+ // Add "Open in New Incognito Tab" option.
+ NSString* openInNewIncognitoTabTitle = l10n_util::GetNSStringWithFixup(
+ IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
+ ProceduralBlock openInNewIncognitoTabAction = ^{
+ [weakSelf openURLInNewIncognitoTab:entry.URL];
+ };
+ [self.contextMenuCoordinator addItemWithTitle:openInNewIncognitoTabTitle
+ action:openInNewIncognitoTabAction];
+
+ // Add "Copy URL" option.
+ NSString* copyURLTitle =
+ l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
+ ProceduralBlock copyURLAction = ^{
+ [weakSelf copyURL:entry.URL];
+ };
+ [self.contextMenuCoordinator addItemWithTitle:copyURLTitle
+ action:copyURLAction];
+ [self.parentViewController.view endEditing:YES];
+ [self.contextMenuCoordinator start];
+}
+
+- (void)openURL:(const GURL&)URL {
+ GURL copiedURL(URL);
+ [self.delegate historyCollectionViewController:self
+ shouldCloseWithCompletion:^{
+ [self.URLLoader
+ loadURL:copiedURL
+ referrer:web::Referrer()
+ transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK
+ rendererInitiated:NO];
+ }];
+}
+
+- (void)openURLInNewTab:(const GURL&)URL {
+ GURL copiedURL(URL);
+ [self.delegate historyCollectionViewController:self
+ shouldCloseWithCompletion:^{
+ [self.URLLoader webPageOrderedOpen:copiedURL
+ referrer:web::Referrer()
+ windowName:nil
+ inIncognito:NO
+ inBackground:NO
+ appendTo:kLastTab];
+ }];
+}
+
+- (void)openURLInNewIncognitoTab:(const GURL&)URL {
+ GURL copiedURL(URL);
+ [self.delegate historyCollectionViewController:self
+ shouldCloseWithCompletion:^{
+ [self.URLLoader webPageOrderedOpen:copiedURL
+ referrer:web::Referrer()
+ windowName:nil
+ inIncognito:YES
+ inBackground:NO
+ appendTo:kLastTab];
+ }];
+}
+
+- (void)copyURL:(const GURL&)URL {
+ DCHECK(URL.is_valid());
+ NSData* plainText = [base::SysUTF8ToNSString(URL.spec())
+ dataUsingEncoding:NSUTF8StringEncoding];
+ NSDictionary* copiedItem = @{
+ (NSString*)kUTTypeURL : net::NSURLWithGURL(URL),
+ (NSString*)kUTTypeUTF8PlainText : plainText,
+ };
+ [[UIPasteboard generalPasteboard] setItems:@[ copiedItem ]];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698