// 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/grid/regular/regular_grid_mediator.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "components/sessions/core/tab_restore_service.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/tabs/model/tabs_closer.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_collection_consumer.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_consumer.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_toolbars_configuration_provider.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_toolbars_mutator.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_idle_status_handler.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_metrics.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_paging.h"
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_configuration.h"
#import "ios/web/public/web_state.h"
// TODO(crbug.com/40273478): Needed for `TabPresentationDelegate`, should be
// refactored.
#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_view_controller.h"
@implementation RegularGridMediator {
// TabsClosed used to implement the "close all tabs" operation with support
// for undoing the operation.
std::unique_ptr<TabsCloser> _tabsCloser;
// Whether the current grid is selected.
BOOL _selected;
}
#pragma mark - GridCommands
- (void)closeItemWithID:(web::WebStateID)itemID {
// Record when a regular tab is closed.
base::RecordAction(base::UserMetricsAction("MobileTabGridCloseRegularTab"));
[super closeItemWithID:itemID];
}
// TODO(crbug.com/40273478): Refactor the grid commands to have the same
// function name to close all.
- (void)closeAllItems {
NOTREACHED() << "Regular tabs should be saved before close all.";
}
- (void)saveAndCloseAllItems {
[self.inactiveTabsGridCommands saveAndCloseAllItems];
if (![self canCloseTabs]) {
return;
}
base::RecordAction(
base::UserMetricsAction("MobileTabGridCloseAllRegularTabs"));
const int tabGroupCount = self.webStateList->GetGroups().size();
const int closedTabs = _tabsCloser->CloseTabs();
RecordTabGridCloseTabsCount(closedTabs);
[self showTabGroupSnackbarOrIPH:tabGroupCount];
}
- (void)undoCloseAllItems {
[self.inactiveTabsGridCommands undoCloseAllItems];
if (![self canUndoCloseTabs]) {
return;
}
base::RecordAction(
base::UserMetricsAction("MobileTabGridUndoCloseAllRegularTabs"));
_tabsCloser->UndoCloseTabs();
}
- (void)discardSavedClosedItems {
[self.inactiveTabsGridCommands discardSavedClosedItems];
if (![self canUndoCloseTabs]) {
return;
}
_tabsCloser->ConfirmDeletion();
[self configureToolbarsButtons];
}
#pragma mark - TabGridPageMutator
- (void)currentlySelectedGrid:(BOOL)selected {
_selected = selected;
if (selected) {
base::RecordAction(
base::UserMetricsAction("MobileTabGridSelectRegularPanel"));
[self configureToolbarsButtons];
}
}
- (void)setPageAsActive {
[self.gridConsumer setActivePageFromPage:TabGridPageRegularTabs];
}
#pragma mark - TabGridToolbarsGridDelegate
- (void)closeAllButtonTapped:(id)sender {
// TODO(crbug.com/40273478): Clean this in order to have "Close All" and
// "Undo" separated actions. This was saved as a stack: first save the
// inactive tabs, then the active tabs. So undo in the reverse order: first
// undo the active tabs, then the inactive tabs.
if ([self canUndoCloseRegularOrInactiveTabs]) {
[self.consumer willUndoCloseAll];
[self undoCloseAllItems];
[self.consumer didUndoCloseAll];
} else {
[self.consumer willCloseAll];
[self saveAndCloseAllItems];
[self.consumer didCloseAll];
}
// This is needed because configure button is called (web state list observer
// in base grid mediator) when regular tabs are modified but not when inactive
// tabs are modified.
[self configureToolbarsButtons];
}
- (void)newTabButtonTapped:(id)sender {
// Ignore the tap if the current page is disabled for some reason, by policy
// for instance. This is to avoid situations where the tap action from an
// enabled page can make it to a disabled page by releasing the
// button press after switching to the disabled page (b/273416844 is an
// example).
if (IsIncognitoModeForced(self.browser->GetBrowserState()->GetPrefs())) {
return;
}
[self.tabGridIdleStatusHandler
tabGridDidPerformAction:TabGridActionType::kInPageAction];
base::RecordAction(base::UserMetricsAction("MobileTabNewTab"));
[self.gridConsumer prepareForDismissal];
// Shows the tab only if has been created.
if ([self addNewItem]) {
[self displayActiveTab];
base::RecordAction(
base::UserMetricsAction("MobileTabGridCreateRegularTab"));
} else {
base::RecordAction(
base::UserMetricsAction("MobileTabGridFailedCreateRegularTab"));
}
}
#pragma mark - Parent's function
- (void)disconnect {
_tabsCloser.reset();
[super disconnect];
}
- (void)configureToolbarsButtons {
if (!_selected) {
return;
}
// Start to configure the delegate, so configured buttons will depend on the
// correct delegate.
[self.toolbarsMutator setToolbarsButtonsDelegate:self];
if (IsIncognitoModeForced(self.browser->GetBrowserState()->GetPrefs())) {
[self.toolbarsMutator
setToolbarConfiguration:
[TabGridToolbarsConfiguration
disabledConfigurationForPage:TabGridPageRegularTabs]];
return;
}
TabGridToolbarsConfiguration* toolbarsConfiguration =
[[TabGridToolbarsConfiguration alloc]
initWithPage:TabGridPageRegularTabs];
if (self.modeHolder.mode == TabGridMode::kSelection) {
[self configureButtonsInSelectionMode:toolbarsConfiguration];
} else {
toolbarsConfiguration.closeAllButton = [self canCloseRegularOrInactiveTabs];
toolbarsConfiguration.doneButton = !self.webStateList->empty();
toolbarsConfiguration.newTabButton = YES;
toolbarsConfiguration.searchButton = YES;
toolbarsConfiguration.selectTabsButton = [self hasRegularTabs];
toolbarsConfiguration.undoButton = [self canUndoCloseRegularOrInactiveTabs];
}
[self.toolbarsMutator setToolbarConfiguration:toolbarsConfiguration];
}
- (void)displayActiveTab {
[self.gridConsumer setActivePageFromPage:TabGridPageRegularTabs];
[self.tabPresentationDelegate showActiveTabInPage:TabGridPageRegularTabs
focusOmnibox:NO];
}
- (void)updateForTabInserted {
if (!self.webStateList->empty()) {
[self discardSavedClosedItems];
}
}
#pragma mark - Private
// YES if there are regular tabs in the grid.
- (BOOL)hasRegularTabs {
return [self canCloseTabs];
}
- (BOOL)canCloseTabs {
return _tabsCloser && _tabsCloser->CanCloseTabs();
}
- (BOOL)canUndoCloseTabs {
return _tabsCloser && _tabsCloser->CanUndoCloseTabs();
}
- (BOOL)canCloseRegularOrInactiveTabs {
if ([self canCloseTabs]) {
return YES;
}
// This is an indirect way to check whether the inactive tabs can close
// tabs or undo a close tabs action.
TabGridToolbarsConfiguration* containedGridToolbarsConfiguration =
[self.containedGridToolbarsProvider toolbarsConfiguration];
return containedGridToolbarsConfiguration.closeAllButton;
}
- (BOOL)canUndoCloseRegularOrInactiveTabs {
if ([self canUndoCloseTabs]) {
return YES;
}
// This is an indirect way to check whether the inactive tabs can close
// tabs or undo a close tabs action.
TabGridToolbarsConfiguration* containedGridToolbarsConfiguration =
[self.containedGridToolbarsProvider toolbarsConfiguration];
return containedGridToolbarsConfiguration.undoButton;
}
#pragma mark - Properties
- (void)setBrowser:(Browser*)browser {
[super setBrowser:browser];
if (browser) {
_tabsCloser = std::make_unique<TabsCloser>(
browser, TabsCloser::ClosePolicy::kRegularTabs);
} else {
_tabsCloser.reset();
}
}
@end