| // 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. |
| |
| #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h" |
| |
| #include "base/mac/foundation_util.h" |
| #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h" |
| #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h" |
| #import "ios/chrome/browser/ui/collection_view/collection_view_model.h" |
| #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_article_item.h" |
| #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_reading_list_item.h" |
| #import "ios/chrome/browser/ui/content_suggestions/content_suggestion.h" |
| #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h" |
| #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_commands.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| @interface ContentSuggestionsViewController () |
| |
| @property(nonatomic, strong) |
| ContentSuggestionsCollectionUpdater* collectionUpdater; |
| |
| @end |
| |
| @implementation ContentSuggestionsViewController |
| |
| @synthesize suggestionCommandHandler = _suggestionCommandHandler; |
| @synthesize collectionUpdater = _collectionUpdater; |
| @dynamic collectionViewModel; |
| |
| #pragma mark - Public |
| |
| - (instancetype)initWithStyle:(CollectionViewControllerStyle)style |
| dataSource:(id<ContentSuggestionsDataSource>)dataSource { |
| self = [super initWithStyle:style]; |
| if (self) { |
| _collectionUpdater = [[ContentSuggestionsCollectionUpdater alloc] |
| initWithDataSource:dataSource]; |
| } |
| return self; |
| } |
| |
| - (void)dismissEntryAtIndexPath:(NSIndexPath*)indexPath { |
| if (!indexPath || ![self.collectionViewModel hasItemAtIndexPath:indexPath]) { |
| return; |
| } |
| |
| [self.collectionView performBatchUpdates:^{ |
| [self collectionView:self.collectionView |
| willDeleteItemsAtIndexPaths:@[ indexPath ]]; |
| |
| [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]]; |
| |
| // Check if the section is now empty. |
| [self addEmptySectionPlaceholderIfNeeded:indexPath.section]; |
| } |
| completion:^(BOOL) { |
| // The context menu could be displayed for the deleted entry. |
| [self.suggestionCommandHandler dismissContextMenu]; |
| }]; |
| } |
| |
| - (void)dismissSection:(NSInteger)section { |
| if (section >= [self numberOfSectionsInCollectionView:self.collectionView]) { |
| return; |
| } |
| |
| NSInteger sectionIdentifier = |
| [self.collectionViewModel sectionIdentifierForSection:section]; |
| |
| [self.collectionView performBatchUpdates:^{ |
| [self.collectionViewModel removeSectionWithIdentifier:sectionIdentifier]; |
| [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:section]]; |
| } |
| completion:^(BOOL) { |
| // The context menu could be displayed for the deleted entries. |
| [self.suggestionCommandHandler dismissContextMenu]; |
| }]; |
| } |
| |
| - (void)addSuggestions:(NSArray<ContentSuggestion*>*)suggestions { |
| if (suggestions.count == 0) { |
| return; |
| } |
| |
| [self.collectionView performBatchUpdates:^{ |
| NSIndexSet* addedSections = |
| [self.collectionUpdater addSectionsForSuggestionsToModel:suggestions]; |
| [self.collectionView insertSections:addedSections]; |
| } |
| completion:nil]; |
| |
| [self.collectionView performBatchUpdates:^{ |
| NSArray<NSIndexPath*>* addedItems = |
| [self.collectionUpdater addSuggestionsToModel:suggestions]; |
| [self.collectionView insertItemsAtIndexPaths:addedItems]; |
| } |
| completion:nil]; |
| } |
| |
| #pragma mark - UIViewController |
| |
| - (void)viewDidLoad { |
| [super viewDidLoad]; |
| |
| _collectionUpdater.collectionViewController = self; |
| |
| self.collectionView.delegate = self; |
| self.styler.cellStyle = MDCCollectionViewCellStyleCard; |
| |
| UILongPressGestureRecognizer* longPressRecognizer = |
| [[UILongPressGestureRecognizer alloc] |
| initWithTarget:self |
| action:@selector(handleLongPress:)]; |
| longPressRecognizer.numberOfTouchesRequired = 1; |
| [self.collectionView addGestureRecognizer:longPressRecognizer]; |
| } |
| |
| #pragma mark - UICollectionViewDelegate |
| |
| - (void)collectionView:(UICollectionView*)collectionView |
| didSelectItemAtIndexPath:(NSIndexPath*)indexPath { |
| [super collectionView:collectionView didSelectItemAtIndexPath:indexPath]; |
| |
| CollectionViewItem* item = |
| [self.collectionViewModel itemAtIndexPath:indexPath]; |
| switch ([self.collectionUpdater contentSuggestionTypeForItem:item]) { |
| case ContentSuggestionTypeReadingList: |
| [self openReadingListItem:item]; |
| break; |
| case ContentSuggestionTypeArticle: |
| [self openArticle:item]; |
| break; |
| case ContentSuggestionTypeEmpty: |
| break; |
| } |
| } |
| |
| #pragma mark - MDCCollectionViewStylingDelegate |
| |
| - (UIColor*)collectionView:(nonnull UICollectionView*)collectionView |
| cellBackgroundColorAtIndexPath:(nonnull NSIndexPath*)indexPath { |
| if ([self.collectionUpdater |
| shouldUseCustomStyleForSection:indexPath.section]) { |
| return [UIColor clearColor]; |
| } |
| return [UIColor whiteColor]; |
| } |
| |
| - (BOOL)collectionView:(nonnull UICollectionView*)collectionView |
| shouldHideItemBackgroundAtIndexPath:(nonnull NSIndexPath*)indexPath { |
| if ([self.collectionUpdater |
| shouldUseCustomStyleForSection:indexPath.section]) { |
| return YES; |
| } |
| return NO; |
| } |
| |
| - (BOOL)collectionView:(UICollectionView*)collectionView |
| shouldHideHeaderBackgroundForSection:(NSInteger)section { |
| return YES; |
| } |
| |
| - (CGFloat)collectionView:(UICollectionView*)collectionView |
| cellHeightAtIndexPath:(NSIndexPath*)indexPath { |
| CollectionViewItem* item = |
| [self.collectionViewModel itemAtIndexPath:indexPath]; |
| UIEdgeInsets inset = [self collectionView:collectionView |
| layout:collectionView.collectionViewLayout |
| insetForSectionAtIndex:indexPath.section]; |
| |
| return [MDCCollectionViewCell |
| cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds) - |
| inset.left - inset.right |
| forItem:item]; |
| } |
| |
| #pragma mark - Private |
| |
| // Opens the Reading List entry associated with |item|. |item| must be a |
| // ContentSuggestionsReadingListItem. |
| - (void)openReadingListItem:(CollectionViewItem*)item { |
| ContentSuggestionsReadingListItem* readingListItem = |
| base::mac::ObjCCastStrict<ContentSuggestionsReadingListItem>(item); |
| [self.suggestionCommandHandler openURL:readingListItem.url]; |
| } |
| |
| // Opens the article associated with |item|. |item| must be a |
| // ContentSuggestionsArticleItem. |
| - (void)openArticle:(CollectionViewItem*)item { |
| ContentSuggestionsArticleItem* article = |
| base::mac::ObjCCastStrict<ContentSuggestionsArticleItem>(item); |
| [self.suggestionCommandHandler openURL:article.articleURL]; |
| } |
| |
| - (void)handleLongPress:(UILongPressGestureRecognizer*)gestureRecognizer { |
| if (self.editor.editing || |
| gestureRecognizer.state != UIGestureRecognizerStateBegan) { |
| return; |
| } |
| |
| CGPoint touchLocation = |
| [gestureRecognizer locationOfTouch:0 inView:self.collectionView]; |
| NSIndexPath* touchedItemIndexPath = |
| [self.collectionView indexPathForItemAtPoint:touchLocation]; |
| if (!touchedItemIndexPath || |
| ![self.collectionViewModel hasItemAtIndexPath:touchedItemIndexPath]) { |
| // Make sure there is an item at this position. |
| return; |
| } |
| CollectionViewItem* touchedItem = |
| [self.collectionViewModel itemAtIndexPath:touchedItemIndexPath]; |
| |
| if ([self.collectionUpdater contentSuggestionTypeForItem:touchedItem] != |
| ContentSuggestionTypeArticle) { |
| // Only trigger context menu on articles. |
| return; |
| } |
| |
| ContentSuggestionsArticleItem* articleItem = |
| base::mac::ObjCCastStrict<ContentSuggestionsArticleItem>(touchedItem); |
| |
| [self.suggestionCommandHandler |
| displayContextMenuForArticle:articleItem |
| atPoint:touchLocation |
| atIndexPath:touchedItemIndexPath]; |
| } |
| |
| // Checks if the |section| is empty and add an empty element if it is the case. |
| // Must be called from inside a performBatchUpdates: block. |
| - (void)addEmptySectionPlaceholderIfNeeded:(NSInteger)section { |
| if ([self.collectionViewModel numberOfItemsInSection:section] > 0) |
| return; |
| |
| NSIndexPath* emptyItem = |
| [self.collectionUpdater addEmptyItemForSection:section]; |
| [self.collectionView insertItemsAtIndexPaths:@[ emptyItem ]]; |
| } |
| |
| @end |