Chromium Code Reviews| Index: ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm |
| diff --git a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm |
| index 5da64ccf4d6905a3260a8ef48439e1b92413bfbe..02f93f2d2179cf9508f719fa8d2898f62cb9b34c 100644 |
| --- a/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm |
| +++ b/ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.mm |
| @@ -4,10 +4,28 @@ |
| #import "ios/chrome/browser/ui/reading_list/reading_list_view_controller_container.h" |
| -#import "ios/chrome/browser/ui/uikit_ui_util.h" |
| +#import <MobileCoreServices/MobileCoreServices.h> |
| + |
| +#import "base/mac/foundation_util.h" |
| +#include "base/metrics/user_metrics.h" |
| +#include "base/metrics/user_metrics_action.h" |
| +#include "base/strings/sys_string_conversions.h" |
| +#include "components/reading_list/ios/reading_list_model.h" |
| +#include "ios/chrome/browser/reading_list/offline_url_utils.h" |
| +#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.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/keyboard/UIKeyCommand+Chrome.h" |
| +#import "ios/chrome/browser/ui/reading_list/reading_list_collection_view_item.h" |
| #import "ios/chrome/browser/ui/reading_list/reading_list_toolbar.h" |
| #import "ios/chrome/browser/ui/reading_list/reading_list_view_controller.h" |
| +#import "ios/chrome/browser/ui/uikit_ui_util.h" |
| +#import "ios/chrome/browser/ui/url_loader.h" |
| +#include "ios/chrome/grit/ios_strings.h" |
| +#include "ios/web/public/navigation_manager.h" |
| +#import "net/base/mac/url_conversions.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| +#include "ui/strings/grit/ui_strings.h" |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| @@ -30,31 +48,55 @@ typedef NS_ENUM(NSInteger, LayoutPriority) { |
| ReadingListToolbarHeightDelegate> { |
| // Toolbar with the actions. |
| ReadingListToolbar* _toolbar; |
| - ReadingListViewController* _collectionController; |
| // This constraint control the expanded mode of the toolbar. |
| NSLayoutConstraint* _expandedToolbarConstraint; |
| + // Alert displayed when the user long press an entry. |
| + AlertCoordinator* _alertCoordinator; |
| } |
| +// UrlLoader for navigating to entries. |
| +@property(nonatomic, weak, readonly) id<UrlLoader> URLLoader; |
| + |
| +// Closes the ReadingList view. |
| +- (void)dismiss; |
| +// Opens |entry| in a new tab. |
| +- (void)openInNewTabEntry:(const ReadingListEntry*)entry |
| + fromReadingListViewController: |
| + (ReadingListViewController*)readingListViewController; |
| +// Opens |entry| in a new incognito tab. |
| +- (void)openInNewIncognitoTabEntry:(const ReadingListEntry*)entry |
| + fromReadingListViewController: |
| + (ReadingListViewController*)readingListViewController; |
| +// Opens the offline version of |entry|. |
| +- (void)openOfflineEntry:(const ReadingListEntry*)entry |
| + fromReadingListViewController: |
| + (ReadingListViewController*)readingListViewController; |
| +// Copies |URL| in the pasteboard. |
| +- (void)copyURL:(const GURL&)URL; |
| + |
| @end |
| @implementation ReadingListViewControllerContainer |
| +@synthesize collectionController = _collectionController; |
| +@synthesize URLLoader = _URLLoader; |
| + |
| - (instancetype)initWithModel:(ReadingListModel*)model |
| - tabModel:(TabModel*)tabModel |
| + loader:(id<UrlLoader>)loader |
| largeIconService:(favicon::LargeIconService*)largeIconService |
| readingListDownloadService: |
| (ReadingListDownloadService*)readingListDownloadService { |
| self = [super initWithNibName:nil bundle:nil]; |
| if (self) { |
| + _URLLoader = loader; |
| _toolbar = [[ReadingListToolbar alloc] initWithFrame:CGRectZero]; |
| _toolbar.heightDelegate = self; |
| _collectionController = [[ReadingListViewController alloc] |
| initWithModel:model |
| - tabModel:tabModel |
| largeIconService:largeIconService |
| readingListDownloadService:readingListDownloadService |
| toolbar:_toolbar]; |
| - _collectionController.audience = self; |
| + _collectionController.delegate = self; |
| // Configure modal presentation. |
| [self setModalPresentationStyle:UIModalPresentationFormSheet]; |
| @@ -64,22 +106,23 @@ typedef NS_ENUM(NSInteger, LayoutPriority) { |
| } |
| - (void)viewDidLoad { |
| - [self addChildViewController:_collectionController]; |
| - [self.view addSubview:[_collectionController view]]; |
| - [_collectionController didMoveToParentViewController:self]; |
| + [self addChildViewController:self.collectionController]; |
| + [self.view addSubview:[self.collectionController view]]; |
| + [self.collectionController didMoveToParentViewController:self]; |
| [_toolbar setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| - [[_collectionController view] |
| + [[self.collectionController view] |
| setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| - NSDictionary* views = @{ @"collection" : [_collectionController view] }; |
| + NSDictionary* views = @{ @"collection" : [self.collectionController view] }; |
| NSArray* constraints = @[ @"V:|[collection]", @"H:|[collection]|" ]; |
| ApplyVisualConstraints(constraints, views); |
| // This constraints is not required. It will be activated only when the |
| // toolbar is not present, allowing the collection to take the whole page. |
| - NSLayoutConstraint* constraint = [[_collectionController view].bottomAnchor |
| - constraintEqualToAnchor:[self view].bottomAnchor]; |
| + NSLayoutConstraint* constraint = |
| + [[self.collectionController view].bottomAnchor |
| + constraintEqualToAnchor:[self view].bottomAnchor]; |
| constraint.priority = LayoutPriorityLow; |
| constraint.active = YES; |
| } |
| @@ -95,15 +138,17 @@ typedef NS_ENUM(NSInteger, LayoutPriority) { |
| return YES; |
| } |
| -#pragma mark - ReadingListViewControllerAudience |
| +#pragma mark - ReadingListViewControllerDelegate |
| -- (void)setCollectionHasItems:(BOOL)hasItems { |
| +- (void)readingListViewController: |
| + (ReadingListViewController*)readingListViewController |
| + hasItems:(BOOL)hasItems { |
| if (hasItems) { |
| // If there are items, add the toolbar. |
| [self.view addSubview:_toolbar]; |
| NSDictionary* views = @{ |
| @"toolbar" : _toolbar, |
| - @"collection" : [_collectionController view] |
| + @"collection" : [self.collectionController view] |
| }; |
| NSArray* constraints = @[ @"V:[collection][toolbar]|", @"H:|[toolbar]|" ]; |
| ApplyVisualConstraints(constraints, views); |
| @@ -122,27 +167,156 @@ typedef NS_ENUM(NSInteger, LayoutPriority) { |
| } |
| } |
| -- (void)dismiss { |
| - [self.presentingViewController dismissViewControllerAnimated:YES |
| - completion:nil]; |
| +- (void)dismissReadingListViewController: |
| + (ReadingListViewController*)readingListViewController { |
| + [self dismiss]; |
| +} |
| + |
| +- (void) |
| +readingListViewController:(ReadingListViewController*)readingListViewController |
| + didRecognizeLongPress:(UILongPressGestureRecognizer*)gestureRecognizer |
| + duringEdit:(BOOL)duringEdit { |
| + if (gestureRecognizer.numberOfTouches != 1 || duringEdit || |
|
stkhapugin
2017/01/30 10:34:53
Instead configure numberOfTouchesRequired when you
gambard
2017/01/31 09:35:40
The idea was to move as much intelligence as possi
stkhapugin
2017/02/01 10:42:44
Per offline discussion:
CollectionViewController
|
| + gestureRecognizer.state != UIGestureRecognizerStateBegan) { |
| + return; |
| + } |
| + |
| + CGPoint touchLocation = [gestureRecognizer |
| + locationOfTouch:0 |
| + inView:readingListViewController.collectionView]; |
| + NSIndexPath* touchedItemIndexPath = [readingListViewController.collectionView |
| + indexPathForItemAtPoint:touchLocation]; |
| + if (!touchedItemIndexPath || |
| + ![readingListViewController.collectionViewModel |
| + hasItemAtIndexPath:touchedItemIndexPath]) { |
| + // Make sure there is an item at this position. |
| + return; |
| + } |
| + CollectionViewItem* touchedItem = |
| + [readingListViewController.collectionViewModel |
| + itemAtIndexPath:touchedItemIndexPath]; |
| + |
| + if (touchedItem == [readingListViewController.collectionViewModel |
| + headerForSection:touchedItemIndexPath.section] || |
| + ![touchedItem isKindOfClass:[ReadingListCollectionViewItem class]]) { |
| + // Do trigger context menu on headers. |
| + return; |
| + } |
| + |
| + ReadingListCollectionViewItem* readingListItem = |
| + base::mac::ObjCCastStrict<ReadingListCollectionViewItem>(touchedItem); |
| + const ReadingListEntry* entry = |
| + readingListViewController.readingListModel->GetEntryByURL( |
| + readingListItem.url); |
| + |
| + if (!entry) { |
| + [readingListViewController reloadData]; |
| + return; |
| + } |
| + |
| + __weak ReadingListViewControllerContainer* weakSelf = self; |
| + |
| + _alertCoordinator = [[ActionSheetCoordinator alloc] |
| + initWithBaseViewController:self |
| + title:readingListItem.text |
| + message:readingListItem.detailText |
| + rect:CGRectMake(touchLocation.x, touchLocation.y, 0, |
| + 0) |
| + view:readingListViewController.collectionView]; |
| + |
| + NSString* openInNewTabTitle = |
| + l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB); |
| + [_alertCoordinator |
| + addItemWithTitle:openInNewTabTitle |
| + action:^{ |
| + [weakSelf openInNewTabEntry:entry |
|
jif
2017/01/30 17:33:27
The object pointed to by |entry| may have been del
jif
2017/01/30 17:33:27
We are supposed to use a strongSelf.
gambard
2017/01/31 09:35:40
For one function call?
stkhapugin@: as ARC expert,
gambard
2017/01/31 09:35:40
Done.
stkhapugin
2017/01/31 10:18:26
__weak id weakSelf = self;
// No problem. When -fo
|
| + fromReadingListViewController:readingListViewController]; |
| + } |
| + style:UIAlertActionStyleDefault]; |
| + |
| + NSString* openInNewTabIncognitoTitle = |
| + l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB); |
| + [_alertCoordinator |
| + addItemWithTitle:openInNewTabIncognitoTitle |
| + action:^{ |
| + [weakSelf |
| + openInNewIncognitoTabEntry:entry |
| + fromReadingListViewController:readingListViewController]; |
| + } |
| + style:UIAlertActionStyleDefault]; |
| + |
| + NSString* copyLinkTitle = |
| + l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_COPY); |
| + [_alertCoordinator addItemWithTitle:copyLinkTitle |
| + action:^{ |
| + [weakSelf copyURL:readingListItem.url]; |
| + } |
| + style:UIAlertActionStyleDefault]; |
| + |
| + if (entry->DistilledState() == ReadingListEntry::PROCESSED) { |
| + NSString* viewOfflineVersionTitle = |
| + l10n_util::GetNSString(IDS_IOS_READING_LIST_CONTENT_CONTEXT_OFFLINE); |
| + [_alertCoordinator addItemWithTitle:viewOfflineVersionTitle |
| + action:^{ |
| + [weakSelf openOfflineEntry:entry |
| + fromReadingListViewController: |
| + readingListViewController]; |
| + } |
| + style:UIAlertActionStyleDefault]; |
| + } |
| + |
| + [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_APP_CANCEL) |
| + action:nil |
| + style:UIAlertActionStyleCancel]; |
| + |
| + [_alertCoordinator start]; |
| +} |
| + |
| +- (void)readingListViewController: |
| + (ReadingListViewController*)readingListViewController |
| + openItemAtIndexPath:(NSIndexPath*)indexPath { |
| + ReadingListCollectionViewItem* readingListItem = |
| + base::mac::ObjCCastStrict<ReadingListCollectionViewItem>( |
| + [readingListViewController.collectionViewModel |
| + itemAtIndexPath:indexPath]); |
| + const ReadingListEntry* entry = |
| + readingListViewController.readingListModel->GetEntryByURL( |
| + readingListItem.url); |
| + |
| + if (!entry) { |
| + [readingListViewController reloadData]; |
|
jif
2017/01/30 17:33:27
Do we really need to call reloadData?
gambard
2017/01/31 09:35:40
This happens when the entry does not exist in the
|
| + return; |
| + } |
| + |
| + // Reset observer to prevent further model update notifications. |
| + [readingListViewController stopObservingReadingListModel]; |
| + |
| + base::RecordAction(base::UserMetricsAction("MobileReadingListOpen")); |
| + |
| + [self.URLLoader loadURL:entry->URL() |
| + referrer:web::Referrer() |
| + transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK |
| + rendererInitiated:NO]; |
| + |
| + [self dismiss]; |
| } |
| #pragma mark - ReadingListToolbarActionTarget |
| - (void)markPressed { |
| - [_collectionController markPressed]; |
| + [self.collectionController markPressed]; |
| } |
| - (void)deletePressed { |
| - [_collectionController deletePressed]; |
| + [self.collectionController deletePressed]; |
| } |
| - (void)enterEditingModePressed { |
| - [_collectionController enterEditingModePressed]; |
| + [self.collectionController enterEditingModePressed]; |
| } |
| - (void)exitEditingModePressed { |
| - [_collectionController exitEditingModePressed]; |
| + [self.collectionController exitEditingModePressed]; |
| } |
| #pragma mark - ReadingListToolbarHeightDelegate |
| @@ -161,6 +335,85 @@ typedef NS_ENUM(NSInteger, LayoutPriority) { |
| }); |
| } |
| +#pragma mark - Private |
| + |
| +- (void)dismiss { |
| + [self.presentingViewController dismissViewControllerAnimated:YES |
|
stkhapugin
2017/01/30 10:34:53
Ideally view controllers should not dismiss themse
gambard
2017/01/31 09:35:40
Yes. This will change when the use of coordinators
|
| + completion:nil]; |
| +} |
| + |
| +- (void)openInNewTabEntry:(const ReadingListEntry*)entry |
| + fromReadingListViewController: |
| + (ReadingListViewController*)readingListViewController { |
| + // Reset observer to prevent further model update notifications. |
| + [readingListViewController stopObservingReadingListModel]; |
| + |
| + base::RecordAction(base::UserMetricsAction("MobileReadingListOpen")); |
| + |
| + [self.URLLoader webPageOrderedOpen:entry->URL() |
| + referrer:web::Referrer() |
| + windowName:nil |
| + inIncognito:NO |
| + inBackground:NO |
| + appendTo:kLastTab]; |
| + |
| + [self dismiss]; |
| +} |
| + |
| +- (void)openInNewIncognitoTabEntry:(const ReadingListEntry*)entry |
| + fromReadingListViewController: |
| + (ReadingListViewController*)readingListViewController { |
| + // Reset observer to prevent further model update notifications. |
| + [readingListViewController stopObservingReadingListModel]; |
| + |
| + base::RecordAction(base::UserMetricsAction("MobileReadingListOpen")); |
| + |
| + [self.URLLoader webPageOrderedOpen:entry->URL() |
| + referrer:web::Referrer() |
| + windowName:nil |
| + inIncognito:YES |
| + inBackground:NO |
| + appendTo:kLastTab]; |
| + |
| + [self dismiss]; |
| +} |
| + |
| +- (void)openOfflineEntry:(const ReadingListEntry*)entry |
| + fromReadingListViewController: |
| + (ReadingListViewController*)readingListViewController { |
| + if (entry->DistilledState() != ReadingListEntry::PROCESSED) { |
| + return; |
| + } |
| + |
| + // Reset observer to prevent further model update notifications. |
| + [readingListViewController stopObservingReadingListModel]; |
| + |
| + base::RecordAction(base::UserMetricsAction("MobileReadingListOpen")); |
| + |
| + GURL url = reading_list::OfflineURLForPath( |
| + entry->DistilledPath(), entry->URL(), entry->DistilledURL()); |
| + |
| + [self.URLLoader loadURL:url |
| + referrer:web::Referrer() |
| + transition:ui::PAGE_TRANSITION_AUTO_BOOKMARK |
| + rendererInitiated:NO]; |
| + |
| + readingListViewController.readingListModel->SetReadStatus(entry->URL(), true); |
| + |
| + [self dismiss]; |
| +} |
| + |
| +- (void)copyURL:(const GURL&)URL { |
|
stkhapugin
2017/01/30 10:34:53
Please rename to storeURLInPasteboard or something
gambard
2017/01/31 09:35:40
Done.
|
| + DCHECK(URL.is_valid()); |
|
stkhapugin
2017/01/30 10:34:53
I think this is the fourth time in our code base t
gambard
2017/01/31 09:35:40
Done.
|
| + NSData* plainText = [base::SysUTF8ToNSString(URL.spec()) |
| + dataUsingEncoding:NSUTF8StringEncoding]; |
| + NSDictionary* copiedItem = @{ |
| + (NSString*)kUTTypeURL : net::NSURLWithGURL(URL), |
| + (NSString*)kUTTypeUTF8PlainText : plainText, |
| + }; |
| + [[UIPasteboard generalPasteboard] setItems:@[ copiedItem ]]; |
| +} |
| + |
| #pragma mark - UIResponder |
| - (NSArray*)keyCommands { |