chromium/ios/chrome/browser/home_customization/coordinator/home_customization_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/home_customization/coordinator/home_customization_coordinator.h"

#import "ios/chrome/browser/home_customization/coordinator/home_customization_delegate.h"
#import "ios/chrome/browser/home_customization/coordinator/home_customization_mediator.h"
#import "ios/chrome/browser/home_customization/ui/home_customization_discover_view_controller.h"
#import "ios/chrome/browser/home_customization/ui/home_customization_magic_stack_view_controller.h"
#import "ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h"
#import "ios/chrome/browser/home_customization/utils/home_customization_constants.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"

namespace {

// The height of the menu's initial detent, which roughly represents a header
// and 3 cells.
const CGFloat kInitialDetentHeight = 350;

// The corner radius of the customization menu sheet.
CGFloat const kSheetCornerRadius = 30;

}  // namespace

@interface HomeCustomizationCoordinator () <
    UISheetPresentationControllerDelegate>

// The main page of the customization menu.
@property(nonatomic, strong)
    HomeCustomizationMainViewController* mainViewController;

// The Magic Stack page of the customization menu.
@property(nonatomic, strong)
    HomeCustomizationMagicStackViewController* magicStackViewController;

// The Discover page of the customization menu.
@property(nonatomic, strong)
    HomeCustomizationDiscoverViewController* discoverViewController;

// The mediator for the Home customization menu.
@property(nonatomic, strong) HomeCustomizationMediator* mediator;

// This menu consists of several sheets that can be overlayed on top of each
// other, each representing a submenu.
// This property points to the view controller that is at the base of the stack.
@property(nonatomic, weak) UIViewController* firstPageViewController;
// This property points to the view controller that is at the top of the stack.
@property(nonatomic, weak) UIViewController* currentPageViewController;

@end

@implementation HomeCustomizationCoordinator

#pragma mark - ChromeCoordinator

- (void)start {
  _mediator = [[HomeCustomizationMediator alloc]
      initWithPrefService:ChromeBrowserState::FromBrowserState(
                              self.browser->GetBrowserState())
                              ->GetPrefs()];
  _mediator.navigationDelegate = self;

  _currentPageViewController = self.baseViewController;

  [super start];
}

- (void)stop {
  [self.baseViewController dismissViewControllerAnimated:NO completion:nil];

  _mainViewController = nil;
  _magicStackViewController = nil;
  _discoverViewController = nil;
  _mediator = nil;

  [super stop];
}

#pragma mark - Public

- (void)updateMenuData {
  if (self.mainViewController) {
    [self.mediator configureMainPageData];
  }

  if (self.magicStackViewController) {
    [self.mediator configureMagicStackPageData];
  }

  if (self.discoverViewController) {
    [self.mediator configureDiscoverPageData];
  }
}

#pragma mark - HomeCustomizationNavigationDelegate

- (void)presentCustomizationMenuPage:(CustomizationMenuPage)page {
  UIViewController* menuPage = [self createMenuPage:page];

  // If this is the first page being presented, set a reference to it in
  // `firstPageViewController`.
  if (self.baseViewController == self.currentPageViewController) {
    self.firstPageViewController = menuPage;
  }

  [self.currentPageViewController
      presentViewController:menuPage
                   animated:YES
                 completion:^{
                   UIAccessibilityPostNotification(
                       UIAccessibilityScreenChangedNotification, menuPage);
                 }];

  self.currentPageViewController.view.accessibilityElementsHidden = YES;
  self.currentPageViewController = menuPage;
}

- (void)dismissMenuPage {
  // If the page being dismissed is the first page of the stack, then the entire
  // menu should be dismissed. Otherwise, dismiss the topmost page and update
  // the currently visible page.
  if (self.currentPageViewController == self.firstPageViewController) {
    [self.delegate dismissCustomizationMenu];
  } else {
    [self.currentPageViewController dismissViewControllerAnimated:YES
                                                       completion:nil];
    self.currentPageViewController =
        self.currentPageViewController.presentingViewController;
    self.currentPageViewController.view.accessibilityElementsHidden = NO;
  }
}

- (void)navigateToURL:(GURL)URL {
  UrlLoadingBrowserAgent::FromBrowser(self.browser)
      ->Load(UrlLoadParams::InCurrentTab(URL));
  [self.delegate dismissCustomizationMenu];
}

#pragma mark - UISheetPresentationControllerDelegate

- (void)presentationControllerWillDismiss:
    (UIPresentationController*)presentationController {
  [self dismissMenuPage];
}

#pragma mark - Private

// Creates a view controller for a page in the menu.
- (UIViewController*)createMenuPage:(CustomizationMenuPage)page {
  UIViewController* menuPage;

  // Create view controller for the `page` and configure it with the mediator.
  switch (page) {
    case CustomizationMenuPage::kMain: {
      self.mainViewController =
          [[HomeCustomizationMainViewController alloc] init];
      self.mainViewController.mutator = _mediator;
      self.mediator.mainPageConsumer = self.mainViewController;
      [self.mediator configureMainPageData];
      menuPage = self.mainViewController;
      break;
    }
    case CustomizationMenuPage::kMagicStack: {
      self.magicStackViewController =
          [[HomeCustomizationMagicStackViewController alloc] init];
      self.magicStackViewController.mutator = _mediator;
      self.mediator.magicStackPageConsumer = self.magicStackViewController;
      [self.mediator configureMagicStackPageData];
      menuPage = self.magicStackViewController;
      break;
    }
    case CustomizationMenuPage::kDiscover: {
      self.discoverViewController =
          [[HomeCustomizationDiscoverViewController alloc] init];
      self.discoverViewController.mutator = _mediator;
      self.mediator.discoverPageConsumer = self.discoverViewController;
      [self.mediator configureDiscoverPageData];
      menuPage = self.discoverViewController;
      break;
    }
    case CustomizationMenuPage::kUnknown:
      NOTREACHED_NORETURN();
  }

  // Configure the navigation controller.
  UINavigationController* navigationController =
      [[UINavigationController alloc] initWithRootViewController:menuPage];
  navigationController.modalPresentationStyle = UIModalPresentationFormSheet;

  // Configure the presentation controller with a custom initial detent.
  UISheetPresentationController* presentationController =
      navigationController.sheetPresentationController;
  presentationController.prefersEdgeAttachedInCompactHeight = YES;
  presentationController.preferredCornerRadius = kSheetCornerRadius;
  presentationController.delegate = self;

  auto detentResolver = ^CGFloat(
      id<UISheetPresentationControllerDetentResolutionContext> context) {
    return kInitialDetentHeight;
  };
  UISheetPresentationControllerDetent* initialDetent =
      [UISheetPresentationControllerDetent
          customDetentWithIdentifier:kBottomSheetDetentIdentifier
                            resolver:detentResolver];
  presentationController.detents = @[
    initialDetent,
  ];
  presentationController.selectedDetentIdentifier =
      kBottomSheetDetentIdentifier;
  presentationController.largestUndimmedDetentIdentifier =
      kBottomSheetDetentIdentifier;

  return navigationController;
}

@end