1// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/ui/toolbar/primary_toolbar_coordinator.h"
6
7#import <CoreLocation/CoreLocation.h>
8
9#include <memory>
10
11#include "base/mac/foundation_util.h"
12#include "base/metrics/histogram_macros.h"
13#include "base/strings/sys_string_conversions.h"
14#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
15#import "ios/chrome/browser/main/browser.h"
16#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
17#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
18#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
19#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
20#import "ios/chrome/browser/ui/location_bar/location_bar_coordinator.h"
21#import "ios/chrome/browser/ui/ntp/ntp_util.h"
22#import "ios/chrome/browser/ui/omnibox/omnibox_text_field_ios.h"
23#import "ios/chrome/browser/ui/orchestrator/omnibox_focus_orchestrator.h"
24#import "ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator+subclassing.h"
25#import "ios/chrome/browser/ui/toolbar/primary_toolbar_view_controller.h"
26#import "ios/chrome/browser/ui/toolbar/primary_toolbar_view_controller_delegate.h"
27#import "ios/chrome/browser/ui/toolbar/toolbar_coordinator_delegate.h"
28#import "ios/chrome/browser/ui/util/ui_util.h"
29#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
30#import "ios/chrome/browser/web_state_list/web_state_list.h"
31#include "ios/web/public/navigation/referrer.h"
32
33#if !defined(__has_feature) || !__has_feature(objc_arc)
34#error "This file requires ARC support."
35#endif
36
37@interface PrimaryToolbarCoordinator () <PrimaryToolbarViewControllerDelegate> {
38  // Observer that updates |toolbarViewController| for fullscreen events.
39  std::unique_ptr<FullscreenUIUpdater> _fullscreenUIUpdater;
40}
41
42// Whether the coordinator is started.
43@property(nonatomic, assign, getter=isStarted) BOOL started;
44// Redefined as PrimaryToolbarViewController.
45@property(nonatomic, strong) PrimaryToolbarViewController* viewController;
46// The coordinator for the location bar in the toolbar.
47@property(nonatomic, strong) LocationBarCoordinator* locationBarCoordinator;
48// Orchestrator for the expansion animation.
49@property(nonatomic, strong) OmniboxFocusOrchestrator* orchestrator;
50// Whether the omnibox focusing should happen with animation.
51@property(nonatomic, assign) BOOL enableAnimationsForOmniboxFocus;
52
53@end
54
55@implementation PrimaryToolbarCoordinator
56
57@dynamic viewController;
58@synthesize popupPresenterDelegate = _popupPresenterDelegate;
59@synthesize delegate = _delegate;
60
61#pragma mark - ChromeCoordinator
62
63- (void)start {
64  DCHECK(self.browser);
65  if (self.started)
66    return;
67
68  self.enableAnimationsForOmniboxFocus = YES;
69
70  [self.browser->GetCommandDispatcher()
71      startDispatchingToTarget:self
72                   forProtocol:@protocol(FakeboxFocuser)];
73
74  self.viewController = [[PrimaryToolbarViewController alloc] init];
75  self.viewController.shouldHideOmniboxOnNTP =
76      !self.browser->GetBrowserState()->IsOffTheRecord();
77  self.viewController.buttonFactory = [self buttonFactoryWithType:PRIMARY];
78  // TODO(crbug.com/1045047): Use HandlerForProtocol after commands protocol
79  // clean up.
80  self.viewController.dispatcher =
81      static_cast<id<ApplicationCommands, BrowserCommands, OmniboxCommands>>(
82          self.browser->GetCommandDispatcher());
83  self.viewController.delegate = self;
84
85  self.orchestrator = [[OmniboxFocusOrchestrator alloc] init];
86  self.orchestrator.toolbarAnimatee = self.viewController;
87
88  [self setUpLocationBar];
89  self.viewController.locationBarViewController =
90      self.locationBarCoordinator.locationBarViewController;
91  self.orchestrator.locationBarAnimatee =
92      [self.locationBarCoordinator locationBarAnimatee];
93
94  self.orchestrator.editViewAnimatee =
95      [self.locationBarCoordinator editViewAnimatee];
96
97  if (fullscreen::features::ShouldScopeFullscreenControllerToBrowser()) {
98    _fullscreenUIUpdater = std::make_unique<FullscreenUIUpdater>(
99        FullscreenController::FromBrowser(self.browser), self.viewController);
100  } else {
101    _fullscreenUIUpdater = std::make_unique<FullscreenUIUpdater>(
102        FullscreenController::FromBrowserState(self.browser->GetBrowserState()),
103        self.viewController);
104  }
105
106  [super start];
107  self.started = YES;
108}
109
110- (void)stop {
111  if (!self.started)
112    return;
113  [super stop];
114  [self.browser->GetCommandDispatcher() stopDispatchingToTarget:self];
115  [self.locationBarCoordinator stop];
116  _fullscreenUIUpdater = nullptr;
117  self.started = NO;
118}
119
120#pragma mark - Public
121
122- (id<ActivityServicePositioner>)activityServicePositioner {
123  return self.viewController;
124}
125
126- (void)showPrerenderingAnimation {
127  [self.viewController showPrerenderingAnimation];
128}
129
130- (BOOL)isOmniboxFirstResponder {
131  return [self.locationBarCoordinator isOmniboxFirstResponder];
132}
133
134- (BOOL)showingOmniboxPopup {
135  return [self.locationBarCoordinator showingOmniboxPopup];
136}
137
138- (void)transitionToLocationBarFocusedState:(BOOL)focused {
139  if (self.viewController.traitCollection.verticalSizeClass ==
140      UIUserInterfaceSizeClassUnspecified) {
141    return;
142  }
143
144  [self.orchestrator
145      transitionToStateOmniboxFocused:focused
146                      toolbarExpanded:focused && !IsRegularXRegularSizeClass(
147                                                     self.viewController)
148                             animated:self.enableAnimationsForOmniboxFocus];
149}
150
151- (id<ViewRevealingAnimatee>)animatee {
152  return self.viewController;
153}
154
155- (void)setPanGestureHandler:
156    (ViewRevealingVerticalPanHandler*)panGestureHandler {
157  self.viewController.panGestureHandler = panGestureHandler;
158}
159
160#pragma mark - PrimaryToolbarViewControllerDelegate
161
162- (void)viewControllerTraitCollectionDidChange:
163    (UITraitCollection*)previousTraitCollection {
164  BOOL omniboxFocused = self.isOmniboxFirstResponder ||
165                        [self.locationBarCoordinator showingOmniboxPopup];
166  [self.orchestrator
167      transitionToStateOmniboxFocused:omniboxFocused
168                      toolbarExpanded:omniboxFocused &&
169                                      !IsRegularXRegularSizeClass(
170                                          self.viewController)
171                             animated:NO];
172}
173
174- (void)exitFullscreen {
175  if (fullscreen::features::ShouldScopeFullscreenControllerToBrowser()) {
176    FullscreenController::FromBrowser(self.browser)->ExitFullscreen();
177  } else {
178    FullscreenController::FromBrowserState(self.browser->GetBrowserState())
179        ->ExitFullscreen();
180  }
181}
182
183#pragma mark - NewTabPageControllerDelegate
184
185- (UIResponder<UITextInput>*)fakeboxScribbleForwardingTarget {
186  return self.locationBarCoordinator.omniboxScribbleForwardingTarget;
187}
188
189#pragma mark - FakeboxFocuser
190
191- (void)focusOmniboxNoAnimation {
192  self.enableAnimationsForOmniboxFocus = NO;
193  [self fakeboxFocused];
194  self.enableAnimationsForOmniboxFocus = YES;
195  // If the pasteboard is containing a URL, the omnibox popup suggestions are
196  // displayed as soon as the omnibox is focused.
197  // If the fake omnibox animation is triggered at the same time, it is possible
198  // to see the NTP going up where the real omnibox should be displayed.
199  if ([self.locationBarCoordinator omniboxPopupHasAutocompleteResults])
200    [self onFakeboxAnimationComplete];
201}
202
203- (void)fakeboxFocused {
204  [self.locationBarCoordinator focusOmniboxFromFakebox];
205}
206
207- (void)onFakeboxBlur {
208  // Hide the toolbar if the NTP is currently displayed.
209  web::WebState* webState =
210      self.browser->GetWebStateList()->GetActiveWebState();
211  if (webState && IsVisibleURLNewTabPage(webState)) {
212    self.viewController.view.hidden = IsSplitToolbarMode(self.viewController);
213  }
214}
215
216- (void)onFakeboxAnimationComplete {
217  self.viewController.view.hidden = NO;
218}
219
220#pragma mark - Protected override
221
222- (void)updateToolbarForSideSwipeSnapshot:(web::WebState*)webState {
223  [super updateToolbarForSideSwipeSnapshot:webState];
224
225  BOOL isNTP = IsVisibleURLNewTabPage(webState);
226
227  // Don't do anything for a live non-ntp tab.
228  if (webState == self.browser->GetWebStateList()->GetActiveWebState() &&
229      !isNTP) {
230    [self.locationBarCoordinator.locationBarViewController.view setHidden:NO];
231  } else {
232    self.viewController.view.hidden = NO;
233    [self.locationBarCoordinator.locationBarViewController.view setHidden:YES];
234  }
235}
236
237- (void)resetToolbarAfterSideSwipeSnapshot {
238  [super resetToolbarAfterSideSwipeSnapshot];
239  [self.locationBarCoordinator.locationBarViewController.view setHidden:NO];
240}
241
242#pragma mark - Private
243
244// Sets the location bar up.
245- (void)setUpLocationBar {
246  self.locationBarCoordinator =
247      [[LocationBarCoordinator alloc] initWithBaseViewController:nil
248                                                         browser:self.browser];
249  self.locationBarCoordinator.delegate = self.delegate;
250  self.locationBarCoordinator.popupPresenterDelegate =
251      self.popupPresenterDelegate;
252  [self.locationBarCoordinator start];
253}
254
255@end
256