OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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/ui/tab_switcher/tab_switcher_panel_cell.h" |
| 6 |
| 7 #include "base/mac/scoped_nsobject.h" |
| 8 #import "ios/chrome/browser/tabs/tab.h" |
| 9 #import "ios/chrome/browser/ui/fade_truncated_label.h" |
| 10 #import "ios/chrome/browser/ui/image_util.h" |
| 11 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_button.h" |
| 12 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_cache.h" |
| 13 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_utils.h" |
| 14 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 15 #include "ios/chrome/grit/ios_strings.h" |
| 16 #include "ios/chrome/grit/ios_theme_resources.h" |
| 17 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat
erialPalettes.h" |
| 18 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF
ontLoader.h" |
| 19 #import "ios/third_party/material_text_accessibility_ios/src/src/MDFTextAccessib
ility.h" |
| 20 #include "ui/base/l10n/l10n_util.h" |
| 21 #include "ui/base/resource/resource_bundle.h" |
| 22 #include "ui/gfx/image/image.h" |
| 23 #include "url/gurl.h" |
| 24 |
| 25 namespace gfx { |
| 26 class ImageSkia; |
| 27 } |
| 28 |
| 29 namespace { |
| 30 const CGFloat kFontSize = 16; |
| 31 const CGFloat kCellCornerRadius = 2; |
| 32 const CGFloat kBarHeight = 44; |
| 33 const CGFloat kTitleLabelTextAlpha = .54; |
| 34 const CGFloat kNewTabIconAlpha = .87; |
| 35 } // Anonymous namespace |
| 36 |
| 37 CGFloat tabSwitcherLocalSessionCellTopBarHeight() { |
| 38 return kBarHeight; |
| 39 } |
| 40 |
| 41 @interface TabSwitcherSessionCell () |
| 42 |
| 43 // Returns the container view with rounded corners to which all cell subviews |
| 44 // should be added. |
| 45 - (UIView*)containerView; |
| 46 |
| 47 @end |
| 48 |
| 49 @implementation TabSwitcherSessionCell { |
| 50 base::scoped_nsobject<UIView> _containerView; |
| 51 CGSize _cachedShadowSize; |
| 52 } |
| 53 |
| 54 // Returns the cell's identifier used for the cell's re-use. |
| 55 + (NSString*)identifier { |
| 56 return NSStringFromClass([self class]); |
| 57 } |
| 58 |
| 59 - (instancetype)initWithFrame:(CGRect)frame { |
| 60 self = [super initWithFrame:frame]; |
| 61 if (self) { |
| 62 [self contentView].isAccessibilityElement = YES; |
| 63 _containerView.reset([[UIView alloc] initWithFrame:self.bounds]); |
| 64 [_containerView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | |
| 65 UIViewAutoresizingFlexibleWidth]; |
| 66 [[_containerView layer] setCornerRadius:kCellCornerRadius]; |
| 67 [[_containerView layer] setMasksToBounds:YES]; |
| 68 [[self contentView] addSubview:_containerView]; |
| 69 [self updateShadow]; |
| 70 [[[self contentView] layer] setShouldRasterize:YES]; |
| 71 [[[self contentView] layer] |
| 72 setRasterizationScale:[[UIScreen mainScreen] scale]]; |
| 73 } |
| 74 return self; |
| 75 } |
| 76 |
| 77 - (void)layoutSubviews { |
| 78 [super layoutSubviews]; |
| 79 [self updateShadow]; |
| 80 } |
| 81 |
| 82 - (void)updateShadow { |
| 83 if (!CGSizeEqualToSize(_cachedShadowSize, self.bounds.size)) { |
| 84 CGRect offsetedRectangle = CGRectOffset(self.bounds, 0, 6); |
| 85 UIBezierPath* shadowPath = |
| 86 [UIBezierPath bezierPathWithRoundedRect:offsetedRectangle |
| 87 cornerRadius:kCellCornerRadius]; |
| 88 [[self contentView].layer setShadowPath:shadowPath.CGPath]; |
| 89 [[self contentView].layer setShadowColor:[UIColor blackColor].CGColor]; |
| 90 [[self contentView].layer setShadowOpacity:0.5]; |
| 91 _cachedShadowSize = self.bounds.size; |
| 92 } |
| 93 } |
| 94 |
| 95 - (UIView*)containerView { |
| 96 return _containerView.get(); |
| 97 } |
| 98 |
| 99 @end |
| 100 |
| 101 @implementation TabSwitcherLocalSessionCell { |
| 102 base::scoped_nsobject<UIView> _topBar; |
| 103 base::scoped_nsobject<UILabel> _titleLabel; |
| 104 base::scoped_nsobject<UIImageView> _favicon; |
| 105 base::scoped_nsobject<UIButton> _closeButton; |
| 106 base::scoped_nsobject<UIImageView> _shadow; |
| 107 base::scoped_nsobject<UIImageView> _snapshot; |
| 108 base::scoped_nsobject<TabSwitcherButton> _snapshotButton; |
| 109 PendingSnapshotRequest _currentPendingSnapshotRequest; |
| 110 id<SessionCellDelegate> _delegate; // weak |
| 111 } |
| 112 |
| 113 - (instancetype)initWithFrame:(CGRect)frame { |
| 114 self = [super initWithFrame:frame]; |
| 115 if (self) { |
| 116 // Top bar. |
| 117 _topBar.reset([[UIView alloc] initWithFrame:CGRectZero]); |
| 118 [_topBar setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 119 [[self containerView] addSubview:_topBar]; |
| 120 |
| 121 // Snapshot view. |
| 122 _snapshot.reset([[UIImageView alloc] initWithFrame:CGRectZero]); |
| 123 [_snapshot setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 124 [_snapshot setContentMode:UIViewContentModeScaleAspectFill]; |
| 125 [_snapshot setClipsToBounds:YES]; |
| 126 [[self containerView] addSubview:_snapshot]; |
| 127 |
| 128 // Cell button. |
| 129 _snapshotButton.reset([[TabSwitcherButton alloc] initWithFrame:CGRectZero]); |
| 130 [_snapshotButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 131 [_snapshotButton addTarget:self |
| 132 action:@selector(snapshotPressed) |
| 133 forControlEvents:UIControlEventTouchUpInside]; |
| 134 [[self containerView] addSubview:_snapshotButton]; |
| 135 |
| 136 // Shadow view. |
| 137 _shadow.reset([[UIImageView alloc] initWithFrame:CGRectZero]); |
| 138 [_shadow setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 139 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 140 gfx::Image shadow = rb.GetNativeImageNamed(IDR_IOS_TOOLBAR_SHADOW); |
| 141 [_shadow setImage:shadow.ToUIImage()]; |
| 142 [[self containerView] addSubview:_shadow]; |
| 143 |
| 144 // Constraints on the Top bar, snapshot view, and shadow view. |
| 145 NSDictionary* viewsDictionary = @{ |
| 146 @"bar" : _topBar.get(), |
| 147 @"shadow" : _shadow.get(), |
| 148 @"snapshot" : _snapshot.get(), |
| 149 @"snapshotButton" : _snapshotButton.get(), |
| 150 }; |
| 151 NSArray* constraints = @[ |
| 152 @"H:|-0-[bar]-0-|", |
| 153 @"H:|-0-[shadow]-0-|", |
| 154 @"H:|-0-[snapshot]-0-|", |
| 155 @"H:|-0-[snapshotButton]-0-|", |
| 156 @"V:|-0-[bar(==barHeight)]-0-[snapshot]-0-|", |
| 157 @"V:[bar]-0-[snapshotButton]-0-|", |
| 158 @"V:[bar]-0-[shadow]", |
| 159 ]; |
| 160 NSDictionary* metrics = |
| 161 @{ @"barHeight" : @(tabSwitcherLocalSessionCellTopBarHeight()) }; |
| 162 ApplyVisualConstraintsWithMetrics(constraints, viewsDictionary, metrics, |
| 163 [self containerView]); |
| 164 |
| 165 // Create and add subviews to the cell bar. |
| 166 // Title label. |
| 167 _titleLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 168 [_titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 169 [_titleLabel setFont:[[MDFRobotoFontLoader sharedInstance] |
| 170 regularFontOfSize:kFontSize]]; |
| 171 [_topBar addSubview:_titleLabel]; |
| 172 |
| 173 // Favicon. |
| 174 _favicon.reset([[UIImageView alloc] initWithFrame:CGRectZero]); |
| 175 [_favicon setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 176 [_topBar addSubview:_favicon]; |
| 177 |
| 178 // Close button. |
| 179 _closeButton.reset([[UIButton alloc] initWithFrame:CGRectZero]); |
| 180 [_closeButton |
| 181 setImage:[[UIImage imageNamed:@"card_close_button"] |
| 182 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] |
| 183 forState:UIControlStateNormal]; |
| 184 [_closeButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 185 [_closeButton addTarget:self |
| 186 action:@selector(closeButtonPressed) |
| 187 forControlEvents:UIControlEventTouchUpInside]; |
| 188 [_closeButton setExclusiveTouch:YES]; |
| 189 [_topBar addSubview:_closeButton]; |
| 190 |
| 191 // Constraints on the title label, favicon, and close button. |
| 192 NSDictionary* barViewsDictionary = @{ |
| 193 @"favicon" : _favicon.get(), |
| 194 @"title" : _titleLabel.get(), |
| 195 @"closeButton" : _closeButton.get() |
| 196 }; |
| 197 NSArray* barConstraints = @[ |
| 198 @"H:|-16-[favicon(==24)]-8-[title]-0-[closeButton(==32)]-8-|", |
| 199 @"V:[favicon(==24)]", |
| 200 @"V:[closeButton(==32)]", |
| 201 ]; |
| 202 ApplyVisualConstraints(barConstraints, barViewsDictionary, _topBar); |
| 203 AddSameCenterYConstraint(_topBar, _favicon); |
| 204 AddSameCenterYConstraint(_topBar, _titleLabel); |
| 205 AddSameCenterYConstraint(_topBar, _closeButton); |
| 206 } |
| 207 return self; |
| 208 } |
| 209 |
| 210 - (UIView*)topBar { |
| 211 return _topBar.get(); |
| 212 } |
| 213 |
| 214 - (UIImage*)screenshot { |
| 215 return [_snapshot image]; |
| 216 } |
| 217 |
| 218 - (void)setSnapshot:(UIImage*)image { |
| 219 DCHECK(!ImageHasAlphaChannel(image)); |
| 220 [_snapshot setImage:image]; |
| 221 } |
| 222 |
| 223 - (void)setAppearanceForTab:(Tab*)tab cellSize:(CGSize)cellSize { |
| 224 [_titleLabel setText:tab.title]; |
| 225 [self contentView].accessibilityLabel = tab.title; |
| 226 if (tab.favicon) { |
| 227 [_favicon setImage:tab.favicon]; |
| 228 } else { |
| 229 // No favicon is available, use placeholder instead. |
| 230 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| 231 [_favicon |
| 232 setImage:rb.GetNativeImageNamed(IDR_IOS_OMNIBOX_HTTP).ToUIImage()]; |
| 233 } |
| 234 |
| 235 CGSize snapshotSize = cellSize; |
| 236 snapshotSize.height -= tabSwitcherLocalSessionCellTopBarHeight(); |
| 237 base::WeakNSObject<TabSwitcherLocalSessionCell> weakCell(self); |
| 238 _currentPendingSnapshotRequest = |
| 239 [[self cache] requestSnapshotForTab:tab |
| 240 withSize:snapshotSize |
| 241 completionBlock:^(UIImage* image) { |
| 242 DCHECK([NSThread isMainThread]); |
| 243 [weakCell setSnapshot:image]; |
| 244 _currentPendingSnapshotRequest = {}; |
| 245 }]; |
| 246 } |
| 247 |
| 248 - (void)setSessionType:(ios_internal::SessionType)type { |
| 249 UIColor* topBarBackgroundColor; |
| 250 UIColor* closeButtonTintColor; |
| 251 UIColor* textColor; |
| 252 UIColor* snapshotBackgroundColor; |
| 253 if (type == ios_internal::SessionType::OFF_THE_RECORD_SESSION) { |
| 254 topBarBackgroundColor = [[MDCPalette greyPalette] tint700]; |
| 255 closeButtonTintColor = [[MDCPalette greyPalette] tint100]; |
| 256 textColor = [[MDCPalette greyPalette] tint100]; |
| 257 snapshotBackgroundColor = [[MDCPalette greyPalette] tint900]; |
| 258 } else { |
| 259 topBarBackgroundColor = [[MDCPalette greyPalette] tint100]; |
| 260 closeButtonTintColor = [[MDCPalette greyPalette] tint700]; |
| 261 textColor = [[MDCPalette greyPalette] tint700]; |
| 262 snapshotBackgroundColor = [UIColor whiteColor]; |
| 263 } |
| 264 [_topBar setBackgroundColor:topBarBackgroundColor]; |
| 265 [[_closeButton imageView] setTintColor:closeButtonTintColor]; |
| 266 [_titleLabel setTextColor:textColor]; |
| 267 [_titleLabel setBackgroundColor:topBarBackgroundColor]; |
| 268 [_snapshot setBackgroundColor:snapshotBackgroundColor]; |
| 269 } |
| 270 |
| 271 - (void)setDelegate:(id<SessionCellDelegate>)delegate { |
| 272 _delegate = delegate; |
| 273 } |
| 274 |
| 275 - (void)snapshotPressed { |
| 276 [_delegate cellPressed:self]; |
| 277 } |
| 278 |
| 279 - (void)closeButtonPressed { |
| 280 [_delegate deleteButtonPressedForCell:self]; |
| 281 } |
| 282 |
| 283 - (void)prepareForReuse { |
| 284 [[self cache] cancelPendingSnapshotRequest:_currentPendingSnapshotRequest]; |
| 285 _currentPendingSnapshotRequest.clear(); |
| 286 [_snapshot setImage:nil]; |
| 287 [_snapshotButton resetState]; |
| 288 [super prepareForReuse]; |
| 289 } |
| 290 |
| 291 - (TabSwitcherCache*)cache { |
| 292 return [_delegate tabSwitcherCache]; |
| 293 } |
| 294 |
| 295 #pragma mark - UIAccessibilityAction |
| 296 |
| 297 - (NSArray*)accessibilityCustomActions { |
| 298 base::scoped_nsobject<NSMutableArray> customActions( |
| 299 [[NSMutableArray alloc] init]); |
| 300 base::scoped_nsobject<UIAccessibilityCustomAction> customAction( |
| 301 [[UIAccessibilityCustomAction alloc] |
| 302 initWithName:l10n_util::GetNSString(IDS_IOS_TAB_SWITCHER_CLOSE_TAB) |
| 303 target:self |
| 304 selector:@selector(closeButtonPressed)]); |
| 305 [customActions addObject:customAction.autorelease()]; |
| 306 return customActions.autorelease(); |
| 307 } |
| 308 |
| 309 @end |
| 310 |
| 311 @implementation TabSwitcherDistantSessionCell { |
| 312 base::scoped_nsobject<UILabel> _titleLabel; |
| 313 base::scoped_nsobject<UIImageView> _favicon; |
| 314 base::scoped_nsobject<UIImageView> _newTabIcon; |
| 315 base::scoped_nsobject<UIView> _verticallyCenteredView; |
| 316 base::scoped_nsobject<TabSwitcherButton> _raisedButton; |
| 317 base::scoped_nsobject<NSOperation> _faviconObtainer; |
| 318 id<SessionCellDelegate> _delegate; // weak |
| 319 } |
| 320 |
| 321 - (instancetype)initWithFrame:(CGRect)frame { |
| 322 self = [super initWithFrame:frame]; |
| 323 if (self) { |
| 324 // Create and add the button that contains all other subviews. |
| 325 _raisedButton.reset([[TabSwitcherButton alloc] initWithFrame:CGRectZero]); |
| 326 [_raisedButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 327 [_raisedButton addTarget:self |
| 328 action:@selector(cellPressed) |
| 329 forControlEvents:UIControlEventTouchUpInside]; |
| 330 [[self containerView] addSubview:_raisedButton]; |
| 331 ApplyVisualConstraints(@[ @"H:|-0-[button]-0-|", @"V:|-0-[button]-0-|" ], |
| 332 @{ @"button" : _raisedButton.get() }, |
| 333 [self containerView]); |
| 334 |
| 335 // Create and add view that will be vertically centered in the space over |
| 336 // the favicon. |
| 337 _verticallyCenteredView.reset([[UIView alloc] initWithFrame:CGRectZero]); |
| 338 [_verticallyCenteredView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 339 [_verticallyCenteredView setUserInteractionEnabled:NO]; |
| 340 [_raisedButton addSubview:_verticallyCenteredView]; |
| 341 |
| 342 // Create and add title label to |_verticallyCenteredContent|. |
| 343 _titleLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); |
| 344 [_titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 345 [_titleLabel setNumberOfLines:5]; |
| 346 [_titleLabel setTextAlignment:NSTextAlignmentCenter]; |
| 347 [_titleLabel setFont:[[MDFRobotoFontLoader sharedInstance] |
| 348 regularFontOfSize:kFontSize]]; |
| 349 [_verticallyCenteredView addSubview:_titleLabel]; |
| 350 |
| 351 // Create and add new tab icon to |_verticallyCenteredContent|. |
| 352 UIImage* newTabIcon = [[UIImage imageNamed:@"tabswitcher_new_tab"] |
| 353 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| 354 _newTabIcon.reset([[UIImageView alloc] initWithImage:newTabIcon]); |
| 355 [_newTabIcon setAlpha:0]; |
| 356 [_newTabIcon setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 357 [_verticallyCenteredView addSubview:_newTabIcon]; |
| 358 |
| 359 // Create and add favicon image container. |
| 360 _favicon.reset([[UIImageView alloc] initWithFrame:CGRectZero]); |
| 361 [_favicon setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 362 [_raisedButton addSubview:_favicon]; |
| 363 |
| 364 // Add constraints to the button's subviews. |
| 365 NSDictionary* viewsDictionary = @{ |
| 366 @"newTabIcon" : _newTabIcon.get(), |
| 367 @"title" : _titleLabel.get(), |
| 368 @"favicon" : _favicon.get(), |
| 369 @"centeredView" : _verticallyCenteredView.get(), |
| 370 }; |
| 371 NSArray* constraintsInButton = @[ |
| 372 @"H:|-0-[centeredView]-0-|", |
| 373 @"H:[favicon(==16)]", |
| 374 @"V:|-(>=16)-[centeredView]-(>=16)-[favicon(==16)]-16-|", |
| 375 ]; |
| 376 ApplyVisualConstraints(constraintsInButton, viewsDictionary, _raisedButton); |
| 377 AddSameCenterXConstraint(_raisedButton, _favicon.get()); |
| 378 [_raisedButton |
| 379 addConstraint:[NSLayoutConstraint |
| 380 constraintWithItem:_verticallyCenteredView.get() |
| 381 attribute:NSLayoutAttributeCenterY |
| 382 relatedBy:NSLayoutRelationEqual |
| 383 toItem:_favicon.get() |
| 384 attribute:NSLayoutAttributeCenterY |
| 385 multiplier:0.5 |
| 386 constant:0]]; |
| 387 |
| 388 // Add constraints to the subviews of the vertically centered view. |
| 389 NSArray* constraintsInVerticallyCenteredView = @[ |
| 390 @"H:|-16-[title]-16-|", |
| 391 @"V:|-0-[newTabIcon(==24)]-16-[title(>=16)]-0-|", |
| 392 ]; |
| 393 ApplyVisualConstraints(constraintsInVerticallyCenteredView, viewsDictionary, |
| 394 _verticallyCenteredView); |
| 395 AddSameCenterXConstraint(_verticallyCenteredView, _newTabIcon.get()); |
| 396 } |
| 397 return self; |
| 398 } |
| 399 |
| 400 - (void)setTitle:(NSString*)titleString { |
| 401 [_titleLabel setText:titleString]; |
| 402 [self contentView].accessibilityLabel = titleString; |
| 403 } |
| 404 |
| 405 - (void)setSessionGURL:(GURL const&)gurl |
| 406 withBrowserState:(ios::ChromeBrowserState*)browserState { |
| 407 ios_internal::FaviconGetterCompletionBlock block = ^(UIImage* favicon) { |
| 408 UIColor* imageDominantColor = |
| 409 DominantColorForImage(gfx::Image(favicon), 1.0); |
| 410 MDCPalette* dominantPalette = |
| 411 [MDCPalette paletteGeneratedFromColor:imageDominantColor]; |
| 412 UIColor* backgroundColor = dominantPalette.tint300; |
| 413 UIColor* textColor = |
| 414 [MDFTextAccessibility textColorOnBackgroundColor:backgroundColor |
| 415 targetTextAlpha:kTitleLabelTextAlpha |
| 416 font:[_titleLabel font]]; |
| 417 UIColor* iconColor = |
| 418 [MDFTextAccessibility textColorOnBackgroundColor:backgroundColor |
| 419 targetTextAlpha:kNewTabIconAlpha |
| 420 font:[_titleLabel font]]; |
| 421 [_raisedButton setBackgroundColor:backgroundColor]; |
| 422 [_titleLabel setTextColor:textColor]; |
| 423 [_newTabIcon setTintColor:iconColor]; |
| 424 [_newTabIcon setAlpha:1.0]; |
| 425 [_favicon setImage:favicon]; |
| 426 [UIView animateWithDuration:0.2 |
| 427 animations:^{ |
| 428 [_raisedButton setAlpha:1.0]; |
| 429 }]; |
| 430 }; |
| 431 GURL gurlCopy = gurl; |
| 432 _faviconObtainer.reset([[NSBlockOperation blockOperationWithBlock:^{ |
| 433 ios_internal::GetFavicon(gurlCopy, browserState, block); |
| 434 }] retain]); |
| 435 NSOperationQueue* operationQueue = [NSOperationQueue mainQueue]; |
| 436 [operationQueue addOperation:_faviconObtainer]; |
| 437 } |
| 438 |
| 439 - (void)setDelegate:(id<SessionCellDelegate>)delegate { |
| 440 _delegate = delegate; |
| 441 } |
| 442 |
| 443 - (void)cellPressed { |
| 444 [_delegate cellPressed:self]; |
| 445 } |
| 446 |
| 447 - (void)prepareForReuse { |
| 448 [_newTabIcon setAlpha:0]; |
| 449 [_faviconObtainer cancel]; |
| 450 _faviconObtainer.reset(); |
| 451 [_raisedButton setAlpha:0]; |
| 452 [_raisedButton resetState]; |
| 453 [super prepareForReuse]; |
| 454 } |
| 455 |
| 456 @end |
OLD | NEW |