Commit fcdb3ef9 authored by Jérôme Lebel's avatar Jérôme Lebel Committed by Commit Bot

[iOS] Adding unified user consent dialog

This patch is to implement unified user consent dialog. This dialog is
not used yet. This dialog will be used by ChromeSigninViewController in
the workflow of sign-in.


This dialog has 2 goals:
  - Asks to the user to choose an account
  - Shows the user consent to the user

Some assets are not ready and are shown in red. The strings are not
final neither.

Implementation:
with identity:
https://drive.google.com/open?id=1ebwI5_pmWCuz09x-zJQYCuJIKxKoLVH5
https://drive.google.com/open?id=1WUdN_NtzF74tn28HaRwpqbUZ3L2vtJS8
https://drive.google.com/open?id=1zSBHzGsk-vwuhL4fJQw0vsDZ7sDzCwnk
https://drive.google.com/open?id=1rWf_KT6hnKHUvtEoAYA3wr7koyepXEXF
without identity:
https://drive.google.com/open?id=1HQjRFu_cODRzKY2ZKpyJ7hTjJMXPjEui

Mock:
https://docs.google.com/presentation/d/1cZfr5FGWGSy0PNaQ8uzik0alLAH-5glh1vsb030vha8/edit?ts=5aba5455#slide=id.g353bc22139_3_19

Related to previous patch:
crrev.com/c/995417
Related to next patch:
crrev.com/c/1010346

Bug: 827072
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I706c6024ff59d1c7870a39afcc04443e88dcb6dc
Reviewed-on: https://chromium-review.googlesource.com/1009906
Commit-Queue: Jérôme Lebel <jlebel@chromium.org>
Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#551340}
parent 02ba7243
......@@ -137,6 +137,15 @@ locale. The strings in this file are specific to iOS.
<message name="IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_TITLE" desc="Title of the screen where users select an account to use. [Length: 20em] [iOS only]">
Sign in to Chromium
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_BETTER_BROWSER" desc="Description on the sign-in consent dialog to explain the benefit to sign-in. [Length: unlimited] [iOS only]" translateable="false">
Improve Chromium and its security by sending system and usage data to Google
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_SETTINGS" desc="Description on the sign-in consent dialog to explain the benefit to sign-in. [Length: unlimited] [iOS only]" translateable="false">
Google may use content on sites you visit, browser interactions and activity to personalize Chromium and Google services like Translate, Search and ads. You can customize this in <ph name="BEGIN_LINK">BEGIN_LINK</ph>Settings<ph name="END_LINK">END_LINK</ph>
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_TITLE" desc="Title on the sign-in consent dialog to explain why sign-in can be useful for the user. [Length: 14em] [iOS only]" translateable="false">
Get Google smarts in Chromium
</message>
<message name="IDS_IOS_APP_RATING_PROMO_STRING" desc="Text displayed for the rate this app promo. [iOS only]">
Enjoying Chromium? <ph name="BEGIN_LINK"><ex>BEGIN_LINK</ex>BEGIN_LINK</ph>Rate this app<ph name="END_LINK"><ex>END_LINK</ex>END_LINK</ph>
</message>
......
......@@ -137,6 +137,15 @@ locale. The strings in this file are specific to iOS.
<message name="IDS_IOS_ACCOUNT_CONSISTENCY_SETUP_TITLE" desc="Title of the screen where users select an account to use. [Length: 20em] [iOS only]">
Sign in to Chrome
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_BETTER_BROWSER" desc="Description on the sign-in consent dialog to explain the benefit to sign-in. [Length: unlimited] [iOS only]" translateable="false">
Improve Chrome and its security by sending system and usage data to Google
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_SETTINGS" desc="Description on the sign-in consent dialog to explain the benefit to sign-in. [Length: unlimited] [iOS only]" translateable="false">
Google may use content on sites you visit, browser interactions and activity to personalize Chrome and Google services like Translate, Search and ads. You can customize this in <ph name="BEGIN_LINK">BEGIN_LINK</ph>Settings<ph name="END_LINK">END_LINK</ph>
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_TITLE" desc="Title on the sign-in consent dialog to explain why sign-in can be useful for the user. [Length: 14em] [iOS only]" translateable="false">
Get Google smarts in Chrome
</message>
<message name="IDS_IOS_APP_RATING_PROMO_STRING" desc="Text displayed for the rate this app promo. [iOS only]">
Enjoying Chrome? <ph name="BEGIN_LINK"><ex>BEGIN_LINK</ex>BEGIN_LINK</ph>Rate this app<ph name="END_LINK"><ex>END_LINK</ex>END_LINK</ph>
</message>
......
......@@ -140,6 +140,12 @@ locale. The strings in this file are specific to iOS.
<message name="IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_OK_BUTTON" desc="Title of the button to accept sign in and navigate away from the confirmation screen. [Length: 20m] [iOS only]">
Ok, Got it
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_SYNC_DATA" desc="Description on the sign-in consent dialog to explain the benefit to sign-in. [Length: unlimited] [iOS only]" translateable="false">
Your passwords, history &amp; more on all devices
</message>
<message name="IDS_IOS_ACCOUNT_UNIFIED_CONSENT_PERSONALIZED" desc="Description on the sign-in consent dialog to explain the benefit to sign-in. [Length: unlimited] [iOS only]" translateable="false">
Personalized Google services like Google Pay
</message>
<message name="IDS_IOS_ACCOUNT_CONSISTENCY_CONFIRMATION_OPEN_SETTINGS" desc="Description of how to configure sync and Google services on the sign-in confirmation screen. [Length: unlimited]">
Manage Chrome Sync and personalization in <ph name="BEGIN_LINK">BEGIN_LINK</ph>Settings<ph name="END_LINK">END_LINK</ph>
</message>
......
// 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_AUTHENTICATION_UNIFIED_CONSENT_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_COORDINATOR_H_
#import <UIKit/UIKit.h>
#include <vector>
@class ChromeIdentity;
@class UnifiedConsentCoordinator;
// Delegate protocol for UnifiedConsentCoordinator.
@protocol UnifiedConsentCoordinatorDelegate<NSObject>
// Called when the user taps on the settings link.
- (void)unifiedConsentCoordinatorDidTapSettingsLink:
(UnifiedConsentCoordinator*)controller;
@end
// UnityConsentCoordinator coordinates UnityConsentViewController, which is a
// sub view controller to ask for the user consent before the user can sign-in.
// All the string ids displayed by the view are available with
// |consentStringIds| and |openSettingsStringId|. Those can be used to record
// the consent agreed by the user.
@interface UnifiedConsentCoordinator : NSObject
@property(nonatomic, weak) id<UnifiedConsentCoordinatorDelegate> delegate;
// Identity selected by the user to sign-in.
@property(nonatomic, strong, readonly) ChromeIdentity* selectedIdentity;
// String id for text to open the settings (related to record the user consent).
@property(nonatomic, readonly) int openSettingsStringId;
// View controller used to display the view.
@property(nonatomic, strong, readonly) UIViewController* viewController;
// Starts this coordinator.
- (void)start;
// List of string ids used for the user consent. The string ids order matches
// the way they appear on the screen.
- (const std::vector<int>&)consentStringIds;
@end
#endif // IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_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.
#include "ios/chrome/browser/ui/authentication/unified_consent_coordinator.h"
#include "base/logging.h"
#import "ios/chrome/browser/ui/authentication/unified_consent_mediator.h"
#import "ios/chrome/browser/ui/authentication/unified_consent_view_controller.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface UnifiedConsentCoordinator ()<UnifiedConsentViewControllerDelegate>
@property(nonatomic, strong) UnifiedConsentMediator* unifiedConsentMediator;
@property(nonatomic, strong, readwrite)
UnifiedConsentViewController* unifiedConsentViewController;
@end
@implementation UnifiedConsentCoordinator
@synthesize delegate = _delegate;
@synthesize unifiedConsentMediator = _unifiedConsentMediator;
@synthesize unifiedConsentViewController = _unifiedConsentViewController;
- (void)start {
self.unifiedConsentViewController =
[[UnifiedConsentViewController alloc] init];
self.unifiedConsentViewController.delegate = self;
self.unifiedConsentMediator = [[UnifiedConsentMediator alloc]
initWithUnifiedConsentViewController:self.unifiedConsentViewController];
[self.unifiedConsentMediator start];
}
- (ChromeIdentity*)selectedIdentity {
return self.unifiedConsentMediator.selectedIdentity;
}
- (UIViewController*)viewController {
return self.unifiedConsentViewController;
}
- (int)openSettingsStringId {
return self.unifiedConsentViewController.openSettingsStringId;
}
- (const std::vector<int>&)consentStringIds {
return [self.unifiedConsentViewController consentStringIds];
}
#pragma mark - UnifiedConsentViewControllerDelegate
- (void)unifiedConsentViewControllerDidTapSettingsLink:
(UnifiedConsentViewController*)controller {
DCHECK_EQ(self.unifiedConsentViewController, controller);
[self.delegate unifiedConsentCoordinatorDidTapSettingsLink:self];
}
- (void)unifiedConsentViewControllerDidTapIdentityPickerView:
(UnifiedConsentViewController*)controller {
DCHECK_EQ(self.unifiedConsentViewController, controller);
// TODO(crbug.com/827072): Needs implementation.
}
@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.
#ifndef IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_MEDIATOR_H_
#define IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_MEDIATOR_H_
#import <Foundation/Foundation.h>
@class ChromeIdentity;
@class UnifiedConsentViewController;
// A mediator object that monitor updates of the selecte chrome identity, and
// updates the UnifiedConsentViewController.
@interface UnifiedConsentMediator : NSObject
// Identity selected by the user to sign-in.
@property(nonatomic) ChromeIdentity* selectedIdentity;
- (instancetype)initWithUnifiedConsentViewController:
(UnifiedConsentViewController*)viewController NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
// Starts this mediator.
- (void)start;
@end
#endif // IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_MEDIATOR_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.
#include "ios/chrome/browser/ui/authentication/unified_consent_mediator.h"
#include "base/logging.h"
#import "ios/chrome/browser/chrome_browser_provider_observer_bridge.h"
#import "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h"
#include "ios/chrome/browser/ui/authentication/unified_consent_view_controller.h"
#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface UnifiedConsentMediator ()<ChromeIdentityServiceObserver,
ChromeBrowserProviderObserver> {
std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
std::unique_ptr<ChromeBrowserProviderObserverBridge> _browserProviderObserver;
}
@property(nonatomic, weak)
UnifiedConsentViewController* unifiedConsentViewController;
@property(nonatomic, strong) UIImage* selectedIdentityAvatar;
@end
@implementation UnifiedConsentMediator
@synthesize selectedIdentityAvatar = _selectedIdentityAvatar;
@synthesize selectedIdentity = _selectedIdentity;
@synthesize unifiedConsentViewController = _unifiedConsentViewController;
- (instancetype)initWithUnifiedConsentViewController:
(UnifiedConsentViewController*)viewController {
self = [super init];
if (self) {
_unifiedConsentViewController = viewController;
}
return self;
}
- (void)setSelectedIdentity:(ChromeIdentity*)selectedIdentity {
if (selectedIdentity == self.selectedIdentity)
return;
_selectedIdentity = selectedIdentity;
self.selectedIdentityAvatar = nil;
__weak UnifiedConsentMediator* weakSelf = self;
ios::GetChromeBrowserProvider()
->GetChromeIdentityService()
->GetAvatarForIdentity(self.selectedIdentity, ^(UIImage* identityAvatar) {
if (weakSelf.selectedIdentity != selectedIdentity) {
return;
}
[weakSelf identityAvatarUpdated:identityAvatar];
});
[self updateViewController];
}
- (void)start {
_identityServiceObserver =
std::make_unique<ChromeIdentityServiceObserverBridge>(self);
_browserProviderObserver =
std::make_unique<ChromeBrowserProviderObserverBridge>(self);
NSArray* identities = ios::GetChromeBrowserProvider()
->GetChromeIdentityService()
->GetAllIdentitiesSortedForDisplay();
// Make sure the view is loaded so the mediator can set it up.
[self.unifiedConsentViewController loadViewIfNeeded];
if (identities.count != 0) {
self.selectedIdentity = identities[0];
}
}
#pragma mark - Private
- (void)updateViewController {
if (self.selectedIdentity) {
[self.unifiedConsentViewController
updateIdentityPickerViewWithUserFullName:self.selectedIdentity
.userFullName
email:self.selectedIdentity
.userEmail];
[self.unifiedConsentViewController
updateIdentityPickerViewWithAvatar:self.selectedIdentityAvatar];
} else {
[self.unifiedConsentViewController hideIdentityPickerView];
}
}
- (void)identityAvatarUpdated:(UIImage*)identityAvatar {
if (_selectedIdentityAvatar == identityAvatar)
return;
_selectedIdentityAvatar = identityAvatar;
[self.unifiedConsentViewController
updateIdentityPickerViewWithAvatar:self.selectedIdentityAvatar];
}
#pragma mark - ChromeBrowserProviderObserver
- (void)chromeIdentityServiceDidChange:(ios::ChromeIdentityService*)identity {
DCHECK(!_identityServiceObserver.get());
_identityServiceObserver =
std::make_unique<ChromeIdentityServiceObserverBridge>(self);
}
- (void)chromeBrowserProviderWillBeDestroyed {
_browserProviderObserver.reset();
}
#pragma mark - ChromeIdentityServiceObserver
- (void)identityListChanged {
ChromeIdentity* newIdentity = nil;
NSArray* identities = ios::GetChromeBrowserProvider()
->GetChromeIdentityService()
->GetAllIdentitiesSortedForDisplay();
if (identities.count != 0) {
newIdentity = identities[0];
}
if (newIdentity != self.selectedIdentity) {
self.selectedIdentity = newIdentity;
}
}
- (void)profileUpdate:(ChromeIdentity*)identity {
if (identity == self.selectedIdentity) {
[self updateViewController];
}
}
- (void)chromeIdentityServiceWillBeDestroyed {
_identityServiceObserver.reset();
}
@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.
#ifndef IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_VIEW_CONTROLLER_H_
#import <UIKit/UIKit.h>
#include <vector>
@class ChromeIdentity;
@class UnifiedConsentViewController;
// Delegate protocol for UnityConsentViewController.
@protocol UnifiedConsentViewControllerDelegate<NSObject>
// Called when the user taps on the settings link.
- (void)unifiedConsentViewControllerDidTapSettingsLink:
(UnifiedConsentViewController*)controller;
// Called when the user taps on the IdentityPickerView.
- (void)unifiedConsentViewControllerDidTapIdentityPickerView:
(UnifiedConsentViewController*)controller;
@end
// UnityConsentViewController is a sub view controller to ask for the user
// consent before the user can sign-in.
// All the string ids displayed by the view are available with
// |consentStringIds| and |openSettingsStringId|. Those can be used to record
// the consent agreed by the user.
@interface UnifiedConsentViewController : UIViewController
@property(nonatomic, weak) id<UnifiedConsentViewControllerDelegate> delegate;
// String id for text to open the settings (related to record the user consent).
@property(nonatomic, readonly) int openSettingsStringId;
// -[UnifiedConsentViewController init] should be used.
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
// List of string ids used for the user consent. The string ids order matches
// the way they appear on the screen.
- (const std::vector<int>&)consentStringIds;
// Shows (if hidden) and updates the IdentityPickerView.
- (void)updateIdentityPickerViewWithUserFullName:(NSString*)fullName
email:(NSString*)email;
// Updates the IdentityPickerView avatar.
- (void)updateIdentityPickerViewWithAvatar:(UIImage*)avatar;
// Hides the IdentityPickerView.
- (void)hideIdentityPickerView;
@end
#endif // IOS_CHROME_BROWSER_UI_AUTHENTICATION_UNIFIED_CONSENT_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/authentication/unified_consent_view_controller.h"
#include "base/logging.h"
#include "components/google/core/browser/google_util.h"
#include "ios/chrome/browser/application_context.h"
#import "ios/chrome/browser/ui/authentication/identity_picker_view.h"
#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#import "ios/chrome/browser/ui/util/constraints_ui_util.h"
#import "ios/chrome/browser/ui/util/label_link_controller.h"
#include "ios/chrome/common/string_util.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Sizes.
// Height of the header image at the top.
CGFloat kHeaderImageHeight = 144.;
// Size of the small icons next to the text.
CGFloat kIconSize = 16.;
// Separator line height.
CGFloat kSeparatorHeight = 2.;
// Font sizes
CGFloat kTitleFontSize = 23.;
CGFloat kTextFontSize = 12.;
// Horizontal margin between the container view and any elements inside.
CGFloat kHorizontalMargin = 16.;
// Horizontal margin between the small icon and the text next to it.
CGFloat kIconTextMargin = 16.;
// Horizontal margin on the left part of the separator.
CGFloat kLeftSeparatorMargin = kHorizontalMargin + kIconSize + kIconTextMargin;
// Vertical margin between texts.
CGFloat kVerticalBetweenTextMargin = 16.;
// Vertical margin above the first text and after the last text.
CGFloat kVerticalTextMargin = 32.;
// Vertical margin the main title and the identity picker.
CGFloat KTitlePickerMargin = 26.;
// Color displayed in the non-safe area.
const int kHeaderBackgroundColor = 0xf8f9fa;
// Alpha for the separator color.
const CGFloat kSeparatorColorAlpha = 0.12;
// Alpha for the title color.
const CGFloat kTitleColorAlpha = 0.87;
// Alpha for the text color.
const CGFloat kTextColorAlpha = 0.54;
const char* kSettingsSyncURL = "internal://settings-sync";
} // namespace
@interface UnifiedConsentViewController () {
std::vector<int> _consentStringIds;
}
// Read/write internal.
@property(nonatomic, readwrite) int openSettingsStringId;
// Main view.
@property(nonatomic, strong) UIScrollView* scrollView;
// Identity picker to change the identity to sign-in.
@property(nonatomic, strong) IdentityPickerView* identityPickerView;
// Vertical constraint on imageBackgroundView to have it over non-safe area.
@property(nonatomic, strong)
NSLayoutConstraint* imageBackgroundViewHeightConstraint;
// Constraint when identityPickerView is hidden.
@property(nonatomic, strong) NSLayoutConstraint* noIdentityConstraint;
// Constraint when identityPickerView is visible.
@property(nonatomic, strong) NSLayoutConstraint* withIdentityConstraint;
// Settings link controller.
@property(nonatomic, strong) LabelLinkController* settingsLinkController;
@end
@implementation UnifiedConsentViewController
@synthesize delegate = _delegate;
@synthesize identityPickerView = _identityPickerView;
@synthesize imageBackgroundViewHeightConstraint =
_imageBackgroundViewHeightConstraint;
@synthesize noIdentityConstraint = _noIdentityConstraint;
@synthesize openSettingsStringId = _openSettingsStringId;
@synthesize scrollView = _scrollView;
@synthesize settingsLinkController = _settingsLinkController;
@synthesize withIdentityConstraint = _withIdentityConstraint;
- (const std::vector<int>&)consentStringIds {
return _consentStringIds;
}
- (void)updateIdentityPickerViewWithUserFullName:(NSString*)fullName
email:(NSString*)email {
DCHECK(email);
self.identityPickerView.hidden = NO;
self.noIdentityConstraint.active = NO;
self.withIdentityConstraint.active = YES;
[self.identityPickerView setIdentityName:fullName email:email];
}
- (void)updateIdentityPickerViewWithAvatar:(UIImage*)avatar {
[self.identityPickerView setIdentityAvatar:avatar];
}
- (void)hideIdentityPickerView {
self.identityPickerView.hidden = YES;
self.withIdentityConstraint.active = NO;
self.noIdentityConstraint.active = YES;
}
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Main scroll view.
self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
if (@available(iOS 11, *)) {
// The observed behavior was buggy. When the view appears on the screen,
// the scrollvie was not scrolled all the way to the top. Adjusting the
// safe area manually fixes the issue.
self.scrollView.contentInsetAdjustmentBehavior =
UIScrollViewContentInsetAdjustmentNever;
}
[self.view addSubview:self.scrollView];
// Scroll view container.
UIView* container = [[UIView alloc] initWithFrame:CGRectZero];
container.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:container];
// View used to draw the background color of the header image, in the non-safe
// areas (like the status bar).
UIView* imageBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
imageBackgroundView.translatesAutoresizingMaskIntoConstraints = NO;
imageBackgroundView.backgroundColor = UIColorFromRGB(kHeaderBackgroundColor);
[container addSubview:imageBackgroundView];
// Header image.
UIImageView* headerImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
headerImageView.translatesAutoresizingMaskIntoConstraints = NO;
// TODO(crbug.com/827072): Needs the real image.
headerImageView.backgroundColor = [UIColor redColor];
[container addSubview:headerImageView];
// Title.
UILabel* title = [[UILabel alloc] initWithFrame:CGRectZero];
title.translatesAutoresizingMaskIntoConstraints = NO;
title.text = l10n_util::GetNSString(IDS_IOS_ACCOUNT_UNIFIED_CONSENT_TITLE);
_consentStringIds.push_back(IDS_IOS_ACCOUNT_UNIFIED_CONSENT_TITLE);
title.textColor = [UIColor colorWithWhite:0 alpha:kTitleColorAlpha];
title.font = [UIFont systemFontOfSize:kTitleFontSize];
[container addSubview:title];
// Identity picker view.
self.identityPickerView =
[[IdentityPickerView alloc] initWithFrame:CGRectZero];
self.identityPickerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.identityPickerView addTarget:self
action:@selector(identityPickerAction:)
forControlEvents:UIControlEventTouchUpInside];
[container addSubview:self.identityPickerView];
// Sync bookmark label.
UILabel* syncBookmarkLabel =
[self addLabelWithStringId:IDS_IOS_ACCOUNT_UNIFIED_CONSENT_SYNC_DATA
withIcon:nil
iconVerticallyCentered:YES
parentView:container];
// Get more personalized label.
UILabel* morePersonalizedLabel =
[self addLabelWithStringId:IDS_IOS_ACCOUNT_UNIFIED_CONSENT_PERSONALIZED
withIcon:nil
iconVerticallyCentered:YES
parentView:container];
// Powerful Google label.
UILabel* powerfulGoogleLabel =
[self addLabelWithStringId:IDS_IOS_ACCOUNT_UNIFIED_CONSENT_BETTER_BROWSER
withIcon:nil
iconVerticallyCentered:YES
parentView:container];
// Separator.
UIView* separator = [[UIView alloc] initWithFrame:CGRectZero];
separator.translatesAutoresizingMaskIntoConstraints = NO;
separator.backgroundColor =
[UIColor colorWithWhite:0 alpha:kSeparatorColorAlpha];
[container addSubview:separator];
// Customize label.
UILabel* customizeLabel =
[self addLabelWithStringId:IDS_IOS_ACCOUNT_UNIFIED_CONSENT_SETTINGS
withIcon:nil
iconVerticallyCentered:NO
parentView:container];
self.openSettingsStringId = IDS_IOS_ACCOUNT_UNIFIED_CONSENT_SETTINGS;
[self addSettingsLinkURLWithLabel:customizeLabel];
// Layouts
NSDictionary* views = @{
@"header" : headerImageView,
@"title" : title,
@"picker" : self.identityPickerView,
@"container" : container,
@"scrollview" : self.scrollView,
@"separator" : separator,
@"synctext" : syncBookmarkLabel,
@"personalizedtext" : morePersonalizedLabel,
@"powerfultext" : powerfulGoogleLabel,
@"customizetext" : customizeLabel,
};
NSDictionary* metrics = @{
@"TitlePickerMargin" : @(KTitlePickerMargin),
@"HMargin" : @(kHorizontalMargin),
@"VBetweenText" : @(kVerticalBetweenTextMargin),
@"LeftSeparMrg" : @(kLeftSeparatorMargin),
@"VTextMargin" : @(kVerticalTextMargin),
@"SeparatorHeight" : @(kSeparatorHeight),
@"HeaderHeight" : @(kHeaderImageHeight),
};
NSArray* constraints = @[
// Horitizontal constraints.
@"H:|[scrollview]|",
@"H:|[container]|",
@"H:|[header]|",
@"H:|-(HMargin)-[title]-(HMargin)-|",
@"H:|-(HMargin)-[picker]-(HMargin)-|",
@"H:|-(LeftSeparMrg)-[separator]-(HMargin)-|",
// Vertical constraints.
@"V:|[scrollview]|",
@"V:|[container]|",
@"V:|[header][title]-(TitlePickerMargin)-[picker]",
@"V:[synctext]-(VBetweenText)-[personalizedtext]",
@"V:[personalizedtext]-(VBetweenText)-[powerfultext]",
@"V:[powerfultext]-(VTextMargin)-[separator]",
@"V:[separator]-(VBetweenText)-[customizetext]-(VTextMargin)-|",
// Size constraints.
@"V:[header(HeaderHeight)]",
@"V:[separator(SeparatorHeight)]",
];
ApplyVisualConstraintsWithMetrics(constraints, views, metrics);
// Adding constraints with or without identity.
self.noIdentityConstraint =
[syncBookmarkLabel.topAnchor constraintEqualToAnchor:title.bottomAnchor
constant:kVerticalTextMargin];
self.withIdentityConstraint = [syncBookmarkLabel.topAnchor
constraintEqualToAnchor:self.identityPickerView.bottomAnchor
constant:kVerticalTextMargin];
// Adding constraints for the container.
id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self.view);
[container.widthAnchor constraintEqualToAnchor:safeArea.widthAnchor].active =
YES;
// Adding constraints for |imageBackgroundView|.
AddSameCenterXConstraint(self.view, imageBackgroundView);
[imageBackgroundView.widthAnchor
constraintEqualToAnchor:self.view.widthAnchor]
.active = YES;
self.imageBackgroundViewHeightConstraint = [imageBackgroundView.heightAnchor
constraintEqualToAnchor:headerImageView.heightAnchor];
self.imageBackgroundViewHeightConstraint.active = YES;
[imageBackgroundView.bottomAnchor
constraintEqualToAnchor:headerImageView.bottomAnchor]
.active = YES;
// Update UI.
[self hideIdentityPickerView];
[self updateScrollViewAndImageBackgroundView];
}
- (void)viewSafeAreaInsetsDidChange {
// Updates the scroll view content inset, used by iOS 11 or later.
[super viewSafeAreaInsetsDidChange];
[self updateScrollViewAndImageBackgroundView];
}
// Updates the scroll view content inset, used by pre iOS 11.
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator
animateAlongsideTransition:^(
id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self updateScrollViewAndImageBackgroundView];
}
completion:nil];
}
#pragma mark - UI actions
- (void)identityPickerAction:(id)sender {
[self.delegate unifiedConsentViewControllerDidTapIdentityPickerView:self];
}
#pragma mark - Private
// Adds an icon and a label, into |parentView|, next to each other with the
// constraints between the icon and the label.
// If |iconVerticallyCentered| is true, the |icon| is vertically centered with
// the label. Otherwise, it the |icon| is vertically aligned with the first line
// of the label.
- (UILabel*)addLabelWithStringId:(int)stringId
withIcon:(UIImage*)icon
iconVerticallyCentered:(BOOL)iconVerticallyCentered
parentView:(UIView*)parentView {
UILabel* label = [[UILabel alloc] initWithFrame:CGRectZero];
label.translatesAutoresizingMaskIntoConstraints = NO;
label.font = [UIFont systemFontOfSize:kTextFontSize];
label.text = l10n_util::GetNSString(stringId);
_consentStringIds.push_back(stringId);
label.textColor = [UIColor colorWithWhite:0 alpha:kTextColorAlpha];
label.numberOfLines = 0;
[parentView addSubview:label];
UIImageView* image = [[UIImageView alloc] initWithFrame:CGRectZero];
if (icon) {
image.image = icon;
} else {
// TODO(crbug.com/827072): Remove this once the bug will be fixed, and add
// DCHECK(icon);
image.backgroundColor = [UIColor redColor];
}
image.translatesAutoresizingMaskIntoConstraints = NO;
[parentView addSubview:image];
NSDictionary* views = NSDictionaryOfVariableBindings(label, image);
NSArray* constraints = @[
@"H:|-(HMargin)-[image]-(IconTextMargin)-[label]-(HMargin)-|",
@"H:[image(IconSize)]",
@"V:[image(IconSize)]",
];
NSDictionary* metrics = @{
@"HMargin" : @(kHorizontalMargin),
@"IconSize" : @(kIconSize),
@"IconTextMargin" : @(kIconTextMargin),
};
ApplyVisualConstraintsWithMetrics(constraints, views, metrics);
if (iconVerticallyCentered) {
AddSameCenterYConstraint(label, image);
} else {
// |icon| should be aligned to first line of |label|. This has to be done
// according to the middle of the cap height of the font.
// The cap height position is based on the ascender.
UIFont* font = label.font;
[image.centerYAnchor
constraintEqualToAnchor:label.topAnchor
constant:font.ascender - font.capHeight +
font.capHeight / 2.]
.active = YES;
}
return label;
}
// Adds settings link in the customize label.
- (void)addSettingsLinkURLWithLabel:(UILabel*)label {
DCHECK(!self.settingsLinkController);
GURL URL = google_util::AppendGoogleLocaleParam(
GURL(kSettingsSyncURL), GetApplicationContext()->GetApplicationLocale());
NSRange range;
NSString* text = label.text;
label.text = ParseStringWithLink(text, &range);
DCHECK(range.location != NSNotFound && range.length != 0);
__weak UnifiedConsentViewController* weakSelf = self;
self.settingsLinkController =
[[LabelLinkController alloc] initWithLabel:label
action:^(const GURL& URL) {
[weakSelf openSettings];
}];
[self.settingsLinkController
setLinkColor:[[MDCPalette cr_bluePalette] tint500]];
[self.settingsLinkController addLinkWithRange:range url:URL];
}
// Updates constraints and content insets for the |scrollView| and
// |imageBackgroundView| related to non-safe area.
- (void)updateScrollViewAndImageBackgroundView {
if (@available(iOS 11, *)) {
self.scrollView.contentInset = self.view.safeAreaInsets;
self.imageBackgroundViewHeightConstraint.constant =
self.view.safeAreaInsets.top;
} else {
CGFloat statusBarHeight =
[UIApplication sharedApplication].isStatusBarHidden ? 0.
: StatusBarHeight();
self.scrollView.contentInset = UIEdgeInsetsMake(statusBarHeight, 0, 0, 0);
self.imageBackgroundViewHeightConstraint.constant = statusBarHeight;
}
}
// Notifies |delegate| that the user tapped on "Settings" link.
- (void)openSettings {
[self.delegate unifiedConsentViewControllerDidTapSettingsLink:self];
}
@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