Commit f49f7768 authored by Chris Lu's avatar Chris Lu Committed by Chromium LUCI CQ

[ios] Add Delay Action to ConfirmationActionViewController

This CL add a third "Delay Action" button above the primary
action button. This will be used by the default browser
fullscreen promo to experiment with a "Remind Me Later" option.

iphone: https://drive.google.com/file/d/1MiEbfPkKUZ2N9CdGUVoHVENYUK9l5lJ9/view?usp=sharing
ipad: https://drive.google.com/file/d/1LGd3-uSift0E2poJUPoUcLVgM_XQ_PVt/view?usp=sharing

Bug: 1155778
Change-Id: I98f7ef9183dadeb806ff44a632934eece0b211fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2575903
Commit-Queue: Chris Lu <thegreenfrog@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Reviewed-by: default avataredchin <edchin@chromium.org>
Reviewed-by: default avatarJavier Flores <javierrobles@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842128}
parent 7fba0784
......@@ -26,6 +26,7 @@ source_set("whats_new") {
"//base",
"//ios/chrome/app/strings:ios_google_chrome_strings",
"//ios/chrome/app/strings:ios_strings",
"//ios/chrome/browser/ui:feature_flags",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/browser/ui/whats_new/resources",
"//ios/chrome/common/ui/confirmation_alert",
......
......@@ -4,6 +4,8 @@
#import "ios/chrome/browser/ui/whats_new/default_browser_promo_view_controller.h"
#include "base/feature_list.h"
#include "ios/chrome/browser/ui/ui_feature_flags.h"
#include "ios/chrome/grit/ios_google_chrome_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ui/base/l10n/l10n_util_mac.h"
......@@ -30,9 +32,18 @@
l10n_util::GetNSString(IDS_IOS_DEFAULT_BROWSER_DESCRIPTION);
self.primaryActionString =
l10n_util::GetNSString(IDS_IOS_DEFAULT_BROWSER_MAIN_BUTTON_TEXT);
self.secondaryActionString =
l10n_util::GetNSString(IDS_IOS_DEFAULT_BROWSER_SECONDARY_BUTTON_TEXT);
if (base::FeatureList::IsEnabled(kDefaultBrowserFullscreenPromoExperiment)) {
// TODO:(crubg.com/1155778): Add translation string.
self.secondaryActionString = @"Remind Me Later";
self.tertiaryActionAvailable = YES;
self.tertiaryActionString =
l10n_util::GetNSString(IDS_IOS_DEFAULT_BROWSER_SECONDARY_BUTTON_TEXT);
} else {
self.secondaryActionString =
l10n_util::GetNSString(IDS_IOS_DEFAULT_BROWSER_SECONDARY_BUTTON_TEXT);
}
self.dismissBarButtonSystemItem = UIBarButtonSystemItemCancel;
#if defined(__IPHONE_13_4)
if (@available(iOS 13.4, *)) {
self.pointerInteractionEnabled = YES;
......
......@@ -21,6 +21,10 @@
// The "Learn More" button was touched.
- (void)confirmationAlertLearnMoreAction;
@optional
// The "Tertiary Action" was touched.
- (void)confirmationAlertTertiaryAction;
@end
#endif // IOS_CHROME_COMMON_UI_CONFIRMATION_ALERT_CONFIRMATION_ALERT_ACTION_HANDLER_H_
......@@ -12,6 +12,7 @@ extern NSString* const kConfirmationAlertMoreInfoAccessibilityIdentifier;
extern NSString* const kConfirmationAlertTitleAccessibilityIdentifier;
extern NSString* const kConfirmationAlertSubtitleAccessibilityIdentifier;
extern NSString* const kConfirmationAlertPrimaryActionAccessibilityIdentifier;
extern NSString* const kConfirmationAlertSecondaryActionAccessibilityIdentifier;
extern NSString* const
kConfirmationAlertBarPrimaryActionAccessibilityIdentifier;
......@@ -20,6 +21,11 @@ extern NSString* const
// A view controller useful to show modal alerts and confirmations. The main
// content consists in a big image, a title, and a subtitle which are contained
// in a scroll view for cases when the content doesn't fit in the screen.
// The view controller can have up to three action buttons, which are position
// in the bottom. They are arranged, from top to bottom,
// |primaryActionAvailable|, |secondaryActionAvailable|,
// |tertiaryActionAvailable|. Setting those properties to YES will make those
// buttons be added to the view controller.
@interface ConfirmationAlertViewController : UIViewController
// The headline below the image. Must be set before the view is loaded.
......@@ -45,6 +51,13 @@ extern NSString* const
// The text for the secondary action. Must be set before the view is loaded.
@property(nonatomic, copy) NSString* secondaryActionString;
// Controls if there is a tertiary action in the view. Must be set before the
// view is loaded.
@property(nonatomic) BOOL tertiaryActionAvailable;
// The text for the tertiary action. Must be set before the view is loaded.
@property(nonatomic, copy) NSString* tertiaryActionString;
// The image. Must be set before the view is loaded.
@property(nonatomic, strong) UIImage* image;
......
......@@ -26,6 +26,8 @@ NSString* const kConfirmationAlertPrimaryActionAccessibilityIdentifier =
@"kConfirmationAlertPrimaryActionAccessibilityIdentifier";
NSString* const kConfirmationAlertSecondaryActionAccessibilityIdentifier =
@"kConfirmationAlertSecondaryActionAccessibilityIdentifier";
NSString* const kConfirmationAlertTertiaryActionAccessibilityIdentifier =
@"kConfirmationAlertTertiaryActionAccessibilityIdentifier";
NSString* const kConfirmationAlertBarPrimaryActionAccessibilityIdentifier =
@"kConfirmationAlertBarPrimaryActionAccessibilityIdentifier";
......@@ -33,6 +35,7 @@ namespace {
constexpr CGFloat kButtonVerticalInsets = 17;
constexpr CGFloat kPrimaryButtonCornerRadius = 13;
constexpr CGFloat kScrollViewBottomInsets = 20;
constexpr CGFloat kStackViewSpacing = 8;
constexpr CGFloat kStackViewSpacingAfterIllustration = 27;
constexpr CGFloat kGeneratedImagePadding = 20;
......@@ -50,6 +53,7 @@ constexpr CGFloat kSafeAreaMultiplier = 0.8;
// collection changes.
@property(nonatomic, strong) UIButton* primaryActionButton;
@property(nonatomic, strong) UIButton* secondaryActionButton;
@property(nonatomic, strong) UIButton* tertiaryActionButton;
@property(nonatomic, strong) UIToolbar* topToolbar;
@property(nonatomic, strong) NSArray* regularHeightToolbarItems;
@property(nonatomic, strong) NSArray* compactHeightToolbarItems;
......@@ -63,7 +67,8 @@ constexpr CGFloat kSafeAreaMultiplier = 0.8;
NSLayoutConstraint* regularHeightScrollViewBottomVerticalConstraint;
@property(nonatomic, strong)
NSLayoutConstraint* compactHeightScrollViewBottomVerticalConstraint;
@property(nonatomic, strong) NSLayoutConstraint* buttonBottomVerticalConstraint;
@property(nonatomic, strong)
NSLayoutConstraint* buttonStackViewBottomVerticalConstraint;
@end
@implementation ConfirmationAlertViewController
......@@ -125,9 +130,12 @@ constexpr CGFloat kSafeAreaMultiplier = 0.8;
centerYConstraint.priority = heightConstraint.priority - 1;
centerYConstraint.active = YES;
// Constraint the content of the scroll view to the size of the stack view.
// This defines the content area.
AddSameConstraints(self.stackView, scrollView);
// Constraint the content of the scroll view to the size of the stack view
// with some bottom margin space in between the two. This defines the content
// area.
AddSameConstraintsWithInsets(
self.stackView, scrollView,
ChromeDirectionalEdgeInsetsMake(0, 0, kScrollViewBottomInsets, 0));
// Disable horizontal scrolling and constraint the content size to the scroll
// view size.
......@@ -147,65 +155,44 @@ constexpr CGFloat kSafeAreaMultiplier = 0.8;
multiplier:kSafeAreaMultiplier],
];
if (self.primaryActionAvailable) {
UIButton* primaryActionButton = [self createPrimaryActionButton];
[self.view addSubview:primaryActionButton];
// Primary Action Button constraints.
self.buttonBottomVerticalConstraint = [primaryActionButton.bottomAnchor
constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor];
[NSLayoutConstraint activateConstraints:@[
[primaryActionButton.leadingAnchor
constraintEqualToAnchor:scrollView.leadingAnchor],
[primaryActionButton.trailingAnchor
constraintEqualToAnchor:scrollView.trailingAnchor],
]];
// The bottom anchor for the scroll view.
NSLayoutYAxisAnchor* scrollViewBottomAnchor =
self.view.safeAreaLayoutGuide.bottomAnchor;
BOOL hasActionButton = self.primaryActionAvailable ||
self.secondaryActionAvailable ||
self.tertiaryActionAvailable;
if (hasActionButton) {
UIStackView* actionStackView = [[UIStackView alloc] init];
actionStackView.alignment = UIStackViewAlignmentFill;
actionStackView.axis = UILayoutConstraintAxisVertical;
actionStackView.translatesAutoresizingMaskIntoConstraints = NO;
if (self.primaryActionAvailable) {
self.primaryActionButton = [self createPrimaryActionButton];
[actionStackView addArrangedSubview:self.primaryActionButton];
}
self.primaryActionButton = primaryActionButton;
}
if (self.secondaryActionAvailable) {
self.secondaryActionButton = [self createSecondaryActionButton];
[actionStackView addArrangedSubview:self.secondaryActionButton];
}
if (self.secondaryActionAvailable) {
UIButton* secondaryActionButton = [self createSecondaryActionButton];
[self.view addSubview:secondaryActionButton];
if (self.tertiaryActionAvailable) {
self.tertiaryActionButton = [self createTertiaryButton];
[actionStackView addArrangedSubview:self.tertiaryActionButton];
}
// Secondary Action Button constraints.
self.buttonBottomVerticalConstraint = [secondaryActionButton.bottomAnchor
[self.view addSubview:actionStackView];
self.buttonStackViewBottomVerticalConstraint = [actionStackView.bottomAnchor
constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor];
[NSLayoutConstraint activateConstraints:@[
[secondaryActionButton.leadingAnchor
[actionStackView.leadingAnchor
constraintEqualToAnchor:scrollView.leadingAnchor],
[secondaryActionButton.trailingAnchor
constraintEqualToAnchor:scrollView.trailingAnchor]
[actionStackView.trailingAnchor
constraintEqualToAnchor:scrollView.trailingAnchor],
self.buttonStackViewBottomVerticalConstraint
]];
self.secondaryActionButton = secondaryActionButton;
}
// The bottom anchor for the scroll view. It will be updated to the button top
// anchor if it exists.
NSLayoutYAxisAnchor* scrollViewBottomAnchor =
self.view.safeAreaLayoutGuide.bottomAnchor;
if (self.primaryActionAvailable || self.secondaryActionAvailable) {
// Set the ScrollView bottom anchor to the top anchor of the highest
// positioned button. It is always |primaryActionButton| if it is there,
// |secondaryActionButton| otherwise.
scrollViewBottomAnchor = self.primaryActionButton
? self.primaryActionButton.topAnchor
: self.secondaryActionButton.topAnchor;
// Add |buttonBottomVerticalConstraint|. It is always the
// |secondaryActionButton|'s bottom anchor if it is there,
// |primaryActionButton|'s otherwise.
self.buttonBottomVerticalConstraint.active = YES;
if (self.primaryActionAvailable && self.secondaryActionAvailable) {
// If both buttons are there, then |primaryActionButton| needs to be
// constrainted to the top of |secondaryActionButton|.
[NSLayoutConstraint activateConstraints:@[
[self.primaryActionButton.bottomAnchor
constraintEqualToAnchor:self.secondaryActionButton.topAnchor]
]];
}
scrollViewBottomAnchor = actionStackView.topAnchor;
}
self.regularHeightScrollViewBottomVerticalConstraint =
......@@ -289,10 +276,11 @@ constexpr CGFloat kSafeAreaMultiplier = 0.8;
CGFloat marginValue =
self.view.layoutMargins.left - self.view.safeAreaInsets.left;
if (!self.secondaryActionAvailable) {
// Do not add margin badding between the bottom button and the containing
// view if there is a secondary action button to allow for more spacing
// between the content and buttons.
self.buttonBottomVerticalConstraint.constant = -marginValue;
// Do not add margin padding between the bottom button and the containing
// view if the primary button is the bottom button to allow for more visual
// spacing between the content and the button. The secondary button has a
// transparent background so the visual spacing already exists.
self.buttonStackViewBottomVerticalConstraint.constant = -marginValue;
}
if (self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassCompact) {
......@@ -380,6 +368,15 @@ constexpr CGFloat kSafeAreaMultiplier = 0.8;
[self.actionHandler confirmationAlertSecondaryAction];
}
- (void)didTapTertiaryActionButton {
DCHECK(self.tertiaryActionAvailable);
if (![self.actionHandler
respondsToSelector:@selector(confirmationAlertTertiaryAction)]) {
return;
}
[self.actionHandler confirmationAlertTertiaryAction];
}
// Helper to create the top toolbar.
- (UIToolbar*)createTopToolbar {
UIToolbar* topToolbar = [[UIToolbar alloc] init];
......@@ -611,4 +608,37 @@ constexpr CGFloat kSafeAreaMultiplier = 0.8;
return secondaryActionButton;
}
- (UIButton*)createTertiaryButton {
DCHECK(self.tertiaryActionAvailable);
UIButton* tertiaryActionButton = [UIButton buttonWithType:UIButtonTypeSystem];
[tertiaryActionButton addTarget:self
action:@selector(didTapTertiaryActionButton)
forControlEvents:UIControlEventTouchUpInside];
[tertiaryActionButton setTitle:self.tertiaryActionString
forState:UIControlStateNormal];
tertiaryActionButton.contentEdgeInsets =
UIEdgeInsetsMake(kButtonVerticalInsets, 0, kButtonVerticalInsets, 0);
[tertiaryActionButton setBackgroundColor:[UIColor clearColor]];
UIColor* titleColor = [UIColor colorNamed:kBlueColor];
[tertiaryActionButton setTitleColor:titleColor forState:UIControlStateNormal];
tertiaryActionButton.titleLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleBody];
tertiaryActionButton.titleLabel.adjustsFontForContentSizeCategory = NO;
tertiaryActionButton.translatesAutoresizingMaskIntoConstraints = NO;
tertiaryActionButton.accessibilityIdentifier =
kConfirmationAlertTertiaryActionAccessibilityIdentifier;
#if defined(__IPHONE_13_4)
if (@available(iOS 13.4, *)) {
if (self.pointerInteractionEnabled) {
tertiaryActionButton.pointerInteractionEnabled = YES;
tertiaryActionButton.pointerStyleProvider =
CreateOpaqueButtonPointerStyleProvider();
}
}
#endif // defined(__IPHONE_13_4)
return tertiaryActionButton;
}
@end
......@@ -30,6 +30,7 @@ group("features") {
"//ios/showcase/bubble",
"//ios/showcase/content_suggestions",
"//ios/showcase/credential_provider",
"//ios/showcase/default_browser",
"//ios/showcase/incognito_reauth",
"//ios/showcase/infobars",
"//ios/showcase/omnibox_popup",
......@@ -76,6 +77,7 @@ ios_eg2_test("ios_showcase_eg2tests_module") {
"//ios/showcase/content_suggestions:eg2_tests",
"//ios/showcase/core:eg2_tests",
"//ios/showcase/credential_provider:eg2_tests",
"//ios/showcase/default_browser:eg2_tests",
"//ios/showcase/infobars:eg2_tests",
"//ios/showcase/text_badge_view:eg2_tests",
]
......
......@@ -167,6 +167,12 @@
showcase::kClassForInstantiationKey : @"SCIncognitoReauthViewController",
showcase::kUseCaseKey : @"Incognito Reauth Blocker",
},
@{
showcase::kClassForDisplayKey : @"DefaultBrowserPromoViewController",
showcase::
kClassForInstantiationKey : @"SCDefaultBrowserFullscreenPromoCoordinator",
showcase::kUseCaseKey : @"Default Browser Fullscreen Promo UI",
},
];
}
......
# Copyright 2020 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/build/chrome_build.gni")
source_set("default_browser") {
sources = []
deps = [ "//ios/showcase/common" ]
sources += [
"sc_default_browser_fullscreen_promo_coordinator.h",
"sc_default_browser_fullscreen_promo_coordinator.mm",
]
deps += [ "//ios/chrome/browser/ui/whats_new" ]
frameworks = [ "UIKit.framework" ]
configs += [ "//build/config/compiler:enable_arc" ]
}
source_set("eg2_tests") {
defines = [ "CHROME_EARL_GREY_2" ]
configs += [
"//build/config/compiler:enable_arc",
"//build/config/ios:xctest_config",
]
testonly = true
sources = [ "default_browser_fullscreen_promo_egtest.mm" ]
deps = [
"//base",
"//ios/chrome/common/ui/confirmation_alert",
"//ios/showcase/test:eg2_test",
"//ios/testing/earl_grey:eg_test_support+eg2",
"//ios/third_party/earl_grey2:test_lib",
"//ui/base",
]
frameworks = [ "UIKit.framework" ]
}
thegreenfrog@chromium.org
// Copyright 2020 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.
#include "base/ios/ios_util.h"
#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h"
#import "ios/showcase/test/showcase_eg_utils.h"
#import "ios/showcase/test/showcase_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.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 {
id<GREYMatcher> TitleMatcher() {
return grey_accessibilityID(kConfirmationAlertTitleAccessibilityIdentifier);
}
id<GREYMatcher> SubtitleMatcher() {
return grey_accessibilityID(
kConfirmationAlertSubtitleAccessibilityIdentifier);
}
id<GREYMatcher> PrimaryActionButtonMatcher() {
return grey_accessibilityID(
kConfirmationAlertPrimaryActionAccessibilityIdentifier);
}
id<GREYMatcher> SecondaryActionButtonMatcher() {
return grey_accessibilityID(
kConfirmationAlertSecondaryActionAccessibilityIdentifier);
}
} // namespace
// Tests for the default browser fullscreen promo view controller.
@interface SCDefaultBrowserFullscreenPromoTestCase : ShowcaseTestCase
@end
@implementation SCDefaultBrowserFullscreenPromoTestCase
// Tests the correct elements are visible in the fullscreen modal.
- (void)testFullscreenModal {
showcase_utils::Open(@"DefaultBrowserPromoViewController");
[[EarlGrey selectElementWithMatcher:TitleMatcher()]
assertWithMatcher:grey_interactable()];
[[EarlGrey selectElementWithMatcher:SubtitleMatcher()]
assertWithMatcher:grey_interactable()];
[[EarlGrey selectElementWithMatcher:PrimaryActionButtonMatcher()]
assertWithMatcher:grey_interactable()];
[[EarlGrey selectElementWithMatcher:SecondaryActionButtonMatcher()]
assertWithMatcher:grey_interactable()];
showcase_utils::Close();
}
@end
// Copyright 2020 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_SHOWCASE_DEFAULT_BROWSER_SC_DEFAULT_BROWSER_FULLSCREEN_PROMO_COORDINATOR_H_
#define IOS_SHOWCASE_DEFAULT_BROWSER_SC_DEFAULT_BROWSER_FULLSCREEN_PROMO_COORDINATOR_H_
#import "ios/showcase/common/navigation_coordinator.h"
@interface SCDefaultBrowserFullscreenPromoCoordinator
: NSObject <NavigationCoordinator>
@end
#endif // IOS_SHOWCASE_DEFAULT_BROWSER_SC_DEFAULT_BROWSER_FULLSCREEN_PROMO_COORDINATOR_H_
// Copyright 2020 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/showcase/default_browser/sc_default_browser_fullscreen_promo_coordinator.h"
#import "ios/chrome/browser/ui/whats_new/default_browser_promo_view_controller.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface SCDefaultBrowserFullscreenPromoCoordinator ()
@property(nonatomic, strong)
DefaultBrowserPromoViewController* defaultBrowerPromoViewController;
@end
@implementation SCDefaultBrowserFullscreenPromoCoordinator
@synthesize baseViewController = _baseViewController;
#pragma mark - Public Methods.
- (void)start {
self.defaultBrowerPromoViewController =
[[DefaultBrowserPromoViewController alloc] init];
self.defaultBrowerPromoViewController.modalPresentationStyle =
UIModalPresentationFormSheet;
[self.baseViewController setHidesBarsOnSwipe:NO];
[self.baseViewController
pushViewController:self.defaultBrowerPromoViewController
animated:YES];
}
@end
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