// 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/ui/settings/clear_browsing_data/quick_delete_coordinator.h"
#import "base/metrics/histogram_functions.h"
#import "components/prefs/pref_service.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remove_mask.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remover.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remover_factory.h"
#import "ios/chrome/browser/browsing_data/model/tabs_closure_util.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service_factory.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/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/quick_delete_commands.h"
#import "ios/chrome/browser/shared/public/commands/tabs_animation_commands.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/browsing_data_counter_wrapper_producer.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_constants.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/quick_delete_browsing_data_coordinator.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/quick_delete_browsing_data_delegate.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/quick_delete_mediator.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/quick_delete_presentation_commands.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/quick_delete_view_controller.h"
@interface QuickDeleteCoordinator () <QuickDeleteBrowsingDataDelegate,
QuickDeletePresentationCommands,
UIAdaptivePresentationControllerDelegate>
@end
@implementation QuickDeleteCoordinator {
QuickDeleteViewController* _viewController;
QuickDeleteMediator* _mediator;
QuickDeleteBrowsingDataCoordinator* _browsingDataCoordinator;
// The tabs closure animation should only be performed if Quick Delete is
// opened on top of a tab or the tab grid.
BOOL _canPerformTabsClosureAnimation;
}
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser
canPerformTabsClosureAnimation:
(BOOL)canPerformTabsClosureAnimation {
if ((self = [super initWithBaseViewController:viewController
browser:browser])) {
_canPerformTabsClosureAnimation = canPerformTabsClosureAnimation;
}
return self;
}
#pragma mark - ChromeCoordinator
- (void)start {
ChromeBrowserState* browserState = self.browser->GetBrowserState();
CHECK(!browserState->IsOffTheRecord());
BrowsingDataCounterWrapperProducer* producer =
[[BrowsingDataCounterWrapperProducer alloc]
initWithBrowserState:browserState];
signin::IdentityManager* identityManager =
IdentityManagerFactory::GetForBrowserState(browserState);
BrowsingDataRemover* browsingDataRemover =
BrowsingDataRemoverFactory::GetForBrowserState(browserState);
DiscoverFeedService* discoverFeedService =
DiscoverFeedServiceFactory::GetForBrowserState(browserState);
_mediator = [[QuickDeleteMediator alloc]
initWithPrefs:browserState->GetPrefs()
browsingDataCounterWrapperProducer:producer
identityManager:identityManager
browsingDataRemover:browsingDataRemover
discoverFeedService:discoverFeedService
canPerformTabsClosureAnimation:_canPerformTabsClosureAnimation];
_viewController = [[QuickDeleteViewController alloc] init];
_viewController.modalPresentationStyle = UIModalPresentationFormSheet;
_mediator.consumer = _viewController;
_mediator.presentationHandler = self;
_viewController.presentationHandler = self;
_viewController.mutator = _mediator;
_viewController.presentationController.delegate = self;
[self.baseViewController presentViewController:_viewController
animated:YES
completion:nil];
}
- (void)stop {
[_viewController.presentingViewController dismissViewControllerAnimated:YES
completion:nil];
[self disconnect];
}
#pragma mark - QuickDeletePresentationCommands
- (void)dismissQuickDelete {
id<QuickDeleteCommands> quickDeleteHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), QuickDeleteCommands);
[quickDeleteHandler stopQuickDelete];
}
- (void)openMyActivityURL:(const GURL&)URL {
if (URL == GURL(kClearBrowsingDataDSESearchUrlInFooterURL)) {
base::UmaHistogramEnumeration("Settings.ClearBrowsingData.OpenMyActivity",
MyActivityNavigation::kSearchHistory);
} else if (URL == GURL(kClearBrowsingDataDSEMyActivityUrlInFooterURL)) {
base::UmaHistogramEnumeration("Settings.ClearBrowsingData.OpenMyActivity",
MyActivityNavigation::kTopLevel);
} else {
NOTREACHED();
}
id<ApplicationCommands> handler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), ApplicationCommands);
OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
[handler closeSettingsUIAndOpenURL:command];
}
- (void)showBrowsingDataPage {
[_browsingDataCoordinator stop];
QuickDeleteBrowsingDataCoordinator* browsingDataCoordinator =
[[QuickDeleteBrowsingDataCoordinator alloc]
initWithBaseViewController:_viewController
browser:self.browser];
_browsingDataCoordinator = browsingDataCoordinator;
[_browsingDataCoordinator start];
_browsingDataCoordinator.delegate = self;
}
- (void)triggerTabsClosureAnimationWithBeginTime:(base::Time)beginTime
endTime:(base::Time)endTime
cachedTabsInfo:
(tabs_closure_util::WebStateIDToTime)
cachedTabsInfo {
CHECK(_canPerformTabsClosureAnimation);
CHECK_EQ(Browser::Type::kRegular, self.browser->type());
// Get the active and inactive WebStates and the TabGroups of WebStates with a
// last navigation timestamp between `beginTime` and `endTime`. This
// information will be used by the tabs closure animation.
// TODO(crbug.com/335387869): Consider only returning tabs not in tab groups
// for `activeTabsToClose`.
std::set<web::WebStateID> activeTabsToClose =
tabs_closure_util::GetTabsToClose(self.browser->GetWebStateList(),
beginTime, endTime, cachedTabsInfo);
std::map<tab_groups::TabGroupId, std::set<int>> tabGroupsWithTabsToClose =
tabs_closure_util::GetTabGroupsWithTabsToClose(
self.browser->GetWebStateList(), beginTime, endTime, cachedTabsInfo);
BOOL allInactiveTabsWillClose = NO;
if (Browser* inactiveBrowser = self.browser->GetInactiveBrowser()) {
std::set<web::WebStateID> inactiveTabsToClose =
tabs_closure_util::GetTabsToClose(inactiveBrowser->GetWebStateList(),
beginTime, endTime, cachedTabsInfo);
allInactiveTabsWillClose = inactiveBrowser->GetWebStateList()->count() ==
(int)inactiveTabsToClose.size();
}
BrowsingDataRemover* browsingDataRemover =
BrowsingDataRemoverFactory::GetForBrowserState(
self.browser->GetBrowserState());
browsingDataRemover->SetCachedTabsInfo(cachedTabsInfo);
__weak QuickDeleteCoordinator* weakSelf = self;
ProceduralBlock dismissCompletionBlock = ^() {
[weakSelf animateTabsClosureWithBeginTime:beginTime
endTime:endTime
activeTabs:activeTabsToClose
groups:tabGroupsWithTabsToClose
allInactiveTabs:allInactiveTabsWillClose
browsingDataRemover:browsingDataRemover];
};
[_viewController.presentingViewController
dismissViewControllerAnimated:YES
completion:dismissCompletionBlock];
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
[self disconnect];
[self dismissQuickDelete];
}
#pragma mark - QuickDeleteBrowsingDataDelegate
- (void)stopBrowsingDataPage {
[_browsingDataCoordinator stop];
_browsingDataCoordinator = nil;
// Move Voiceover focus to the browsing data row.
[_viewController focusOnBrowsingDataRow];
}
#pragma mark - Private
// Triggers the tabs closure animation on the tab grid for the WebStates in
// `tabsToClose`, for the groups in `groupsWithTabsToClose`, and if
// `animateAllInactiveTabs` is true, then for the inactive tabs banner. It also
// closes all WebStates with a last navigation between [`beginTime`, `endTime`[
// in all browsers through `browsingDataRemover` after the animation has run.
- (void)
animateTabsClosureWithBeginTime:(base::Time)beginTime
endTime:(base::Time)endTime
activeTabs:(std::set<web::WebStateID>)activeTabsToClose
groups:(std::map<tab_groups::TabGroupId,
std::set<int>>)
tabGroupsWithTabsToClose
allInactiveTabs:(BOOL)animateAllInactiveTabs
browsingDataRemover:(BrowsingDataRemover*)browsingDataRemover {
id<ApplicationCommands> applicationCommandsHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), ApplicationCommands);
[applicationCommandsHandler
displayTabGridInMode:TabGridOpeningMode::kRegular];
id<TabsAnimationCommands> handler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), TabsAnimationCommands);
base::OnceClosure onRemoverCompletion = base::BindOnce(
[](UIWindow* window) {
window.userInteractionEnabled = YES;
// Add vibration at the end of the animation including after
// the tabs rearrange.
TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
},
self.baseViewController.view.window);
base::OnceClosure onAnimationCompletion = base::BindOnce(
&BrowsingDataRemover::RemoveInRange, browsingDataRemover->AsWeakPtr(),
beginTime, endTime, BrowsingDataRemoveMask::CLOSE_TABS,
std::move(onRemoverCompletion));
[handler animateTabsClosureForTabs:activeTabsToClose
groups:tabGroupsWithTabsToClose
allInactiveTabs:animateAllInactiveTabs
completionHandler:base::CallbackToBlock(
std::move(onAnimationCompletion))];
}
// Disconnects all instances.
- (void)disconnect {
_viewController.presentationHandler = nil;
_viewController.mutator = nil;
_viewController.presentationController.delegate = nil;
_viewController = nil;
_mediator.consumer = nil;
[_mediator disconnect];
_mediator = nil;
_browsingDataCoordinator.delegate = nil;
[_browsingDataCoordinator stop];
_browsingDataCoordinator = nil;
}
@end