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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 #include "ios/chrome/browser/ui/history/history_collection_view_controller.h"
6
7 #import <MobileCoreServices/MobileCoreServices.h>
8
9 #include <memory>
10
11 #import "base/ios/weak_nsobject.h"
12 #include "base/mac/foundation_util.h"
13 #import "base/mac/objc_property_releaser.h"
14 #include "base/mac/scoped_nsobject.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "components/browsing_data/core/history_notice_utils.h"
18 #include "components/strings/grit/components_strings.h"
19 #include "components/url_formatter/url_formatter.h"
20 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
21 #include "ios/chrome/browser/chrome_url_constants.h"
22 #import "ios/chrome/browser/signin/authentication_service.h"
23 #include "ios/chrome/browser/signin/authentication_service_factory.h"
24 #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrom e.h"
25 #import "ios/chrome/browser/ui/collection_view/cells/activity_indicator_cell.h"
26 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
27 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_text_item.h "
28 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
29 #import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
30 #include "ios/chrome/browser/ui/history/history_entries_status_item.h"
31 #include "ios/chrome/browser/ui/history/history_entry.h"
32 #include "ios/chrome/browser/ui/history/history_entry_inserter.h"
33 #import "ios/chrome/browser/ui/history/history_entry_item.h"
34 #include "ios/chrome/browser/ui/history/history_service_facade.h"
35 #include "ios/chrome/browser/ui/history/history_service_facade_delegate.h"
36 #include "ios/chrome/browser/ui/history/history_util.h"
37 #import "ios/chrome/browser/ui/url_loader.h"
38 #include "ios/chrome/grit/ios_strings.h"
39 #import "ios/third_party/material_components_ios/src/components/Collections/src/ MaterialCollections.h"
40 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat erialPalettes.h"
41 #import "ios/web/public/referrer.h"
42 #import "ios/web/public/web_state/context_menu_params.h"
43 #import "net/base/mac/url_conversions.h"
44 #include "ui/base/l10n/l10n_util.h"
45 #include "ui/base/l10n/l10n_util_mac.h"
46
47 namespace {
48 // Section identifier for the header (sync information) section.
49 const NSInteger kEntriesStatusSectionIdentifier = kSectionIdentifierEnumZero;
50 // Maximum number of entries to retrieve in a single query to history service.
51 const int kMaxFetchCount = 100;
52 // Horizontal inset for item separators.
53 const CGFloat kSeparatorInset = 10;
54 }
55
56 @interface HistoryCollectionViewController ()<HistoryEntriesStatusItemDelegate,
57 HistoryEntryInserterDelegate,
58 HistoryEntryItemDelegate,
59 HistoryServiceFacadeDelegate> {
60 base::mac::ObjCPropertyReleaser
61 _propertyReleaser_HistoryCollectionViewController;
62 // Facade for communicating with HistoryService and WebHistoryService.
63 std::unique_ptr<HistoryServiceFacade> _historyServiceFacade;
64 // The main browser state. Not owned by HistoryCollectionViewController.
65 ios::ChromeBrowserState* _browserState;
66 // Backing ivar for delegate property.
67 base::WeakNSProtocol<id<HistoryCollectionViewControllerDelegate>> _delegate;
68 // Backing ivar for URLLoader property.
69 base::WeakNSProtocol<id<UrlLoader>> _URLLoader;
70 }
71
72 // Object to manage insertion of history entries into the collection view model.
73 @property(nonatomic, retain) HistoryEntryInserter* entryInserter;
74 // Delegate for the history collection view.
75 @property(nonatomic, assign, readonly)
76 id<HistoryCollectionViewControllerDelegate>
77 delegate;
78 // UrlLoader for navigating to history entries.
79 @property(nonatomic, assign, readonly) id<UrlLoader> URLLoader;
80 // The current query for visible history entries.
81 @property(nonatomic, copy) NSString* currentQuery;
82 // Coordinator for displaying context menus for history entries.
83 @property(nonatomic, assign) ContextMenuCoordinator* contextMenuCoordinator;
84 // Type of displayed history entries. Entries can be synced or local, or there
85 // may be no history entries.
86 @property(nonatomic, assign) HistoryEntriesStatus entriesType;
87 // YES if the history panel should show a notice about additional forms of
88 // browsing history.
89 @property(nonatomic, assign)
90 BOOL shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
91 // YES if there is an outstanding history query.
92 @property(nonatomic, assign, getter=isLoading) BOOL loading;
93 // YES if there are no more history entries to load.
94 @property(nonatomic, assign, getter=hasFinishedLoading) BOOL finishedLoading;
95 // YES if the collection should be filtered by the next received query result.
96 @property(nonatomic, assign) BOOL filterForNextQueryResult;
97
98 // Fetches history prior to |time| for search text |query|. If |query| is nil or
99 // the empty string, all history is fetched.
100 - (void)fetchHistoryForQuery:(NSString*)query
101 priorToTime:(const base::Time&)time;
102 // Updates header section to provide relevant information about the currently
103 // displayed history entries.
104 - (void)updateEntriesStatusMessage;
105 // Removes selected items from the visible collection, but does not delete them
106 // from browser history.
107 - (void)removeSelectedItemsFromCollection;
108 // Removes all items in the collection that are not included in entries.
109 - (void)filterForHistoryEntries:(NSArray*)entries;
110 // Displays context menu on cell pressed with gestureRecognizer.
111 - (void)displayContextMenuInvokedByGestureRecognizer:
112 (UILongPressGestureRecognizer*)gestureRecognizer;
113 // Opens URL in the current tab and dismisses the history view.
114 - (void)openURL:(const GURL&)URL;
115 // Opens URL in a new non-incognito tab and dismisses the history view.
116 - (void)openURLInNewTab:(const GURL&)URL;
117 // Opens URL in a new incognito tab and dismisses the history view.
118 - (void)openURLInNewIncognitoTab:(const GURL&)URL;
119 // Copies URL to the clipboard.
120 - (void)copyURL:(const GURL&)URL;
121 @end
122
123 @implementation HistoryCollectionViewController
124
125 @synthesize searching = _searching;
126 @synthesize entryInserter = _entryInserter;
127 @synthesize currentQuery = _currentQuery;
128 @synthesize contextMenuCoordinator = _contextMenuCoordinator;
129 @synthesize entriesType = _entriesType;
130 @synthesize shouldShowNoticeAboutOtherFormsOfBrowsingHistory =
131 _shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
132 @synthesize loading = _loading;
133 @synthesize finishedLoading = _finishedLoading;
134 @synthesize filterForNextQueryResult = _filterForNextQueryResult;
135
136 - (instancetype)initWithLoader:(id<UrlLoader>)loader
137 browserState:(ios::ChromeBrowserState*)browserState
138 delegate:(id<HistoryCollectionViewControllerDelegate>)
139 delegate {
140 self = [super initWithStyle:CollectionViewControllerStyleDefault];
141 if (self) {
142 _propertyReleaser_HistoryCollectionViewController.Init(
143 self, [HistoryCollectionViewController class]);
144 _historyServiceFacade.reset(new HistoryServiceFacade(browserState, self));
145 _browserState = browserState;
146 _delegate.reset(delegate);
147 _URLLoader.reset(loader);
148 [self loadModel];
149 // Add initial info section as header.
150 [self.collectionViewModel
151 addSectionWithIdentifier:kEntriesStatusSectionIdentifier];
152 _entryInserter =
153 [[HistoryEntryInserter alloc] initWithModel:self.collectionViewModel];
154 _entryInserter.delegate = self;
155 _entriesType = NO_ENTRIES;
156 [self showHistoryMatchingQuery:nil];
157 }
158 return self;
159 }
160
161 - (void)viewDidLoad {
162 [super viewDidLoad];
163 self.styler.cellLayoutType = MDCCollectionViewCellLayoutTypeList;
164 self.styler.separatorInset =
165 UIEdgeInsetsMake(0, kSeparatorInset, 0, kSeparatorInset);
166 self.styler.allowsItemInlay = NO;
167
168 self.clearsSelectionOnViewWillAppear = NO;
169 self.collectionView.keyboardDismissMode =
170 UIScrollViewKeyboardDismissModeOnDrag;
171
172 base::scoped_nsobject<UILongPressGestureRecognizer> longPressRecognizer([
173 [UILongPressGestureRecognizer alloc]
174 initWithTarget:self
175 action:@selector(displayContextMenuInvokedByGestureRecognizer:)]);
176 [self.collectionView addGestureRecognizer:longPressRecognizer];
177 }
178
179 - (BOOL)isEditing {
180 return self.editor.isEditing;
181 }
182
183 - (void)setEditing:(BOOL)editing {
184 [self.editor setEditing:editing animated:YES];
185 }
186
187 - (void)setSearching:(BOOL)searching {
188 _searching = searching;
189 [self updateEntriesStatusMessage];
190 }
191
192 - (BOOL)hasHistoryEntries {
193 return self.entriesType != NO_ENTRIES;
194 }
195
196 - (BOOL)hasSelectedEntries {
197 return self.collectionView.indexPathsForSelectedItems.count;
198 }
199
200 - (void)showHistoryMatchingQuery:(NSString*)query {
201 self.finishedLoading = NO;
202 self.currentQuery = query;
203 [self fetchHistoryForQuery:query priorToTime:base::Time::Now()];
204 }
205
206 - (void)deleteSelectedItemsFromHistory {
207 NSArray* deletedIndexPaths = self.collectionView.indexPathsForSelectedItems;
208 std::vector<HistoryServiceFacade::RemovedEntry> entries;
209 for (NSIndexPath* indexPath in deletedIndexPaths) {
210 HistoryEntryItem* object = base::mac::ObjCCastStrict<HistoryEntryItem>(
211 [self.collectionViewModel itemAtIndexPath:indexPath]);
212 entries.push_back(
213 HistoryServiceFacade::RemovedEntry(object.URL, object.timestamp));
214 }
215 _historyServiceFacade->RemoveHistoryEntries(entries);
216 [self removeSelectedItemsFromCollection];
217 }
218
219 - (id<HistoryCollectionViewControllerDelegate>)delegate {
220 return _delegate;
221 }
222
223 - (id<UrlLoader>)URLLoader {
224 return _URLLoader;
225 }
226
227 #pragma mark - MDCollectionViewController
228
229 // TODO(crbug.com/653547): Remove this once the MDC adds an option for
230 // preventing the infobar from showing.
231 - (void)updateFooterInfoBarIfNecessary {
232 // No-op. This prevents the default infobar from showing.
233 }
234
235 #pragma mark - HistoryEntriesStatusItemDelegate
236
237 - (void)historyEntriesStatusItem:(HistoryEntriesStatusItem*)item
238 didRequestOpenURL:(const GURL&)URL {
239 [self openURL:URL];
240 }
241
242 #pragma mark - HistoryEntryInserterDelegate
243
244 - (void)historyEntryInserter:(HistoryEntryInserter*)inserter
245 didInsertItemAtIndexPath:(NSIndexPath*)indexPath {
246 [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
247 }
248
249 - (void)historyEntryInserter:(HistoryEntryInserter*)inserter
250 didInsertSectionAtIndex:(NSInteger)sectionIndex {
251 [self.collectionView
252 insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
253 }
254
255 - (void)historyEntryInserter:(HistoryEntryInserter*)inserter
256 didRemoveSectionAtIndex:(NSInteger)sectionIndex {
257 [self.collectionView
258 deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
259 }
260
261 #pragma mark - HistoryEntryItemDelegate
262
263 - (void)historyEntryItemDidRequestOpen:(HistoryEntryItem*)item {
264 [self openURL:item.URL];
265 }
266
267 - (void)historyEntryItemDidRequestDelete:(HistoryEntryItem*)item {
268 NSInteger sectionIdentifier =
269 [self.entryInserter sectionIdentifierForTimestamp:item.timestamp];
270 if ([self.collectionViewModel
271 hasSectionForSectionIdentifier:sectionIdentifier] &&
272 [self.collectionViewModel hasItem:item
273 inSectionWithIdentifier:sectionIdentifier]) {
274 NSIndexPath* indexPath =
275 [self.collectionViewModel indexPathForItem:item
276 inSectionWithIdentifier:sectionIdentifier];
277 [self.collectionView
278 selectItemAtIndexPath:indexPath
279 animated:NO
280 scrollPosition:UICollectionViewScrollPositionNone];
281 [self deleteSelectedItemsFromHistory];
282 }
283 }
284
285 - (void)historyEntryItemDidRequestCopy:(HistoryEntryItem*)item {
286 [self copyURL:item.URL];
287 }
288
289 - (void)historyEntryItemDidRequestOpenInNewTab:(HistoryEntryItem*)item {
290 [self openURLInNewTab:item.URL];
291 }
292
293 - (void)historyEntryItemDidRequestOpenInNewIncognitoTab:
294 (HistoryEntryItem*)item {
295 [self openURLInNewIncognitoTab:item.URL];
296 }
297
298 - (void)historyEntryItemShouldUpdateView:(HistoryEntryItem*)item {
299 NSInteger sectionIdentifier =
300 [self.entryInserter sectionIdentifierForTimestamp:item.timestamp];
301 // If the item is still in the model, reconfigure it.
302 if ([self.collectionViewModel
303 hasSectionForSectionIdentifier:sectionIdentifier] &&
304 [self.collectionViewModel hasItem:item
305 inSectionWithIdentifier:sectionIdentifier]) {
306 [self
307 reconfigureCellsForItems:@[ item ]
308 inSectionWithIdentifier:
309 [self.entryInserter sectionIdentifierForTimestamp:item.timestamp]];
310 }
311 }
312
313 #pragma mark - HistoryServiceFacadeDelegate
314
315 - (void)historyServiceFacade:(HistoryServiceFacade*)facade
316 didReceiveQueryResult:(HistoryServiceFacade::QueryResult)result {
317 self.loading = NO;
318 // Remove loading indicator.
319 CollectionViewItem* headerItem = [self.collectionViewModel
320 itemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
321 if ([headerItem.cellClass isSubclassOfClass:[ActivityIndicatorCell class]]) {
322 [self.collectionViewModel removeItemWithType:kItemTypeEnumZero
323 fromSectionWithIdentifier:kSectionIdentifierEnumZero];
324 [self.collectionView
325 deleteItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0
326 inSection:0] ]];
327 }
328
329 // If there are no results and no URLs have been loaded, report that no
330 // history entries were found.
331 if (result.entries.empty() && !self.hasHistoryEntries) {
332 DCHECK(self.entriesType == NO_ENTRIES);
333 [self updateEntriesStatusMessage];
334 [self.delegate historyCollectionViewControllerDidChangeEntries:self];
335 return;
336 }
337
338 self.finishedLoading = result.has_synced_results
339 ? result.finished && result.sync_finished
340 : result.finished;
341 self.entriesType = result.has_synced_results ? SYNCED_ENTRIES : LOCAL_ENTRIES;
342 std::vector<history::HistoryEntry> entries = result.entries;
343
344 // Header section should be updated outside of batch updates, otherwise
345 // loading indicator removal will not be observed.
346 [self updateEntriesStatusMessage];
347
348 __block base::scoped_nsobject<NSMutableArray> searchResults(
349 [[NSMutableArray array] retain]);
350 __block base::scoped_nsobject<NSString> searchQuery(
351 [base::SysUTF16ToNSString(result.query) copy]);
352 [self.collectionView performBatchUpdates:^{
353 // There should always be at least a header section present.
354 DCHECK([[self collectionViewModel] numberOfSections]);
355 for (const history::HistoryEntry& entry : entries) {
356 HistoryEntryItem* item =
357 [[[HistoryEntryItem alloc] initWithType:kItemTypeEnumZero
358 historyEntry:entry
359 browserState:_browserState
360 delegate:self] autorelease];
361 [self.entryInserter insertHistoryEntryItem:item];
362 if ([self isSearching]) {
363 [searchResults addObject:item];
364 }
365 }
366 [self.delegate historyCollectionViewControllerDidChangeEntries:self];
367 }
368 completion:^(BOOL) {
369 if (([self isSearching] && [searchQuery length] > 0 &&
370 [self.currentQuery isEqualToString:searchQuery]) ||
371 self.filterForNextQueryResult) {
372 // If in search mode, filter out entries that are not
373 // part of the search result.
374 [self filterForHistoryEntries:searchResults];
375 self.filterForNextQueryResult = NO;
376 }
377 }];
378 }
379
380 - (void)historyServiceFacade:(HistoryServiceFacade*)facade
381 shouldShowNoticeAboutOtherFormsOfBrowsingHistory:(BOOL)shouldShowNotice {
382 self.shouldShowNoticeAboutOtherFormsOfBrowsingHistory = shouldShowNotice;
383 }
384
385 - (void)historyServiceFacadeDidObserveHistoryDeletion:
386 (HistoryServiceFacade*)facade {
387 // If history has been deleted, reload history filtering for the current
388 // results. This only observes local changes to history, i.e. removing
389 // history via the clear browsing data page.
390 self.filterForNextQueryResult = YES;
391 [self showHistoryMatchingQuery:nil];
392 }
393
394 #pragma mark - MDCCollectionViewEditingDelegate
395
396 - (BOOL)collectionViewAllowsEditing:(UICollectionView*)collectionView {
397 return YES;
398 }
399
400 - (BOOL)collectionView:(UICollectionView*)collectionView
401 canEditItemAtIndexPath:(NSIndexPath*)indexPath {
402 // All items except those in the header section may be edited.
403 return indexPath.section;
404 }
405
406 - (BOOL)collectionView:(UICollectionView*)collectionView
407 canSelectItemDuringEditingAtIndexPath:(NSIndexPath*)indexPath {
408 // All items except those in the header section may be edited.
409 return indexPath.section;
410 }
411
412 #pragma mark - MDCCollectionViewStylingDelegate
413
414 - (BOOL)collectionView:(UICollectionView*)collectionView
415 shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath {
416 // Display the entries status section (always the first section) without any
417 // background image or shadowing.
418 return !indexPath.section;
419 }
420
421 - (BOOL)collectionView:(UICollectionView*)collectionView
422 hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
423 return [indexPath isEqual:[NSIndexPath indexPathForItem:0 inSection:0]];
424 }
425
426 - (CGFloat)collectionView:(UICollectionView*)collectionView
427 cellHeightAtIndexPath:(NSIndexPath*)indexPath {
428 if (indexPath.section) {
429 return MDCCellDefaultTwoLineHeight;
430 } else {
431 DCHECK([indexPath isEqual:[NSIndexPath indexPathForItem:0 inSection:0]]);
432 // Configure size for loading indicator and entries status cells.
433 CollectionViewItem* item =
434 [self.collectionViewModel itemAtIndexPath:indexPath];
435 if ([item isKindOfClass:[CollectionViewTextItem class]]) {
436 return MDCCellDefaultOneLineHeight;
437 }
438 CGFloat height = [[item cellClass]
439 cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
440 forItem:item];
441 return height;
442 }
443 }
444
445 - (MDCCollectionViewCellStyle)collectionView:(UICollectionView*)collectionView
446 cellStyleForSection:(NSInteger)section {
447 return section ? MDCCollectionViewCellStyleCard
448 : MDCCollectionViewCellStyleDefault;
449 }
450
451 #pragma mark - UICollectionViewDelegate
452
453 - (BOOL)collectionView:(UICollectionView*)collectionView
454 shouldSelectItemAtIndexPath:(NSIndexPath*)indexPath {
455 // The first section is not selectable.
456 return indexPath.section && [super collectionView:collectionView
457 shouldSelectItemAtIndexPath:indexPath];
458 }
459
460 - (void)collectionView:(UICollectionView*)collectionView
461 didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
462 [super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
463
464 if (self.isEditing) {
465 [self.delegate historyCollectionViewControllerDidChangeEntrySelection:self];
466 } else {
467 HistoryEntryItem* item = base::mac::ObjCCastStrict<HistoryEntryItem>(
468 [self.collectionViewModel itemAtIndexPath:indexPath]);
469 [self openURL:item.URL];
470 }
471 }
472
473 - (void)collectionView:(UICollectionView*)collectionView
474 didDeselectItemAtIndexPath:(NSIndexPath*)indexPath {
475 [super collectionView:collectionView didDeselectItemAtIndexPath:indexPath];
476 [self.delegate historyCollectionViewControllerDidChangeEntrySelection:self];
477 }
478
479 - (void)collectionView:(UICollectionView*)collectionView
480 didEndDisplayingCell:(UICollectionViewCell*)cell
481 forItemAtIndexPath:(NSIndexPath*)indexPath {
482 if ([cell isKindOfClass:[ActivityIndicatorCell class]]) {
483 [[base::mac::ObjCCast<ActivityIndicatorCell>(cell) activityIndicator]
484 stopAnimating];
485 }
486 }
487 #pragma mark - UIScrollViewDelegate
488
489 - (void)scrollViewDidScroll:(UIScrollView*)scrollView {
490 [super scrollViewDidScroll:scrollView];
491 // Adjust header shadow.
492 [self.delegate historyCollectionViewController:self
493 didScrollToOffset:scrollView.contentOffset];
494
495 if (self.hasFinishedLoading)
496 return;
497
498 CGFloat insetHeight =
499 scrollView.contentInset.top + scrollView.contentInset.bottom;
500 CGFloat contentViewHeight = scrollView.bounds.size.height - insetHeight;
501 CGFloat contentHeight = scrollView.contentSize.height;
502 CGFloat contentOffset = scrollView.contentOffset.y;
503 CGFloat buffer = contentViewHeight;
504 // If the scroll view is approaching the end of loaded history, try to fetch
505 // more history. Do so when the content offset is greater than the content
506 // height minus the view height, minus a buffer to start the fetch early.
507 if (contentOffset > (contentHeight - contentViewHeight) - buffer &&
508 !self.isLoading) {
509 // If at end, try to grab more history.
510 NSInteger lastSection = [self.collectionViewModel numberOfSections] - 1;
511 NSInteger lastItemIndex =
512 [self.collectionViewModel numberOfItemsInSection:lastSection] - 1;
513 if (lastSection == 0 || lastItemIndex < 0) {
514 return;
515 }
516 NSIndexPath* indexPath =
517 [NSIndexPath indexPathForItem:lastItemIndex inSection:lastSection];
518 HistoryEntryItem* lastItem = base::mac::ObjCCastStrict<HistoryEntryItem>(
519 [self.collectionViewModel itemAtIndexPath:indexPath]);
520 [self fetchHistoryForQuery:_currentQuery priorToTime:lastItem.timestamp];
521 }
522 }
523
524 #pragma mark - Private methods
525
526 - (void)fetchHistoryForQuery:(NSString*)query
527 priorToTime:(const base::Time&)time {
528 self.loading = YES;
529 // Add loading indicator if nothing else is shown.
530 if (!self.hasHistoryEntries && !self.isSearching) {
531 [self.collectionView performBatchUpdates:^{
532 NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
533 if ([self.collectionViewModel hasItemAtIndexPath:indexPath]) {
534 [self.collectionViewModel
535 removeItemWithType:kItemTypeEnumZero
536 fromSectionWithIdentifier:kSectionIdentifierEnumZero];
537 [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]];
538 }
539 CollectionViewItem* loadingIndicatorItem = [[[CollectionViewItem alloc]
540 initWithType:kItemTypeEnumZero] autorelease];
541 loadingIndicatorItem.cellClass = [ActivityIndicatorCell class];
542 [self.collectionViewModel addItem:loadingIndicatorItem
543 toSectionWithIdentifier:kEntriesStatusSectionIdentifier];
544 [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
545 }
546 completion:nil];
547 }
548
549 BOOL fetchAllHistory = !query || [query isEqualToString:@""];
550 base::string16 queryString =
551 fetchAllHistory ? base::string16() : base::SysNSStringToUTF16(query);
552 history::QueryOptions options;
553 options.end_time = time;
554 options.duplicate_policy =
555 fetchAllHistory ? history::QueryOptions::REMOVE_DUPLICATES_PER_DAY
556 : history::QueryOptions::REMOVE_ALL_DUPLICATES;
557 options.max_count = kMaxFetchCount;
558 options.matching_algorithm =
559 query_parser::MatchingAlgorithm::ALWAYS_PREFIX_SEARCH;
560 _historyServiceFacade->QueryOtherFormsOfBrowsingHistory();
561 _historyServiceFacade->QueryHistory(queryString, options);
562 // Also determine whether notice regarding other forms of browsing history
563 // should be shown.
564 _historyServiceFacade->QueryOtherFormsOfBrowsingHistory();
565 }
566
567 - (void)updateEntriesStatusMessage {
568 CollectionViewItem* entriesStatusItem = nil;
569 if (!self.hasHistoryEntries) {
570 CollectionViewTextItem* noResultsItem = [[[CollectionViewTextItem alloc]
571 initWithType:kItemTypeEnumZero] autorelease];
572 noResultsItem.text =
573 self.isSearching ? l10n_util::GetNSString(IDS_HISTORY_NO_SEARCH_RESULTS)
574 : l10n_util::GetNSString(IDS_HISTORY_NO_RESULTS);
575 entriesStatusItem = noResultsItem;
576 } else {
577 HistoryEntriesStatusItem* historyEntriesStatusItem =
578 [[[HistoryEntriesStatusItem alloc] initWithType:kItemTypeEnumZero]
579 autorelease];
580 historyEntriesStatusItem.delegate = self;
581 AuthenticationService* authService =
582 AuthenticationServiceFactory::GetForBrowserState(_browserState);
583 BOOL signedIn = authService->IsAuthenticated();
584
585 historyEntriesStatusItem.hidden =
586 self.isSearching || (!signedIn && self.hasHistoryEntries);
587 historyEntriesStatusItem.entriesStatus = self.entriesType;
588 historyEntriesStatusItem.showsOtherBrowsingDataNotice =
589 _shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
590 entriesStatusItem = historyEntriesStatusItem;
591 }
592 // Replace the item in the first section, which is always present.
593 NSArray* items = [self.collectionViewModel
594 itemsInSectionWithIdentifier:kEntriesStatusSectionIdentifier];
595 if ([items count]) {
596 // There should only ever be one item in this section.
597 DCHECK([items count] == 1);
598 // Only update if the item has changed.
599 if ([items[0] isEqual:entriesStatusItem]) {
600 return;
601 }
602 }
603 [self.collectionView performBatchUpdates:^{
604 NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
605 if ([items count]) {
606 [self.collectionViewModel
607 removeItemWithType:kItemTypeEnumZero
608 fromSectionWithIdentifier:kEntriesStatusSectionIdentifier];
609 [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]];
610 }
611 [self.collectionViewModel addItem:entriesStatusItem
612 toSectionWithIdentifier:kEntriesStatusSectionIdentifier];
613 [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
614 }
615 completion:nil];
616 }
617
618 - (void)removeSelectedItemsFromCollection {
619 NSArray* deletedIndexPaths = self.collectionView.indexPathsForSelectedItems;
620 [self.collectionView performBatchUpdates:^{
621 [self collectionView:self.collectionView
622 willDeleteItemsAtIndexPaths:deletedIndexPaths];
623 [self.collectionView deleteItemsAtIndexPaths:deletedIndexPaths];
624
625 // Remove any empty sections, except the header section.
626 for (int section = self.collectionView.numberOfSections - 1; section > 0;
627 --section) {
628 if (![self.collectionViewModel numberOfItemsInSection:section]) {
629 [self.entryInserter removeSection:section];
630 }
631 }
632 }
633 completion:^(BOOL) {
634 // If only the header section remains, there are no history entries.
635 if ([self.collectionViewModel numberOfSections] == 1) {
636 self.entriesType = NO_ENTRIES;
637 }
638 [self updateEntriesStatusMessage];
639 }];
640 }
641
642 - (void)filterForHistoryEntries:(NSArray*)entries {
643 self.collectionView.allowsMultipleSelection = YES;
644 for (int section = 1; section < [self.collectionViewModel numberOfSections];
645 ++section) {
646 NSInteger sectionIdentifier =
647 [self.collectionViewModel sectionIdentifierForSection:section];
648 if ([self.collectionViewModel
649 hasSectionForSectionIdentifier:sectionIdentifier]) {
650 NSArray* items = [self.collectionViewModel
651 itemsInSectionWithIdentifier:sectionIdentifier];
652 for (id item in items) {
653 HistoryEntryItem* historyItem =
654 base::mac::ObjCCastStrict<HistoryEntryItem>(item);
655 if (![entries containsObject:historyItem]) {
656 NSIndexPath* indexPath =
657 [self.collectionViewModel indexPathForItem:historyItem
658 inSectionWithIdentifier:sectionIdentifier];
659 [self.collectionView
660 selectItemAtIndexPath:indexPath
661 animated:NO
662 scrollPosition:UICollectionViewScrollPositionNone];
663 }
664 }
665 }
666 }
667 [self removeSelectedItemsFromCollection];
668 }
669
670 #pragma mark Context Menu
671
672 - (void)displayContextMenuInvokedByGestureRecognizer:
673 (UILongPressGestureRecognizer*)gestureRecognizer {
674 if (gestureRecognizer.numberOfTouches != 1 || self.editing ||
675 gestureRecognizer.state != UIGestureRecognizerStateBegan) {
676 return;
677 }
678
679 CGPoint touchLocation =
680 [gestureRecognizer locationOfTouch:0 inView:self.collectionView];
681 NSIndexPath* touchedItemIndexPath =
682 [self.collectionView indexPathForItemAtPoint:touchLocation];
683 // If there's no index path, or the index path is for the header item, do not
684 // display a contextual menu.
685 if (!touchedItemIndexPath ||
686 [touchedItemIndexPath
687 isEqual:[NSIndexPath indexPathForItem:0 inSection:0]])
688 return;
689
690 HistoryEntryItem* entry = base::mac::ObjCCastStrict<HistoryEntryItem>(
691 [self.collectionViewModel itemAtIndexPath:touchedItemIndexPath]);
692
693 base::WeakNSObject<HistoryCollectionViewController> weakSelf(self);
694 web::ContextMenuParams params;
695 params.location = touchLocation;
696 params.view.reset([self.collectionView retain]);
697 NSString* menuTitle =
698 base::SysUTF16ToNSString(url_formatter::FormatUrl(entry.URL));
699 params.menu_title.reset([menuTitle copy]);
700
701 // Present sheet/popover using controller that is added to view hierarchy.
702 UIViewController* topController = [params.view window].rootViewController;
703 while (topController.presentedViewController)
704 topController = topController.presentedViewController;
705
706 self.contextMenuCoordinator =
707 [[ContextMenuCoordinator alloc] initWithBaseViewController:topController
708 params:params];
709
710 // TODO(crbug.com/606503): Refactor context menu creation code to be shared
711 // with BrowserViewController.
712 // Add "Open in New Tab" option.
713 NSString* openInNewTabTitle =
714 l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
715 ProceduralBlock openInNewTabAction = ^{
716 [weakSelf openURLInNewTab:entry.URL];
717 };
718 [self.contextMenuCoordinator addItemWithTitle:openInNewTabTitle
719 action:openInNewTabAction];
720
721 // Add "Open in New Incognito Tab" option.
722 NSString* openInNewIncognitoTabTitle = l10n_util::GetNSStringWithFixup(
723 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
724 ProceduralBlock openInNewIncognitoTabAction = ^{
725 [weakSelf openURLInNewIncognitoTab:entry.URL];
726 };
727 [self.contextMenuCoordinator addItemWithTitle:openInNewIncognitoTabTitle
728 action:openInNewIncognitoTabAction];
729
730 // Add "Copy URL" option.
731 NSString* copyURLTitle =
732 l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
733 ProceduralBlock copyURLAction = ^{
734 [weakSelf copyURL:entry.URL];
735 };
736 [self.contextMenuCoordinator addItemWithTitle:copyURLTitle
737 action:copyURLAction];
738 [self.parentViewController.view endEditing:YES];
739 [self.contextMenuCoordinator start];
740 }
741
742 - (void)openURL:(const GURL&)URL {
743 GURL copiedURL(URL);
744 [self.delegate historyCollectionViewController:self
745 shouldCloseWithCompletion:^{
746 [self.URLLoader
747 loadURL:copiedURL
748 referrer:web::Referrer()
749 transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK
750 rendererInitiated:NO];
751 }];
752 }
753
754 - (void)openURLInNewTab:(const GURL&)URL {
755 GURL copiedURL(URL);
756 [self.delegate historyCollectionViewController:self
757 shouldCloseWithCompletion:^{
758 [self.URLLoader webPageOrderedOpen:copiedURL
759 referrer:web::Referrer()
760 windowName:nil
761 inIncognito:NO
762 inBackground:NO
763 appendTo:kLastTab];
764 }];
765 }
766
767 - (void)openURLInNewIncognitoTab:(const GURL&)URL {
768 GURL copiedURL(URL);
769 [self.delegate historyCollectionViewController:self
770 shouldCloseWithCompletion:^{
771 [self.URLLoader webPageOrderedOpen:copiedURL
772 referrer:web::Referrer()
773 windowName:nil
774 inIncognito:YES
775 inBackground:NO
776 appendTo:kLastTab];
777 }];
778 }
779
780 - (void)copyURL:(const GURL&)URL {
781 DCHECK(URL.is_valid());
782 NSData* plainText = [base::SysUTF8ToNSString(URL.spec())
783 dataUsingEncoding:NSUTF8StringEncoding];
784 NSDictionary* copiedItem = @{
785 (NSString*)kUTTypeURL : net::NSURLWithGURL(URL),
786 (NSString*)kUTTypeUTF8PlainText : plainText,
787 };
788 [[UIPasteboard generalPasteboard] setItems:@[ copiedItem ]];
789 }
790
791 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698