chromium/ios/chrome/browser/contextual_panel/coordinator/contextual_sheet_coordinator.mm

// Copyright 2024 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/contextual_panel/coordinator/contextual_sheet_coordinator.h"

#import "ios/chrome/browser/contextual_panel/coordinator/contextual_sheet_presenter.h"
#import "ios/chrome/browser/contextual_panel/coordinator/panel_content_coordinator.h"
#import "ios/chrome/browser/contextual_panel/ui/contextual_sheet_view_controller.h"
#import "ios/chrome/browser/contextual_panel/ui/trait_collection_change_delegate.h"
#import "ios/chrome/browser/omnibox/model/omnibox_position_browser_agent.h"
#import "ios/chrome/browser/omnibox/model/omnibox_position_browser_agent_observer_bridge.h"
#import "ios/chrome/browser/omnibox/model/omnibox_position_browser_agent_observing.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/contextual_sheet_commands.h"
#import "ios/chrome/browser/ui/fullscreen/animated_scoped_fullscreen_disabler.h"
#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
#import "ios/chrome/common/ui/util/ui_util.h"

@interface ContextualSheetCoordinator () <OmniboxPositionBrowserAgentObserving,
                                          TraitCollectionChangeDelegate>

@end

@implementation ContextualSheetCoordinator {
  ContextualSheetViewController* _viewController;

  PanelContentCoordinator* _panelContentCoordinator;

  // The AnimatedFullscreenDisabler to disable fullscreen when the bottom
  // omnibox + contextual sheet are open.
  std::unique_ptr<AnimatedScopedFullscreenDisabler> _animatedFullscreenDisabler;

  // Bridge to observe the OmniboxPositionBrowserAgent.
  std::unique_ptr<OmniboxPositionBrowserAgentObserverBridge> _observerBridge;
}

- (void)start {
  OmniboxPositionBrowserAgent* browserAgent =
      OmniboxPositionBrowserAgent::FromBrowser(self.browser);
  _observerBridge = std::make_unique<OmniboxPositionBrowserAgentObserverBridge>(
      self, browserAgent);

  // On iPad, let the panel coordinator present directly using iOS's built-in
  // UISheetController.
  if (IsRegularXRegularSizeClass(self.baseViewController)) {
    [self createPanelCoordinatorWithBaseViewController:self.baseViewController];
  } else {
    // On iPhone/iPad multiwindow, use the custom sheet this coordinator
    // provides.
    __weak __typeof(self) weakSelf = self;
    [self addViewControllerToBaseViewControllerAnimated:YES
                                     panelAdditionBlock:^{
                                       [weakSelf
                                           createPanelCoordinatorAtStartBlock];
                                     }];
  }
}

- (void)stop {
  [_panelContentCoordinator stop];
  _panelContentCoordinator = nil;

  if (_viewController) {
    [self removeViewControllerFromBaseViewController];
  }

  _animatedFullscreenDisabler = nullptr;
  _observerBridge = nullptr;
}

- (void)traitCollectionDidChangeForViewController:
    (UIViewController*)viewController {
  // Use existence of `_viewController` as a signal for whether the new layout
  // has been prepared.
  if (IsRegularXRegularSizeClass(self.baseViewController) && _viewController) {
    [self removeViewControllerFromBaseViewController];
    [_panelContentCoordinator
        presentFromNewBaseViewController:self.baseViewController];
  } else if (!IsRegularXRegularSizeClass(self.baseViewController) &&
             !_viewController) {
    __weak __typeof(self) weakSelf = self;
    [self addViewControllerToBaseViewControllerAnimated:NO
                                     panelAdditionBlock:^{
                                       [weakSelf embedPanelCoordinator];
                                     }];
  }
}

// Helper block for creating the panel coordinator in a block from -start.
- (void)createPanelCoordinatorAtStartBlock {
  [self createPanelCoordinatorWithBaseViewController:_viewController];
}

// Creates the panel coordinator using the given base view controller for the
// new coordinator.
- (void)createPanelCoordinatorWithBaseViewController:
    (UIViewController*)baseViewController {
  _panelContentCoordinator = [[PanelContentCoordinator alloc]
      initWithBaseViewController:baseViewController
                         browser:self.browser];
  _panelContentCoordinator.traitCollectionDelegate = self;
  [_panelContentCoordinator start];
}

// Embeds the panel coordinator into the right parent view controller.
- (void)embedPanelCoordinator {
  [_panelContentCoordinator embedInParentViewController:_viewController];
}

// Removes this coordinator' view controller from the base.
- (void)removeViewControllerFromBaseViewController {
  [_viewController willMoveToParentViewController:nil];
  [_viewController.view removeFromSuperview];
  [_viewController removeFromParentViewController];

  _viewController = nil;
}

// Adds this coordinator's view controller to the base view controller.
// `additionBlock` is the block that actually adds the panel's content into this
// coordinator's view controller.
- (void)addViewControllerToBaseViewControllerAnimated:(BOOL)animated
                                   panelAdditionBlock:
                                       (void (^)(void))additionBlock {
  _viewController = [[ContextualSheetViewController alloc] init];
  _viewController.contextualSheetHandler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ContextualSheetCommands);
  _viewController.traitCollectionDelegate = self;

  [self.baseViewController addChildViewController:_viewController];

  [self.presenter insertContextualSheet:_viewController.view];

  [_viewController didMoveToParentViewController:self.baseViewController];

  OmniboxPositionBrowserAgent* browserAgent =
      OmniboxPositionBrowserAgent::FromBrowser(self.browser);
  if (browserAgent->IsCurrentLayoutBottomOmnibox()) {
    [self disableFullscreen];
  }

  if (additionBlock) {
    additionBlock();
  }

  if (animated) {
    [_viewController animateAppearance];
  }
}

- (void)disableFullscreen {
  _animatedFullscreenDisabler =
      std::make_unique<AnimatedScopedFullscreenDisabler>(
          FullscreenController::FromBrowser(self.browser));
  _animatedFullscreenDisabler->StartAnimation();
}

- (void)enableFullscreen {
  _animatedFullscreenDisabler = nullptr;
}

#pragma mark - Boolean Observer

- (void)omniboxPositionBrowserAgent:(OmniboxPositionBrowserAgent*)browser_agent
       isCurrentLayoutBottomOmnibox:(BOOL)isCurrentLayoutBottomOmnibox {
  if (isCurrentLayoutBottomOmnibox) {
    [self disableFullscreen];
  } else {
    [self enableFullscreen];
  }
}

@end