Commit 53c8682d authored by Javier Ernesto Flores Robles's avatar Javier Ernesto Flores Robles Committed by Commit Bot

[iOS][Password-Breach] Style view controller

Style all the views with the UI specification.
Add support for dynamic type.
Wrap contents in a scroll view for the bigger font sizes.
Add support for horizontal and vertical size class changes.
Present as Form on iPad.

Bug: 1008862
Change-Id: I9a107bb14e9bd1669462c9a383243a213a970aa8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1847339
Commit-Queue: Javier Ernesto Flores Robles <javierrobles@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#705084}
parent 9d11427a
......@@ -21,6 +21,7 @@ source_set("passwords") {
"//ios/chrome/app/strings",
"//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/browser/ui/util",
"//ios/chrome/common/colors",
"//ios/chrome/common/ui_util",
"//ui/base",
......
......@@ -63,6 +63,10 @@
- (void)showPasswordBreachForLeakType:(CredentialLeakType)leakType
URL:(const GURL&)URL {
self.viewController = [[PasswordBreachViewController alloc] init];
self.viewController.modalPresentationStyle = UIModalPresentationFormSheet;
if (@available(iOS 13, *)) {
self.viewController.modalInPresentation = YES;
}
id<ApplicationCommands> dispatcher =
static_cast<id<ApplicationCommands>>(self.dispatcher);
self.mediator =
......
......@@ -5,6 +5,7 @@
#import "ios/chrome/browser/ui/passwords/password_breach_view_controller.h"
#import "ios/chrome/browser/ui/passwords/password_breach_action_handler.h"
#include "ios/chrome/browser/ui/util/dynamic_type_util.h"
#import "ios/chrome/common/colors/semantic_color_names.h"
#import "ios/chrome/common/ui_util/constraints_ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
......@@ -17,10 +18,15 @@
using l10n_util::GetNSString;
namespace {
constexpr CGFloat kStackViewSpacing = 16.0;
constexpr CGFloat kButtonVerticalInsets = 17;
constexpr CGFloat kPrimaryButtonCornerRadius = 13;
constexpr CGFloat kStackViewSpacing = 8;
constexpr CGFloat kStackViewSpacingAfterIllustration = 27;
// The multiplier used when in regular horizontal size class.
constexpr CGFloat kSafeAreaMultiplier = 0.8;
} // namespace
@interface PasswordBreachViewController ()
@interface PasswordBreachViewController () <UIToolbarDelegate>
// Properties backing up the respective consumer setter.
@property(nonatomic, strong) NSString* titleString;
......@@ -28,6 +34,19 @@ constexpr CGFloat kStackViewSpacing = 16.0;
@property(nonatomic, strong) NSString* primaryActionString;
@property(nonatomic) BOOL primaryActionAvailable;
// References to the UI properties that need to be updated when the trait
// collection changes.
@property(nonatomic, strong) UIButton* primaryActionButton;
@property(nonatomic, strong) UIImageView* imageView;
// Constraints.
@property(nonatomic, strong)
NSArray<NSLayoutConstraint*>* compactWidthConstraints;
@property(nonatomic, strong)
NSArray<NSLayoutConstraint*>* regularWidthConstraints;
@property(nonatomic, strong)
NSLayoutConstraint* scrollViewBottomVerticalConstraint;
@property(nonatomic, strong)
NSLayoutConstraint* primaryButtonBottomVerticalConstraint;
@end
@implementation PasswordBreachViewController
......@@ -38,72 +57,166 @@ constexpr CGFloat kStackViewSpacing = 16.0;
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorNamed:kBackgroundColor];
UIButton* doneButton = [UIButton buttonWithType:UIButtonTypeSystem];
[doneButton addTarget:self
action:@selector(didTapDoneButton)
forControlEvents:UIControlEventTouchUpInside];
[doneButton setTitle:GetNSString(IDS_IOS_NAVIGATION_BAR_DONE_BUTTON)
forState:UIControlStateNormal];
doneButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:doneButton];
UIImage* image = [UIImage imageNamed:@"password_breach_illustration"];
UIImageView* imageView = [[UIImageView alloc] initWithImage:image];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
UIToolbar* topToolbar = [self createTopToolbar];
[self.view addSubview:topToolbar];
UILabel* title = [[UILabel alloc] init];
title.numberOfLines = 0;
title.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
title.textColor = [UIColor colorNamed:kTextPrimaryColor];
title.text = self.titleString;
title.translatesAutoresizingMaskIntoConstraints = NO;
self.imageView = [self createImageView];
UILabel* title = [self createTitleLabel];
UILabel* subtitle = [self createSubtitleLabel];
UILabel* subtitle = [[UILabel alloc] init];
subtitle.numberOfLines = 0;
subtitle.textColor = [UIColor colorNamed:kTextSecondaryColor];
subtitle.text = self.subtitleString;
subtitle.translatesAutoresizingMaskIntoConstraints = NO;
UIStackView* stackView = [self
createStackViewWithArrangedSubviews:@[ self.imageView, title, subtitle ]];
UIStackView* stackView = [[UIStackView alloc]
initWithArrangedSubviews:@[ imageView, title, subtitle ]];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.alignment = UIStackViewAlignmentFill;
stackView.translatesAutoresizingMaskIntoConstraints = NO;
stackView.spacing = kStackViewSpacing;
[self.view addSubview:stackView];
UIScrollView* scrollView = [self createScrollView];
[scrollView addSubview:stackView];
[self.view addSubview:scrollView];
self.view.preservesSuperviewLayoutMargins = YES;
UILayoutGuide* margins = self.view.layoutMarginsGuide;
// Toolbar constraints to the top.
AddSameConstraintsToSides(
topToolbar, self.view.safeAreaLayoutGuide,
LayoutSides::kTrailing | LayoutSides::kTop | LayoutSides::kLeading);
// Scroll View constraints to the height of its content. Can be overridden.
NSLayoutConstraint* heightConstraint = [scrollView.heightAnchor
constraintEqualToAnchor:scrollView.contentLayoutGuide.heightAnchor];
// UILayoutPriorityDefaultHigh is the default priority for content
// compression. Setting this lower avoids compressing the content of the
// scroll view.
heightConstraint.priority = UILayoutPriorityDefaultHigh - 1;
heightConstraint.active = YES;
// Scroll View constraint to the vertical center. Can be overridden.
NSLayoutConstraint* centerYConstraint =
[scrollView.centerYAnchor constraintEqualToAnchor:margins.centerYAnchor];
// This needs to be lower than the height constraint, so it's deprioritized.
// If this breaks, the scroll view is still constrained to the top toolbar and
// the bottom safe area or button.
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(stackView, scrollView);
// Disable horizontal scrolling and constraint the content size to the scroll
// view size.
[scrollView.widthAnchor
constraintEqualToAnchor:scrollView.contentLayoutGuide.widthAnchor]
.active = YES;
[scrollView.centerXAnchor constraintEqualToAnchor:margins.centerXAnchor]
.active = YES;
// Width Scroll View constraint. It changes based on the size class.
self.compactWidthConstraints = @[
[scrollView.widthAnchor constraintEqualToAnchor:margins.widthAnchor],
];
self.regularWidthConstraints = @[
[scrollView.widthAnchor constraintEqualToAnchor:margins.widthAnchor
multiplier:kSafeAreaMultiplier],
];
// 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) {
UIButton* primaryActionButton =
[UIButton buttonWithType:UIButtonTypeSystem];
[primaryActionButton addTarget:self
action:@selector(didTapPrimaryActionButton)
forControlEvents:UIControlEventTouchUpInside];
[primaryActionButton setTitle:self.primaryActionString
forState:UIControlStateNormal];
primaryActionButton.translatesAutoresizingMaskIntoConstraints = NO;
UIButton* primaryActionButton = [self createPrimaryActionButton];
[self.view addSubview:primaryActionButton];
// Primary Action Button constraints.
AddSameConstraintsToSides(
margins, primaryActionButton,
LayoutSides::kLeading | LayoutSides::kTrailing | LayoutSides::kBottom);
self.primaryButtonBottomVerticalConstraint =
[primaryActionButton.bottomAnchor
constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor];
[NSLayoutConstraint activateConstraints:@[
[primaryActionButton.leadingAnchor
constraintEqualToAnchor:scrollView.leadingAnchor],
[primaryActionButton.trailingAnchor
constraintEqualToAnchor:scrollView.trailingAnchor],
self.primaryButtonBottomVerticalConstraint,
]];
scrollViewBottomAnchor = primaryActionButton.topAnchor;
self.primaryActionButton = primaryActionButton;
}
// Done Button constraints.
AddSameConstraintsToSides(margins, doneButton,
LayoutSides::kTrailing | LayoutSides::kTop);
// Constraing the image to the scroll view size and its aspect ratio.
[self.imageView
setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
[self.imageView
setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisVertical];
CGFloat imageAspectRatio =
self.imageView.image.size.width / self.imageView.image.size.height;
self.scrollViewBottomVerticalConstraint = [scrollView.bottomAnchor
constraintLessThanOrEqualToAnchor:scrollViewBottomAnchor];
// Stack View (and its contents) constraints.
CGFloat imageAspectRatio = image.size.width / image.size.height;
[imageView.widthAnchor constraintEqualToAnchor:imageView.heightAnchor
multiplier:imageAspectRatio]
.active = YES;
AddSameCenterConstraints(margins, stackView);
AddSameConstraintsToSides(margins, stackView,
LayoutSides::kLeading | LayoutSides::kTrailing);
[NSLayoutConstraint activateConstraints:@[
[self.imageView.widthAnchor
constraintEqualToAnchor:self.imageView.heightAnchor
multiplier:imageAspectRatio],
[scrollView.topAnchor
constraintGreaterThanOrEqualToAnchor:topToolbar.bottomAnchor],
self.scrollViewBottomVerticalConstraint,
]];
}
- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
// Update fonts for specific content sizes.
if (previousTraitCollection.preferredContentSizeCategory !=
self.traitCollection.preferredContentSizeCategory) {
self.primaryActionButton.titleLabel.font =
PreferredFontForTextStyleWithMaxCategory(
UIFontTextStyleHeadline,
self.traitCollection.preferredContentSizeCategory,
UIContentSizeCategoryExtraExtraExtraLarge);
}
// Update constraints for different size classes.
BOOL hasNewHorizontalSizeClass =
previousTraitCollection.horizontalSizeClass !=
self.traitCollection.horizontalSizeClass;
BOOL hasNewVerticalSizeClass = previousTraitCollection.verticalSizeClass !=
self.traitCollection.verticalSizeClass;
if (hasNewHorizontalSizeClass || hasNewVerticalSizeClass) {
[self.view setNeedsUpdateConstraints];
}
}
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
[self.view setNeedsUpdateConstraints];
}
- (void)viewLayoutMarginsDidChange {
[super viewLayoutMarginsDidChange];
[self.view setNeedsUpdateConstraints];
}
- (void)updateViewConstraints {
CGFloat marginValue =
self.view.layoutMargins.left - self.view.safeAreaInsets.left;
self.scrollViewBottomVerticalConstraint.constant = -marginValue;
self.primaryButtonBottomVerticalConstraint.constant = -marginValue;
if (self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassCompact) {
[NSLayoutConstraint deactivateConstraints:self.regularWidthConstraints];
[NSLayoutConstraint activateConstraints:self.compactWidthConstraints];
} else {
[NSLayoutConstraint deactivateConstraints:self.compactWidthConstraints];
[NSLayoutConstraint activateConstraints:self.regularWidthConstraints];
}
self.imageView.hidden =
self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact;
[super updateViewConstraints];
}
#pragma mark - PasswordBreachConsumer
......@@ -118,6 +231,12 @@ constexpr CGFloat kStackViewSpacing = 16.0;
self.primaryActionAvailable = primaryActionAvailable;
}
#pragma mark - UIToolbarDelegate
- (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar {
return UIBarPositionTopAttached;
}
#pragma mark - Private
// Handle taps on the done button.
......@@ -130,4 +249,110 @@ constexpr CGFloat kStackViewSpacing = 16.0;
[self.actionHandler passwordBreachPrimaryAction];
}
// Helper to create the top toolbar.
- (UIToolbar*)createTopToolbar {
UIToolbar* topToolbar = [[UIToolbar alloc] init];
topToolbar.translucent = NO;
[topToolbar setShadowImage:[[UIImage alloc] init]
forToolbarPosition:UIBarPositionAny];
[topToolbar setBarTintColor:[UIColor colorNamed:kBackgroundColor]];
topToolbar.delegate = self;
UIBarButtonItem* doneButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(didTapDoneButton)];
UIBarButtonItem* spacer = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];
topToolbar.items = @[ spacer, doneButton ];
topToolbar.translatesAutoresizingMaskIntoConstraints = NO;
return topToolbar;
}
// Helper to create the image view.
- (UIImageView*)createImageView {
UIImage* image = [UIImage imageNamed:@"password_breach_illustration"];
UIImageView* imageView = [[UIImageView alloc] initWithImage:image];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
return imageView;
}
// Helper to create the title label.
- (UILabel*)createTitleLabel {
UILabel* title = [[UILabel alloc] init];
title.numberOfLines = 0;
UIFontDescriptor* descriptor = [UIFontDescriptor
preferredFontDescriptorWithTextStyle:UIFontTextStyleTitle1];
UIFont* font = [UIFont systemFontOfSize:descriptor.pointSize
weight:UIFontWeightBold];
UIFontMetrics* fontMetrics =
[UIFontMetrics metricsForTextStyle:UIFontTextStyleTitle1];
title.font = [fontMetrics scaledFontForFont:font];
title.textColor = [UIColor colorNamed:kTextPrimaryColor];
title.text = self.titleString;
title.textAlignment = NSTextAlignmentCenter;
title.translatesAutoresizingMaskIntoConstraints = NO;
title.adjustsFontForContentSizeCategory = YES;
return title;
}
// Helper to create the subtitle label.
- (UILabel*)createSubtitleLabel {
UILabel* subtitle = [[UILabel alloc] init];
subtitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
subtitle.numberOfLines = 0;
subtitle.textColor = [UIColor colorNamed:kTextSecondaryColor];
subtitle.text = self.subtitleString;
subtitle.textAlignment = NSTextAlignmentCenter;
subtitle.translatesAutoresizingMaskIntoConstraints = NO;
subtitle.adjustsFontForContentSizeCategory = YES;
return subtitle;
}
// Helper to create the scroll view.
- (UIScrollView*)createScrollView {
UIScrollView* scrollView = [[UIScrollView alloc] init];
scrollView.alwaysBounceVertical = NO;
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
return scrollView;
}
// Helper to create the stack view.
- (UIStackView*)createStackViewWithArrangedSubviews:
(NSArray<UIView*>*)subviews {
UIStackView* stackView =
[[UIStackView alloc] initWithArrangedSubviews:subviews];
[stackView setCustomSpacing:kStackViewSpacingAfterIllustration
afterView:self.imageView];
stackView.axis = UILayoutConstraintAxisVertical;
stackView.alignment = UIStackViewAlignmentFill;
stackView.translatesAutoresizingMaskIntoConstraints = NO;
stackView.spacing = kStackViewSpacing;
return stackView;
}
// Helper to create the primary action button.
- (UIButton*)createPrimaryActionButton {
UIButton* primaryActionButton = [UIButton buttonWithType:UIButtonTypeSystem];
[primaryActionButton addTarget:self
action:@selector(didTapPrimaryActionButton)
forControlEvents:UIControlEventTouchUpInside];
[primaryActionButton setTitle:self.primaryActionString
forState:UIControlStateNormal];
primaryActionButton.contentEdgeInsets =
UIEdgeInsetsMake(kButtonVerticalInsets, 0, kButtonVerticalInsets, 0);
[primaryActionButton setBackgroundColor:[UIColor colorNamed:kBlueColor]];
UIColor* titleColor = [UIColor colorNamed:kSolidButtonTextColor];
[primaryActionButton setTitleColor:titleColor forState:UIControlStateNormal];
primaryActionButton.titleLabel.font =
[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
primaryActionButton.layer.cornerRadius = kPrimaryButtonCornerRadius;
primaryActionButton.titleLabel.adjustsFontForContentSizeCategory = NO;
primaryActionButton.translatesAutoresizingMaskIntoConstraints = NO;
return primaryActionButton;
}
@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