// Copyright 2018 The Chromium Authors
// 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_coordinator.h"
#import <MaterialComponents/MaterialSnackbar.h>
#import "base/check.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
#import "ios/chrome/browser/bubble/ui_bundled/bubble_view_controller_presenter.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/follow/model/follow_action_state.h"
#import "ios/chrome/browser/follow/model/follow_browser_agent.h"
#import "ios/chrome/browser/iph_for_new_chrome_user/model/tab_based_iph_browser_agent.h"
#import "ios/chrome/browser/lens_overlay/coordinator/lens_overlay_availability.h"
#import "ios/chrome/browser/overlays/model/public/overlay_presenter.h"
#import "ios/chrome/browser/promos_manager/model/promos_manager_factory.h"
#import "ios/chrome/browser/reading_list/model/reading_list_browser_agent.h"
#import "ios/chrome/browser/reading_list/model/reading_list_model_factory.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/coordinator/default_browser_promo/non_modal_default_browser_promo_scheduler_scene_agent.h"
#import "ios/chrome/browser/shared/coordinator/layout_guide/layout_guide_util.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/activity_service_commands.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/bookmarks_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/find_in_page_commands.h"
#import "ios/chrome/browser/shared/public/commands/help_commands.h"
#import "ios/chrome/browser/shared/public/commands/lens_commands.h"
#import "ios/chrome/browser/shared/public/commands/lens_overlay_commands.h"
#import "ios/chrome/browser/shared/public/commands/omnibox_commands.h"
#import "ios/chrome/browser/shared/public/commands/overflow_menu_customization_commands.h"
#import "ios/chrome/browser/shared/public/commands/page_info_commands.h"
#import "ios/chrome/browser/shared/public/commands/popup_menu_commands.h"
#import "ios/chrome/browser/shared/public/commands/price_notifications_commands.h"
#import "ios/chrome/browser/shared/public/commands/qr_scanner_commands.h"
#import "ios/chrome/browser/shared/public/commands/quick_delete_commands.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/public/commands/text_zoom_commands.h"
#import "ios/chrome/browser/shared/public/commands/whats_new_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/shared/ui/util/util_swift.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_service_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/browser_container/browser_container_mediator.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_metrics.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_orderer.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_swift.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_action_handler.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_help_coordinator.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_mediator.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_metrics_handler.h"
#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_presenter.h"
#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_presenter_delegate.h"
#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_table_view_controller.h"
#import "ios/chrome/browser/ui/presenters/contained_presenter_delegate.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/browser/web/model/web_navigation_browser_agent.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/web_state.h"
#import "ui/base/l10n/l10n_util.h"
using base::RecordAction;
using base::UserMetricsAction;
@interface PopupMenuCoordinator () <MenuCustomizationEventHandler,
OverflowMenuCustomizationCommands,
PopupMenuCommands,
PopupMenuMetricsHandler,
PopupMenuPresenterDelegate,
UIPopoverPresentationControllerDelegate,
UISheetPresentationControllerDelegate>
// Presenter for the popup menu, managing the animations.
@property(nonatomic, strong) PopupMenuPresenter* presenter;
// Mediator for the popup menu.
@property(nonatomic, strong) PopupMenuMediator* mediator;
// Mediator for the overflow menu
@property(nonatomic, strong) OverflowMenuMediator* overflowMenuMediator;
// Mediator to that alerts the main `mediator` when the web content area
// is blocked by an overlay.
@property(nonatomic, strong) BrowserContainerMediator* contentBlockerMediator;
// ViewController for this mediator.
@property(nonatomic, strong) PopupMenuTableViewController* viewController;
// Handles user interaction with the popup menu items.
@property(nonatomic, strong) PopupMenuActionHandler* actionHandler;
// Time when the tools menu opened.
@property(nonatomic, assign) NSTimeInterval toolsMenuOpenTime;
// Whether the tools menu was scrolled vertically while it was open.
@property(nonatomic, assign) BOOL toolsMenuWasScrolledVertically;
// Whether the tools menu was scrolled horizontally while it was open.
@property(nonatomic, assign) BOOL toolsMenuWasScrolledHorizontally;
// Whether the user took an action on the tools menu while it was open.
@property(nonatomic, assign) BOOL toolsMenuUserTookAction;
// Whether the user selected an Action on the overflow menu (the vertical list).
@property(nonatomic, assign) BOOL overflowMenuUserSelectedAction;
// Whether the user selected a Destination on the overflow menu (the horizontal
// list).
@property(nonatomic, assign) BOOL overflowMenuUserSelectedDestination;
// Whether the user scrolled to the end of the actions section during their
// interaction.
@property(nonatomic, assign) BOOL overflowMenuUserScrolledToEndOfActions;
@property(nonatomic, strong) PopupMenuHelpCoordinator* popupMenuHelpCoordinator;
@end
@implementation PopupMenuCoordinator {
OverflowMenuModel* _overflowMenuModel;
OverflowMenuOrderer* _overflowMenuOrderer;
// Stores whether certain events occured during an overflow menu session for
// logs.
OverflowMenuVisitedEvent _event;
}
@synthesize mediator = _mediator;
@synthesize presenter = _presenter;
@synthesize UIUpdater = _UIUpdater;
@synthesize viewController = _viewController;
@synthesize baseViewController = _baseViewController;
- (instancetype)initWithBrowser:(Browser*)browser {
DCHECK(browser);
return [super initWithBaseViewController:nil browser:browser];
}
#pragma mark - ChromeCoordinator
- (void)start {
[self.browser->GetCommandDispatcher()
startDispatchingToTarget:self
forProtocol:@protocol(PopupMenuCommands)];
[self.browser->GetCommandDispatcher()
startDispatchingToTarget:self
forProtocol:@protocol(OverflowMenuCustomizationCommands)];
NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
- (void)stop {
if (self.isShowingPopupMenu) {
[self dismissPopupMenuAnimated:NO];
}
[self.popupMenuHelpCoordinator stop];
[self.browser->GetCommandDispatcher() stopDispatchingToTarget:self];
[self.overflowMenuMediator disconnect];
self.overflowMenuMediator = nil;
[self.mediator disconnect];
self.mediator = nil;
self.viewController = nil;
}
#pragma mark - Public
- (BOOL)isShowingPopupMenu {
return self.presenter != nil || self.overflowMenuMediator != nil;
}
- (void)startPopupMenuHelpCoordinator {
self.popupMenuHelpCoordinator = [[PopupMenuHelpCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:self.browser];
self.popupMenuHelpCoordinator.UIUpdater = self.UIUpdater;
[self.popupMenuHelpCoordinator start];
}
#pragma mark - PopupMenuCommands
- (void)showToolsMenuPopup {
if (self.presenter || self.overflowMenuMediator) {
[self dismissPopupMenuAnimated:YES];
}
id<OmniboxCommands> omniboxCommandsHandler =
HandlerForProtocol(self.browser->GetCommandDispatcher(), OmniboxCommands);
// Dismiss the omnibox (if open).
[omniboxCommandsHandler cancelOmniboxEdit];
id<BrowserCommands> callableDispatcher =
HandlerForProtocol(self.browser->GetCommandDispatcher(), BrowserCommands);
[callableDispatcher dismissSoftKeyboard];
id<FindInPageCommands> findInPageCommandsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), FindInPageCommands);
// Dismiss Find in Page focus.
[findInPageCommandsHandler defocusFindInPage];
SceneState* sceneState = self.browser->GetSceneState();
NonModalDefaultBrowserPromoSchedulerSceneAgent* nonModalPromoScheduler =
[NonModalDefaultBrowserPromoSchedulerSceneAgent
agentFromScene:sceneState];
// Allow the non-modal promo scheduler to close the promo.
[nonModalPromoScheduler logPopupMenuEntered];
PopupMenuTableViewController* tableViewController =
[[PopupMenuTableViewController alloc] init];
tableViewController.baseViewController = self.baseViewController;
tableViewController.tableView.accessibilityIdentifier =
kPopupMenuToolsMenuTableViewId;
self.viewController = tableViewController;
OverlayPresenter* overlayPresenter = OverlayPresenter::FromBrowser(
self.browser, OverlayModality::kWebContentArea);
self.contentBlockerMediator = [[BrowserContainerMediator alloc]
initWithWebStateList:self.browser->GetWebStateList()
webContentAreaOverlayPresenter:overlayPresenter];
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserState(
self.browser->GetBrowserState());
// Create the overflow menu mediator first so the popup mediator isn't created
// if not needed.
self.toolsMenuOpenTime = [NSDate timeIntervalSinceReferenceDate];
self.toolsMenuWasScrolledVertically = NO;
self.toolsMenuWasScrolledHorizontally = NO;
self.toolsMenuUserTookAction = NO;
if (IsNewOverflowMenuEnabled()) {
OverflowMenuMediator* mediator = [[OverflowMenuMediator alloc] init];
CGFloat screenWidth = self.baseViewController.view.frame.size.width;
UIContentSizeCategory contentSizeCategory =
self.baseViewController.traitCollection.preferredContentSizeCategory;
BOOL isIncognito = self.browser->GetBrowserState()->IsOffTheRecord();
mediator.isIncognito = isIncognito;
_overflowMenuOrderer =
[[OverflowMenuOrderer alloc] initWithIsIncognito:isIncognito];
_overflowMenuOrderer.visibleDestinationsCount = [OverflowMenuUIConfiguration
numDestinationsVisibleWithoutHorizontalScrollingForScreenWidth:
screenWidth
forContentSizeCategory:
contentSizeCategory];
_overflowMenuOrderer.localStatePrefs =
GetApplicationContext()->GetLocalState();
mediator.menuOrderer = _overflowMenuOrderer;
CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher();
mediator.activityServiceHandler =
HandlerForProtocol(dispatcher, ActivityServiceCommands);
mediator.applicationHandler =
HandlerForProtocol(dispatcher, ApplicationCommands);
mediator.settingsHandler = HandlerForProtocol(dispatcher, SettingsCommands);
mediator.bookmarksHandler =
HandlerForProtocol(dispatcher, BookmarksCommands);
if (IsLensOverlayAvailable()) {
mediator.lensOverlayHandler =
HandlerForProtocol(dispatcher, LensOverlayCommands);
}
mediator.browserCoordinatorHandler =
HandlerForProtocol(dispatcher, BrowserCoordinatorCommands);
mediator.findInPageHandler =
HandlerForProtocol(dispatcher, FindInPageCommands);
mediator.helpHandler = HandlerForProtocol(dispatcher, HelpCommands);
mediator.overflowMenuCustomizationHandler =
HandlerForProtocol(dispatcher, OverflowMenuCustomizationCommands);
mediator.pageInfoHandler = HandlerForProtocol(dispatcher, PageInfoCommands);
mediator.popupMenuHandler =
HandlerForProtocol(dispatcher, PopupMenuCommands);
mediator.priceNotificationHandler =
HandlerForProtocol(dispatcher, PriceNotificationsCommands);
mediator.textZoomHandler = HandlerForProtocol(dispatcher, TextZoomCommands);
mediator.quickDeleteHandler =
HandlerForProtocol(dispatcher, QuickDeleteCommands);
mediator.whatsNewHandler = HandlerForProtocol(dispatcher, WhatsNewCommands);
mediator.webStateList = self.browser->GetWebStateList();
mediator.navigationAgent =
WebNavigationBrowserAgent::FromBrowser(self.browser);
mediator.baseViewController = self.baseViewController;
mediator.bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(
self.browser->GetBrowserState());
mediator.readingListModel =
ReadingListModelFactory::GetInstance()->GetForBrowserState(
self.browser->GetBrowserState());
mediator.browserStatePrefs = self.browser->GetBrowserState()->GetPrefs();
mediator.engagementTracker = tracker;
mediator.webContentAreaOverlayPresenter = overlayPresenter;
mediator.browserPolicyConnector =
GetApplicationContext()->GetBrowserPolicyConnector();
mediator.syncService =
SyncServiceFactory::GetForBrowserState(self.browser->GetBrowserState());
mediator.promosManager = PromosManagerFactory::GetForBrowserState(
self.browser->GetBrowserState());
mediator.readingListBrowserAgent =
ReadingListBrowserAgent::FromBrowser(self.browser);
if (IsWebChannelsEnabled()) {
mediator.followBrowserAgent =
FollowBrowserAgent::FromBrowser(self.browser);
}
// Set the AuthenticationService with the one from the original
// ChromeBrowserState as the incognito one doesn't have that service.
mediator.authenticationService =
AuthenticationServiceFactory::GetForBrowserState(
self.browser->GetBrowserState()->GetOriginalChromeBrowserState());
mediator.tabBasedIPHBrowserAgent =
TabBasedIPHBrowserAgent::FromBrowser(self.browser);
mediator.hasSettingsBlueDot =
[self.popupMenuHelpCoordinator hasBlueDotForOverflowMenu];
self.contentBlockerMediator.consumer = mediator;
NSInteger highlightDestination =
[self.popupMenuHelpCoordinator highlightDestination] == nil
? -1
: [[self.popupMenuHelpCoordinator highlightDestination]
integerValue];
UITraitCollection* traits = self.baseViewController.traitCollection;
OverflowMenuUIConfiguration* uiConfiguration =
[[OverflowMenuUIConfiguration alloc]
initWithPresentingViewControllerHorizontalSizeClass:
traits.horizontalSizeClass
presentingViewControllerVerticalSizeClass:
traits.verticalSizeClass
highlightDestination:
highlightDestination];
self.popupMenuHelpCoordinator.uiConfiguration = uiConfiguration;
_overflowMenuModel = [[OverflowMenuModel alloc] initWithDestinations:@[]
actionGroups:@[]];
_overflowMenuOrderer.model = _overflowMenuModel;
mediator.model = _overflowMenuModel;
self.popupMenuHelpCoordinator.actionProvider = mediator;
self.overflowMenuMediator = mediator;
UIViewController* menu =
[OverflowMenuViewProvider makeViewControllerWithModel:_overflowMenuModel
uiConfiguration:uiConfiguration
metricsHandler:self
customizationEventHandler:self];
LayoutGuideCenter* layoutGuideCenter =
LayoutGuideCenterForBrowser(self.browser);
UILayoutGuide* layoutGuide =
[layoutGuideCenter makeLayoutGuideNamed:kToolsMenuGuide];
[self.baseViewController.view addLayoutGuide:layoutGuide];
menu.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController* popoverPresentationController =
menu.popoverPresentationController;
popoverPresentationController.sourceView = self.baseViewController.view;
popoverPresentationController.sourceRect = layoutGuide.layoutFrame;
popoverPresentationController.permittedArrowDirections =
UIPopoverArrowDirectionUp;
popoverPresentationController.delegate = self;
popoverPresentationController.backgroundColor =
[UIColor colorNamed:kBackgroundColor];
[self setupSheetForMenu:menu isCustomizationScreen:NO animated:NO];
// Reset event before presenting.
_event = OverflowMenuVisitedEvent();
__weak __typeof(self) weakSelf = self;
[self.baseViewController
presentViewController:menu
animated:YES
completion:^{
[weakSelf.popupMenuHelpCoordinator
showIPHAfterOpenOfOverflowMenu:menu];
}];
// Log to FET overflow menu opened if opened with blue dot.
if ([self.popupMenuHelpCoordinator hasBlueDotForOverflowMenu] && tracker) {
tracker->NotifyEvent(
feature_engagement::events::kBlueDotPromoOverflowMenuOpened);
[self updateToolsMenuBlueDotVisibility];
}
return;
}
self.mediator = [[PopupMenuMediator alloc]
initWithIsIncognito:self.browser->GetBrowserState()
->IsOffTheRecord()
readingListModel:ReadingListModelFactory::GetForBrowserState(
self.browser->GetBrowserState())
browserPolicyConnector:GetApplicationContext()
->GetBrowserPolicyConnector()];
self.mediator.engagementTracker = tracker;
self.mediator.webStateList = self.browser->GetWebStateList();
self.mediator.readingListBrowserAgent =
ReadingListBrowserAgent::FromBrowser(self.browser);
self.mediator.lensCommandsHandler =
HandlerForProtocol(self.browser->GetCommandDispatcher(), LensCommands);
self.mediator.bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(
self.browser->GetBrowserState());
self.mediator.prefService = self.browser->GetBrowserState()->GetPrefs();
self.mediator.templateURLService =
ios::TemplateURLServiceFactory::GetForBrowserState(
self.browser->GetBrowserState());
self.mediator.popupMenu = tableViewController;
self.mediator.webContentAreaOverlayPresenter = overlayPresenter;
self.mediator.URLLoadingBrowserAgent =
UrlLoadingBrowserAgent::FromBrowser(self.browser);
if (IsWebChannelsEnabled()) {
self.mediator.followBrowserAgent =
FollowBrowserAgent::FromBrowser(self.browser);
}
self.contentBlockerMediator.consumer = self.mediator;
self.actionHandler = [[PopupMenuActionHandler alloc] init];
self.actionHandler.baseViewController = self.baseViewController;
self.actionHandler.dispatcher = static_cast<
id<ApplicationCommands, BrowserCommands, FindInPageCommands,
LoadQueryCommands, PriceNotificationsCommands, TextZoomCommands>>(
self.browser->GetCommandDispatcher());
self.actionHandler.bookmarksCommandsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), BookmarksCommands);
self.actionHandler.browserCoordinatorCommandsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), BrowserCoordinatorCommands);
self.actionHandler.pageInfoCommandsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), PageInfoCommands);
self.actionHandler.popupMenuCommandsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), PopupMenuCommands);
self.actionHandler.qrScannerCommandsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), QRScannerCommands);
self.actionHandler.helpHandler =
HandlerForProtocol(self.browser->GetCommandDispatcher(), HelpCommands);
self.actionHandler.delegate = self.mediator;
self.actionHandler.navigationAgent =
WebNavigationBrowserAgent::FromBrowser(self.browser);
tableViewController.delegate = self.actionHandler;
self.presenter = [[PopupMenuPresenter alloc] init];
self.presenter.baseViewController = self.baseViewController;
self.presenter.presentedViewController = tableViewController;
LayoutGuideCenter* layoutGuideCenter =
LayoutGuideCenterForBrowser(self.browser);
UILayoutGuide* layoutGuide =
[layoutGuideCenter makeLayoutGuideNamed:kToolsMenuGuide];
[self.baseViewController.view addLayoutGuide:layoutGuide];
self.presenter.layoutGuide = layoutGuide;
self.presenter.delegate = self;
[self.presenter prepareForPresentation];
[self.presenter presentAnimated:YES];
// Scrolls happen during prepareForPresentation, so only attach the metrics
// handler after presentation is done.
tableViewController.metricsHandler = self;
}
- (void)dismissPopupMenuAnimated:(BOOL)animated {
if (self.toolsMenuOpenTime != 0) {
OverflowMenuVisitedEvent event;
base::TimeDelta elapsed = base::Seconds(
[NSDate timeIntervalSinceReferenceDate] - self.toolsMenuOpenTime);
UMA_HISTOGRAM_MEDIUM_TIMES("IOS.OverflowMenu.TimeOpen", elapsed);
if (self.toolsMenuUserTookAction && self.overflowMenuUserSelectedAction) {
UMA_HISTOGRAM_MEDIUM_TIMES("IOS.OverflowMenu.TimeOpen.ActionChosen",
elapsed);
} else if (self.toolsMenuUserTookAction &&
self.overflowMenuUserSelectedDestination) {
UMA_HISTOGRAM_MEDIUM_TIMES("IOS.OverflowMenu.TimeOpen.DestinationChosen",
elapsed);
}
event.PutOrRemove(OverflowMenuVisitedEventFields::kUserSelectedDestination,
self.overflowMenuUserSelectedDestination);
event.PutOrRemove(OverflowMenuVisitedEventFields::kUserSelectedAction,
self.overflowMenuUserSelectedAction);
// Reset the start time to ensure that whatever happens, we only record
// this once.
self.toolsMenuOpenTime = 0;
IOSOverflowMenuActionType actionType;
if (self.toolsMenuWasScrolledVertically) {
if (self.toolsMenuUserTookAction) {
actionType = IOSOverflowMenuActionType::kScrollAction;
} else {
actionType = IOSOverflowMenuActionType::kScrollNoAction;
}
} else {
if (self.toolsMenuUserTookAction) {
actionType = IOSOverflowMenuActionType::kNoScrollAction;
} else {
actionType = IOSOverflowMenuActionType::kNoScrollNoAction;
}
}
base::UmaHistogramEnumeration("IOS.OverflowMenu.ActionType", actionType);
if (!self.toolsMenuWasScrolledHorizontally &&
!self.toolsMenuUserTookAction) {
[self trackToolsMenuNoHorizontalScrollOrAction];
}
RecordOverflowMenuVisitedEvent(_event);
if (IsOverflowMenuCustomizationEnabled() &&
self.overflowMenuUserScrolledToEndOfActions) {
base::UmaHistogramBoolean(
"IOS.OverflowMenu.UserScrolledToEndAndStartedCustomization",
_event.Has(
OverflowMenuVisitedEventFields::kUserStartedCustomization));
}
_event = OverflowMenuVisitedEvent();
self.toolsMenuWasScrolledVertically = NO;
self.toolsMenuWasScrolledHorizontally = NO;
self.toolsMenuUserTookAction = NO;
self.overflowMenuUserSelectedAction = NO;
self.overflowMenuUserSelectedDestination = NO;
self.overflowMenuUserScrolledToEndOfActions = NO;
}
if (self.overflowMenuMediator) {
[self.baseViewController dismissViewControllerAnimated:animated
completion:nil];
_overflowMenuModel = nil;
[_overflowMenuOrderer updateForMenuDisappearance];
[_overflowMenuOrderer disconnect];
_overflowMenuOrderer = nil;
[self.overflowMenuMediator disconnect];
self.overflowMenuMediator = nil;
}
[self.presenter dismissAnimated:animated];
self.presenter = nil;
[self.mediator disconnect];
self.mediator = nil;
self.viewController = nil;
}
- (void)adjustPopupSize {
if (self.overflowMenuMediator) {
UIViewController* menu = self.baseViewController.presentedViewController;
UIPopoverPresentationController* popoverPresentationController =
menu.popoverPresentationController;
LayoutGuideCenter* layoutGuideCenter =
LayoutGuideCenterForBrowser(self.browser);
UILayoutGuide* layoutGuide =
[layoutGuideCenter makeLayoutGuideNamed:kToolsMenuGuide];
[self.baseViewController.view addLayoutGuide:layoutGuide];
// Re-anchor the popover if necessary, when the parent view's size changes.
popoverPresentationController.sourceRect = layoutGuide.layoutFrame;
}
}
- (void)updateToolsMenuBlueDotVisibility {
[self.popupMenuHelpCoordinator updateBlueDotVisibility];
}
#pragma mark - OverflowMenuCustomizationCommands
- (void)showMenuCustomization {
_event.Put(OverflowMenuVisitedEventFields::kUserStartedCustomization);
[self logFeatureEngagementCustomizationStarted];
[_overflowMenuModel
startCustomizationWithActions:_overflowMenuOrderer
.actionCustomizationModel
destinations:_overflowMenuOrderer
.destinationCustomizationModel];
[self setupSheetForMenu:self.baseViewController.presentedViewController
isCustomizationScreen:YES
animated:YES];
}
- (void)showMenuCustomizationFromActionType:
(overflow_menu::ActionType)actionType {
for (OverflowMenuAction* action in _overflowMenuOrderer
.actionCustomizationModel.actionsGroup.actions) {
if (action.actionType == static_cast<NSInteger>(actionType)) {
action.highlighted = YES;
}
}
[self showMenuCustomization];
}
- (void)hideMenuCustomization {
[self setupSheetForMenu:self.baseViewController.presentedViewController
isCustomizationScreen:NO
animated:YES];
[_overflowMenuModel endCustomization];
}
#pragma mark - MenuCustomizationEventHandler
- (void)doneWasTapped {
if (_overflowMenuOrderer.destinationCustomizationModel.hasChanged) {
_event.Put(OverflowMenuVisitedEventFields::kUserCustomizedDestinations);
}
if (_overflowMenuOrderer.actionCustomizationModel.hasChanged) {
_event.Put(OverflowMenuVisitedEventFields::kUserCustomizedActions);
}
[_overflowMenuOrderer commitActionsUpdate];
[_overflowMenuOrderer commitDestinationsUpdate];
[self hideMenuCustomization];
}
- (void)cancelWasTapped {
[_overflowMenuOrderer cancelActionsUpdate];
[_overflowMenuOrderer cancelDestinationsUpdate];
_event.Put(OverflowMenuVisitedEventFields::kUserCancelledCustomization);
[self hideMenuCustomization];
}
#pragma mark - ContainedPresenterDelegate
- (void)containedPresenterDidPresent:(id<ContainedPresenter>)presenter {
if (presenter != self.presenter)
return;
}
#pragma mark - PopupMenuPresenterDelegate
- (void)popupMenuPresenterWillDismiss:(PopupMenuPresenter*)presenter {
[self dismissPopupMenuAnimated:NO];
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
[self dismissPopupMenuAnimated:NO];
}
- (BOOL)presentationControllerShouldDismiss:
(UIPresentationController*)presentationController {
return _overflowMenuModel.isCustomizationActive ? NO : YES;
}
#pragma mark - UISheetPresentationControllerDelegate
- (void)sheetPresentationControllerDidChangeSelectedDetentIdentifier:
(UISheetPresentationController*)sheetPresentationController {
[self popupMenuScrolledVertically];
}
#pragma mark - PopupMenuMetricsHandler
- (void)popupMenuScrolledVertically {
self.toolsMenuWasScrolledVertically = YES;
_event.Put(OverflowMenuVisitedEventFields::kUserScrolledVertically);
}
- (void)popupMenuScrolledHorizontally {
self.toolsMenuWasScrolledHorizontally = YES;
_event.Put(OverflowMenuVisitedEventFields::kUserScrolledHorizontally);
}
- (void)popupMenuTookAction {
self.toolsMenuUserTookAction = YES;
}
- (void)popupMenuUserSelectedAction {
self.overflowMenuUserSelectedAction = YES;
_event.Put(OverflowMenuVisitedEventFields::kUserSelectedAction);
}
- (void)popupMenuUserSelectedDestination {
self.overflowMenuUserSelectedDestination = YES;
_event.Put(OverflowMenuVisitedEventFields::kUserSelectedDestination);
}
- (void)popupMenuUserScrolledToEndOfActions {
self.overflowMenuUserScrolledToEndOfActions = YES;
}
#pragma mark - Notification callback
- (void)applicationDidEnterBackground:(NSNotification*)note {
[self dismissPopupMenuAnimated:NO];
}
#pragma mark - Private
- (void)trackToolsMenuNoHorizontalScrollOrAction {
ChromeBrowserState* browserState = self.browser->GetBrowserState();
if (!browserState) {
return;
}
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserState(browserState);
if (!tracker) {
return;
}
tracker->NotifyEvent(
feature_engagement::events::kOverflowMenuNoHorizontalScrollOrAction);
}
- (void)logFeatureEngagementCustomizationStarted {
ChromeBrowserState* browserState = self.browser->GetBrowserState();
if (!browserState) {
return;
}
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserState(browserState);
if (!tracker) {
return;
}
tracker->NotifyEvent(
feature_engagement::events::kIOSOverflowMenuCustomizationUsed);
}
- (void)setupSheetForMenu:(UIViewController*)menu
isCustomizationScreen:(BOOL)isCustomizationScreen
animated:(BOOL)animated {
// The adaptive controller adjusts styles based on window size: sheet
// for slim windows on iPhone and iPad, popover for larger windows on
// iPad.
UISheetPresentationController* sheetPresentationController =
menu.popoverPresentationController.adaptiveSheetPresentationController;
if (!sheetPresentationController) {
return;
}
sheetPresentationController.delegate = self;
void (^changes)(void) = ^{
sheetPresentationController.prefersEdgeAttachedInCompactHeight = YES;
sheetPresentationController
.widthFollowsPreferredContentSizeWhenEdgeAttached = YES;
if (isCustomizationScreen) {
sheetPresentationController.prefersGrabberVisible = NO;
sheetPresentationController.detents =
@[ [UISheetPresentationControllerDetent largeDetent] ];
} else {
sheetPresentationController.prefersGrabberVisible = YES;
NSArray<UISheetPresentationControllerDetent*>* regularDetents = @[
[UISheetPresentationControllerDetent mediumDetent],
[UISheetPresentationControllerDetent largeDetent]
];
NSArray<UISheetPresentationControllerDetent*>* largeTextDetents =
@[ [UISheetPresentationControllerDetent largeDetent] ];
BOOL hasLargeText = UIContentSizeCategoryIsAccessibilityCategory(
menu.traitCollection.preferredContentSizeCategory);
sheetPresentationController.detents =
hasLargeText ? largeTextDetents : regularDetents;
}
};
if (animated) {
[sheetPresentationController animateChanges:changes];
} else {
changes();
}
}
@end