Commit f6d70f29 authored by Chris Lu's avatar Chris Lu Committed by Commit Bot

[ios] Display activity indicator modal during clear browsing data

Creates new NativeActivityOverlayCoordinator that presents a NativeActivityOverlayViewController to show the activity indicator. Presents it from ClearBrowsingDataTableViewController before executing remove browsing data, and removes it in the callback.

Video: https://drive.google.com/file/d/1qWKW7L4Q1K_SWRkJirl1oogPvjRxgfLh/view?usp=sharing
https://drive.google.com/file/d/1cZ43c6iJA9mhbr_95DgJXGHS33zGCvAO/view?usp=sharing

Bug: 760598
Change-Id: I4b4bde0811502691438e520c87e128496cdd0e1b
Reviewed-on: https://chromium-review.googlesource.com/c/1259483
Commit-Queue: Chris Lu <thegreenfrog@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Reviewed-by: default avatarSergio Collazos <sczs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#597276}
parent 11b25d7c
...@@ -453,6 +453,9 @@ locale. The strings in this file are specific to iOS. ...@@ -453,6 +453,9 @@ locale. The strings in this file are specific to iOS.
<message name="IDS_IOS_CLEAR_AUTOFILL" desc="Label for the option in settings to clear form data. In titlecase. [Length: 20em] [iOS only]"> <message name="IDS_IOS_CLEAR_AUTOFILL" desc="Label for the option in settings to clear form data. In titlecase. [Length: 20em] [iOS only]">
Autofill Data Autofill Data
</message> </message>
<message name="IDS_IOS_CLEAR_BROWSING_DATA_ACTIVITY_MODAL" desc="Message text for the activity indicator modal during clear browsing data.">
Clearing browsing data...
</message>
<message name="IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_ACCOUNT" desc="Footer message in the settings to clear browsing data that informs the user that clearing browsing data will not sign them out of Chrome."> <message name="IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_ACCOUNT" desc="Footer message in the settings to clear browsing data that informs the user that clearing browsing data will not sign them out of Chrome.">
You won't be signed out of your Google Account. You won't be signed out of your Google Account.
</message> </message>
......
...@@ -25,6 +25,7 @@ source_set("unit_tests") { ...@@ -25,6 +25,7 @@ source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"activity_overlay_coordinator_unittest.mm", "activity_overlay_coordinator_unittest.mm",
"chrome_activity_overlay_coordinator_unittest.mm",
"selector_coordinator_unittest.mm", "selector_coordinator_unittest.mm",
"selector_picker_view_controller_unittest.mm", "selector_picker_view_controller_unittest.mm",
] ]
...@@ -45,6 +46,10 @@ source_set("elements_internal") { ...@@ -45,6 +46,10 @@ source_set("elements_internal") {
"activity_overlay_coordinator.mm", "activity_overlay_coordinator.mm",
"activity_overlay_view_controller.h", "activity_overlay_view_controller.h",
"activity_overlay_view_controller.mm", "activity_overlay_view_controller.mm",
"chrome_activity_overlay_coordinator.h",
"chrome_activity_overlay_coordinator.mm",
"chrome_activity_overlay_view_controller.h",
"chrome_activity_overlay_view_controller.mm",
] ]
deps = [ deps = [
"//base", "//base",
......
// 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.
#ifndef IOS_CHROME_BROWSER_UI_ELEMENTS_CHROME_ACTIVITY_OVERLAY_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_ELEMENTS_CHROME_ACTIVITY_OVERLAY_COORDINATOR_H_
#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
// Coordinator for displaying a UIActivityIndicatorView overlay over the current
// context.
@interface ChromeActivityOverlayCoordinator : ChromeCoordinator
// Text that will be shown above the UIActivityIndicatorView.
@property(nonatomic, copy) NSString* messageText;
@end
#endif // IOS_CHROME_BROWSER_UI_ELEMENTS_CHROME_ACTIVITY_OVERLAY_COORDINATOR_H_
// 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/elements/chrome_activity_overlay_coordinator.h"
#import "ios/chrome/browser/ui/elements/chrome_activity_overlay_view_controller.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface ChromeActivityOverlayCoordinator ()
// View controller that displays a native active indicator.
@property(nonatomic, strong)
ChromeActivityOverlayViewController* chromeActivityOverlayViewController;
@end
@implementation ChromeActivityOverlayCoordinator
- (void)start {
if (self.chromeActivityOverlayViewController)
return;
self.chromeActivityOverlayViewController =
[[ChromeActivityOverlayViewController alloc] init];
self.chromeActivityOverlayViewController.messageText = self.messageText;
[self.baseViewController
addChildViewController:self.chromeActivityOverlayViewController];
// Make sure frame of view is exactly the same size as its presenting view.
// Especially important when the presenting view is a bubble.
CGRect frame = self.chromeActivityOverlayViewController.view.frame;
frame.origin.x = 0;
frame.size.width = self.baseViewController.view.bounds.size.width;
frame.size.height = self.baseViewController.view.bounds.size.height;
self.chromeActivityOverlayViewController.view.frame = frame;
[self.baseViewController.view
addSubview:self.chromeActivityOverlayViewController.view];
[self.chromeActivityOverlayViewController
didMoveToParentViewController:self.baseViewController];
}
- (void)stop {
if (!self.chromeActivityOverlayViewController)
return;
[self.chromeActivityOverlayViewController willMoveToParentViewController:nil];
[self.chromeActivityOverlayViewController.view removeFromSuperview];
[self.chromeActivityOverlayViewController removeFromParentViewController];
self.chromeActivityOverlayViewController = nil;
}
@end
// 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/elements/chrome_activity_overlay_coordinator.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using ChromeActivityOverlayCoordinatorTest = PlatformTest;
// Tests that invoking start and stop on the coordinator presents and dismisses
// the activity overlay view, respectively.
TEST_F(ChromeActivityOverlayCoordinatorTest, StartAndStop) {
__weak UIView* overlay_view;
@autoreleasepool {
UIViewController* base_view_controller = [[UIViewController alloc] init];
ChromeActivityOverlayCoordinator* coordinator =
[[ChromeActivityOverlayCoordinator alloc]
initWithBaseViewController:base_view_controller];
EXPECT_EQ(0u, [base_view_controller.childViewControllers count]);
[coordinator start];
EXPECT_EQ(1u, [base_view_controller.childViewControllers count]);
overlay_view = [base_view_controller.childViewControllers firstObject].view;
EXPECT_TRUE(
[[base_view_controller.view subviews] containsObject:overlay_view]);
[coordinator stop];
EXPECT_EQ(0u, [base_view_controller.childViewControllers count]);
}
EXPECT_FALSE(overlay_view);
}
// 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.
#ifndef IOS_CHROME_BROWSER_UI_ELEMENTS_CHROME_ACTIVITY_OVERLAY_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_ELEMENTS_CHROME_ACTIVITY_OVERLAY_VIEW_CONTROLLER_H_
#import <UIKit/UIKit.h>
// View controller that displays a UIActivityIndicatorView and informative
// |messageText| over a translucent background.
@interface ChromeActivityOverlayViewController : UIViewController
// Text that will be shown above the UIActivityIndicatorView.
@property(nonatomic, copy) NSString* messageText;
@end
#endif // IOS_CHROME_BROWSER_UI_ELEMENTS_CHROME_ACTIVITY_OVERLAY_VIEW_CONTROLLER_H_
// 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/elements/chrome_activity_overlay_view_controller.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Alpha of the presented view's background.
const CGFloat kViewBackgroundAlpha = 0.5;
// Background white color of container view.
const CGFloat kContainerBackgroundWhiteColor = 0.9;
// Spacing between container view and subviews.
const CGFloat kContainerViewSpacing = 25;
// Corner radius of container view.
const CGFloat kContainerCornerRadius = 10;
// UIActivityIndicatorView's height and width
const CGFloat kActivityIndicatorViewSize = 55;
}
@implementation ChromeActivityOverlayViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor =
[UIColor colorWithWhite:0 alpha:kViewBackgroundAlpha];
UIView* containerView = [[UIView alloc] init];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
containerView.backgroundColor =
[UIColor colorWithWhite:kContainerBackgroundWhiteColor alpha:1.0];
containerView.layer.cornerRadius = kContainerCornerRadius;
containerView.layer.masksToBounds = YES;
UIActivityIndicatorView* activityView = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.color = [UIColor blackColor];
activityView.translatesAutoresizingMaskIntoConstraints = NO;
[activityView startAnimating];
[containerView addSubview:activityView];
UILabel* label = [[UILabel alloc] init];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.text = self.messageText;
label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
[containerView addSubview:label];
NSArray* constraints = @[
[label.leadingAnchor constraintEqualToAnchor:containerView.leadingAnchor
constant:kContainerViewSpacing],
[label.trailingAnchor constraintEqualToAnchor:containerView.trailingAnchor
constant:-kContainerViewSpacing],
[label.topAnchor constraintEqualToAnchor:containerView.topAnchor
constant:kContainerViewSpacing],
[label.bottomAnchor constraintEqualToAnchor:activityView.topAnchor
constant:-5],
[activityView.bottomAnchor
constraintEqualToAnchor:containerView.bottomAnchor
constant:-kContainerViewSpacing],
[activityView.centerXAnchor constraintEqualToAnchor:label.centerXAnchor
constant:0],
[activityView.heightAnchor
constraintEqualToConstant:kActivityIndicatorViewSize],
[activityView.widthAnchor
constraintEqualToConstant:kActivityIndicatorViewSize]
];
[NSLayoutConstraint activateConstraints:constraints];
[self.view addSubview:containerView];
AddSameCenterConstraints(self.view, containerView);
}
@end
...@@ -181,6 +181,7 @@ source_set("settings") { ...@@ -181,6 +181,7 @@ source_set("settings") {
"//ios/chrome/browser/ui/content_suggestions/cells", "//ios/chrome/browser/ui/content_suggestions/cells",
"//ios/chrome/browser/ui/content_suggestions/cells:cells_ui", "//ios/chrome/browser/ui/content_suggestions/cells:cells_ui",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators", "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/browser/ui/elements:elements_internal",
"//ios/chrome/browser/ui/icons", "//ios/chrome/browser/ui/icons",
"//ios/chrome/browser/ui/keyboard", "//ios/chrome/browser/ui/keyboard",
"//ios/chrome/browser/ui/list_model", "//ios/chrome/browser/ui/list_model",
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#import "ios/chrome/browser/ui/commands/application_commands.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/commands/browsing_data_commands.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h" #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/elements/chrome_activity_overlay_coordinator.h"
#import "ios/chrome/browser/ui/icons/chrome_icon.h" #import "ios/chrome/browser/ui/icons/chrome_icon.h"
#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h" #import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h"
#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_item.h" #import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_item.h"
...@@ -104,6 +105,11 @@ void BrowsingDataRemoverObserverWrapper::OnBrowsingDataRemoved( ...@@ -104,6 +105,11 @@ void BrowsingDataRemoverObserverWrapper::OnBrowsingDataRemoved(
// Coordinator that managers an action sheet to clear browsing data. // Coordinator that managers an action sheet to clear browsing data.
@property(nonatomic, strong) ActionSheetCoordinator* actionSheetCoordinator; @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* chromeActivityOverlayCoordinator;
// Restarts the counters for data types specified in the mask. // Restarts the counters for data types specified in the mask.
- (void)restartCounters:(BrowsingDataRemoveMask)mask; - (void)restartCounters:(BrowsingDataRemoveMask)mask;
...@@ -236,10 +242,40 @@ void BrowsingDataRemoverObserverWrapper::OnBrowsingDataRemoved( ...@@ -236,10 +242,40 @@ void BrowsingDataRemoverObserverWrapper::OnBrowsingDataRemoved(
completionBlock:(ProceduralBlock)completionBlock { completionBlock:(ProceduralBlock)completionBlock {
base::RecordAction( base::RecordAction(
base::UserMetricsAction("MobileClearBrowsingDataTriggeredFromLegacyUI")); base::UserMetricsAction("MobileClearBrowsingDataTriggeredFromLegacyUI"));
[self.dispatcher removeBrowsingDataForBrowserState:browserState
timePeriod:timePeriod // Show activity indicator modal while removal is happening.
removeMask:removeMask self.chromeActivityOverlayCoordinator =
completionBlock:completionBlock]; [[ChromeActivityOverlayCoordinator alloc]
initWithBaseViewController:self.navigationController];
self.chromeActivityOverlayCoordinator.messageText =
l10n_util::GetNSStringWithFixup(
IDS_HISTORY_OPEN_CLEAR_BROWSING_DATA_DIALOG);
[self.chromeActivityOverlayCoordinator start];
__weak ClearBrowsingDataCollectionViewController* weakSelf = self;
dispatch_time_t timeOneSecondLater =
dispatch_time(DISPATCH_TIME_NOW, (1 * NSEC_PER_SEC));
void (^removeBrowsingDidFinishCompletionBlock)(void) = ^void() {
ClearBrowsingDataCollectionViewController* 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.chromeActivityOverlayCoordinator stop];
if (completionBlock)
completionBlock();
});
};
[self.dispatcher
removeBrowsingDataForBrowserState:browserState
timePeriod:timePeriod
removeMask:removeMask
completionBlock:removeBrowsingDidFinishCompletionBlock];
} }
#pragma mark UICollectionViewDelegate #pragma mark UICollectionViewDelegate
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.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/alert_coordinator/alert_coordinator.h"
#import "ios/chrome/browser/ui/commands/application_commands.h" #import "ios/chrome/browser/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/elements/chrome_activity_overlay_coordinator.h"
#import "ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.h" #import "ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.h"
#include "ios/chrome/browser/ui/settings/clear_browsing_data_local_commands.h" #include "ios/chrome/browser/ui/settings/clear_browsing_data_local_commands.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data_manager.h" #import "ios/chrome/browser/ui/settings/clear_browsing_data_manager.h"
...@@ -51,6 +52,11 @@ class ChromeBrowserState; ...@@ -51,6 +52,11 @@ class ChromeBrowserState;
// Coordinator that managers a UIAlertController to clear browsing data. // Coordinator that managers a UIAlertController to clear browsing data.
@property(nonatomic, strong) ActionSheetCoordinator* actionSheetCoordinator; @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* chromeActivityOverlayCoordinator;
// Reference to clear browsing data button for positioning popover confirmation // Reference to clear browsing data button for positioning popover confirmation
// dialog. // dialog.
@property(nonatomic, strong) UIButton* clearBrowsingDataButton; @property(nonatomic, strong) UIButton* clearBrowsingDataButton;
...@@ -250,10 +256,39 @@ class ChromeBrowserState; ...@@ -250,10 +256,39 @@ class ChromeBrowserState;
completionBlock:(ProceduralBlock)completionBlock { completionBlock:(ProceduralBlock)completionBlock {
base::RecordAction( base::RecordAction(
base::UserMetricsAction("MobileClearBrowsingDataTriggeredFromUIRefresh")); base::UserMetricsAction("MobileClearBrowsingDataTriggeredFromUIRefresh"));
[self.dispatcher removeBrowsingDataForBrowserState:browserState
timePeriod:timePeriod // Show activity indicator modal while removal is happening.
removeMask:removeMask self.chromeActivityOverlayCoordinator =
completionBlock:completionBlock]; [[ChromeActivityOverlayCoordinator alloc]
initWithBaseViewController:self.navigationController];
self.chromeActivityOverlayCoordinator.messageText =
l10n_util::GetNSStringWithFixup(
IDS_IOS_CLEAR_BROWSING_DATA_ACTIVITY_MODAL);
[self.chromeActivityOverlayCoordinator 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.chromeActivityOverlayCoordinator stop];
if (completionBlock)
completionBlock();
});
};
[self.dispatcher
removeBrowsingDataForBrowserState:browserState
timePeriod:timePeriod
removeMask:removeMask
completionBlock:removeBrowsingDidFinishCompletionBlock];
} }
- (void)showBrowsingHistoryRemovedDialog { - (void)showBrowsingHistoryRemovedDialog {
......
...@@ -111,6 +111,10 @@ id<GREYAction> ScrollDown() { ...@@ -111,6 +111,10 @@ id<GREYAction> ScrollDown() {
ConfirmClearBrowsingDataButton()] ConfirmClearBrowsingDataButton()]
performAction:grey_tap()]; performAction:grey_tap()];
// Wait until activity indicator modal is cleared, meaning clearing browsing
// data has been finished.
[[GREYUIThreadExecutor sharedInstance] drainUntilIdle];
// Include sufficientlyVisible condition for the case of the clear browsing // Include sufficientlyVisible condition for the case of the clear browsing
// dialog, which also has a "Done" button and is displayed over the history // dialog, which also has a "Done" button and is displayed over the history
// panel. // panel.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment