blob: a39bfa4b5bb355f9085730c2e8e42a08e649afbb [file] [log] [blame]
// Copyright 2016 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/history/history_panel_view_controller.h"
#include "base/ios/block_types.h"
#include "base/ios/ios_util.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/ui/history/clear_browsing_bar.h"
#import "ios/chrome/browser/ui/history/history_collection_view_controller.h"
#import "ios/chrome/browser/ui/history/history_search_view_controller.h"
#import "ios/chrome/browser/ui/icons/chrome_icon.h"
#import "ios/chrome/browser/ui/keyboard/UIKeyCommand+Chrome.h"
#import "ios/chrome/browser/ui/material_components/utils.h"
#import "ios/chrome/browser/ui/ntp/recent_tabs/views/panel_bar_view.h"
#import "ios/chrome/browser/ui/show_privacy_settings_util.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#import "ios/chrome/browser/ui/url_loader.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/third_party/material_components_ios/src/components/AppBar/src/MaterialAppBar.h"
#import "ios/third_party/material_components_ios/src/components/NavigationBar/src/MaterialNavigationBar.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Shadow opacity for the clear browsing button and the header when scrolling.
CGFloat kShadowOpacity = 0.2f;
} // namespace
@interface HistoryPanelViewController ()<
HistoryCollectionViewControllerDelegate,
HistorySearchViewControllerDelegate> {
// Controller for collection view that displays history entries.
HistoryCollectionViewController* _historyCollectionController;
// Bar at the bottom of the history panel the displays options for entry
// deletion, including "Clear Browsing Data..." which takes the user to
// Privacy settings, or "Edit" for entering a mode for deleting individual
// entries. When in edit mode, the bar displays options to Delete or Cancel.
ClearBrowsingBar* _clearBrowsingBar;
// View controller for the search bar.
HistorySearchViewController* _searchViewController;
// Container view for history collection and clear browsing button to enable
// use of autolayout in conjuction with Material App Bar.
UIView* _containerView;
// The header view.
MDCAppBar* _appBar;
// Left bar button item for Search.
UIBarButtonItem* _leftBarButtonItem;
// Right bar button item for Dismiss history action.
UIBarButtonItem* _rightBarButtonItem;
}
// Closes history.
- (void)closeHistory;
// Closes history, invoking completionHandler once dismissal is complete.
- (void)closeHistoryWithCompletion:(ProceduralBlock)completionHandler;
// Opens Privacy settings.
- (void)openPrivacySettings;
// Configure view for editing mode.
- (void)enterEditingMode;
// Configure view for non-editing mode.
- (void)exitEditingMode;
// Deletes selected history entries.
- (void)deleteSelectedItems;
// Displays a search bar for searching history entries.
- (void)enterSearchMode;
// Dismisses the search bar.
- (void)exitSearchMode;
// Configures navigation bar for current state of the history collection.
- (void)configureNavigationBar;
// Configures the clear browsing data bar for the current state of the history
// collection.
- (void)configureClearBrowsingBar;
@end
@implementation HistoryPanelViewController
- (instancetype)initWithLoader:(id<UrlLoader>)loader
browserState:(ios::ChromeBrowserState*)browserState {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_historyCollectionController =
[[HistoryCollectionViewController alloc] initWithLoader:loader
browserState:browserState
delegate:self];
// Configure modal presentation.
[self setModalPresentationStyle:UIModalPresentationFormSheet];
[self setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
// Add and configure header.
_appBar = [[MDCAppBar alloc] init];
[self addChildViewController:_appBar.headerViewController];
}
return self;
}
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
bundle:(NSBundle*)nibBundleOrNil {
NOTREACHED();
return nil;
}
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
NOTREACHED();
return nil;
}
+ (UIViewController*)controllerToPresentForBrowserState:
(ios::ChromeBrowserState*)browserState
loader:(id<UrlLoader>)loader {
HistoryPanelViewController* historyPanelController =
[[HistoryPanelViewController alloc] initWithLoader:loader
browserState:browserState];
return historyPanelController;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setTitle:l10n_util::GetNSString(IDS_HISTORY_TITLE)];
_containerView = [[UIView alloc] initWithFrame:self.view.frame];
[_containerView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight];
[self.view addSubview:_containerView];
[[_historyCollectionController view]
setTranslatesAutoresizingMaskIntoConstraints:NO];
[_historyCollectionController willMoveToParentViewController:self];
[_containerView addSubview:[_historyCollectionController view]];
[self addChildViewController:_historyCollectionController];
[_historyCollectionController didMoveToParentViewController:self];
_clearBrowsingBar = [[ClearBrowsingBar alloc] initWithFrame:CGRectZero];
[_clearBrowsingBar setClearBrowsingDataTarget:self
action:@selector(openPrivacySettings)];
[_clearBrowsingBar setEditTarget:self action:@selector(enterEditingMode)];
[_clearBrowsingBar setCancelTarget:self action:@selector(exitEditingMode)];
[_clearBrowsingBar setDeleteTarget:self
action:@selector(deleteSelectedItems)];
[_clearBrowsingBar setTranslatesAutoresizingMaskIntoConstraints:NO];
[_containerView addSubview:_clearBrowsingBar];
[self configureClearBrowsingBar];
ConfigureAppBarWithCardStyle(_appBar);
[_appBar headerViewController].headerView.trackingScrollView =
[_historyCollectionController collectionView];
[_appBar addSubviewsToParent];
// Add navigation bar buttons.
_leftBarButtonItem =
[ChromeIcon templateBarButtonItemWithImage:[ChromeIcon searchIcon]
target:self
action:@selector(enterSearchMode)];
self.navigationItem.leftBarButtonItem = _leftBarButtonItem;
_rightBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:l10n_util::GetNSString(IDS_IOS_NAVIGATION_BAR_DONE_BUTTON)
style:UIBarButtonItemStylePlain
target:self
action:@selector(closeHistory)];
self.navigationItem.rightBarButtonItem = _rightBarButtonItem;
[self configureNavigationBar];
}
- (void)updateViewConstraints {
NSDictionary* views = @{
@"collectionView" : [_historyCollectionController view],
@"clearBrowsingBar" : _clearBrowsingBar,
};
NSArray* constraints = @[
@"V:|[collectionView][clearBrowsingBar(==48)]|", @"H:|[collectionView]|",
@"H:|[clearBrowsingBar]|"
];
ApplyVisualConstraints(constraints, views);
[super updateViewConstraints];
}
- (BOOL)disablesAutomaticKeyboardDismissal {
return NO;
}
#pragma mark - Status bar
- (BOOL)modalPresentationCapturesStatusBarAppearance {
if (!base::ios::IsRunningOnIOS10OrLater()) {
// TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
// dropped.
return YES;
} else {
return [super modalPresentationCapturesStatusBarAppearance];
}
}
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (!base::ios::IsRunningOnIOS10OrLater()) {
// TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
// dropped.
[self setNeedsStatusBarAppearanceUpdate];
}
}
- (UIViewController*)childViewControllerForStatusBarHidden {
if (!base::ios::IsRunningOnIOS10OrLater()) {
// TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
// dropped.
return nil;
} else {
return _appBar.headerViewController;
}
}
- (BOOL)prefersStatusBarHidden {
if (!base::ios::IsRunningOnIOS10OrLater()) {
// TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
// dropped.
return NO;
} else {
return [super prefersStatusBarHidden];
}
}
- (UIViewController*)childViewControllerForStatusBarStyle {
if (!base::ios::IsRunningOnIOS10OrLater()) {
// TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
// dropped.
return nil;
} else {
return _appBar.headerViewController;
}
}
- (UIStatusBarStyle)preferredStatusBarStyle {
if (!base::ios::IsRunningOnIOS10OrLater()) {
// TODO(crbug.com/620361): Remove the entire method override when iOS 9 is
// dropped.
if (IsIPadIdiom() && !IsCompact()) {
return UIStatusBarStyleLightContent;
} else {
return UIStatusBarStyleDefault;
}
} else {
return [super preferredStatusBarStyle];
}
}
#pragma mark - HistoryCollectionViewControllerDelegate
- (void)historyCollectionViewController:
(HistoryCollectionViewController*)collectionViewcontroller
shouldCloseWithCompletion:(ProceduralBlock)completionHandler {
[self closeHistoryWithCompletion:completionHandler];
}
- (void)historyCollectionViewController:
(HistoryCollectionViewController*)controller
didScrollToOffset:(CGPoint)offset {
// Display a shadow on the header when the collection is scrolled.
MDCFlexibleHeaderView* headerView = _appBar.headerViewController.headerView;
headerView.visibleShadowOpacity =
offset.y > -CGRectGetHeight(headerView.frame) ? kShadowOpacity : 0.0f;
}
- (void)historyCollectionViewControllerDidChangeEntries:
(HistoryCollectionViewController*)controller {
// Reconfigure the navigation and clear browsing bars to reflect currently
// displayed entries.
[self configureNavigationBar];
[self configureClearBrowsingBar];
}
- (void)historyCollectionViewControllerDidChangeEntrySelection:
(HistoryCollectionViewController*)controller {
// Reconfigure the clear browsing bar to reflect current availability of
// entries for deletion.
[self configureClearBrowsingBar];
}
#pragma mark - HistorySearchViewControllerDelegate
- (void)historySearchViewController:
(HistorySearchViewController*)searchViewController
didRequestSearchForTerm:(NSString*)searchTerm {
[_historyCollectionController showHistoryMatchingQuery:searchTerm];
}
- (void)historySearchViewControllerDidCancel:
(HistorySearchViewController*)searchViewController {
DCHECK([_searchViewController isEqual:searchViewController]);
[self exitSearchMode];
}
#pragma mark - Private methods
- (void)closeHistory {
[self closeHistoryWithCompletion:nil];
}
- (void)closeHistoryWithCompletion:(ProceduralBlock)completion {
[self.presentingViewController dismissViewControllerAnimated:YES
completion:completion];
}
- (void)openPrivacySettings {
[self exitSearchMode];
base::RecordAction(
base::UserMetricsAction("HistoryPage_InitClearBrowsingData"));
ShowClearBrowsingData();
}
- (void)enterEditingMode {
[_historyCollectionController setEditing:YES];
[_clearBrowsingBar setEditing:YES];
if (_historyCollectionController.searching) {
[_searchViewController setEnabled:NO];
}
DCHECK([_historyCollectionController isEditing]);
[self configureNavigationBar];
}
- (void)exitEditingMode {
[_historyCollectionController setEditing:NO];
[_clearBrowsingBar setEditing:NO];
if (_historyCollectionController.searching) {
[_searchViewController setEnabled:YES];
}
DCHECK(![_historyCollectionController isEditing]);
[self configureNavigationBar];
}
- (void)deleteSelectedItems {
[_historyCollectionController deleteSelectedItemsFromHistory];
base::RecordAction(base::UserMetricsAction("HistoryPage_RemoveSelected"));
[self exitEditingMode];
}
- (void)enterSearchMode {
if (!_searchViewController) {
_searchViewController = [[HistorySearchViewController alloc] init];
[_searchViewController setDelegate:self];
}
UIView* searchBarView = [_searchViewController view];
[_searchViewController willMoveToParentViewController:self];
[self.view addSubview:searchBarView];
_historyCollectionController.searching = YES;
[_searchViewController didMoveToParentViewController:self];
base::RecordAction(base::UserMetricsAction("HistoryPage_Search"));
// Constraints to make search bar cover header.
[searchBarView setTranslatesAutoresizingMaskIntoConstraints:NO];
MDCFlexibleHeaderView* headerView = _appBar.headerViewController.headerView;
NSArray* constraints = @[
[[searchBarView topAnchor] constraintEqualToAnchor:headerView.topAnchor],
[[searchBarView leadingAnchor]
constraintEqualToAnchor:headerView.leadingAnchor],
[[searchBarView heightAnchor]
constraintEqualToAnchor:headerView.heightAnchor],
[[searchBarView widthAnchor] constraintEqualToAnchor:headerView.widthAnchor]
];
[NSLayoutConstraint activateConstraints:constraints];
// Workaround so navigationItems are not voice-over selectable while hidden by
// the search view. We might have to re factor the view hierarchy in order to
// properly solve this issue. See: https://codereview.chromium.org/2605023002/
self.navigationItem.leftBarButtonItem = nil;
self.navigationItem.rightBarButtonItem = nil;
}
- (void)exitSearchMode {
if (_historyCollectionController.searching) {
// Resets the navigation items to their initial state.
self.navigationItem.leftBarButtonItem = _leftBarButtonItem;
self.navigationItem.rightBarButtonItem = _rightBarButtonItem;
[self configureNavigationBar];
[[_searchViewController view] removeFromSuperview];
[_searchViewController removeFromParentViewController];
_historyCollectionController.searching = NO;
[_historyCollectionController showHistoryMatchingQuery:nil];
}
}
- (void)configureNavigationBar {
// The search button should only be enabled if there are history entries to
// search, and if history is not in edit mode.
self.navigationItem.leftBarButtonItem.enabled =
[_historyCollectionController hasHistoryEntries] &&
![_historyCollectionController isEditing];
}
- (void)configureClearBrowsingBar {
_clearBrowsingBar.editing = _historyCollectionController.editing;
_clearBrowsingBar.deleteButtonEnabled =
[_historyCollectionController hasSelectedEntries];
_clearBrowsingBar.editButtonEnabled =
[_historyCollectionController hasHistoryEntries];
}
#pragma mark - UIResponder
- (NSArray*)keyCommands {
__weak HistoryPanelViewController* weakSelf = self;
return @[ [UIKeyCommand cr_keyCommandWithInput:UIKeyInputEscape
modifierFlags:Cr_UIKeyModifierNone
title:nil
action:^{
[weakSelf closeHistory];
}] ];
}
@end