OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/passwords/password_generation_prompt_view.h" |
| 6 |
| 7 #include <memory> |
| 8 |
| 9 #include "base/ios/weak_nsobject.h" |
| 10 #include "base/mac/scoped_nsobject.h" |
| 11 #include "base/strings/sys_string_conversions.h" |
| 12 #include "components/strings/grit/components_strings.h" |
| 13 #import "ios/chrome/browser/passwords/password_generation_prompt_delegate.h" |
| 14 #import "ios/chrome/browser/ui/rtl_geometry.h" |
| 15 #include "ios/chrome/browser/ui/ui_util.h" |
| 16 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 17 #include "ios/chrome/common/string_util.h" |
| 18 #include "ios/chrome/grit/ios_strings.h" |
| 19 #include "ios/chrome/grit/ios_theme_resources.h" |
| 20 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate
rialButtons.h" |
| 21 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF
ontLoader.h" |
| 22 #include "ui/base/l10n/l10n_util.h" |
| 23 #include "ui/base/resource/resource_bundle.h" |
| 24 |
| 25 namespace { |
| 26 |
| 27 // Material Design Component constraints. |
| 28 const CGFloat kMDCPadding = 24; |
| 29 |
| 30 // Horizontal and vertical padding for the entire view. |
| 31 const CGFloat kPadding = 8.0f; |
| 32 |
| 33 // Colors for primary and secondary user interactions. |
| 34 const int kPrimaryActionColor = 0x5595FE; |
| 35 |
| 36 // Constants for the password label. |
| 37 const int kPasswordLabelFontSize = 16; |
| 38 const int kPasswordLabelFontColor = 0x787878; |
| 39 const CGFloat kPasswordLabelVerticalPadding = 5.0f; |
| 40 |
| 41 // Constants for the title label. |
| 42 const int kTitleLabelFontSize = 16; |
| 43 const int kTitleLabelFontColor = 0x333333; |
| 44 const CGFloat kTitleLabelVerticalPadding = 5.0f; |
| 45 |
| 46 // Constants for the description label. |
| 47 const int kDescriptionLabelFontSize = 14; |
| 48 const int kDescriptionLabelFontColor = 0x787878; |
| 49 const int kDescriptionLabelLineSpacing = 8; |
| 50 const CGFloat kDescriptionLabelTopPadding = 10.0f; |
| 51 |
| 52 } // namespace |
| 53 |
| 54 // A view that prompts the user with a password generated by Chrome and explains |
| 55 // what that means. The user can accept the password, cancel password |
| 56 // generation, or click a link to view all their saved passwords. |
| 57 @interface PasswordGenerationPromptView : UIView<UITextViewDelegate> |
| 58 |
| 59 // Initializes a PasswordGenerationPromptView that shows the specified |
| 60 // |password| and delegates user interaction events to |delegate|. |
| 61 - (instancetype)initWithPassword:(NSString*)password |
| 62 delegate:(id<PasswordGenerationPromptDelegate>)delegate |
| 63 NS_DESIGNATED_INITIALIZER; |
| 64 |
| 65 - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; |
| 66 |
| 67 - (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE; |
| 68 |
| 69 // Configure the view, adding subviews and constraints. |
| 70 - (void)configure; |
| 71 |
| 72 // Returns an autoreleased label for the title of the view. |
| 73 - (UILabel*)titleLabel; |
| 74 |
| 75 // Returns an autoreleased label to propose a generated password. |
| 76 - (UILabel*)passwordLabel:(NSString*)password; |
| 77 |
| 78 // Returns an autoreleased text view to explain password generation with a link. |
| 79 - (UITextView*)description; |
| 80 |
| 81 // Returns an autoreleased view that shows the lock icon. |
| 82 - (UIImageView*)keyIconView; |
| 83 |
| 84 @end |
| 85 |
| 86 @implementation PasswordGenerationPromptView { |
| 87 base::scoped_nsobject<NSString> _password; |
| 88 base::WeakNSProtocol<id<PasswordGenerationPromptDelegate>> _delegate; |
| 89 base::scoped_nsobject<NSURL> _URL; |
| 90 base::scoped_nsobject<UILabel> _title; |
| 91 } |
| 92 |
| 93 - (instancetype)initWithPassword:(NSString*)password |
| 94 delegate: |
| 95 (id<PasswordGenerationPromptDelegate>)delegate { |
| 96 self = [super initWithFrame:CGRectZero]; |
| 97 if (self) { |
| 98 _URL.reset( |
| 99 [[NSURL URLWithString:@"chromeinternal://showpasswords"] retain]); |
| 100 _delegate.reset(delegate); |
| 101 _password.reset([password copy]); |
| 102 } |
| 103 return self; |
| 104 } |
| 105 |
| 106 - (void)configure { |
| 107 UIView* headerView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
| 108 UIImageView* icon = [self keyIconView]; |
| 109 UILabel* title = [self titleLabel]; |
| 110 UILabel* password = [self passwordLabel:_password]; |
| 111 UITextView* description = [self description]; |
| 112 |
| 113 _title.reset([title retain]); |
| 114 |
| 115 [headerView addSubview:icon]; |
| 116 [headerView addSubview:title]; |
| 117 [headerView addSubview:password]; |
| 118 [self addSubview:headerView]; |
| 119 [self addSubview:description]; |
| 120 |
| 121 // ----------------------------------------------- |
| 122 // | | |
| 123 // | (lock) Use password generated by Chrome? | |
| 124 // | Fsf6s88fssdf | |
| 125 // | | |
| 126 // | blah blah blah description blah blah | |
| 127 // | blah blah [link to passwords] blah. | |
| 128 // | | |
| 129 // ----------------------------------------------- |
| 130 |
| 131 [headerView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 132 [icon setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 133 [title setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 134 [password setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 135 [description setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 136 |
| 137 NSArray* constraints = @[ |
| 138 @"H:|[keyIcon]-[title]|", @"V:|[keyIcon]", |
| 139 @"V:|-(padding)-[header]-(descriptionPadding)-[description]|", |
| 140 @"H:|-(padding)-[header]-(padding)-|", |
| 141 @"H:|-(padding)-[description]-(padding)-|" |
| 142 ]; |
| 143 |
| 144 NSDictionary* viewsDictionary = @{ |
| 145 @"keyIcon" : icon, |
| 146 @"title" : title, |
| 147 @"passwd" : password, |
| 148 @"header" : headerView, |
| 149 @"description" : description |
| 150 }; |
| 151 |
| 152 NSDictionary* metrics = @{ |
| 153 @"padding" : @(kPadding), |
| 154 @"passwordPadding" : @(kPasswordLabelVerticalPadding), |
| 155 @"descriptionPadding" : @(kDescriptionLabelTopPadding), |
| 156 @"titlePadding" : @(kTitleLabelVerticalPadding) |
| 157 }; |
| 158 |
| 159 ApplyVisualConstraintsWithMetricsAndOptions( |
| 160 constraints, viewsDictionary, metrics, LayoutOptionForRTLSupport()); |
| 161 |
| 162 [headerView |
| 163 addConstraints: |
| 164 [NSLayoutConstraint |
| 165 constraintsWithVisualFormat: |
| 166 @"V:|-(titlePadding)-[title]-(passwordPadding)-[passwd]|" |
| 167 options:NSLayoutFormatAlignAllLeading |
| 168 metrics:metrics |
| 169 views:viewsDictionary]]; |
| 170 |
| 171 [title setContentHuggingPriority:UILayoutPriorityRequired |
| 172 forAxis:UILayoutConstraintAxisVertical]; |
| 173 [icon setContentHuggingPriority:UILayoutPriorityRequired |
| 174 forAxis:UILayoutConstraintAxisHorizontal]; |
| 175 [headerView setContentHuggingPriority:UILayoutPriorityRequired |
| 176 forAxis:UILayoutConstraintAxisVertical]; |
| 177 } |
| 178 |
| 179 - (void)layoutSubviews { |
| 180 [super layoutSubviews]; |
| 181 // Make sure the title is spread on multiple lines if needed. |
| 182 [_title setPreferredMaxLayoutWidth:[_title frame].size.width]; |
| 183 } |
| 184 |
| 185 - (UILabel*)titleLabel { |
| 186 NSMutableDictionary* attrsDictionary = [NSMutableDictionary |
| 187 dictionaryWithObject:[[MDFRobotoFontLoader sharedInstance] |
| 188 mediumFontOfSize:kTitleLabelFontSize] |
| 189 forKey:NSFontAttributeName]; |
| 190 [attrsDictionary setObject:UIColorFromRGB(kTitleLabelFontColor) |
| 191 forKey:NSForegroundColorAttributeName]; |
| 192 |
| 193 NSMutableAttributedString* string = [[[NSMutableAttributedString alloc] |
| 194 initWithString:l10n_util::GetNSString( |
| 195 IDS_IOS_GENERATED_PASSWORD_PROMPT_TITLE) |
| 196 attributes:attrsDictionary] autorelease]; |
| 197 |
| 198 base::scoped_nsobject<UILabel> titleLabel([[UILabel alloc] init]); |
| 199 [titleLabel setAttributedText:string]; |
| 200 [titleLabel setNumberOfLines:0]; |
| 201 [titleLabel sizeToFit]; |
| 202 return titleLabel.autorelease(); |
| 203 } |
| 204 |
| 205 - (UILabel*)passwordLabel:(NSString*)password { |
| 206 base::scoped_nsobject<UILabel> passwordLabel([[UILabel alloc] init]); |
| 207 [passwordLabel setText:password]; |
| 208 [passwordLabel setTextColor:UIColorFromRGB(kPasswordLabelFontColor)]; |
| 209 [passwordLabel setFont:[[MDFRobotoFontLoader sharedInstance] |
| 210 regularFontOfSize:kPasswordLabelFontSize]]; |
| 211 [passwordLabel setNumberOfLines:1]; |
| 212 [passwordLabel sizeToFit]; |
| 213 return passwordLabel.autorelease(); |
| 214 } |
| 215 |
| 216 - (UITextView*)description { |
| 217 NSRange linkRange; |
| 218 NSString* description = ParseStringWithLink( |
| 219 l10n_util::GetNSString(IDS_IOS_GENERATED_PASSWORD_PROMPT_DESCRIPTION), |
| 220 &linkRange); |
| 221 |
| 222 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( |
| 223 [[NSMutableParagraphStyle alloc] init]); |
| 224 [paragraphStyle setLineSpacing:kDescriptionLabelLineSpacing]; |
| 225 |
| 226 NSDictionary* attributeDictionary = |
| 227 [NSDictionary dictionaryWithObjectsAndKeys: |
| 228 UIColorFromRGB(kDescriptionLabelFontColor), |
| 229 NSForegroundColorAttributeName, paragraphStyle.get(), |
| 230 NSParagraphStyleAttributeName, |
| 231 [[MDFRobotoFontLoader sharedInstance] |
| 232 regularFontOfSize:kDescriptionLabelFontSize], |
| 233 NSFontAttributeName, nil]; |
| 234 |
| 235 base::scoped_nsobject<NSMutableAttributedString> attributedString( |
| 236 [[NSMutableAttributedString alloc] initWithString:description |
| 237 attributes:attributeDictionary]); |
| 238 |
| 239 UITextView* descriptionView = |
| 240 [[[UITextView alloc] initWithFrame:CGRectZero textContainer:nil] |
| 241 autorelease]; |
| 242 descriptionView.scrollEnabled = NO; |
| 243 descriptionView.selectable = YES; |
| 244 descriptionView.editable = NO; |
| 245 descriptionView.delegate = self; |
| 246 descriptionView.userInteractionEnabled = YES; |
| 247 |
| 248 descriptionView.linkTextAttributes = |
| 249 [NSDictionary dictionaryWithObject:UIColorFromRGB(kPrimaryActionColor) |
| 250 forKey:NSForegroundColorAttributeName]; |
| 251 |
| 252 [attributedString addAttribute:NSLinkAttributeName |
| 253 value:_URL |
| 254 range:linkRange]; |
| 255 descriptionView.attributedText = attributedString; |
| 256 return descriptionView; |
| 257 } |
| 258 |
| 259 - (UIImageView*)keyIconView { |
| 260 UIImage* keyIcon = ui::ResourceBundle::GetSharedInstance() |
| 261 .GetImageNamed(IDR_IOS_INFOBAR_AUTOLOGIN) |
| 262 .ToUIImage(); |
| 263 UIImageView* keyIconView = |
| 264 [[[UIImageView alloc] initWithImage:keyIcon] autorelease]; |
| 265 [keyIconView setFrame:{CGPointZero, keyIcon.size}]; |
| 266 return keyIconView; |
| 267 } |
| 268 |
| 269 #pragma mark - UITextViewDelegate |
| 270 |
| 271 - (BOOL)textView:(UITextView*)textView |
| 272 shouldInteractWithURL:(NSURL*)URL |
| 273 inRange:(NSRange)characterRange |
| 274 interaction:(UITextItemInteraction)interaction { |
| 275 DCHECK([URL isEqual:_URL.get()]); |
| 276 [_delegate showSavedPasswords:self]; |
| 277 return NO; |
| 278 } |
| 279 |
| 280 @end |
| 281 |
| 282 #pragma mark - Classes emulating MDCDialog |
| 283 |
| 284 @interface PasswordGenerationPromptDialog () { |
| 285 base::WeakNSObject<UIViewController> _viewController; |
| 286 base::WeakNSProtocol<id<PasswordGenerationPromptDelegate>> _weakDelegate; |
| 287 } |
| 288 |
| 289 // Dismiss the dialog. |
| 290 - (void)dismiss; |
| 291 // Callback called when the user accept to use the password. |
| 292 - (void)acceptPassword; |
| 293 // Creates the view containing the buttons. |
| 294 - (UIView*)createButtons; |
| 295 |
| 296 @end |
| 297 |
| 298 @implementation PasswordGenerationPromptDialog |
| 299 |
| 300 - (instancetype)initWithDelegate:(id<PasswordGenerationPromptDelegate>)delegate |
| 301 viewController:(UIViewController*)viewController { |
| 302 self = [super initWithFrame:CGRectZero]; |
| 303 if (self) { |
| 304 _viewController.reset(viewController); |
| 305 _weakDelegate.reset(delegate); |
| 306 } |
| 307 return self; |
| 308 } |
| 309 |
| 310 - (void)dismiss { |
| 311 [_viewController dismissViewControllerAnimated:NO completion:nil]; |
| 312 } |
| 313 |
| 314 - (void)acceptPassword { |
| 315 [_weakDelegate acceptPasswordGeneration:nil]; |
| 316 [self dismiss]; |
| 317 } |
| 318 |
| 319 // Creates the view containing the buttons. |
| 320 - (UIView*)createButtons { |
| 321 UIView* view = [[[UIView alloc] initWithFrame:CGRectZero] autorelease]; |
| 322 |
| 323 NSString* cancelTitle = l10n_util::GetNSString(IDS_CANCEL); |
| 324 MDCFlatButton* cancelButton = [[[MDCFlatButton alloc] init] autorelease]; |
| 325 [cancelButton setTitle:cancelTitle forState:UIControlStateNormal]; |
| 326 [cancelButton sizeToFit]; |
| 327 [cancelButton setCustomTitleColor:[UIColor blackColor]]; |
| 328 [cancelButton addTarget:self |
| 329 action:@selector(dismiss) |
| 330 forControlEvents:UIControlEventTouchUpInside]; |
| 331 |
| 332 NSString* acceptTitle = |
| 333 l10n_util::GetNSString(IDS_IOS_GENERATED_PASSWORD_ACCEPT); |
| 334 MDCFlatButton* OKButton = [[[MDCFlatButton alloc] init] autorelease]; |
| 335 [OKButton setTitle:acceptTitle forState:UIControlStateNormal]; |
| 336 [OKButton sizeToFit]; |
| 337 [OKButton setCustomTitleColor:[UIColor blackColor]]; |
| 338 [OKButton addTarget:self |
| 339 action:@selector(acceptPassword) |
| 340 forControlEvents:UIControlEventTouchUpInside]; |
| 341 |
| 342 [cancelButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 343 [OKButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 344 |
| 345 [view addSubview:cancelButton]; |
| 346 [view addSubview:OKButton]; |
| 347 |
| 348 NSDictionary* views = @{ @"cancel" : cancelButton, @"ok" : OKButton }; |
| 349 [view addConstraints:[NSLayoutConstraint |
| 350 constraintsWithVisualFormat:@"V:|-[cancel]-|" |
| 351 options:0 |
| 352 metrics:nil |
| 353 views:views]]; |
| 354 [view addConstraints:[NSLayoutConstraint |
| 355 constraintsWithVisualFormat:@"H:[cancel]-[ok]-|" |
| 356 options:NSLayoutFormatAlignAllTop |
| 357 metrics:nil |
| 358 views:views]]; |
| 359 |
| 360 return view; |
| 361 } |
| 362 |
| 363 // Creates the view containing the password text and the buttons. |
| 364 - (void)configureGlobalViewWithPassword:(NSString*)password { |
| 365 PasswordGenerationPromptView* passwordContentView = |
| 366 [[[PasswordGenerationPromptView alloc] initWithPassword:password |
| 367 delegate:_weakDelegate] |
| 368 autorelease]; |
| 369 |
| 370 [passwordContentView configure]; |
| 371 |
| 372 [passwordContentView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 373 |
| 374 UIView* buttons = [self createButtons]; |
| 375 [buttons setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 376 |
| 377 [self addSubview:passwordContentView]; |
| 378 [self addSubview:buttons]; |
| 379 |
| 380 NSDictionary* views = |
| 381 @{ @"view" : passwordContentView, |
| 382 @"buttons" : buttons }; |
| 383 NSDictionary* metrics = @{ @"MDCPadding" : @(kMDCPadding) }; |
| 384 [self addConstraints:[NSLayoutConstraint |
| 385 constraintsWithVisualFormat: |
| 386 @"V:|[view]-(MDCPadding)-[buttons]-|" |
| 387 options:NSLayoutAttributeTrailing |
| 388 metrics:metrics |
| 389 views:views]]; |
| 390 [self addConstraints:[NSLayoutConstraint |
| 391 constraintsWithVisualFormat:@"H:|[view]|" |
| 392 options:0 |
| 393 metrics:nil |
| 394 views:views]]; |
| 395 } |
| 396 |
| 397 @end |
OLD | NEW |