| OLD | NEW |
| (Empty) |
| 1 // Copyright 2017 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/content_suggestions/cells/content_suggestions_art
icle_item.h" | |
| 6 | |
| 7 #include "base/time/time.h" | |
| 8 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" | |
| 9 #import "ios/chrome/browser/ui/favicon/favicon_attributes.h" | |
| 10 #import "ios/chrome/browser/ui/favicon/favicon_view.h" | |
| 11 #import "ios/chrome/browser/ui/uikit_ui_util.h" | |
| 12 #import "ios/chrome/browser/ui/util/i18n_string.h" | |
| 13 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat
erialPalettes.h" | |
| 14 #import "ios/third_party/material_components_ios/src/components/Typography/src/M
aterialTypography.h" | |
| 15 #include "url/gurl.h" | |
| 16 | |
| 17 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
| 18 #error "This file requires ARC support." | |
| 19 #endif | |
| 20 | |
| 21 namespace { | |
| 22 const CGFloat kImageSize = 72; | |
| 23 const CGFloat kStandardSpacing = 16; | |
| 24 const CGFloat kSmallSpacing = 8; | |
| 25 | |
| 26 // Size of the favicon view. | |
| 27 const CGFloat kFaviconSize = 16; | |
| 28 // Size of the icon displayed when there is not image. | |
| 29 const CGFloat kIconSize = 24; | |
| 30 // Name of the icon displayed when there is not image. | |
| 31 NSString* const kNoImageIconName = @"content_suggestions_no_image"; | |
| 32 // No image icon percentage of white. | |
| 33 const CGFloat kNoImageIconWhite = 0.38; | |
| 34 // No image background percentage of white. | |
| 35 const CGFloat kNoImageBackgroundWhite = 0.95; | |
| 36 // Duration of the animation to display the image for the article. | |
| 37 const CGFloat kAnimationDuration = 0.3; | |
| 38 } | |
| 39 | |
| 40 @interface ContentSuggestionsArticleItem () | |
| 41 | |
| 42 @property(nonatomic, copy) NSString* subtitle; | |
| 43 // Used to check if the image has already been fetched. There is no way to | |
| 44 // discriminate between failed image download and nonexitent image. The article | |
| 45 // tries to download the image only once. | |
| 46 @property(nonatomic, assign) BOOL imageFetched; | |
| 47 | |
| 48 @end | |
| 49 | |
| 50 #pragma mark - ContentSuggestionsArticleItem | |
| 51 | |
| 52 @implementation ContentSuggestionsArticleItem | |
| 53 | |
| 54 @synthesize title = _title; | |
| 55 @synthesize subtitle = _subtitle; | |
| 56 @synthesize image = _image; | |
| 57 @synthesize articleURL = _articleURL; | |
| 58 @synthesize publisher = _publisher; | |
| 59 @synthesize publishDate = _publishDate; | |
| 60 @synthesize suggestionIdentifier = _suggestionIdentifier; | |
| 61 @synthesize delegate = _delegate; | |
| 62 @synthesize imageFetched = _imageFetched; | |
| 63 @synthesize attributes = _attributes; | |
| 64 | |
| 65 - (instancetype)initWithType:(NSInteger)type | |
| 66 title:(NSString*)title | |
| 67 subtitle:(NSString*)subtitle | |
| 68 delegate:(id<ContentSuggestionsArticleItemDelegate>)delegate | |
| 69 url:(const GURL&)url { | |
| 70 self = [super initWithType:type]; | |
| 71 if (self) { | |
| 72 self.cellClass = [ContentSuggestionsArticleCell class]; | |
| 73 _title = [title copy]; | |
| 74 _subtitle = [subtitle copy]; | |
| 75 _articleURL = url; | |
| 76 _delegate = delegate; | |
| 77 } | |
| 78 return self; | |
| 79 } | |
| 80 | |
| 81 - (void)configureCell:(ContentSuggestionsArticleCell*)cell { | |
| 82 [super configureCell:cell]; | |
| 83 if (!self.imageFetched) { | |
| 84 self.imageFetched = YES; | |
| 85 // Fetch the image. During the fetch the cell's image should still be set. | |
| 86 [self.delegate loadImageForArticleItem:self]; | |
| 87 } | |
| 88 if (self.attributes) | |
| 89 [cell.faviconView configureWithAttributes:self.attributes]; | |
| 90 cell.titleLabel.text = self.title; | |
| 91 cell.subtitleLabel.text = self.subtitle; | |
| 92 [cell setContentImage:self.image]; | |
| 93 [cell setPublisherName:self.publisher date:self.publishDate]; | |
| 94 } | |
| 95 | |
| 96 @end | |
| 97 | |
| 98 #pragma mark - ContentSuggestionsArticleCell | |
| 99 | |
| 100 @interface ContentSuggestionsArticleCell () | |
| 101 | |
| 102 @property(nonatomic, strong) UILabel* publisherLabel; | |
| 103 // Contains the no-image icon or the image. | |
| 104 @property(nonatomic, strong) UIView* imageContainer; | |
| 105 // The no-image icon displayed when there is no image. | |
| 106 @property(nonatomic, strong) UIImageView* noImageIcon; | |
| 107 // Displays the image associated with this article. It is added to the | |
| 108 // imageContainer only if there is an image to display, hiding the no-image | |
| 109 // icon. | |
| 110 @property(nonatomic, strong) UIImageView* contentImageView; | |
| 111 | |
| 112 // Applies the constraints on the elements. Called in the init. | |
| 113 - (void)applyConstraints; | |
| 114 | |
| 115 @end | |
| 116 | |
| 117 @implementation ContentSuggestionsArticleCell | |
| 118 | |
| 119 @synthesize titleLabel = _titleLabel; | |
| 120 @synthesize subtitleLabel = _subtitleLabel; | |
| 121 @synthesize imageContainer = _imageContainer; | |
| 122 @synthesize noImageIcon = _noImageIcon; | |
| 123 @synthesize publisherLabel = _publisherLabel; | |
| 124 @synthesize contentImageView = _contentImageView; | |
| 125 @synthesize faviconView = _faviconView; | |
| 126 | |
| 127 - (instancetype)initWithFrame:(CGRect)frame { | |
| 128 self = [super initWithFrame:frame]; | |
| 129 if (self) { | |
| 130 _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | |
| 131 _subtitleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | |
| 132 _imageContainer = [[UIView alloc] initWithFrame:CGRectZero]; | |
| 133 _noImageIcon = [[UIImageView alloc] initWithFrame:CGRectZero]; | |
| 134 _publisherLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | |
| 135 _contentImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; | |
| 136 _faviconView = [[FaviconViewNew alloc] init]; | |
| 137 | |
| 138 _titleLabel.numberOfLines = 2; | |
| 139 _subtitleLabel.numberOfLines = 2; | |
| 140 [_subtitleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh | |
| 141 forAxis:UILayoutConstraintAxisVertical]; | |
| 142 [_titleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh | |
| 143 forAxis:UILayoutConstraintAxisVertical]; | |
| 144 | |
| 145 _contentImageView.contentMode = UIViewContentModeScaleAspectFill; | |
| 146 _contentImageView.clipsToBounds = YES; | |
| 147 _contentImageView.hidden = YES; | |
| 148 | |
| 149 _imageContainer.translatesAutoresizingMaskIntoConstraints = NO; | |
| 150 _noImageIcon.translatesAutoresizingMaskIntoConstraints = NO; | |
| 151 _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; | |
| 152 _subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; | |
| 153 _publisherLabel.translatesAutoresizingMaskIntoConstraints = NO; | |
| 154 _contentImageView.translatesAutoresizingMaskIntoConstraints = NO; | |
| 155 _faviconView.translatesAutoresizingMaskIntoConstraints = NO; | |
| 156 | |
| 157 [self.contentView addSubview:_imageContainer]; | |
| 158 [self.contentView addSubview:_titleLabel]; | |
| 159 [self.contentView addSubview:_subtitleLabel]; | |
| 160 [self.contentView addSubview:_publisherLabel]; | |
| 161 [self.contentView addSubview:_faviconView]; | |
| 162 | |
| 163 [_imageContainer addSubview:_noImageIcon]; | |
| 164 [_imageContainer addSubview:_contentImageView]; | |
| 165 | |
| 166 _imageContainer.backgroundColor = | |
| 167 [UIColor colorWithWhite:kNoImageBackgroundWhite alpha:1]; | |
| 168 _noImageIcon.image = [[UIImage imageNamed:kNoImageIconName] | |
| 169 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; | |
| 170 [_noImageIcon | |
| 171 setTintColor:[UIColor colorWithWhite:kNoImageIconWhite alpha:1]]; | |
| 172 | |
| 173 _titleLabel.font = [MDCTypography subheadFont]; | |
| 174 _subtitleLabel.font = [MDCTypography body1Font]; | |
| 175 _publisherLabel.font = [MDCTypography captionFont]; | |
| 176 _faviconView.font = [[MDCTypography fontLoader] mediumFontOfSize:10]; | |
| 177 | |
| 178 _subtitleLabel.textColor = [[MDCPalette greyPalette] tint700]; | |
| 179 _publisherLabel.textColor = [[MDCPalette greyPalette] tint700]; | |
| 180 | |
| 181 [self applyConstraints]; | |
| 182 } | |
| 183 return self; | |
| 184 } | |
| 185 | |
| 186 - (void)setContentImage:(UIImage*)image { | |
| 187 if (!image) { | |
| 188 self.contentImageView.hidden = YES; | |
| 189 return; | |
| 190 } | |
| 191 | |
| 192 self.contentImageView.image = image; | |
| 193 | |
| 194 self.contentImageView.alpha = 0; | |
| 195 self.contentImageView.hidden = NO; | |
| 196 | |
| 197 [UIView animateWithDuration:kAnimationDuration | |
| 198 animations:^{ | |
| 199 self.contentImageView.alpha = 1; | |
| 200 }]; | |
| 201 } | |
| 202 | |
| 203 - (void)setPublisherName:(NSString*)publisherName date:(base::Time)publishDate { | |
| 204 NSDate* date = [NSDate dateWithTimeIntervalSince1970:publishDate.ToDoubleT()]; | |
| 205 NSString* dateString = | |
| 206 [NSDateFormatter localizedStringFromDate:date | |
| 207 dateStyle:NSDateFormatterMediumStyle | |
| 208 timeStyle:NSDateFormatterNoStyle]; | |
| 209 | |
| 210 self.publisherLabel.text = AdjustStringForLocaleDirection( | |
| 211 [NSString stringWithFormat:@"%@ - %@.", publisherName, dateString]); | |
| 212 } | |
| 213 | |
| 214 - (void)prepareForReuse { | |
| 215 self.contentImageView.hidden = YES; | |
| 216 } | |
| 217 | |
| 218 #pragma mark - UIView | |
| 219 | |
| 220 // Implements -layoutSubviews as per instructions in documentation for | |
| 221 // +[MDCCollectionViewCell cr_preferredHeightForWidth:forItem:]. | |
| 222 - (void)layoutSubviews { | |
| 223 [super layoutSubviews]; | |
| 224 | |
| 225 // Adjust the text label preferredMaxLayoutWidth when the parent's width | |
| 226 // changes, for instance on screen rotation. | |
| 227 CGFloat parentWidth = CGRectGetWidth(self.contentView.bounds); | |
| 228 | |
| 229 self.titleLabel.preferredMaxLayoutWidth = | |
| 230 parentWidth - kImageSize - 3 * kStandardSpacing; | |
| 231 self.subtitleLabel.preferredMaxLayoutWidth = | |
| 232 parentWidth - kImageSize - 3 * kStandardSpacing; | |
| 233 self.publisherLabel.preferredMaxLayoutWidth = | |
| 234 parentWidth - kFaviconSize - kSmallSpacing - 2 * kStandardSpacing; | |
| 235 | |
| 236 // Re-layout with the new preferred width to allow the label to adjust its | |
| 237 // height. | |
| 238 [super layoutSubviews]; | |
| 239 } | |
| 240 | |
| 241 #pragma mark - Private | |
| 242 | |
| 243 - (void)applyConstraints { | |
| 244 [NSLayoutConstraint activateConstraints:@[ | |
| 245 [_imageContainer.widthAnchor constraintEqualToConstant:kImageSize], | |
| 246 [_imageContainer.heightAnchor | |
| 247 constraintEqualToAnchor:_imageContainer.widthAnchor], | |
| 248 [_imageContainer.topAnchor constraintEqualToAnchor:_titleLabel.topAnchor], | |
| 249 | |
| 250 // Publisher. | |
| 251 [_publisherLabel.topAnchor | |
| 252 constraintGreaterThanOrEqualToAnchor:_imageContainer.bottomAnchor | |
| 253 constant:kStandardSpacing], | |
| 254 [_publisherLabel.topAnchor | |
| 255 constraintGreaterThanOrEqualToAnchor:_subtitleLabel.bottomAnchor | |
| 256 constant:kStandardSpacing], | |
| 257 [_publisherLabel.bottomAnchor | |
| 258 constraintLessThanOrEqualToAnchor:self.contentView.bottomAnchor | |
| 259 constant:-kStandardSpacing], | |
| 260 | |
| 261 // Favicon. | |
| 262 [_faviconView.topAnchor | |
| 263 constraintGreaterThanOrEqualToAnchor:_imageContainer.bottomAnchor | |
| 264 constant:kStandardSpacing], | |
| 265 [_faviconView.topAnchor | |
| 266 constraintGreaterThanOrEqualToAnchor:_subtitleLabel.bottomAnchor | |
| 267 constant:kStandardSpacing], | |
| 268 [_faviconView.centerYAnchor | |
| 269 constraintEqualToAnchor:_publisherLabel.centerYAnchor], | |
| 270 [_faviconView.bottomAnchor | |
| 271 constraintLessThanOrEqualToAnchor:self.contentView.bottomAnchor | |
| 272 constant:-kStandardSpacing], | |
| 273 [_faviconView.heightAnchor constraintEqualToConstant:kFaviconSize], | |
| 274 [_faviconView.widthAnchor | |
| 275 constraintEqualToAnchor:_faviconView.heightAnchor], | |
| 276 | |
| 277 // No image icon. | |
| 278 [_noImageIcon.centerXAnchor | |
| 279 constraintEqualToAnchor:_imageContainer.centerXAnchor], | |
| 280 [_noImageIcon.centerYAnchor | |
| 281 constraintEqualToAnchor:_imageContainer.centerYAnchor], | |
| 282 [_noImageIcon.widthAnchor constraintEqualToConstant:kIconSize], | |
| 283 [_noImageIcon.heightAnchor constraintEqualToAnchor:_noImageIcon.widthAnchor] | |
| 284 ]]; | |
| 285 | |
| 286 AddSameConstraints(_contentImageView, _imageContainer); | |
| 287 | |
| 288 ApplyVisualConstraintsWithMetrics( | |
| 289 @[ | |
| 290 @"H:|-(space)-[title]-(space)-[image]-(space)-|", | |
| 291 @"H:|-(space)-[text]-(space)-[image]", | |
| 292 @"V:|-(space)-[title]-[text]", | |
| 293 @"H:|-(space)-[favicon]-(small)-[publish]-(space)-|", | |
| 294 ], | |
| 295 @{ | |
| 296 @"image" : _imageContainer, | |
| 297 @"title" : _titleLabel, | |
| 298 @"text" : _subtitleLabel, | |
| 299 @"publish" : _publisherLabel, | |
| 300 @"favicon" : _faviconView, | |
| 301 }, | |
| 302 @{ @"space" : @(kStandardSpacing), | |
| 303 @"small" : @(kSmallSpacing) }); | |
| 304 } | |
| 305 | |
| 306 @end | |
| OLD | NEW |