blob: 7c519224b68740be16a7e9c5bacb5009909d58b0 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/clear_browsing_data_table_view_controller.h"
#include "base/mac/foundation_util.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "components/browsing_data/core/pref_names.h"
#include "components/prefs/pref_service.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/browsing_data/browsing_data_features.h"
#include "ios/chrome/browser/browsing_data/browsing_data_remove_mask.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
#import "ios/chrome/browser/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/commands/browsing_data_commands.h"
#import "ios/chrome/browser/ui/elements/chrome_activity_overlay_coordinator.h"
#include "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h"
#import "ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_consumer.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_manager.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_constants.h"
#include "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_delegate.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/time_range_selector_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.h"
#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
#import "ios/chrome/browser/ui/table_view/table_view_utils.h"
#include "ios/chrome/browser/ui/ui_feature_flags.h"
#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface ClearBrowsingDataTableViewController () <
TableViewLinkHeaderFooterItemDelegate,
ClearBrowsingDataConsumer,
UIGestureRecognizerDelegate>
// TODO(crbug.com/850699): remove direct dependency and replace with
// delegate.
@property(nonatomic, readonly, strong) ClearBrowsingDataManager* dataManager;
// Browser state.
@property(nonatomic, readonly) ChromeBrowserState* browserState;
// Browser.
@property(nonatomic, readonly) Browser* browser;
// Coordinator that managers a UIAlertController to clear browsing data.
@property(nonatomic, strong) ActionSheetCoordinator* actionSheetCoordinator;
// Coordinator for displaying a modal overlay with native activity indicator to
// prevent the user from interacting with the page.
@property(nonatomic, strong)
ChromeActivityOverlayCoordinator* overlayCoordinator;
@property(nonatomic, readonly, strong)
UIBarButtonItem* clearBrowsingDataBarButton;
// Modal alert for Browsing history removed dialog.
@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
// The data manager might want to reload tableView cells before the tableView
// has loaded, we need to prevent this kind of updates until the tableView
// loads.
@property(nonatomic, assign) BOOL suppressTableViewUpdates;
@end
@implementation ClearBrowsingDataTableViewController
@synthesize actionSheetCoordinator = _actionSheetCoordinator;
@synthesize alertCoordinator = _alertCoordinator;
@synthesize browserState = _browserState;
@synthesize browser = _browser;
@synthesize clearBrowsingDataBarButton = _clearBrowsingDataBarButton;
@synthesize dataManager = _dataManager;
@synthesize dispatcher = _dispatcher;
@synthesize delegate = _delegate;
@synthesize suppressTableViewUpdates = _suppressTableViewUpdates;
#pragma mark - ViewController Lifecycle.
- (instancetype)initWithBrowser:(Browser*)browser {
UITableViewStyle style = base::FeatureList::IsEnabled(kSettingsRefresh)
? ChromeTableViewStyle()
: UITableViewStylePlain;
self = [super initWithStyle:style];
if (self) {
_browser = browser;
_browserState = browser->GetBrowserState();
_dataManager = [[ClearBrowsingDataManager alloc]
initWithBrowserState:browser->GetBrowserState()];
_dataManager.consumer = self;
}
return self;
}
#pragma mark - Property
- (UIBarButtonItem*)clearBrowsingDataBarButton {
if (!_clearBrowsingDataBarButton) {
_clearBrowsingDataBarButton = [[UIBarButtonItem alloc]
initWithTitle:l10n_util::GetNSString(IDS_IOS_CLEAR_BUTTON)
style:UIBarButtonItemStylePlain
target:self
action:@selector(showClearBrowsingDataAlertController:)];
_clearBrowsingDataBarButton.accessibilityIdentifier =
kClearBrowsingDataButtonIdentifier;
_clearBrowsingDataBarButton.tintColor = [UIColor colorNamed:kRedColor];
}
return _clearBrowsingDataBarButton;
}
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem* flexibleSpace = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];
[self setToolbarItems:@[
flexibleSpace, self.clearBrowsingDataBarButton, flexibleSpace
]
animated:YES];
if (!base::FeatureList::IsEnabled(kSettingsRefresh)) {
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.accessibilityIdentifier =
kClearBrowsingDataViewAccessibilityIdentifier;
self.styler.tableViewBackgroundColor =
[UIColor colorNamed:kPrimaryBackgroundColor];
self.tableView.backgroundColor = self.styler.tableViewBackgroundColor;
// TableView configuration
self.tableView.estimatedRowHeight = 56;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionHeaderHeight = 0;
}
// Navigation controller configuration.
self.title = l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_TITLE);
// Adds the "Done" button and hooks it up to |dismiss|.
UIBarButtonItem* dismissButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(dismiss)];
dismissButton.accessibilityIdentifier = kSettingsDoneButtonId;
self.navigationItem.rightBarButtonItem = dismissButton;
// Do not allow any TableView updates until the model is fully loaded. The
// model might try re-loading some cells and the TableView might not be loaded
// at this point (https://crbug.com/873929).
self.suppressTableViewUpdates = YES;
[self loadModel];
self.suppressTableViewUpdates = NO;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.dataManager restartCounters:BrowsingDataRemoveMask::REMOVE_ALL];
[self updateToolbarButtons];
// Showing toolbar here because parent class hides toolbar in
// viewWillDisappear:.
self.navigationController.toolbarHidden = NO;
}
- (void)loadModel {
[super loadModel];
[self.dataManager loadModel:self.tableViewModel];
}
- (void)dismiss {
base::RecordAction(base::UserMetricsAction("MobileClearBrowsingDataClose"));
[self prepareForDismissal];
[self.delegate dismissClearBrowsingData];
}
#pragma mark - Public Methods
- (void)prepareForDismissal {
if (self.actionSheetCoordinator) {
[self.actionSheetCoordinator stop];
self.actionSheetCoordinator = nil;
}
if (self.alertCoordinator) {
[self.alertCoordinator stop];
self.alertCoordinator = nil;
}
if (self.overlayCoordinator.started) {
[self.overlayCoordinator stop];
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
self.overlayCoordinator = nil;
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
if (gestureRecognizer ==
self.navigationController.interactivePopGestureRecognizer) {
// This view controller should only be observing gestures when the activity
// overlay is showing (e.g. when Clear Browsing Data is in progress and the
// user should not be able to swipe away from this view).
return NO;
}
return YES;
}
#pragma mark - UITableViewDataSource
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell* cellToReturn = [super tableView:tableView
cellForRowAtIndexPath:indexPath];
TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
switch (item.type) {
case ItemTypeDataTypeBrowsingHistory:
case ItemTypeDataTypeCookiesSiteData:
case ItemTypeDataTypeCache:
case ItemTypeDataTypeSavedPasswords:
case ItemTypeDataTypeAutofill:
// For these cells the selection style application is specified in the
// corresponding item definition.
cellToReturn.selectionStyle = UITableViewCellSelectionStyleNone;
break;
default:
break;
}
return cellToReturn;
}
#pragma mark - UITableViewDelegate
- (UIView*)tableView:(UITableView*)tableView
viewForFooterInSection:(NSInteger)section {
UIView* view = [super tableView:tableView viewForFooterInSection:section];
NSInteger sectionIdentifier =
[self.tableViewModel sectionIdentifierForSection:section];
switch (sectionIdentifier) {
case SectionIdentifierSavedSiteData:
case SectionIdentifierGoogleAccount: {
TableViewLinkHeaderFooterView* linkView =
base::mac::ObjCCastStrict<TableViewLinkHeaderFooterView>(view);
linkView.delegate = self;
} break;
default:
break;
}
return view;
}
- (CGFloat)tableView:(UITableView*)tableView
heightForHeaderInSection:(NSInteger)section {
NSInteger sectionIdentifier =
[self.tableViewModel sectionIdentifierForSection:section];
switch (sectionIdentifier) {
case SectionIdentifierGoogleAccount:
case SectionIdentifierSavedSiteData:
return 5;
default:
return [super tableView:tableView heightForHeaderInSection:section];
}
}
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
DCHECK(item);
switch (item.type) {
case ItemTypeTimeRange: {
UIViewController* controller =
[[TimeRangeSelectorTableViewController alloc]
initWithPrefs:self.browserState->GetPrefs()];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
[self.navigationController pushViewController:controller animated:YES];
break;
}
case ItemTypeDataTypeBrowsingHistory:
case ItemTypeDataTypeCookiesSiteData:
case ItemTypeDataTypeCache:
case ItemTypeDataTypeSavedPasswords:
case ItemTypeDataTypeAutofill: {
DCHECK([item isKindOfClass:[TableViewClearBrowsingDataItem class]]);
TableViewClearBrowsingDataItem* clearBrowsingDataItem =
base::mac::ObjCCastStrict<TableViewClearBrowsingDataItem>(item);
self.browserState->GetPrefs()->SetBoolean(clearBrowsingDataItem.prefName,
!clearBrowsingDataItem.checked);
// UI update will be trigerred by data manager.
break;
}
default:
break;
}
[self updateToolbarButtons];
}
#pragma mark - TableViewLinkHeaderFooterItemDelegate
- (void)view:(TableViewLinkHeaderFooterView*)view didTapLinkURL:(GURL)url {
[self.delegate openURL:url];
}
#pragma mark - ClearBrowsingDataConsumer
- (void)updateCellsForItem:(TableViewItem*)item reload:(BOOL)reload {
if (self.suppressTableViewUpdates)
return;
if (!reload) {
[self reconfigureCellsForItems:@[ item ]];
NSIndexPath* indexPath = [self.tableViewModel
indexPathForItem:static_cast<TableViewItem*>(item)];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
} else {
// Reload the item instead of reconfiguring it. This might update
// TableViewLinkHeaderFooterView which which can have different number of
// lines, thus the cell height needs to adapt accordingly.
[self reloadCellsForItems:@[ item ]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
- (void)removeBrowsingDataForBrowserState:(ChromeBrowserState*)browserState
timePeriod:(browsing_data::TimePeriod)timePeriod
removeMask:(BrowsingDataRemoveMask)removeMask
completionBlock:(ProceduralBlock)completionBlock {
base::RecordAction(
base::UserMetricsAction("MobileClearBrowsingDataTriggeredFromUIRefresh"));
// Show activity indicator modal while removal is happening.
self.overlayCoordinator = [[ChromeActivityOverlayCoordinator alloc]
initWithBaseViewController:self.navigationController
browser:_browser];
self.overlayCoordinator.messageText = l10n_util::GetNSStringWithFixup(
IDS_IOS_CLEAR_BROWSING_DATA_ACTIVITY_MODAL);
self.overlayCoordinator.blockAllWindows = YES;
// Observe Gestures while overlay is visible to prevent user from swiping away
// from this view during the process of clear browsing data.
self.navigationController.interactivePopGestureRecognizer.delegate = self;
[self.overlayCoordinator start];
__weak ClearBrowsingDataTableViewController* weakSelf = self;
dispatch_time_t timeOneSecondLater =
dispatch_time(DISPATCH_TIME_NOW, (1 * NSEC_PER_SEC));
void (^removeBrowsingDidFinishCompletionBlock)(void) = ^void() {
ClearBrowsingDataTableViewController* strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// Sometimes clear browsing data is really short
// (<1sec), so ensure that overlay displays for at
// least 1 second instead of looking like a glitch.
dispatch_after(timeOneSecondLater, dispatch_get_main_queue(), ^{
[self.overlayCoordinator stop];
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
if (completionBlock)
completionBlock();
});
};
// If browsing History will be cleared set the kLastClearBrowsingDataTime.
// TODO(crbug.com/1085419): This pref is used by the Feed to prevent the
// showing of customized content after history has been cleared. We might want
// to create a specific Pref for this.
if (IsRemoveDataMaskSet(removeMask, BrowsingDataRemoveMask::REMOVE_HISTORY)) {
browserState->GetPrefs()->SetInt64(
browsing_data::prefs::kLastClearBrowsingDataTime,
base::Time::Now().ToTimeT());
}
[self.dispatcher
removeBrowsingDataForBrowserState:browserState
timePeriod:timePeriod
removeMask:removeMask
completionBlock:removeBrowsingDidFinishCompletionBlock];
}
- (void)showBrowsingHistoryRemovedDialog {
NSString* title =
l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_TITLE);
NSString* message = l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_DESCRIPTION);
self.alertCoordinator =
[[AlertCoordinator alloc] initWithBaseViewController:self
browser:_browser
title:title
message:message];
__weak ClearBrowsingDataTableViewController* weakSelf = self;
[self.alertCoordinator
addItemWithTitle:
l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_OPEN_HISTORY_BUTTON)
action:^{
[weakSelf.delegate openURL:GURL(kGoogleMyAccountURL)];
}
style:UIAlertActionStyleDefault];
[self.alertCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_OK_BUTTON)
action:nil
style:UIAlertActionStyleCancel];
[self.alertCoordinator start];
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
base::RecordAction(
base::UserMetricsAction("IOSClearBrowsingDataCloseWithSwipe"));
// Call prepareForDismissal to clean up state and stop the Coordinator.
[self prepareForDismissal];
}
- (BOOL)presentationControllerShouldDismiss:
(UIPresentationController*)presentationController {
return !self.overlayCoordinator.started;
}
#pragma mark - Private Helpers
- (void)showClearBrowsingDataAlertController:(id)sender {
BrowsingDataRemoveMask dataTypeMaskToRemove =
BrowsingDataRemoveMask::REMOVE_NOTHING;
NSArray* dataTypeItems = [self.tableViewModel
itemsInSectionWithIdentifier:SectionIdentifierDataTypes];
for (TableViewClearBrowsingDataItem* dataTypeItem in dataTypeItems) {
DCHECK([dataTypeItem isKindOfClass:[TableViewClearBrowsingDataItem class]]);
if (dataTypeItem.checked) {
dataTypeMaskToRemove = dataTypeMaskToRemove | dataTypeItem.dataTypeMask;
}
}
self.actionSheetCoordinator = [self.dataManager
actionSheetCoordinatorWithDataTypesToRemove:dataTypeMaskToRemove
baseViewController:self
browser:_browser
sourceBarButtonItem:sender];
[self.actionSheetCoordinator start];
}
- (void)updateToolbarButtons {
self.clearBrowsingDataBarButton.enabled = [self hasDataTypeItemsSelected];
}
- (BOOL)hasDataTypeItemsSelected {
// Returns YES iff at least 1 data type cell is selected.
NSArray* dataTypeItems = [self.tableViewModel
itemsInSectionWithIdentifier:SectionIdentifierDataTypes];
for (TableViewClearBrowsingDataItem* dataTypeItem in dataTypeItems) {
DCHECK([dataTypeItem isKindOfClass:[TableViewClearBrowsingDataItem class]]);
if (dataTypeItem.checked) {
return YES;
}
}
return NO;
}
@end