blob: 562a505b32f24cc9a8383e5d15571cdc4c944d4b [file] [log] [blame]
// Copyright 2012 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/ntp/new_tab_page_controller.h"
#import <QuartzCore/QuartzCore.h>
#include "base/logging.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/template_url_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync_sessions/synced_session.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/experimental_flags.h"
#include "ios/chrome/browser/pref_names.h"
#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
#include "ios/chrome/browser/sync/sync_setup_service.h"
#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/ui/commands/browser_commands.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.h"
#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
#import "ios/chrome/browser/ui/ntp/incognito_view_controller.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_bar_item.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_view.h"
#import "ios/chrome/browser/ui/rtl_geometry.h"
#include "ios/chrome/browser/ui/ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using base::UserMetricsAction;
@interface NewTabPageController () {
ios::ChromeBrowserState* _browserState; // weak.
__weak id<UrlLoader> _loader;
IncognitoViewController* _incognitoController;
// The currently visible controller, one of the above.
__weak id<NewTabPagePanelProtocol> _currentController;
// Delegate to focus and blur the omnibox.
__weak id<OmniboxFocuser> _focuser;
// Delegate to fetch the ToolbarModel and current web state from.
__weak id<NewTabPageControllerDelegate> _toolbarDelegate;
TabModel* _tabModel;
}
// Load panel on demand.
- (BOOL)loadPanel:(NewTabPageBarItem*)item;
@property(nonatomic, strong) NewTabPageView* view;
// To ease modernizing the NTP only the internal panels are being converted
// to UIViewControllers. This means all the plumbing between the
// BrowserViewController and the internal NTP panels (WebController, NTP)
// hierarchy is skipped. While normally the logic to push and pop a view
// controller would be owned by a coordinator, in this case the old NTP
// controller adds and removes child view controllers itself when a load
// is initiated, and when WebController calls -willBeDismissed.
@property(nonatomic, weak) UIViewController* parentViewController;
// The command dispatcher.
@property(nonatomic, weak) id<ApplicationCommands,
BrowserCommands,
OmniboxFocuser,
FakeboxFocuser,
SnackbarCommands,
UrlLoader>
dispatcher;
// Panel displaying the "Home" view, with the logo and the fake omnibox.
@property(nonatomic, strong) id<NewTabPagePanelProtocol> homePanel;
// Coordinator for the ContentSuggestions.
@property(nonatomic, strong)
ContentSuggestionsCoordinator* contentSuggestionsCoordinator;
// Controller for the header of the Home panel.
@property(nonatomic, strong) id<LogoAnimationControllerOwnerOwner, ToolbarOwner>
headerController;
@end
@implementation NewTabPageController
@synthesize view = _view;
@synthesize swipeRecognizerProvider = _swipeRecognizerProvider;
@synthesize parentViewController = _parentViewController;
@synthesize dispatcher = _dispatcher;
@synthesize homePanel = _homePanel;
@synthesize contentSuggestionsCoordinator = _contentSuggestionsCoordinator;
@synthesize headerController = _headerController;
- (id)initWithUrl:(const GURL&)url
loader:(id<UrlLoader>)loader
focuser:(id<OmniboxFocuser>)focuser
browserState:(ios::ChromeBrowserState*)browserState
toolbarDelegate:(id<NewTabPageControllerDelegate>)toolbarDelegate
tabModel:(TabModel*)tabModel
parentViewController:(UIViewController*)parentViewController
dispatcher:(id<ApplicationCommands,
BrowserCommands,
OmniboxFocuser,
FakeboxFocuser,
SnackbarCommands,
UrlLoader>)dispatcher
safeAreaInset:(UIEdgeInsets)safeAreaInset {
self = [super initWithNibName:nil url:url];
if (self) {
DCHECK(browserState);
_browserState = browserState;
_loader = loader;
_parentViewController = parentViewController;
_dispatcher = dispatcher;
_focuser = focuser;
_toolbarDelegate = toolbarDelegate;
_tabModel = tabModel;
self.title = l10n_util::GetNSString(IDS_NEW_TAB_TITLE);
NewTabPageBar* tabBar =
[[NewTabPageBar alloc] initWithFrame:CGRectMake(0, 412, 320, 48)];
_view = [[NewTabPageView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)
andTabBar:tabBar];
_view.safeAreaInsetForToolbar = safeAreaInset;
[tabBar setDelegate:self];
bool isIncognito = _browserState->IsOffTheRecord();
NSString* incognito = l10n_util::GetNSString(IDS_IOS_NEW_TAB_INCOGNITO);
NSString* home = l10n_util::GetNSString(IDS_IOS_NEW_TAB_HOME);
NSString* bookmarks =
l10n_util::GetNSString(IDS_IOS_NEW_TAB_BOOKMARKS_PAGE_TITLE_MOBILE);
NSString* openTabs = l10n_util::GetNSString(IDS_IOS_NEW_TAB_RECENT_TABS);
NSMutableArray* tabBarItems = [NSMutableArray array];
NewTabPageBarItem* itemToDisplay = nil;
if (isIncognito) {
NewTabPageBarItem* incognitoItem = [NewTabPageBarItem
newTabPageBarItemWithTitle:incognito
identifier:ntp_home::INCOGNITO_PANEL
image:[UIImage imageNamed:@"ntp_incognito"]];
itemToDisplay = incognitoItem;
} else {
NewTabPageBarItem* homeItem = [NewTabPageBarItem
newTabPageBarItemWithTitle:home
identifier:ntp_home::HOME_PANEL
image:[UIImage imageNamed:@"ntp_mv_search"]];
NewTabPageBarItem* bookmarksItem = [NewTabPageBarItem
newTabPageBarItemWithTitle:bookmarks
identifier:ntp_home::BOOKMARKS_PANEL
image:[UIImage imageNamed:@"ntp_bookmarks"]];
[tabBarItems addObject:bookmarksItem];
NewTabPageBarItem* openTabsItem = [NewTabPageBarItem
newTabPageBarItemWithTitle:openTabs
identifier:ntp_home::RECENT_TABS_PANEL
image:[UIImage imageNamed:@"ntp_opentabs"]];
[tabBarItems addObject:openTabsItem];
self.view.tabBar.items = tabBarItems;
itemToDisplay = homeItem;
base::RecordAction(UserMetricsAction("MobileNTPShowMostVisited"));
}
DCHECK(itemToDisplay);
[self loadPanel:itemToDisplay];
if (isIncognito) {
_currentController = self.incognitoController;
} else {
_currentController = self.homePanel;
}
[_currentController wasShown];
}
return self;
}
- (void)dealloc {
// This is not an ideal place to put view controller contaimnent, rather a
// //web -wasDismissed method on CRWNativeContent would be more accurate. If
// CRWNativeContent leaks, this will not be called.
[_incognitoController removeFromParentViewController];
[[self.contentSuggestionsCoordinator viewController]
removeFromParentViewController];
[self.contentSuggestionsCoordinator stop];
[self.homePanel setDelegate:nil];
}
#pragma mark - CRWNativeContent
- (void)willBeDismissed {
// This methods is called by //web immediately before |self|'s view is removed
// from the view hierarchy, making it an ideal spot to intiate view controller
// containment methods.
[[self.contentSuggestionsCoordinator viewController]
willMoveToParentViewController:nil];
[_incognitoController willMoveToParentViewController:nil];
}
- (void)reload {
[_currentController reload];
[super reload];
}
- (void)wasShown {
[_currentController wasShown];
if (_currentController != self.homePanel) {
// Ensure that the NTP has the latest data when it is shown, except for
// Home.
[self reload];
}
[self.view.tabBar setShadowAlpha:[_currentController alphaForBottomShadow]];
}
- (void)wasHidden {
[_currentController wasHidden];
}
- (BOOL)wantsKeyboardShield {
return NO;
}
- (BOOL)wantsLocationBarHintText {
// Always show hint text on iPhone.
if (!IsIPadIdiom())
return YES;
// Always show the location bar hint text if the search engine is not Google.
TemplateURLService* service =
ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
if (service) {
const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
if (defaultURL &&
defaultURL->GetEngineType(service->search_terms_data()) !=
SEARCH_ENGINE_GOOGLE) {
return YES;
}
}
// Always return true when incognito.
if (_browserState->IsOffTheRecord())
return YES;
return NO;
}
- (void)dismissModals {
[_currentController dismissModals];
}
- (void)willUpdateSnapshot {
[_currentController willUpdateSnapshot];
}
- (CGPoint)scrollOffset {
return [_currentController scrollOffset];
}
#pragma mark -
// Called when the user presses a segment that's not currently selected.
// Pressing a segment that's already selected does not trigger this action.
- (void)newTabBarItemDidChange:(NewTabPageBarItem*)selectedItem {
if (selectedItem.identifier == ntp_home::BOOKMARKS_PANEL) {
[self.dispatcher showBookmarksManager];
} else if (selectedItem.identifier == ntp_home::RECENT_TABS_PANEL) {
[self.dispatcher showRecentTabs];
}
if (_browserState->IsOffTheRecord())
return;
// Update metrics. Intentionally omitting a metric for Incognito panel.
if (selectedItem.identifier == ntp_home::HOME_PANEL) {
base::RecordAction(UserMetricsAction("MobileNTPSwitchToMostVisited"));
} else if (selectedItem.identifier == ntp_home::RECENT_TABS_PANEL) {
base::RecordAction(UserMetricsAction("MobileNTPSwitchToOpenTabs"));
} else if (selectedItem.identifier == ntp_home::BOOKMARKS_PANEL) {
base::RecordAction(UserMetricsAction("MobileNTPSwitchToBookmarks"));
}
}
- (BOOL)loadPanel:(NewTabPageBarItem*)item {
DCHECK(self.parentViewController);
UIViewController* panelController = nil;
UICollectionView* collectionView = nil;
// Only load the controllers once.
if (item.identifier == ntp_home::HOME_PANEL) {
if (!self.contentSuggestionsCoordinator) {
self.contentSuggestionsCoordinator = [
[ContentSuggestionsCoordinator alloc] initWithBaseViewController:nil];
self.contentSuggestionsCoordinator.URLLoader = _loader;
self.contentSuggestionsCoordinator.browserState = _browserState;
self.contentSuggestionsCoordinator.dispatcher = self.dispatcher;
self.contentSuggestionsCoordinator.webStateList =
[_tabModel webStateList];
self.contentSuggestionsCoordinator.toolbarDelegate = _toolbarDelegate;
[self.contentSuggestionsCoordinator start];
self.headerController =
self.contentSuggestionsCoordinator.headerController;
}
panelController = [self.contentSuggestionsCoordinator viewController];
collectionView =
self.contentSuggestionsCoordinator.viewController.collectionView;
self.homePanel = self.contentSuggestionsCoordinator;
[self.homePanel setDelegate:self];
} else if (item.identifier == ntp_home::INCOGNITO_PANEL) {
if (!_incognitoController)
_incognitoController =
[[IncognitoViewController alloc] initWithLoader:_loader
toolbarDelegate:_toolbarDelegate];
panelController = _incognitoController;
} else {
NOTREACHED();
return NO;
}
UIView* view = panelController.view;
if (item.identifier == ntp_home::HOME_PANEL) {
// Update the shadow for the toolbar after the view creation.
[self.view.tabBar setShadowAlpha:[self.homePanel alphaForBottomShadow]];
}
BOOL created = NO;
if (view.superview == nil) {
created = YES;
item.view = view;
// To ease modernizing the NTP only the internal panels are being converted
// to UIViewControllers. This means all the plumbing between the
// BrowserViewController and the internal NTP panels (WebController, NTP)
// hierarchy is skipped. While normally the logic to push and pop a view
// controller would be owned by a coordinator, in this case the old NTP
// controller adds and removes child view controllers itself when a load
// is initiated, and when WebController calls -willBeDismissed.
DCHECK(panelController);
[self.parentViewController addChildViewController:panelController];
[self.view insertSubview:view belowSubview:self.view.tabBar];
self.view.contentView = view;
self.view.contentCollectionView = collectionView;
[panelController didMoveToParentViewController:self.parentViewController];
}
return created;
}
#pragma mark - LogoAnimationControllerOwnerOwner
- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
return [self.headerController logoAnimationControllerOwner];
}
#pragma mark -
#pragma mark ToolbarOwner
- (CGRect)toolbarFrame {
return [self.headerController toolbarFrame];
}
- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
return self.headerController.toolbarSnapshotProvider;
}
- (CGFloat)toolbarHeight {
BOOL isRegularXRegular =
content_suggestions::IsRegularXRegularSizeClass(self.view);
// If the google landing controller is nil, there is no toolbar visible in the
// native content view, finally there is no toolbar on iPad.
return self.headerController && !isRegularXRegular
? ntp_header::ToolbarHeight()
: 0.0;
}
#pragma mark - NewTabPagePanelControllerDelegate
- (void)updateNtpBarShadowForPanelController:
(id<NewTabPagePanelProtocol>)ntpPanelController {
if (_currentController != ntpPanelController)
return;
[self.view.tabBar setShadowAlpha:[ntpPanelController alphaForBottomShadow]];
}
@end
@implementation NewTabPageController (TestSupport)
- (id<NewTabPagePanelProtocol>)currentController {
return _currentController;
}
- (id<NewTabPagePanelProtocol>)incognitoController {
return _incognitoController;
}
@end