chromium/ios/chrome/browser/contextual_panel/coordinator/panel_content_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/panel_content_coordinator.h"

#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/contextual_panel/coordinator/panel_block_modulator.h"
#import "ios/chrome/browser/contextual_panel/coordinator/panel_content_mediator.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_item_configuration.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_item_type.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h"
#import "ios/chrome/browser/contextual_panel/sample/coordinator/sample_block_modulator.h"
#import "ios/chrome/browser/contextual_panel/ui/contextual_sheet_display_controller.h"
#import "ios/chrome/browser/contextual_panel/ui/panel_content_view_controller.h"
#import "ios/chrome/browser/price_insights/coordinator/price_insights_modulator.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.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/fullscreen_controller.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/common/ui/util/ui_util.h"

@interface PanelContentCoordinator () <
    PanelContentViewControllerMetricsDelegate,
    UIAdaptivePresentationControllerDelegate>

@end

@implementation PanelContentCoordinator {
  // The view controller managed by this coordinator.
  PanelContentViewController* _viewController;

  // The mediator managed by this coordinator.
  PanelContentMediator* _mediator;

  // The child modulators owned by this coordinator.
  NSMutableArray<PanelBlockModulator*>* _modulators;

  // The contextual panel tab helper to use for this panel.
  ContextualPanelTabHelper* _contextualPanelTabHelper;

  // Read-write version of `self.baseViewController` as the base view
  // controller for this coordinator changes during its lifetime.
  UIViewController* _modifiableBaseViewController;
}

- (void)start {
  _modifiableBaseViewController = self.baseViewController;

  _viewController = [[PanelContentViewController alloc] init];
  _viewController.metricsDelegate = self;
  _viewController.traitCollectionDelegate = self.traitCollectionDelegate;

  ChromeBroadcaster* broadcaster =
      FullscreenController::FromBrowser(self.browser)->broadcaster();

  _mediator = [[PanelContentMediator alloc] initWithBroadcaster:broadcaster];
  _mediator.consumer = _viewController;

  _modulators = [[NSMutableArray alloc] init];

  web::WebState* activeWebState =
      self.browser->GetWebStateList()->GetActiveWebState();

  _contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(activeWebState);

  std::vector<base::WeakPtr<ContextualPanelItemConfiguration>> configurations =
      _contextualPanelTabHelper->GetCurrentCachedConfigurations();

  NSMutableArray<PanelBlockData*>* panelBlocks = [[NSMutableArray alloc] init];
  for (base::WeakPtr<ContextualPanelItemConfiguration> configuration :
       configurations) {
    PanelBlockModulator* modulator =
        [self modulatorForConfiguration:configuration];
    if (!modulator) {
      continue;
    }
    [modulator start];
    PanelBlockData* panelBlockData = [modulator panelBlockData];
    if (!panelBlockData) {
      [modulator stop];
      continue;
    }
    [_modulators addObject:modulator];
    [panelBlocks addObject:panelBlockData];
  }

  _viewController.contextualSheetCommandHandler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ContextualSheetCommands);
  [_viewController setPanelBlocks:panelBlocks];

  // On iPad, present using iOS's built-in UISheetController.
  if (IsRegularXRegularSizeClass(_modifiableBaseViewController)) {
    [self presentViewControllerFromBaseViewControllerAnimated:YES];
  } else {
    // On iPhone/iPad multiwindow, add the view controller instead of presenting
    // it to use the custom Contextual Panel sheet.
    [self addViewControllerToBaseViewController];
  }
}

- (PanelBlockModulator*)modulatorForConfiguration:
    (base::WeakPtr<ContextualPanelItemConfiguration>)configuration {
  if (!configuration) {
    return nil;
  }

  switch (configuration->item_type) {
    case ContextualPanelItemType::SamplePanelItem:
      return [[SampleBlockModulator alloc]
          initWithBaseViewController:_viewController
                             browser:self.browser
                   itemConfiguration:configuration];
    case ContextualPanelItemType::PriceInsightsItem:
      return [[PriceInsightsModulator alloc]
          initWithBaseViewController:_viewController
                             browser:self.browser
                   itemConfiguration:configuration];
  }
}

- (void)stop {
  if ([_viewController presentingViewController]) {
    [_modifiableBaseViewController dismissViewControllerAnimated:YES
                                                      completion:nil];
  } else {
    [self removeViewControllerFromBaseViewController];
  }
  _viewController = nil;
}

#pragma mark - Public

- (void)presentFromNewBaseViewController:(UIViewController*)viewController {
  [self removeViewControllerFromBaseViewController];

  _modifiableBaseViewController = viewController;

  [self presentViewControllerFromBaseViewControllerAnimated:NO];
}

- (void)embedInParentViewController:(UIViewController*)viewController {
  __weak __typeof(self) weakSelf = self;
  [_modifiableBaseViewController
      dismissViewControllerAnimated:NO
                         completion:^{
                           [weakSelf addViewControllerToBaseViewController];
                         }];

  _modifiableBaseViewController = viewController;
}

#pragma mark - View hierarcy manipulation helper methods

// Adds the view controller as a child of the current base view controller.
- (void)addViewControllerToBaseViewController {
  if ([_modifiableBaseViewController
          conformsToProtocol:@protocol(ContextualSheetDisplayController)]) {
    _viewController.sheetDisplayController =
        static_cast<id<ContextualSheetDisplayController>>(
            _modifiableBaseViewController);
  }

  [_modifiableBaseViewController addChildViewController:_viewController];
  [_modifiableBaseViewController.view addSubview:_viewController.view];
  _viewController.view.translatesAutoresizingMaskIntoConstraints = NO;
  AddSameConstraints(_modifiableBaseViewController.view, _viewController.view);

  [_viewController didMoveToParentViewController:_modifiableBaseViewController];
}

// Removes the view controller from the view hierarcy of the current base view
// controller.
- (void)removeViewControllerFromBaseViewController {
  [_viewController willMoveToParentViewController:nil];
  [_viewController.view removeFromSuperview];
  [_viewController removeFromParentViewController];
}

// Presents the view controller modally from the current base view controller.
- (void)presentViewControllerFromBaseViewControllerAnimated:(BOOL)animated {
  _viewController.modalPresentationStyle = UIModalPresentationFormSheet;
  _viewController.presentationController.delegate = self;
  [_modifiableBaseViewController presentViewController:_viewController
                                              animated:animated
                                            completion:nil];
}

#pragma mark - PanelContentViewControllerMetricsDelegate

- (NSString*)entrypointInfoBlockName {
  auto entrypointConfig = _contextualPanelTabHelper->GetFirstCachedConfig();

  if (!entrypointConfig) {
    return nil;
  }

  return base::SysUTF8ToNSString(
      StringForItemType(entrypointConfig->item_type));
}

- (BOOL)wasLoudEntrypoint {
  return _contextualPanelTabHelper->WasLoudMomentEntrypointShown();
}

#pragma mark - UIAdaptivePresentationControllerDelegate

- (void)presentationControllerDidDismiss:
    (UIPresentationController*)presentationController {
  id<ContextualSheetCommands> contextualSheetCommandHandler =
      HandlerForProtocol(self.browser->GetCommandDispatcher(),
                         ContextualSheetCommands);
  [contextualSheetCommandHandler closeContextualSheet];
}

@end