chromium/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_mediator.mm

// Copyright 2023 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/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_mediator.h"

#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h"
#import "ios/chrome/browser/ui/menu/action_factory.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mode_holder.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mode_observing.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_bottom_toolbar.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_page_control.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_configuration.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_grid_delegate.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_top_toolbar.h"

@interface TabGridToolbarsMediator () <TabGridModeObserving,
                                       WebStateListObserving>

@end

@implementation TabGridToolbarsMediator {
  // Configuration that provides all buttons to display.
  TabGridToolbarsConfiguration* _configuration;
  TabGridToolbarsConfiguration* _previousConfiguration;
  id<TabGridToolbarsGridDelegate> _buttonsDelegate;

  // Bridge for observing WebStateList events.
  std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;

  // YES if buttons are disabled.
  BOOL _isDisabled;

  TabGridModeHolder* _modeHolder;
}

- (instancetype)initWithModeHolder:(TabGridModeHolder*)modeHolder {
  self = [super init];
  if (self) {
    CHECK(modeHolder);
    _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
    _modeHolder = modeHolder;
    [_modeHolder addObserver:self];
  }
  return self;
}

#pragma mark - Public

- (void)disconnect {
  if (_webStateList) {
    _webStateList->RemoveObserver(_webStateListObserver.get());
    _webStateListObserver.reset();
    _webStateList = nullptr;
  }

  [_modeHolder removeObserver:self];
  _modeHolder = nil;
}

#pragma mark - GridToolbarsMutator

- (void)setToolbarConfiguration:(TabGridToolbarsConfiguration*)configuration {
  if (_isDisabled) {
    // Handle page change during drag and drop.
    _previousConfiguration = configuration;
    return;
  }

  _configuration = configuration;

  self.topToolbarConsumer.page = configuration.page;
  self.bottomToolbarConsumer.page = configuration.page;

  // TODO(crbug.com/40273478): Add all buttons management.
  [self configureSelectionModeButtons];

  // Configures titles.
  self.topToolbarConsumer.selectedTabsCount = _configuration.selectedItemsCount;
  self.bottomToolbarConsumer.selectedTabsCount =
      _configuration.selectedItemsCount;
  if (_configuration.selectAllButton) {
    [self.topToolbarConsumer configureSelectAllButtonTitle];
  } else {
    [self.topToolbarConsumer configureDeselectAllButtonTitle];
  }

  [self configureEditOrUndoButton];

  [self.bottomToolbarConsumer
      setNewTabButtonEnabled:_configuration.newTabButton];

  [self.topToolbarConsumer setDoneButtonEnabled:_configuration.doneButton];
  [self.bottomToolbarConsumer setDoneButtonEnabled:_configuration.doneButton];

  [self.topToolbarConsumer setSearchButtonEnabled:_configuration.searchButton];
}

- (void)setToolbarsButtonsDelegate:(id<TabGridToolbarsGridDelegate>)delegate {
  _buttonsDelegate = delegate;
  self.topToolbarConsumer.buttonsDelegate = delegate;
  self.bottomToolbarConsumer.buttonsDelegate = delegate;
}

- (void)setButtonsEnabled:(BOOL)enabled {
  // Do not do anything if the state do not change.
  if (enabled != _isDisabled) {
    return;
  }

  if (enabled) {
    // Set the disabled boolean before modifiying the toolbar configuration
    // because the configuration setup is skipped when disabled.
    _isDisabled = NO;
    [self setToolbarConfiguration:_previousConfiguration];
  } else {
    _previousConfiguration = _configuration;
    [self setToolbarConfiguration:
              [TabGridToolbarsConfiguration
                  disabledConfigurationForPage:TabGridPageRegularTabs]];
    // Set the disabled boolean after modifiying the toolbar configuration
    // because the configuration setup is skipped when disabled.
    _isDisabled = YES;
  }
}

#pragma mark - WebStateListObserving

- (void)didChangeWebStateList:(WebStateList*)webStateList
                       change:(const WebStateListChange&)change
                       status:(const WebStateListStatus&)status {
  if (webStateList->IsBatchInProgress()) {
    return;
  }

  CHECK_EQ(_webStateList, webStateList);
  switch (change.type()) {
    case WebStateListChange::Type::kDetach:
    case WebStateListChange::Type::kInsert: {
      [self updateTabCount:_webStateList->count()];
      break;
    }
    case WebStateListChange::Type::kReplace:
    case WebStateListChange::Type::kStatusOnly:
    case WebStateListChange::Type::kMove:
    case WebStateListChange::Type::kGroupCreate:
    case WebStateListChange::Type::kGroupVisualDataUpdate:
    case WebStateListChange::Type::kGroupMove:
    case WebStateListChange::Type::kGroupDelete:
      break;
  }
}

- (void)webStateListBatchOperationEnded:(WebStateList*)webStateList {
  DCHECK_EQ(_webStateList, webStateList);
  [self updateTabCount:_webStateList->count()];
}

#pragma mark - TabGridModeObserving

- (void)tabGridModeDidChange:(TabGridModeHolder*)modeHolder {
  self.topToolbarConsumer.mode = modeHolder.mode;
  self.bottomToolbarConsumer.mode = modeHolder.mode;
}

#pragma mark - Setters

- (void)setTopToolbarConsumer:(TabGridTopToolbar*)topToolbarConsumer {
  if (_topToolbarConsumer == topToolbarConsumer) {
    return;
  }
  _topToolbarConsumer = topToolbarConsumer;
  _topToolbarConsumer.mode = _modeHolder.mode;
}

- (void)setBottomToolbarConsumer:(TabGridBottomToolbar*)bottomToolbarConsumer {
  if (_bottomToolbarConsumer == bottomToolbarConsumer) {
    return;
  }
  _bottomToolbarConsumer = bottomToolbarConsumer;
  _bottomToolbarConsumer.mode = _modeHolder.mode;
}

- (void)setWebStateList:(WebStateList*)webStateList {
  if (_webStateList) {
    _webStateList->RemoveObserver(_webStateListObserver.get());
  }

  _webStateList = webStateList;

  if (_webStateList) {
    _webStateList->AddObserver(_webStateListObserver.get());
    [self updateTabCount:_webStateList->count()];
  }
}

#pragma mark - Private

// Updates the tab count of the `topToolbarConsumer`.
- (void)updateTabCount:(int)tabCount {
  self.topToolbarConsumer.pageControl.tabCount = tabCount;
}

// Helpers to configure all selection mode buttons.
- (void)configureSelectionModeButtons {
  [self.bottomToolbarConsumer
      setShareTabsButtonEnabled:_configuration.shareButton];

  [self.bottomToolbarConsumer setAddToButtonEnabled:_configuration.addToButton];
  if (_configuration.addToButton) {
    [self.bottomToolbarConsumer
        setAddToButtonMenu:_configuration.addToButtonMenu];
  }

  [self.bottomToolbarConsumer
      setCloseTabsButtonEnabled:_configuration.closeSelectedTabsButton];

  [self.topToolbarConsumer
      setSelectAllButtonEnabled:_configuration.selectAllButton ||
                                _configuration.deselectAllButton];
}

// Helpers to determine which button should be selected between "Edit" or "Undo"
// and if the "Edit" button should be enabled.
// TODO(crbug.com/40273478): Send buttons configuration directly to the correct
// consumer instead of send information to object when it is not necessary.
- (void)configureEditOrUndoButton {
  [self.topToolbarConsumer useUndoCloseAll:_configuration.undoButton];
  [self.bottomToolbarConsumer useUndoCloseAll:_configuration.undoButton];

  // TODO(crbug.com/40273478): Separate "Close All" and "Undo".
  [self.topToolbarConsumer
      setCloseAllButtonEnabled:_configuration.closeAllButton ||
                               _configuration.undoButton];
  [self.bottomToolbarConsumer
      setCloseAllButtonEnabled:_configuration.closeAllButton ||
                               _configuration.undoButton];

  BOOL shouldEnableEditButton =
      _configuration.closeAllButton || _configuration.selectTabsButton;

  [self configureEditButtons];
  [self.bottomToolbarConsumer setEditButtonEnabled:shouldEnableEditButton];
  [self.topToolbarConsumer setEditButtonEnabled:shouldEnableEditButton];
}

// Configures buttons that are available under the edit menu.
- (void)configureEditButtons {
  BOOL shouldEnableEditButton =
      _configuration.closeAllButton || _configuration.selectTabsButton;

  UIMenu* menu = nil;
  if (shouldEnableEditButton) {
    ActionFactory* actionFactory = [[ActionFactory alloc]
        initWithScenario:kMenuScenarioHistogramTabGridEdit];
    __weak id<TabGridToolbarsGridDelegate> weakButtonDelegate =
        _buttonsDelegate;
    NSMutableArray<UIMenuElement*>* menuElements =
        [@[ [actionFactory actionToCloseAllTabsWithBlock:^{
          [weakButtonDelegate closeAllButtonTapped:nil];
        }] ] mutableCopy];
    // Disable the "Select All" option from the edit button when there are no
    // tabs in the regular tab grid. "Close All" can still be called if there
    // are inactive tabs.
    if (_configuration.selectTabsButton) {
      [menuElements addObject:[actionFactory actionToSelectTabsWithBlock:^{
                      [weakButtonDelegate selectTabsButtonTapped:nil];
                    }]];
    }

    menu = [UIMenu menuWithChildren:menuElements];
  }

  [self.topToolbarConsumer setEditButtonMenu:menu];
  [self.bottomToolbarConsumer setEditButtonMenu:menu];
}

@end