| Index: ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm
|
| diff --git a/ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm b/ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0f19ea9722743c7e398622fe4f1de5829ac4ce05
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/tools_menu/tools_menu_view_controller.mm
|
| @@ -0,0 +1,958 @@
|
| +// Copyright 2014 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/tools_menu/tools_menu_view_controller.h"
|
| +
|
| +#include <stdint.h>
|
| +
|
| +#include "base/ios/ios_util.h"
|
| +#import "base/ios/weak_nsobject.h"
|
| +#include "base/logging.h"
|
| +#include "base/mac/objc_property_releaser.h"
|
| +#include "base/mac/scoped_nsobject.h"
|
| +#include "base/metrics/field_trial.h"
|
| +#include "components/reading_list/core/reading_list_switches.h"
|
| +#include "components/strings/grit/components_strings.h"
|
| +#include "ios/chrome/browser/experimental_flags.h"
|
| +#import "ios/chrome/browser/ui/animation_util.h"
|
| +#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
|
| +#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
|
| +#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notification_delegate.h"
|
| +#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
|
| +#include "ios/chrome/browser/ui/rtl_geometry.h"
|
| +#include "ios/chrome/browser/ui/toolbar/toolbar_resource_macros.h"
|
| +#import "ios/chrome/browser/ui/tools_menu/reading_list_menu_view_item.h"
|
| +#import "ios/chrome/browser/ui/tools_menu/tools_menu_context.h"
|
| +#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
|
| +#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
|
| +#include "ios/chrome/browser/ui/ui_util.h"
|
| +#import "ios/chrome/browser/ui/uikit_ui_util.h"
|
| +#import "ios/chrome/common/material_timing.h"
|
| +#include "ios/chrome/grit/ios_strings.h"
|
| +#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
|
| +#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_provider.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Ink/src/MaterialInk.h"
|
| +#include "ui/base/l10n/l10n_util.h"
|
| +#include "ui/base/l10n/l10n_util_mac.h"
|
| +#include "ui/base/resource/resource_bundle.h"
|
| +
|
| +using ios::material::TimingFunction;
|
| +
|
| +NSString* const kToolsMenuNewTabId = @"kToolsMenuNewTabId";
|
| +NSString* const kToolsMenuNewIncognitoTabId = @"kToolsMenuNewIncognitoTabId";
|
| +NSString* const kToolsMenuCloseAllTabsId = @"kToolsMenuCloseAllTabsId";
|
| +NSString* const kToolsMenuCloseAllIncognitoTabsId =
|
| + @"kToolsMenuCloseAllIncognitoTabsId";
|
| +NSString* const kToolsMenuBookmarksId = @"kToolsMenuBookmarksId";
|
| +NSString* const kToolsMenuReadingListId = @"kToolsMenuReadingListId";
|
| +NSString* const kToolsMenuOtherDevicesId = @"kToolsMenuOtherDevicesId";
|
| +NSString* const kToolsMenuHistoryId = @"kToolsMenuHistoryId";
|
| +NSString* const kToolsMenuReportAnIssueId = @"kToolsMenuReportAnIssueId";
|
| +NSString* const kToolsMenuFindInPageId = @"kToolsMenuFindInPageId";
|
| +NSString* const kToolsMenuReaderMode = @"kToolsMenuReaderMode";
|
| +NSString* const kToolsMenuRequestDesktopId = @"kToolsMenuRequestDesktopId";
|
| +NSString* const kToolsMenuSettingsId = @"kToolsMenuSettingsId";
|
| +NSString* const kToolsMenuHelpId = @"kToolsMenuHelpId";
|
| +
|
| +namespace {
|
| +
|
| +// Time for ink to fade into view.
|
| +static const NSTimeInterval kMDCInkTouchDelayInterval = 0.15;
|
| +
|
| +static const CGFloat kMenuItemHeight = 48;
|
| +
|
| +static NSString* const kToolsItemCellID = @"ToolsItemCellID";
|
| +
|
| +// Menu items can be marked as visible or not when Incognito is enabled.
|
| +// The following bits are used for |visibility| field in |MenuItemInfo|.
|
| +const NSInteger kVisibleIncognitoOnly = 1 << 0;
|
| +const NSInteger kVisibleNotIncognitoOnly = 1 << 1;
|
| +
|
| +// Initialization table for all possible commands to initialize the
|
| +// tools menu at run time. Data initialized into this structure is not mutable.
|
| +struct MenuItemInfo {
|
| + int title_id;
|
| + NSString* accessibility_id;
|
| + int command_id;
|
| + int toolbar_types;
|
| + // |visibility| is applied if a menu item is included for a given
|
| + // |toolbar_types|. A value of 0 means the menu item is always visible for
|
| + // the given |toolbar_types|.
|
| + int visibility;
|
| + // Custom class, if any, for the menu item, or |nil|.
|
| + Class item_class;
|
| +};
|
| +
|
| +// Flags for different toolbar types
|
| +typedef NS_OPTIONS(NSUInteger, kToolbarType) {
|
| + // clang-format off
|
| + kToolbarTypeNone = 0,
|
| + kToolbarTypeWebiPhone = 1 << 0,
|
| + kToolbarTypeWebiPad = 1 << 1,
|
| + kToolbarTypeNoTabsiPad = 1 << 2,
|
| + kToolbarTypeSwitcheriPhone = 1 << 3,
|
| + kToolbarTypeWebAll = kToolbarTypeWebiPhone | kToolbarTypeWebiPad,
|
| + kToolbarTypeAll = kToolbarTypeWebAll |
|
| + kToolbarTypeSwitcheriPhone |
|
| + kToolbarTypeNoTabsiPad,
|
| + // clang-format on
|
| +};
|
| +
|
| +// Declare all the possible items.
|
| +static MenuItemInfo itemInfoList[] = {
|
| + // clang-format off
|
| + { IDS_IOS_TOOLS_MENU_NEW_TAB, kToolsMenuNewTabId,
|
| + IDC_NEW_TAB, kToolbarTypeAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB, kToolsMenuNewIncognitoTabId,
|
| + IDC_NEW_INCOGNITO_TAB, kToolbarTypeAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_CLOSE_ALL_TABS, kToolsMenuCloseAllTabsId,
|
| + IDC_CLOSE_ALL_TABS, kToolbarTypeSwitcheriPhone,
|
| + kVisibleNotIncognitoOnly, nil },
|
| + { IDS_IOS_TOOLS_MENU_CLOSE_ALL_INCOGNITO_TABS,
|
| + kToolsMenuCloseAllIncognitoTabsId,
|
| + IDC_CLOSE_ALL_INCOGNITO_TABS, kToolbarTypeSwitcheriPhone,
|
| + kVisibleIncognitoOnly, nil },
|
| + { IDS_IOS_TOOLS_MENU_BOOKMARKS, kToolsMenuBookmarksId,
|
| + IDC_SHOW_BOOKMARK_MANAGER, kToolbarTypeWebAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_READING_LIST, kToolsMenuReadingListId,
|
| + IDC_SHOW_READING_LIST, kToolbarTypeWebAll,
|
| + 0, [ReadingListMenuViewItem class] },
|
| + { IDS_IOS_TOOLS_MENU_RECENT_TABS, kToolsMenuOtherDevicesId,
|
| + IDC_SHOW_OTHER_DEVICES, kToolbarTypeWebAll,
|
| + kVisibleNotIncognitoOnly, nil },
|
| + { IDS_HISTORY_SHOW_HISTORY, kToolsMenuHistoryId,
|
| + IDC_SHOW_HISTORY, kToolbarTypeWebAll,
|
| + 0, nil },
|
| + { IDS_IOS_OPTIONS_REPORT_AN_ISSUE, kToolsMenuReportAnIssueId,
|
| + IDC_REPORT_AN_ISSUE, kToolbarTypeAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_FIND_IN_PAGE, kToolsMenuFindInPageId,
|
| + IDC_FIND, kToolbarTypeWebAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_REQUEST_DESKTOP_SITE, kToolsMenuRequestDesktopId,
|
| + IDC_REQUEST_DESKTOP_SITE, kToolbarTypeWebAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_READER_MODE, kToolsMenuReaderMode,
|
| + IDC_READER_MODE, kToolbarTypeWebAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_SETTINGS, kToolsMenuSettingsId,
|
| + IDC_OPTIONS, kToolbarTypeAll,
|
| + 0, nil },
|
| + { IDS_IOS_TOOLS_MENU_HELP_MOBILE, kToolsMenuHelpId,
|
| + IDC_HELP_PAGE_VIA_MENU, kToolbarTypeWebAll,
|
| + 0, nil },
|
| + // clang-format on
|
| +};
|
| +
|
| +NS_INLINE BOOL ItemShouldBeVisible(const MenuItemInfo& item,
|
| + BOOL incognito,
|
| + kToolbarType toolbarType) {
|
| + if (!(item.toolbar_types & toolbarType))
|
| + return NO;
|
| +
|
| + if (incognito && (item.visibility & kVisibleNotIncognitoOnly))
|
| + return NO;
|
| +
|
| + if (!incognito && (item.visibility & kVisibleIncognitoOnly))
|
| + return NO;
|
| +
|
| + if (item.title_id == IDS_IOS_TOOLBAR_SHOW_TABS) {
|
| + if (!IsIPadIdiom() || !experimental_flags::IsTabSwitcherEnabled()) {
|
| + return NO;
|
| + }
|
| + }
|
| +
|
| + if (item.title_id == IDS_IOS_TOOLS_MENU_READER_MODE) {
|
| + if (!experimental_flags::IsReaderModeEnabled()) {
|
| + return NO;
|
| + }
|
| + }
|
| +
|
| + if (item.title_id == IDS_IOS_TOOLS_MENU_READING_LIST) {
|
| + if (!reading_list::switches::IsReadingListEnabled()) {
|
| + return NO;
|
| + }
|
| + }
|
| +
|
| + if (item.title_id == IDS_IOS_OPTIONS_REPORT_AN_ISSUE) {
|
| + if (!ios::GetChromeBrowserProvider()
|
| + ->GetUserFeedbackProvider()
|
| + ->IsUserFeedbackEnabled()) {
|
| + return NO;
|
| + }
|
| + }
|
| +
|
| + return YES;
|
| +}
|
| +
|
| +NS_INLINE void AnimateInViews(NSArray* views,
|
| + CGFloat initialX,
|
| + CGFloat initialY) {
|
| + [views enumerateObjectsUsingBlock:^(UIView* view, NSUInteger index,
|
| + BOOL* stop) {
|
| + CGFloat beginTime = index * .035;
|
| + CABasicAnimation* transformAnimation =
|
| + [CABasicAnimation animationWithKeyPath:@"transform"];
|
| + [transformAnimation
|
| + setFromValue:[NSValue
|
| + valueWithCATransform3D:CATransform3DMakeTranslation(
|
| + initialX, initialY, 0)]];
|
| + [transformAnimation
|
| + setToValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
|
| + [transformAnimation setDuration:ios::material::kDuration1];
|
| + [transformAnimation setBeginTime:beginTime];
|
| +
|
| + CAAnimation* fadeAnimation = OpacityAnimationMake(0, 1);
|
| + [fadeAnimation setDuration:ios::material::kDuration1];
|
| + [fadeAnimation
|
| + setTimingFunction:TimingFunction(ios::material::CurveEaseOut)];
|
| + [fadeAnimation setBeginTime:beginTime];
|
| +
|
| + [[view layer]
|
| + addAnimation:AnimationGroupMake(@[ transformAnimation, fadeAnimation ])
|
| + forKey:@"animateIn"];
|
| + }];
|
| +}
|
| +} // anonymous namespace
|
| +
|
| +@interface ToolsMenuButton : UIButton
|
| +@end
|
| +
|
| +@implementation ToolsMenuButton
|
| +
|
| +@end
|
| +
|
| +@interface ToolsMenuCollectionView : UICollectionView
|
| +@property(nonatomic, assign) CGPoint touchBeginPoint;
|
| +@property(nonatomic, assign) CGPoint touchEndPoint;
|
| +@end
|
| +
|
| +@implementation ToolsMenuCollectionView
|
| +
|
| +@synthesize touchBeginPoint = _touchBeginPoint;
|
| +@synthesize touchEndPoint = _touchEndPoint;
|
| +
|
| +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
|
| + _touchBeginPoint = [[touches anyObject] locationInView:self];
|
| + [super touchesBegan:touches withEvent:event];
|
| +}
|
| +
|
| +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
|
| + _touchEndPoint = [[touches anyObject] locationInView:self];
|
| + [super touchesEnded:touches withEvent:event];
|
| +}
|
| +
|
| +@end
|
| +
|
| +@interface ToolsMenuViewToolsCell : UICollectionViewCell
|
| +@property(nonatomic, retain) ToolsMenuButton* reloadButton;
|
| +@property(nonatomic, retain) ToolsMenuButton* shareButton;
|
| +@property(nonatomic, retain) ToolsMenuButton* starButton;
|
| +@property(nonatomic, retain) ToolsMenuButton* starredButton;
|
| +@property(nonatomic, retain) ToolsMenuButton* stopButton;
|
| +@property(nonatomic, retain) ToolsMenuButton* toolsButton;
|
| +@end
|
| +
|
| +@implementation ToolsMenuViewToolsCell {
|
| + base::mac::ObjCPropertyReleaser _propertyReleaser_ToolsMenuViewToolsCell;
|
| +}
|
| +
|
| +@synthesize reloadButton = _reloadButton;
|
| +@synthesize shareButton = _shareButton;
|
| +@synthesize starButton = _starButton;
|
| +@synthesize starredButton = _starredButton;
|
| +@synthesize stopButton = _stopButton;
|
| +@synthesize toolsButton = _toolsButton;
|
| +
|
| +- (instancetype)initWithCoder:(NSCoder*)aDecoder {
|
| + self = [super initWithCoder:aDecoder];
|
| + if (self)
|
| + [self commonInitialization];
|
| +
|
| + return self;
|
| +}
|
| +
|
| +- (instancetype)initWithFrame:(CGRect)frame {
|
| + self = [super initWithFrame:frame];
|
| + if (self)
|
| + [self commonInitialization];
|
| +
|
| + return self;
|
| +}
|
| +
|
| +- (void)commonInitialization {
|
| + _propertyReleaser_ToolsMenuViewToolsCell.Init(self,
|
| + [ToolsMenuViewToolsCell class]);
|
| +
|
| + [self setBackgroundColor:[UIColor whiteColor]];
|
| + [self setOpaque:YES];
|
| +
|
| + int star[2][3] = TOOLBAR_IDR_TWO_STATE(STAR);
|
| + _starButton = [self newButtonForImageIds:star
|
| + commandID:IDC_BOOKMARK_PAGE
|
| + accessibilityLabelID:IDS_BOOKMARK_ADD_EDITOR_TITLE
|
| + automationName:@"Add Bookmark"];
|
| +
|
| + int star_pressed[2][3] = TOOLBAR_IDR_ONE_STATE(STAR_PRESSED);
|
| + _starredButton = [self newButtonForImageIds:star_pressed
|
| + commandID:IDC_TEMP_EDIT_BOOKMARK
|
| + accessibilityLabelID:IDS_IOS_TOOLS_MENU_EDIT_BOOKMARK
|
| + automationName:@"Edit Bookmark"];
|
| +
|
| + int reload[2][3] = TOOLBAR_IDR_TWO_STATE(RELOAD);
|
| + _reloadButton = [self newButtonForImageIds:reload
|
| + commandID:IDC_RELOAD
|
| + accessibilityLabelID:IDS_IOS_ACCNAME_RELOAD
|
| + automationName:@"Reload"
|
| + reverseForRTL:YES];
|
| +
|
| + int stop[2][3] = TOOLBAR_IDR_TWO_STATE(STOP);
|
| + _stopButton = [self newButtonForImageIds:stop
|
| + commandID:IDC_STOP
|
| + accessibilityLabelID:IDS_IOS_ACCNAME_STOP
|
| + automationName:@"Stop"];
|
| +
|
| + int share[2][3] = TOOLBAR_IDR_THREE_STATE(SHARE);
|
| + _shareButton = [self newButtonForImageIds:share
|
| + commandID:IDC_SHARE_PAGE
|
| + accessibilityLabelID:IDS_IOS_TOOLS_MENU_SHARE
|
| + automationName:@"Stop"];
|
| + int tools[2][3] = TOOLBAR_IDR_ONE_STATE(TOOLS_PRESSED);
|
| + _toolsButton =
|
| + [self newButtonForImageIds:tools
|
| + commandID:IDC_SHOW_TOOLS_MENU
|
| + accessibilityLabelID:IDS_IOS_TOOLBAR_CLOSE_MENU
|
| + automationName:@"kToolbarToolsMenuButtonIdentifier"];
|
| +
|
| + UIView* contentView = [self contentView];
|
| + [contentView setBackgroundColor:[self backgroundColor]];
|
| + [contentView setOpaque:YES];
|
| +
|
| + [contentView addSubview:_starredButton];
|
| + [contentView addSubview:_starButton];
|
| + [contentView addSubview:_stopButton];
|
| + [contentView addSubview:_reloadButton];
|
| + [contentView addSubview:_shareButton];
|
| +
|
| + [self addConstraints];
|
| +}
|
| +
|
| +- (UIImage*)imageForImageId:(int)imageId reversed:(BOOL)reversed {
|
| + if (imageId == 0) {
|
| + return nil;
|
| + }
|
| + ResourceBundle& rb = ResourceBundle::GetSharedInstance();
|
| + gfx::Image image = rb.GetNativeImageNamed(imageId);
|
| + if (reversed) {
|
| + return [image.ToUIImage() imageFlippedForRightToLeftLayoutDirection];
|
| + } else {
|
| + return image.ToUIImage();
|
| + }
|
| +}
|
| +
|
| +- (ToolsMenuButton*)newButtonForImageIds:(int[2][3])imageIds
|
| + commandID:(int)commandID
|
| + accessibilityLabelID:(int)labelID
|
| + automationName:(NSString*)name {
|
| + return [self newButtonForImageIds:imageIds
|
| + commandID:commandID
|
| + accessibilityLabelID:labelID
|
| + automationName:name
|
| + reverseForRTL:NO];
|
| +}
|
| +
|
| +- (ToolsMenuButton*)newButtonForImageIds:(int[2][3])imageIds
|
| + commandID:(int)commandID
|
| + accessibilityLabelID:(int)labelID
|
| + automationName:(NSString*)name
|
| + reverseForRTL:(BOOL)reverseForRTL {
|
| + ToolsMenuButton* button = [[ToolsMenuButton alloc] initWithFrame:CGRectZero];
|
| + [button setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| + BOOL reverseImage = reverseForRTL && UseRTLLayout();
|
| +
|
| + [button setImage:[self imageForImageId:imageIds[0][0] reversed:reverseImage]
|
| + forState:UIControlStateNormal];
|
| + [[button imageView] setContentMode:UIViewContentModeCenter];
|
| + [button setBackgroundColor:[self backgroundColor]];
|
| + [button setTag:commandID];
|
| + [button setOpaque:YES];
|
| +
|
| + SetA11yLabelAndUiAutomationName(button, labelID, name);
|
| +
|
| + UIImage* pressedImage =
|
| + [self imageForImageId:imageIds[0][1] reversed:reverseImage];
|
| + if (pressedImage) {
|
| + [button setImage:pressedImage forState:UIControlStateHighlighted];
|
| + }
|
| +
|
| + UIImage* disabledImage =
|
| + [self imageForImageId:imageIds[0][2] reversed:reverseImage];
|
| + if (disabledImage) {
|
| + [button setImage:disabledImage forState:UIControlStateDisabled];
|
| + }
|
| +
|
| + return button;
|
| +}
|
| +
|
| +- (void)addConstraints {
|
| + UIView* contentView = [self contentView];
|
| +
|
| + for (UIButton* button in [self allButtons]) {
|
| + NSDictionary* view = @{ @"button" : button };
|
| + NSArray* constraints = @[ @"V:|-(0)-[button]-(0)-|", @"H:[button(==48)]" ];
|
| + ApplyVisualConstraints(constraints, view, self);
|
| + }
|
| +
|
| + NSDictionary* views = @{
|
| + @"share" : _shareButton,
|
| + @"star" : _starButton,
|
| + @"reload" : _reloadButton,
|
| + @"starred" : _starredButton,
|
| + @"stop" : _stopButton
|
| + };
|
| + // Leading offset is 16, minus the button image inset of 12.
|
| + NSDictionary* metrics = @{ @"offset" : @4, @"space" : @24 };
|
| + // clang-format off
|
| + NSArray* constraints = @[
|
| + @"H:|-(offset)-[share]-(space)-[star]-(space)-[reload]",
|
| + @"H:[share]-(space)-[starred]",
|
| + @"H:[star]-(space)-[stop]"
|
| + ];
|
| + // clang-format on
|
| + ApplyVisualConstraintsWithMetricsAndOptions(
|
| + constraints, views, metrics, LayoutOptionForRTLSupport(), contentView);
|
| +}
|
| +
|
| +// These should be added in display order, so they are animated in display
|
| +// order.
|
| +- (NSArray*)allButtons {
|
| + NSMutableArray* allButtons = [NSMutableArray array];
|
| + if (_shareButton)
|
| + [allButtons addObject:_shareButton];
|
| +
|
| + if (_starButton)
|
| + [allButtons addObject:_starButton];
|
| +
|
| + if (_starredButton)
|
| + [allButtons addObject:_starredButton];
|
| +
|
| + if (_reloadButton)
|
| + [allButtons addObject:_reloadButton];
|
| +
|
| + if (_stopButton)
|
| + [allButtons addObject:_stopButton];
|
| +
|
| + return allButtons;
|
| +}
|
| +
|
| +@end
|
| +
|
| +// Class Extension for private methods.
|
| +@interface ToolsMenuViewController ()<UICollectionViewDelegateFlowLayout,
|
| + UICollectionViewDataSource,
|
| + ReadingListMenuNotificationDelegate> {
|
| + base::mac::ObjCPropertyReleaser _propertyReleaser_ToolsMenuViewController;
|
| + BOOL _waitForInk;
|
| + // Weak pointer to ReadingListMenuNotifier, used to set the starting values
|
| + // for the reading list badge.
|
| + base::WeakNSObject<ReadingListMenuNotifier> _readingListMenuNotifier;
|
| +}
|
| +@property(nonatomic, retain) ToolsMenuCollectionView* menuView;
|
| +@property(nonatomic, retain) MDCInkView* touchFeedbackView;
|
| +@property(nonatomic, retain) NSMutableArray* menuItems;
|
| +@property(nonatomic, assign) kToolbarType toolbarType;
|
| +
|
| +// Get the reading list cell.
|
| +- (ReadingListMenuViewCell*)readingListCell;
|
| +@end
|
| +
|
| +@implementation ToolsMenuViewController
|
| +
|
| +@synthesize menuView = _menuView;
|
| +@synthesize isCurrentPageBookmarked = _isCurrentPageBookmarked;
|
| +@synthesize touchFeedbackView = _touchFeedbackView;
|
| +@synthesize isTabLoading = _isTabLoading;
|
| +@synthesize toolbarType = _toolbarType;
|
| +@synthesize menuItems = _menuItems;
|
| +@synthesize delegate = _delegate;
|
| +
|
| +#pragma mark Public methods
|
| +
|
| +- (CGFloat)optimalHeight:(CGFloat)suggestedHeight {
|
| + NSInteger numberOfItems = [self.menuItems count];
|
| + if (_toolbarType == kToolbarTypeWebiPhone) {
|
| + // Account for the height of the first row, not included in |menuItems|.
|
| + numberOfItems++;
|
| + }
|
| + CGFloat maxItems = suggestedHeight / kMenuItemHeight;
|
| + if (maxItems >= numberOfItems) {
|
| + return numberOfItems * kMenuItemHeight;
|
| + } else {
|
| + const CGFloat halfHeight = kMenuItemHeight / 2;
|
| + return round(maxItems) * kMenuItemHeight - halfHeight;
|
| + }
|
| +}
|
| +
|
| +- (void)setItemEnabled:(BOOL)enabled withTag:(NSInteger)tag {
|
| + for (ToolsMenuViewItem* item in _menuItems) {
|
| + if ([item tag] == tag) {
|
| + [item setActive:enabled];
|
| + break;
|
| + }
|
| + }
|
| +
|
| + for (ToolsMenuViewCell* cell in [_menuView visibleCells]) {
|
| + if (![cell isKindOfClass:[ToolsMenuViewCell class]])
|
| + continue;
|
| +
|
| + if ([cell tag] != tag)
|
| + continue;
|
| +
|
| + NSIndexPath* path = [_menuView indexPathForCell:cell];
|
| + NSInteger itemIndex = [self dataIndexForIndexPath:path];
|
| + [cell configureForMenuItem:[_menuItems objectAtIndex:itemIndex]];
|
| + }
|
| +}
|
| +
|
| +- (void)setIsCurrentPageBookmarked:(BOOL)value {
|
| + _isCurrentPageBookmarked = value;
|
| +
|
| + ToolsMenuViewToolsCell* toolsCell = [self toolsCell];
|
| + [[toolsCell starButton] setHidden:_isCurrentPageBookmarked];
|
| + [[toolsCell starredButton] setHidden:!_isCurrentPageBookmarked];
|
| +}
|
| +
|
| +- (void)setCanUseReaderMode:(BOOL)enabled {
|
| + [self setItemEnabled:enabled withTag:IDC_READER_MODE];
|
| +}
|
| +
|
| +- (void)setCanUseDesktopUserAgent:(BOOL)enabled {
|
| + [self setItemEnabled:enabled withTag:IDC_REQUEST_DESKTOP_SITE];
|
| +}
|
| +
|
| +- (void)setCanShowFindBar:(BOOL)enabled {
|
| + [self setItemEnabled:enabled withTag:IDC_FIND];
|
| +}
|
| +
|
| +- (void)setCanShowShareMenu:(BOOL)enabled {
|
| + ToolsMenuViewToolsCell* toolsCell = [self toolsCell];
|
| + [[toolsCell shareButton] setEnabled:enabled];
|
| + [self setItemEnabled:enabled withTag:IDC_SHARE_PAGE];
|
| +}
|
| +
|
| +- (UIButton*)toolsButton {
|
| + UIButton* toolsButton = [[self toolsCell] toolsButton];
|
| + [toolsButton addTarget:self
|
| + action:@selector(buttonPressed:)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + [toolsButton setTranslatesAutoresizingMaskIntoConstraints:YES];
|
| + [toolsButton setOpaque:NO];
|
| + [toolsButton setBackgroundColor:[UIColor clearColor]];
|
| + return toolsButton;
|
| +}
|
| +
|
| +- (void)setIsTabLoading:(BOOL)isTabLoading {
|
| + _isTabLoading = isTabLoading;
|
| +
|
| + ToolsMenuViewToolsCell* toolsCell = [self toolsCell];
|
| + [[toolsCell stopButton] setHidden:!isTabLoading];
|
| + [[toolsCell reloadButton] setHidden:isTabLoading];
|
| +}
|
| +
|
| +- (void)initializeMenu:(ToolsMenuContext*)context {
|
| + if (context.readingListMenuNotifier) {
|
| + _readingListMenuNotifier.reset(context.readingListMenuNotifier);
|
| + [context.readingListMenuNotifier setDelegate:self];
|
| + }
|
| +
|
| + if (IsIPadIdiom()) {
|
| + _toolbarType = context.hasNoOpenedTabs
|
| + ? kToolbarTypeNoTabsiPad
|
| + : (!IsCompactTablet() ? kToolbarTypeWebiPad
|
| + : kToolbarTypeWebiPhone);
|
| + } else {
|
| + // kOptionInTabSwitcher option must be enabled on iPhone with
|
| + // no opened tabs.
|
| + DCHECK(!context.hasNoOpenedTabs || context.isInTabSwitcher);
|
| + _toolbarType = context.isInTabSwitcher ? kToolbarTypeSwitcheriPhone
|
| + : kToolbarTypeWebiPhone;
|
| + }
|
| +
|
| + // Build the menu, adding all relevant items.
|
| + NSMutableArray* menu = [NSMutableArray array];
|
| +
|
| + for (size_t i = 0; i < arraysize(itemInfoList); ++i) {
|
| + const MenuItemInfo& item = itemInfoList[i];
|
| + if (!ItemShouldBeVisible(item, context.isInIncognito, _toolbarType))
|
| + continue;
|
| +
|
| + NSString* title = l10n_util::GetNSStringWithFixup(item.title_id);
|
| + Class itemClass =
|
| + item.item_class ? item.item_class : [ToolsMenuViewItem class];
|
| + // Sanity check that the class is a useful one.
|
| + DCHECK([itemClass respondsToSelector:@selector(menuItemWithTitle:
|
| + accessibilityIdentifier:
|
| + command:)]);
|
| + [menu addObject:[itemClass menuItemWithTitle:title
|
| + accessibilityIdentifier:item.accessibility_id
|
| + command:item.command_id]];
|
| + }
|
| +
|
| +#if !defined(NDEBUG)
|
| + NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults];
|
| + if ((_toolbarType & kToolbarTypeWebAll) &&
|
| + [standardDefaults boolForKey:@"DevViewSource"]) {
|
| + // Debug menu, not localized, only visible if turned on by a default.
|
| + [menu addObject:[self createViewSourceItem]];
|
| + }
|
| +#endif // !defined(NDEBUG)
|
| +
|
| + [self setMenuItems:menu];
|
| +
|
| + // Disable IDC_CLOSE_ALL_TABS menu item if on phone with no tabs.
|
| + if (!IsIPadIdiom()) {
|
| + [self setItemEnabled:!context.hasNoOpenedTabs withTag:IDC_CLOSE_ALL_TABS];
|
| + }
|
| +}
|
| +
|
| +#if !defined(NDEBUG)
|
| +- (ToolsMenuViewItem*)createViewSourceItem {
|
| + return [ToolsMenuViewItem menuItemWithTitle:@"View Source"
|
| + accessibilityIdentifier:@"View Source"
|
| + command:IDC_VIEW_SOURCE];
|
| +}
|
| +#endif // !defined(NDEBUG)
|
| +
|
| +#pragma mark - Data handling utilities
|
| +
|
| +- (ToolsMenuViewToolsCell*)toolsCell {
|
| + for (ToolsMenuViewToolsCell* visibleCell in [_menuView visibleCells]) {
|
| + if ([visibleCell isKindOfClass:[ToolsMenuViewToolsCell class]])
|
| + return visibleCell;
|
| + }
|
| +
|
| + return nil;
|
| +}
|
| +
|
| +- (ReadingListMenuViewCell*)readingListCell {
|
| + for (ReadingListMenuViewCell* visibleCell in [_menuView visibleCells]) {
|
| + if ([visibleCell isKindOfClass:[ReadingListMenuViewCell class]])
|
| + return visibleCell;
|
| + }
|
| +
|
| + return nil;
|
| +}
|
| +
|
| +- (NSInteger)dataIndexForIndexPath:(NSIndexPath*)path {
|
| + NSInteger item = [path item];
|
| +
|
| + if (_toolbarType == kToolbarTypeWebiPhone)
|
| + --item;
|
| +
|
| + return item;
|
| +}
|
| +
|
| +#pragma mark - UIViewController Overrides
|
| +
|
| +- (instancetype)initWithNibName:(NSString*)nibNameOrNil
|
| + bundle:(NSBundle*)nibBundleOrNil {
|
| + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
|
| + if (self)
|
| + [self commonInitialization];
|
| +
|
| + return self;
|
| +}
|
| +
|
| +- (instancetype)initWithCoder:(NSCoder*)aDecoder {
|
| + self = [super initWithCoder:aDecoder];
|
| + if (self)
|
| + [self commonInitialization];
|
| +
|
| + return self;
|
| +}
|
| +
|
| +- (void)commonInitialization {
|
| + _propertyReleaser_ToolsMenuViewController.Init(
|
| + self, [ToolsMenuViewController class]);
|
| + _readingListMenuNotifier.reset();
|
| +}
|
| +
|
| +- (void)loadView {
|
| + [super loadView];
|
| +
|
| + UIView* rootView = [self view];
|
| + [rootView setAutoresizingMask:UIViewAutoresizingFlexibleHeight |
|
| + UIViewAutoresizingFlexibleWidth];
|
| + [rootView setBackgroundColor:[UIColor whiteColor]];
|
| +
|
| + _touchFeedbackView = [[MDCInkView alloc] initWithFrame:CGRectZero];
|
| +
|
| + base::scoped_nsobject<UICollectionViewFlowLayout> menuItemsLayout(
|
| + [[UICollectionViewFlowLayout alloc] init]);
|
| +
|
| + _menuView = [[ToolsMenuCollectionView alloc] initWithFrame:[rootView bounds]
|
| + collectionViewLayout:menuItemsLayout];
|
| + [_menuView setAccessibilityLabel:l10n_util::GetNSString(IDS_IOS_TOOLS_MENU)];
|
| + [_menuView setAccessibilityIdentifier:kToolsMenuTableViewId];
|
| + [_menuView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
| + [_menuView setBackgroundColor:[UIColor whiteColor]];
|
| + [_menuView setDataSource:self];
|
| + [_menuView setDelegate:self];
|
| + [_menuView setOpaque:YES];
|
| +
|
| + [rootView addSubview:_menuView];
|
| + [self updateViewConstraints];
|
| +}
|
| +
|
| +- (void)viewDidLoad {
|
| + [super viewDidLoad];
|
| + [_menuView registerClass:[ToolsMenuViewToolsCell class]
|
| + forCellWithReuseIdentifier:kToolsItemCellID];
|
| +
|
| + [_menuView registerClass:[ToolsMenuViewItem cellClass]
|
| + forCellWithReuseIdentifier:[ToolsMenuViewItem cellID]];
|
| +
|
| + // Register each custom class.
|
| + NSMutableSet* registeredClasses = [NSMutableSet set];
|
| + for (size_t i = 0; i < arraysize(itemInfoList); ++i) {
|
| + const MenuItemInfo& item = itemInfoList[i];
|
| + if (!item.item_class || [registeredClasses containsObject:item.item_class])
|
| + continue;
|
| + [_menuView registerClass:[item.item_class cellClass]
|
| + forCellWithReuseIdentifier:[item.item_class cellID]];
|
| + [registeredClasses addObject:item.item_class];
|
| + }
|
| +}
|
| +
|
| +- (void)updateViewConstraints {
|
| + [super updateViewConstraints];
|
| +
|
| + UIView* rootView = [self view];
|
| + NSDictionary* view = @{ @"menu" : _menuView };
|
| + NSArray* constraints = @[ @"V:|-(0)-[menu]-(0)-|", @"H:|-(0)-[menu]-(0)-|" ];
|
| + ApplyVisualConstraints(constraints, view, rootView);
|
| +}
|
| +
|
| +#pragma mark - Content Animation Stuff
|
| +
|
| +- (void)animateContentIn {
|
| + // Make sure that the collection view has laid out before trying to animate
|
| + // the contents.
|
| + [_menuView layoutIfNeeded];
|
| +
|
| + NSArray* visibleCells =
|
| + [[_menuView visibleCells] sortedArrayUsingComparator:^NSComparisonResult(
|
| + UIView* view1, UIView* view2) {
|
| + CGPoint center1 = [view1 center];
|
| + CGPoint center2 = [view2 center];
|
| +
|
| + if (center1.y < center2.y)
|
| + return NSOrderedAscending;
|
| +
|
| + if (center1.y > center2.y)
|
| + return NSOrderedDescending;
|
| +
|
| + return NSOrderedSame;
|
| + }];
|
| +
|
| + ToolsMenuViewToolsCell* toolsCell = nil;
|
| + if (_toolbarType == kToolbarTypeWebiPhone) {
|
| + toolsCell = [visibleCells firstObject];
|
| + if ([toolsCell isKindOfClass:[ToolsMenuViewToolsCell class]]) {
|
| + visibleCells = [visibleCells
|
| + subarrayWithRange:NSMakeRange(1, [visibleCells count] - 1)];
|
| + } else {
|
| + toolsCell = nil;
|
| + }
|
| + }
|
| +
|
| + [CATransaction begin];
|
| + [CATransaction
|
| + setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseInOut)];
|
| + [CATransaction setAnimationDuration:ios::material::kDuration5];
|
| + AnimateInViews([toolsCell allButtons], 10, 0);
|
| + AnimateInViews(visibleCells, 0, -10);
|
| + [CATransaction commit];
|
| +
|
| + [[self readingListCell]
|
| + updateBadgeCount:_readingListMenuNotifier.get().readingListUnreadCount
|
| + animated:YES];
|
| + [[self readingListCell]
|
| + updateSeenState:_readingListMenuNotifier.get().readingListUnseenItemsExist
|
| + animated:YES];
|
| +}
|
| +
|
| +- (void)hideContent {
|
| + _menuView.alpha = 0;
|
| +
|
| + // Remove the target/action for touching the buttons. VoiceOver may hold on
|
| + // to these buttons after |self| has been released, causing a crash if a
|
| + // button is activated (see http://crbug.com/480974 ).
|
| + UIButton* toolsButton = [[self toolsCell] toolsButton];
|
| + [toolsButton removeTarget:self
|
| + action:@selector(buttonPressed:)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + for (UIButton* button in [[self toolsCell] allButtons]) {
|
| + [button removeTarget:self
|
| + action:@selector(buttonPressed:)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + }
|
| +}
|
| +
|
| +#pragma mark - Button event handling
|
| +
|
| +- (IBAction)buttonPressed:(id)sender {
|
| + int commandId = [sender tag];
|
| + DCHECK(commandId);
|
| + // The bookmark command workaround is only needed for metrics; remap it
|
| + // to the real command for the dispatch. This is very hacky, but it will go
|
| + // away soon. See crbug/228521
|
| + DCHECK([sender respondsToSelector:@selector(setTag:)]);
|
| + if (commandId == IDC_TEMP_EDIT_BOOKMARK)
|
| + [sender setTag:IDC_BOOKMARK_PAGE];
|
| + // Do nothing when tapping the tools menu a second time.
|
| + if (commandId != IDC_SHOW_TOOLS_MENU) {
|
| + [self chromeExecuteCommand:sender];
|
| + }
|
| + if (commandId == IDC_TEMP_EDIT_BOOKMARK)
|
| + [sender setTag:IDC_TEMP_EDIT_BOOKMARK];
|
| +
|
| + [_delegate commandWasSelected:commandId];
|
| +}
|
| +
|
| +#pragma mark - UICollectionViewDelegate Implementation
|
| +
|
| +- (BOOL)collectionView:(ToolsMenuCollectionView*)view
|
| + shouldHighlightItemAtIndexPath:(NSIndexPath*)path {
|
| + if (view.tracking)
|
| + return NO;
|
| + NSInteger item = [self dataIndexForIndexPath:path];
|
| + return (item >= 0);
|
| +}
|
| +
|
| +- (void)collectionView:(ToolsMenuCollectionView*)view
|
| + didHighlightItemAtIndexPath:(NSIndexPath*)path {
|
| + ToolsMenuViewCell* cell =
|
| + (ToolsMenuViewCell*)[view cellForItemAtIndexPath:path];
|
| +
|
| + NSInteger item = [self dataIndexForIndexPath:path];
|
| + DCHECK_GE(item, 0);
|
| + ToolsMenuViewItem* menuItem = [_menuItems objectAtIndex:item];
|
| + DCHECK(menuItem);
|
| + if ([menuItem active]) {
|
| + [_touchFeedbackView setFrame:cell.bounds];
|
| + [cell addSubview:_touchFeedbackView];
|
| +
|
| + CGPoint touchPoint = [view touchBeginPoint];
|
| + touchPoint = [view convertPoint:touchPoint toView:_touchFeedbackView];
|
| + [_touchFeedbackView startTouchBeganAnimationAtPoint:touchPoint
|
| + completion:nil];
|
| + }
|
| +}
|
| +
|
| +- (void)collectionView:(ToolsMenuCollectionView*)view
|
| + didUnhighlightItemAtIndexPath:(NSIndexPath*)path {
|
| + CGPoint touchPoint = [view touchEndPoint];
|
| + touchPoint = [view convertPoint:touchPoint toView:_touchFeedbackView];
|
| + base::WeakNSObject<MDCInkView> inkView(_touchFeedbackView);
|
| + _waitForInk = YES;
|
| + [_touchFeedbackView startTouchEndedAnimationAtPoint:touchPoint
|
| + completion:^{
|
| + _waitForInk = NO;
|
| + [inkView removeFromSuperview];
|
| + }];
|
| +}
|
| +
|
| +- (BOOL)collectionView:(UICollectionView*)view
|
| + shouldSelectItemAtIndexPath:(NSIndexPath*)path {
|
| + NSInteger item = [self dataIndexForIndexPath:path];
|
| + if (item < 0)
|
| + return NO;
|
| +
|
| + return [[_menuItems objectAtIndex:item] active];
|
| +}
|
| +
|
| +- (void)collectionView:(UICollectionView*)view
|
| + didSelectItemAtIndexPath:(NSIndexPath*)path {
|
| + [view deselectItemAtIndexPath:path animated:YES];
|
| +
|
| + NSInteger item = [self dataIndexForIndexPath:path];
|
| + if (item < 0)
|
| + return;
|
| +
|
| + dispatch_time_t delayTime = dispatch_time(
|
| + DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC * kMDCInkTouchDelayInterval));
|
| + dispatch_after(
|
| + _waitForInk ? delayTime : 0, dispatch_get_main_queue(), ^(void) {
|
| + ToolsMenuViewItem* menuItem = [_menuItems objectAtIndex:item];
|
| + DCHECK([menuItem tag]);
|
| + [_delegate commandWasSelected:[menuItem tag]];
|
| + [self chromeExecuteCommand:menuItem];
|
| + });
|
| +}
|
| +
|
| +#pragma mark - UICollectionViewDataSource Implementation
|
| +
|
| +- (NSInteger)collectionView:(UICollectionView*)view
|
| + numberOfItemsInSection:(NSInteger)section {
|
| + NSInteger numberOfItems = [_menuItems count];
|
| + if (_toolbarType == kToolbarTypeWebiPhone)
|
| + ++numberOfItems;
|
| +
|
| + return numberOfItems;
|
| +}
|
| +
|
| +- (UICollectionViewCell*)collectionView:(UICollectionView*)view
|
| + cellForItemAtIndexPath:(NSIndexPath*)path {
|
| + NSInteger item = [self dataIndexForIndexPath:path];
|
| + if (item < 0) {
|
| + ToolsMenuViewToolsCell* cell =
|
| + [view dequeueReusableCellWithReuseIdentifier:kToolsItemCellID
|
| + forIndexPath:path];
|
| + for (UIButton* button in [cell allButtons]) {
|
| + [button addTarget:self
|
| + action:@selector(buttonPressed:)
|
| + forControlEvents:UIControlEventTouchUpInside];
|
| + }
|
| + return cell;
|
| + }
|
| +
|
| + ToolsMenuViewItem* menuItem = [_menuItems objectAtIndex:item];
|
| + ToolsMenuViewCell* menuItemCell =
|
| + [view dequeueReusableCellWithReuseIdentifier:[[menuItem class] cellID]
|
| + forIndexPath:path];
|
| + [menuItemCell configureForMenuItem:menuItem];
|
| +
|
| + return menuItemCell;
|
| +}
|
| +
|
| +#pragma mark - UICollectionViewDelegateFlowLayout Implementation
|
| +
|
| +- (CGSize)collectionView:(UICollectionView*)view
|
| + layout:(UICollectionViewLayout*)collectionViewLayout
|
| + sizeForItemAtIndexPath:(NSIndexPath*)path {
|
| + return CGSizeMake(CGRectGetWidth([_menuView bounds]), kMenuItemHeight);
|
| +}
|
| +
|
| +- (CGFloat)collectionView:(UICollectionView*)collectionView
|
| + layout:(UICollectionViewLayout*)
|
| + collectionViewLayout
|
| + minimumLineSpacingForSectionAtIndex:(NSInteger)section {
|
| + return 0;
|
| +}
|
| +
|
| +#pragma mark - ReadingListMenuNotificationDelegate Implementation
|
| +
|
| +- (void)unreadCountChanged:(NSInteger)unreadCount {
|
| + [[self readingListCell] updateBadgeCount:unreadCount animated:YES];
|
| +}
|
| +
|
| +- (void)unseenStateChanged:(BOOL)unseenItemsExist {
|
| + [[self readingListCell] updateSeenState:unseenItemsExist animated:YES];
|
| +}
|
| +
|
| +@end
|
|
|