Index: ios/chrome/browser/passwords/password_generation_prompt_view.mm |
diff --git a/ios/chrome/browser/passwords/password_generation_prompt_view.mm b/ios/chrome/browser/passwords/password_generation_prompt_view.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bc543260ea9e87ea44d09b0b7273da5fb7487d50 |
--- /dev/null |
+++ b/ios/chrome/browser/passwords/password_generation_prompt_view.mm |
@@ -0,0 +1,397 @@ |
+// Copyright 2014 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/passwords/password_generation_prompt_view.h" |
+ |
+#include <memory> |
+ |
+#include "base/ios/weak_nsobject.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "components/strings/grit/components_strings.h" |
+#import "ios/chrome/browser/passwords/password_generation_prompt_delegate.h" |
+#import "ios/chrome/browser/ui/rtl_geometry.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#include "ios/chrome/common/string_util.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/chrome/grit/ios_theme_resources.h" |
+#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h" |
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/resource/resource_bundle.h" |
+ |
+namespace { |
+ |
+// Material Design Component constraints. |
+const CGFloat kMDCPadding = 24; |
+ |
+// Horizontal and vertical padding for the entire view. |
+const CGFloat kPadding = 8.0f; |
+ |
+// Colors for primary and secondary user interactions. |
+const int kPrimaryActionColor = 0x5595FE; |
+ |
+// Constants for the password label. |
+const int kPasswordLabelFontSize = 16; |
+const int kPasswordLabelFontColor = 0x787878; |
+const CGFloat kPasswordLabelVerticalPadding = 5.0f; |
+ |
+// Constants for the title label. |
+const int kTitleLabelFontSize = 16; |
+const int kTitleLabelFontColor = 0x333333; |
+const CGFloat kTitleLabelVerticalPadding = 5.0f; |
+ |
+// Constants for the description label. |
+const int kDescriptionLabelFontSize = 14; |
+const int kDescriptionLabelFontColor = 0x787878; |
+const int kDescriptionLabelLineSpacing = 8; |
+const CGFloat kDescriptionLabelTopPadding = 10.0f; |
+ |
+} // namespace |
+ |
+// A view that prompts the user with a password generated by Chrome and explains |
+// what that means. The user can accept the password, cancel password |
+// generation, or click a link to view all their saved passwords. |
+@interface PasswordGenerationPromptView : UIView<UITextViewDelegate> |
+ |
+// Initializes a PasswordGenerationPromptView that shows the specified |
+// |password| and delegates user interaction events to |delegate|. |
+- (instancetype)initWithPassword:(NSString*)password |
+ delegate:(id<PasswordGenerationPromptDelegate>)delegate |
+ NS_DESIGNATED_INITIALIZER; |
+ |
+- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; |
+ |
+- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE; |
+ |
+// Configure the view, adding subviews and constraints. |
+- (void)configure; |
+ |
+// Returns an autoreleased label for the title of the view. |
+- (UILabel*)titleLabel; |
+ |
+// Returns an autoreleased label to propose a generated password. |
+- (UILabel*)passwordLabel:(NSString*)password; |
+ |
+// Returns an autoreleased text view to explain password generation with a link. |
+- (UITextView*)description; |
+ |
+// Returns an autoreleased view that shows the lock icon. |
+- (UIImageView*)keyIconView; |
+ |
+@end |
+ |
+@implementation PasswordGenerationPromptView { |
+ base::scoped_nsobject<NSString> _password; |
+ base::WeakNSProtocol<id<PasswordGenerationPromptDelegate>> _delegate; |
+ base::scoped_nsobject<NSURL> _URL; |
+ base::scoped_nsobject<UILabel> _title; |
+} |
+ |
+- (instancetype)initWithPassword:(NSString*)password |
+ delegate: |
+ (id<PasswordGenerationPromptDelegate>)delegate { |
+ self = [super initWithFrame:CGRectZero]; |
+ if (self) { |
+ _URL.reset( |
+ [[NSURL URLWithString:@"chromeinternal://showpasswords"] retain]); |
+ _delegate.reset(delegate); |
+ _password.reset([password copy]); |
+ } |
+ return self; |
+} |
+ |
+- (void)configure { |
+ UIView* headerView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
+ UIImageView* icon = [self keyIconView]; |
+ UILabel* title = [self titleLabel]; |
+ UILabel* password = [self passwordLabel:_password]; |
+ UITextView* description = [self description]; |
+ |
+ _title.reset([title retain]); |
+ |
+ [headerView addSubview:icon]; |
+ [headerView addSubview:title]; |
+ [headerView addSubview:password]; |
+ [self addSubview:headerView]; |
+ [self addSubview:description]; |
+ |
+ // ----------------------------------------------- |
+ // | | |
+ // | (lock) Use password generated by Chrome? | |
+ // | Fsf6s88fssdf | |
+ // | | |
+ // | blah blah blah description blah blah | |
+ // | blah blah [link to passwords] blah. | |
+ // | | |
+ // ----------------------------------------------- |
+ |
+ [headerView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [icon setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [title setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [password setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [description setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ |
+ NSArray* constraints = @[ |
+ @"H:|[keyIcon]-[title]|", @"V:|[keyIcon]", |
+ @"V:|-(padding)-[header]-(descriptionPadding)-[description]|", |
+ @"H:|-(padding)-[header]-(padding)-|", |
+ @"H:|-(padding)-[description]-(padding)-|" |
+ ]; |
+ |
+ NSDictionary* viewsDictionary = @{ |
+ @"keyIcon" : icon, |
+ @"title" : title, |
+ @"passwd" : password, |
+ @"header" : headerView, |
+ @"description" : description |
+ }; |
+ |
+ NSDictionary* metrics = @{ |
+ @"padding" : @(kPadding), |
+ @"passwordPadding" : @(kPasswordLabelVerticalPadding), |
+ @"descriptionPadding" : @(kDescriptionLabelTopPadding), |
+ @"titlePadding" : @(kTitleLabelVerticalPadding) |
+ }; |
+ |
+ ApplyVisualConstraintsWithMetricsAndOptions( |
+ constraints, viewsDictionary, metrics, LayoutOptionForRTLSupport()); |
+ |
+ [headerView |
+ addConstraints: |
+ [NSLayoutConstraint |
+ constraintsWithVisualFormat: |
+ @"V:|-(titlePadding)-[title]-(passwordPadding)-[passwd]|" |
+ options:NSLayoutFormatAlignAllLeading |
+ metrics:metrics |
+ views:viewsDictionary]]; |
+ |
+ [title setContentHuggingPriority:UILayoutPriorityRequired |
+ forAxis:UILayoutConstraintAxisVertical]; |
+ [icon setContentHuggingPriority:UILayoutPriorityRequired |
+ forAxis:UILayoutConstraintAxisHorizontal]; |
+ [headerView setContentHuggingPriority:UILayoutPriorityRequired |
+ forAxis:UILayoutConstraintAxisVertical]; |
+} |
+ |
+- (void)layoutSubviews { |
+ [super layoutSubviews]; |
+ // Make sure the title is spread on multiple lines if needed. |
+ [_title setPreferredMaxLayoutWidth:[_title frame].size.width]; |
+} |
+ |
+- (UILabel*)titleLabel { |
+ NSMutableDictionary* attrsDictionary = [NSMutableDictionary |
+ dictionaryWithObject:[[MDFRobotoFontLoader sharedInstance] |
+ mediumFontOfSize:kTitleLabelFontSize] |
+ forKey:NSFontAttributeName]; |
+ [attrsDictionary setObject:UIColorFromRGB(kTitleLabelFontColor) |
+ forKey:NSForegroundColorAttributeName]; |
+ |
+ NSMutableAttributedString* string = [[[NSMutableAttributedString alloc] |
+ initWithString:l10n_util::GetNSString( |
+ IDS_IOS_GENERATED_PASSWORD_PROMPT_TITLE) |
+ attributes:attrsDictionary] autorelease]; |
+ |
+ base::scoped_nsobject<UILabel> titleLabel([[UILabel alloc] init]); |
+ [titleLabel setAttributedText:string]; |
+ [titleLabel setNumberOfLines:0]; |
+ [titleLabel sizeToFit]; |
+ return titleLabel.autorelease(); |
+} |
+ |
+- (UILabel*)passwordLabel:(NSString*)password { |
+ base::scoped_nsobject<UILabel> passwordLabel([[UILabel alloc] init]); |
+ [passwordLabel setText:password]; |
+ [passwordLabel setTextColor:UIColorFromRGB(kPasswordLabelFontColor)]; |
+ [passwordLabel setFont:[[MDFRobotoFontLoader sharedInstance] |
+ regularFontOfSize:kPasswordLabelFontSize]]; |
+ [passwordLabel setNumberOfLines:1]; |
+ [passwordLabel sizeToFit]; |
+ return passwordLabel.autorelease(); |
+} |
+ |
+- (UITextView*)description { |
+ NSRange linkRange; |
+ NSString* description = ParseStringWithLink( |
+ l10n_util::GetNSString(IDS_IOS_GENERATED_PASSWORD_PROMPT_DESCRIPTION), |
+ &linkRange); |
+ |
+ base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( |
+ [[NSMutableParagraphStyle alloc] init]); |
+ [paragraphStyle setLineSpacing:kDescriptionLabelLineSpacing]; |
+ |
+ NSDictionary* attributeDictionary = |
+ [NSDictionary dictionaryWithObjectsAndKeys: |
+ UIColorFromRGB(kDescriptionLabelFontColor), |
+ NSForegroundColorAttributeName, paragraphStyle.get(), |
+ NSParagraphStyleAttributeName, |
+ [[MDFRobotoFontLoader sharedInstance] |
+ regularFontOfSize:kDescriptionLabelFontSize], |
+ NSFontAttributeName, nil]; |
+ |
+ base::scoped_nsobject<NSMutableAttributedString> attributedString( |
+ [[NSMutableAttributedString alloc] initWithString:description |
+ attributes:attributeDictionary]); |
+ |
+ UITextView* descriptionView = |
+ [[[UITextView alloc] initWithFrame:CGRectZero textContainer:nil] |
+ autorelease]; |
+ descriptionView.scrollEnabled = NO; |
+ descriptionView.selectable = YES; |
+ descriptionView.editable = NO; |
+ descriptionView.delegate = self; |
+ descriptionView.userInteractionEnabled = YES; |
+ |
+ descriptionView.linkTextAttributes = |
+ [NSDictionary dictionaryWithObject:UIColorFromRGB(kPrimaryActionColor) |
+ forKey:NSForegroundColorAttributeName]; |
+ |
+ [attributedString addAttribute:NSLinkAttributeName |
+ value:_URL |
+ range:linkRange]; |
+ descriptionView.attributedText = attributedString; |
+ return descriptionView; |
+} |
+ |
+- (UIImageView*)keyIconView { |
+ UIImage* keyIcon = ui::ResourceBundle::GetSharedInstance() |
+ .GetImageNamed(IDR_IOS_INFOBAR_AUTOLOGIN) |
+ .ToUIImage(); |
+ UIImageView* keyIconView = |
+ [[[UIImageView alloc] initWithImage:keyIcon] autorelease]; |
+ [keyIconView setFrame:{CGPointZero, keyIcon.size}]; |
+ return keyIconView; |
+} |
+ |
+#pragma mark - UITextViewDelegate |
+ |
+- (BOOL)textView:(UITextView*)textView |
+ shouldInteractWithURL:(NSURL*)URL |
+ inRange:(NSRange)characterRange |
+ interaction:(UITextItemInteraction)interaction { |
+ DCHECK([URL isEqual:_URL.get()]); |
+ [_delegate showSavedPasswords:self]; |
+ return NO; |
+} |
+ |
+@end |
+ |
+#pragma mark - Classes emulating MDCDialog |
+ |
+@interface PasswordGenerationPromptDialog () { |
+ base::WeakNSObject<UIViewController> _viewController; |
+ base::WeakNSProtocol<id<PasswordGenerationPromptDelegate>> _weakDelegate; |
+} |
+ |
+// Dismiss the dialog. |
+- (void)dismiss; |
+// Callback called when the user accept to use the password. |
+- (void)acceptPassword; |
+// Creates the view containing the buttons. |
+- (UIView*)createButtons; |
+ |
+@end |
+ |
+@implementation PasswordGenerationPromptDialog |
+ |
+- (instancetype)initWithDelegate:(id<PasswordGenerationPromptDelegate>)delegate |
+ viewController:(UIViewController*)viewController { |
+ self = [super initWithFrame:CGRectZero]; |
+ if (self) { |
+ _viewController.reset(viewController); |
+ _weakDelegate.reset(delegate); |
+ } |
+ return self; |
+} |
+ |
+- (void)dismiss { |
+ [_viewController dismissViewControllerAnimated:NO completion:nil]; |
+} |
+ |
+- (void)acceptPassword { |
+ [_weakDelegate acceptPasswordGeneration:nil]; |
+ [self dismiss]; |
+} |
+ |
+// Creates the view containing the buttons. |
+- (UIView*)createButtons { |
+ UIView* view = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
+ |
+ NSString* cancelTitle = l10n_util::GetNSString(IDS_CANCEL); |
+ MDCFlatButton* cancelButton = [[[MDCFlatButton alloc] init] autorelease]; |
+ [cancelButton setTitle:cancelTitle forState:UIControlStateNormal]; |
+ [cancelButton sizeToFit]; |
+ [cancelButton setCustomTitleColor:[UIColor blackColor]]; |
+ [cancelButton addTarget:self |
+ action:@selector(dismiss) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ |
+ NSString* acceptTitle = |
+ l10n_util::GetNSString(IDS_IOS_GENERATED_PASSWORD_ACCEPT); |
+ MDCFlatButton* OKButton = [[[MDCFlatButton alloc] init] autorelease]; |
+ [OKButton setTitle:acceptTitle forState:UIControlStateNormal]; |
+ [OKButton sizeToFit]; |
+ [OKButton setCustomTitleColor:[UIColor blackColor]]; |
+ [OKButton addTarget:self |
+ action:@selector(acceptPassword) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ |
+ [cancelButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [OKButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ |
+ [view addSubview:cancelButton]; |
+ [view addSubview:OKButton]; |
+ |
+ NSDictionary* views = @{ @"cancel" : cancelButton, @"ok" : OKButton }; |
+ [view addConstraints:[NSLayoutConstraint |
+ constraintsWithVisualFormat:@"V:|-[cancel]-|" |
+ options:0 |
+ metrics:nil |
+ views:views]]; |
+ [view addConstraints:[NSLayoutConstraint |
+ constraintsWithVisualFormat:@"H:[cancel]-[ok]-|" |
+ options:NSLayoutFormatAlignAllTop |
+ metrics:nil |
+ views:views]]; |
+ |
+ return view; |
+} |
+ |
+// Creates the view containing the password text and the buttons. |
+- (void)configureGlobalViewWithPassword:(NSString*)password { |
+ PasswordGenerationPromptView* passwordContentView = |
+ [[[PasswordGenerationPromptView alloc] initWithPassword:password |
+ delegate:_weakDelegate] |
+ autorelease]; |
+ |
+ [passwordContentView configure]; |
+ |
+ [passwordContentView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ |
+ UIView* buttons = [self createButtons]; |
+ [buttons setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ |
+ [self addSubview:passwordContentView]; |
+ [self addSubview:buttons]; |
+ |
+ NSDictionary* views = |
+ @{ @"view" : passwordContentView, |
+ @"buttons" : buttons }; |
+ NSDictionary* metrics = @{ @"MDCPadding" : @(kMDCPadding) }; |
+ [self addConstraints:[NSLayoutConstraint |
+ constraintsWithVisualFormat: |
+ @"V:|[view]-(MDCPadding)-[buttons]-|" |
+ options:NSLayoutAttributeTrailing |
+ metrics:metrics |
+ views:views]]; |
+ [self addConstraints:[NSLayoutConstraint |
+ constraintsWithVisualFormat:@"H:|[view]|" |
+ options:0 |
+ metrics:nil |
+ views:views]]; |
+} |
+ |
+@end |