blob: 225e23aba3e8d9abf8b52e18484a1f093a1f7618 [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/public/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/popup_menu/public/cells/popup_menu_footer_item.h"
#import "ios/chrome/browser/ui/popup_menu/public/cells/popup_menu_item.h"
#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_table_view_controller_delegate.h"
#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_ui_constants.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
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 delegate = _delegate;
@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.delegate popupMenuTableViewController:self
didSelectItem:[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.delegate popupMenuTableViewController:self
didSelectItem:[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];
});
}
@end