blob: 42b4a4f00b6439379d2b0ffcf4a9ccdc356f5fbb [file] [log] [blame]
// Copyright 2018 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/popup_menu/popup_menu_table_view_controller.h"
#include "base/ios/ios_util.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#import "ios/chrome/browser/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/commands/browser_commands.h"
#import "ios/chrome/browser/ui/commands/load_query_commands.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/popup_menu/cells/popup_menu_footer_item.h"
#import "ios/chrome/browser/ui/popup_menu/cells/popup_menu_item.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_table_view_controller_commands.h"
#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using base::UserMetricsAction;
namespace {
const CGFloat kFooterHeight = 21;
const CGFloat kPopupMenuVerticalInsets = 7;
const CGFloat kScrollIndicatorVerticalInsets = 11;
} // namespace
@interface PopupMenuTableViewController ()
// Whether the -viewDidAppear: callback has been called.
@property(nonatomic, assign) BOOL viewDidAppear;
@end
@implementation PopupMenuTableViewController
@dynamic tableViewModel;
@synthesize baseViewController = _baseViewController;
@synthesize commandHandler = _commandHandler;
@synthesize dispatcher = _dispatcher;
@synthesize itemToHighlight = _itemToHighlight;
@synthesize viewDidAppear = _viewDidAppear;
- (instancetype)init {
return [super initWithTableViewStyle:UITableViewStyleGrouped
appBarStyle:ChromeTableViewControllerStyleNoAppBar];
}
- (void)selectRowAtPoint:(CGPoint)point {
NSIndexPath* rowIndexPath = [self indexPathForInnerRowAtPoint:point];
if (!rowIndexPath)
return;
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:rowIndexPath];
if (!cell.userInteractionEnabled)
return;
base::RecordAction(base::UserMetricsAction("MobilePopupMenuSwipeToSelect"));
[self executeActionForItem:[self.tableViewModel itemAtIndexPath:rowIndexPath]
origin:[cell convertPoint:cell.center toView:nil]];
}
- (void)focusRowAtPoint:(CGPoint)point {
NSIndexPath* rowIndexPath = [self indexPathForInnerRowAtPoint:point];
BOOL rowAlreadySelected = NO;
NSArray<NSIndexPath*>* selectedRows =
[self.tableView indexPathsForSelectedRows];
for (NSIndexPath* selectedIndexPath in selectedRows) {
if (selectedIndexPath == rowIndexPath) {
rowAlreadySelected = YES;
continue;
}
[self.tableView deselectRowAtIndexPath:selectedIndexPath animated:NO];
}
if (!rowAlreadySelected && rowIndexPath) {
[self.tableView selectRowAtIndexPath:rowIndexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
TriggerHapticFeedbackForSelectionChange();
}
}
#pragma mark - PopupMenuConsumer
- (void)setItemToHighlight:(TableViewItem<PopupMenuItem>*)itemToHighlight {
DCHECK_GT(self.tableViewModel.numberOfSections, 0L);
_itemToHighlight = itemToHighlight;
if (itemToHighlight && self.viewDidAppear) {
[self highlightItem:itemToHighlight repeat:YES];
}
}
- (void)setPopupMenuItems:
(NSArray<NSArray<TableViewItem<PopupMenuItem>*>*>*)items {
[super loadModel];
for (NSUInteger section = 0; section < items.count; section++) {
NSInteger sectionIdentifier = kSectionIdentifierEnumZero + section;
[self.tableViewModel addSectionWithIdentifier:sectionIdentifier];
for (TableViewItem<PopupMenuItem>* item in items[section]) {
[self.tableViewModel addItem:item
toSectionWithIdentifier:sectionIdentifier];
}
if (section != items.count - 1) {
// Add a footer for all sections except the last one.
TableViewHeaderFooterItem* footer =
[[PopupMenuFooterItem alloc] initWithType:kItemTypeEnumZero];
[self.tableViewModel setFooter:footer
forSectionWithIdentifier:sectionIdentifier];
}
}
[self.tableView reloadData];
}
- (void)itemsHaveChanged:(NSArray<TableViewItem<PopupMenuItem>*>*)items {
[self reconfigureCellsForItems:items];
}
#pragma mark - UIViewController
- (void)viewDidLoad {
self.styler.tableViewBackgroundColor = nil;
[super viewDidLoad];
self.tableView.contentInset = UIEdgeInsetsMake(kPopupMenuVerticalInsets, 0,
kPopupMenuVerticalInsets, 0);
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(
kScrollIndicatorVerticalInsets, 0, kScrollIndicatorVerticalInsets, 0);
self.tableView.rowHeight = 0;
self.tableView.sectionHeaderHeight = 0;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// Adding a tableHeaderView is needed to prevent a wide inset on top of the
// collection.
self.tableView.tableHeaderView = [[UIView alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width,
0.01f)];
self.view.layer.cornerRadius = kPopupMenuCornerRadius;
self.view.layer.masksToBounds = YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.viewDidAppear = YES;
if (self.itemToHighlight) {
[self highlightItem:self.itemToHighlight repeat:YES];
}
}
- (CGSize)preferredContentSize {
CGFloat width = 0;
CGFloat height = 0;
for (NSInteger section = 0; section < [self.tableViewModel numberOfSections];
section++) {
NSInteger sectionIdentifier =
[self.tableViewModel sectionIdentifierForSection:section];
for (TableViewItem<PopupMenuItem>* item in
[self.tableViewModel itemsInSectionWithIdentifier:sectionIdentifier]) {
CGSize sizeForCell = [item cellSizeForWidth:self.view.bounds.size.width];
width = MAX(width, ceil(sizeForCell.width));
height += sizeForCell.height;
}
// Add the separator height (only available the non-final sections).
height += [self tableView:self.tableView heightForFooterInSection:section];
}
height +=
self.tableView.contentInset.top + self.tableView.contentInset.bottom;
return CGSizeMake(width, ceil(height));
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
UIView* cell = [self.tableView cellForRowAtIndexPath:indexPath];
CGPoint center = [cell convertPoint:cell.center toView:nil];
[self executeActionForItem:[self.tableViewModel itemAtIndexPath:indexPath]
origin:center];
}
- (CGFloat)tableView:(UITableView*)tableView
heightForFooterInSection:(NSInteger)section {
if (section == self.tableViewModel.numberOfSections - 1)
return 0;
return kFooterHeight;
}
- (CGFloat)tableView:(UITableView*)tableView
heightForRowAtIndexPath:(NSIndexPath*)indexPath {
TableViewItem<PopupMenuItem>* item =
[self.tableViewModel itemAtIndexPath:indexPath];
return [item cellSizeForWidth:self.view.bounds.size.width].height;
}
#pragma mark - Private
// Returns the index path identifying the the row at the position |point|.
// |point| must be in the window coordinates. Returns nil if |point| is outside
// the bounds of the table view.
- (NSIndexPath*)indexPathForInnerRowAtPoint:(CGPoint)point {
CGPoint pointInTableViewCoordinates =
[self.tableView convertPoint:point fromView:nil];
CGRect insetRect =
CGRectInset(self.tableView.bounds, 0, kPopupMenuVerticalInsets);
BOOL pointInTableViewBounds =
CGRectContainsPoint(insetRect, pointInTableViewCoordinates);
NSIndexPath* indexPath = nil;
if (pointInTableViewBounds) {
indexPath =
[self.tableView indexPathForRowAtPoint:pointInTableViewCoordinates];
}
return indexPath;
}
// Highlights the |item| and |repeat| the highlighting once.
- (void)highlightItem:(TableViewItem<PopupMenuItem>*)item repeat:(BOOL)repeat {
NSIndexPath* indexPath = [self.tableViewModel indexPathForItem:item];
[self.tableView selectRowAtIndexPath:indexPath
animated:YES
scrollPosition:UITableViewScrollPositionNone];
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(kHighlightAnimationDuration * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self unhighlightItem:item repeat:repeat];
});
}
// Removes the highlight from |item| and |repeat| the highlighting once.
- (void)unhighlightItem:(TableViewItem<PopupMenuItem>*)item
repeat:(BOOL)repeat {
NSIndexPath* indexPath = [self.tableViewModel indexPathForItem:item];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if (!repeat)
return;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(kHighlightAnimationDuration * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self highlightItem:item repeat:NO];
});
}
// Executes the action associated with |identifier|, using |origin| as the point
// of origin of the action if one is needed.
- (void)executeActionForItem:(TableViewItem<PopupMenuItem>*)item
origin:(CGPoint)origin {
PopupMenuAction identifier = item.actionIdentifier;
switch (identifier) {
case PopupMenuActionReload:
base::RecordAction(UserMetricsAction("MobileMenuReload"));
[self.dispatcher reload];
break;
case PopupMenuActionStop:
base::RecordAction(UserMetricsAction("MobileMenuStop"));
[self.dispatcher stopLoading];
break;
case PopupMenuActionOpenNewTab:
base::RecordAction(UserMetricsAction("MobileMenuNewTab"));
[self.dispatcher
openURLInNewTab:[OpenNewTabCommand commandWithIncognito:NO
originPoint:origin]];
break;
case PopupMenuActionOpenNewIncognitoTab:
base::RecordAction(UserMetricsAction("MobileMenuNewIncognitoTab"));
[self.dispatcher
openURLInNewTab:[OpenNewTabCommand commandWithIncognito:YES
originPoint:origin]];
break;
case PopupMenuActionReadLater:
base::RecordAction(UserMetricsAction("MobileMenuReadLater"));
[self.commandHandler readPageLater];
break;
case PopupMenuActionPageBookmark:
base::RecordAction(UserMetricsAction("MobileMenuAddToBookmarks"));
[self.dispatcher bookmarkPage];
break;
case PopupMenuActionFindInPage:
base::RecordAction(UserMetricsAction("MobileMenuFindInPage"));
[self.dispatcher showFindInPage];
break;
case PopupMenuActionRequestDesktop:
base::RecordAction(UserMetricsAction("MobileMenuRequestDesktopSite"));
[self.dispatcher requestDesktopSite];
break;
case PopupMenuActionRequestMobile:
base::RecordAction(UserMetricsAction("MobileMenuRequestMobileSite"));
[self.dispatcher requestMobileSite];
break;
case PopupMenuActionSiteInformation:
base::RecordAction(UserMetricsAction("MobileMenuSiteInformation"));
[self.dispatcher
showPageInfoForOriginPoint:self.baseViewController.view.center];
break;
case PopupMenuActionReportIssue:
base::RecordAction(UserMetricsAction("MobileMenuReportAnIssue"));
[self.dispatcher
showReportAnIssueFromViewController:self.baseViewController];
// Dismisses the popup menu without animation to allow the snapshot to be
// taken without the menu presented.
[self.dispatcher dismissPopupMenuAnimated:NO];
break;
case PopupMenuActionHelp:
base::RecordAction(UserMetricsAction("MobileMenuHelp"));
[self.dispatcher showHelpPage];
break;
#if !defined(NDEBUG)
case PopupMenuActionViewSource:
[self.dispatcher viewSource];
break;
#endif // !defined(NDEBUG)
case PopupMenuActionBookmarks:
base::RecordAction(UserMetricsAction("MobileMenuAllBookmarks"));
[self.dispatcher showBookmarksManager];
break;
case PopupMenuActionReadingList:
base::RecordAction(UserMetricsAction("MobileMenuReadingList"));
[self.dispatcher showReadingList];
break;
case PopupMenuActionRecentTabs:
base::RecordAction(UserMetricsAction("MobileMenuRecentTabs"));
[self.dispatcher showRecentTabs];
break;
case PopupMenuActionHistory:
base::RecordAction(UserMetricsAction("MobileMenuHistory"));
[self.dispatcher showHistory];
break;
case PopupMenuActionSettings:
base::RecordAction(UserMetricsAction("MobileMenuSettings"));
[self.dispatcher showSettingsFromViewController:self.baseViewController];
break;
case PopupMenuActionCloseTab:
base::RecordAction(UserMetricsAction("MobileMenuCloseTab"));
[self.dispatcher closeCurrentTab];
break;
case PopupMenuActionNavigate:
// No metrics for this item.
[self.commandHandler navigateToPageForItem:item];
break;
case PopupMenuActionPasteAndGo:
base::RecordAction(UserMetricsAction("MobileMenuPasteAndGo"));
[self.dispatcher loadQuery:[UIPasteboard generalPasteboard].string
immediately:YES];
break;
case PopupMenuActionVoiceSearch:
base::RecordAction(UserMetricsAction("MobileMenuVoiceSearch"));
[self.dispatcher startVoiceSearch];
break;
case PopupMenuActionQRCodeSearch:
base::RecordAction(UserMetricsAction("MobileMenuScanQRCode"));
[self.dispatcher showQRScanner];
break;
}
// Close the tools menu.
[self.dispatcher dismissPopupMenuAnimated:YES];
}
@end