Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_art icle_item.h" | 5 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_art icle_item.h" |
| 6 | 6 |
| 7 #include "base/time/time.h" | 7 #include "base/time/time.h" |
| 8 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" | 8 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
| 9 #import "ios/chrome/browser/ui/uikit_ui_util.h" | 9 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 10 #import "ios/chrome/browser/ui/util/i18n_string.h" | 10 #import "ios/chrome/browser/ui/util/i18n_string.h" |
| 11 #import "ios/third_party/material_components_ios/src/components/Palettes/src/Mat erialPalettes.h" | |
| 11 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h" | 12 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h" |
| 12 #include "url/gurl.h" | 13 #include "url/gurl.h" |
| 13 | 14 |
| 14 #if !defined(__has_feature) || !__has_feature(objc_arc) | 15 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 15 #error "This file requires ARC support." | 16 #error "This file requires ARC support." |
| 16 #endif | 17 #endif |
| 17 | 18 |
| 18 namespace { | 19 namespace { |
| 19 const CGFloat kImageSize = 72; | 20 const CGFloat kImageSize = 72; |
| 20 // When updating this, make sure to update |layoutSubviews|. | 21 // When updating this, make sure to update |layoutSubviews|. |
| 21 const CGFloat kStandardSpacing = 16; | 22 const CGFloat kStandardSpacing = 16; |
| 23 // Size of the icon displayed when there is not image. | |
| 24 const CGFloat kIconSize = 24; | |
| 25 // Name of the image. | |
| 26 NSString* const kImageName = @"content_suggestions_no_image"; | |
|
jif
2017/03/28 09:04:58
kImageName can be made more precise. Possible repl
gambard
2017/03/28 09:40:29
The backend service use "fetchSuggestionImage". I
| |
| 27 // No image icon percentage of white. | |
| 28 const CGFloat kImageWhite = 0.38; | |
| 29 // No image background percentage of white. | |
| 30 const CGFloat kBackgroundWhite = 0.95; | |
| 31 const CGFloat kAnimationDuration = 0.3; | |
| 22 } | 32 } |
| 23 | 33 |
| 24 @interface ContentSuggestionsArticleItem () | 34 @interface ContentSuggestionsArticleItem () |
| 25 | 35 |
| 26 @property(nonatomic, copy) NSString* subtitle; | 36 @property(nonatomic, copy) NSString* subtitle; |
| 27 // Used to check if the image has already been fetched. There is no way to | 37 // Used to check if the image has already been fetched. There is no way to |
| 28 // discriminate between failed image download and nonexitent image. The article | 38 // discriminate between failed image download and nonexitent image. The article |
| 29 // tries to download the image only once. | 39 // tries to download the image only once. |
| 30 @property(nonatomic, assign) BOOL imageFetched; | 40 @property(nonatomic, assign) BOOL imageFetched; |
| 31 | 41 |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 50 subtitle:(NSString*)subtitle | 60 subtitle:(NSString*)subtitle |
| 51 delegate:(id<ContentSuggestionsArticleItemDelegate>)delegate | 61 delegate:(id<ContentSuggestionsArticleItemDelegate>)delegate |
| 52 url:(const GURL&)url { | 62 url:(const GURL&)url { |
| 53 self = [super initWithType:type]; | 63 self = [super initWithType:type]; |
| 54 if (self) { | 64 if (self) { |
| 55 self.cellClass = [ContentSuggestionsArticleCell class]; | 65 self.cellClass = [ContentSuggestionsArticleCell class]; |
| 56 _title = [title copy]; | 66 _title = [title copy]; |
| 57 _subtitle = [subtitle copy]; | 67 _subtitle = [subtitle copy]; |
| 58 _articleURL = url; | 68 _articleURL = url; |
| 59 _delegate = delegate; | 69 _delegate = delegate; |
| 60 _image = [self emptyImageBackground]; | |
| 61 } | 70 } |
| 62 return self; | 71 return self; |
| 63 } | 72 } |
| 64 | 73 |
| 65 - (void)configureCell:(ContentSuggestionsArticleCell*)cell { | 74 - (void)configureCell:(ContentSuggestionsArticleCell*)cell { |
| 66 [super configureCell:cell]; | 75 [super configureCell:cell]; |
| 67 if (!self.imageFetched) { | 76 if (!self.imageFetched) { |
| 68 self.imageFetched = YES; | 77 self.imageFetched = YES; |
| 69 // Fetch the image. During the fetch the cell's image should still be set. | 78 // Fetch the image. During the fetch the cell's image should still be set. |
| 70 [self.delegate loadImageForArticleItem:self]; | 79 [self.delegate loadImageForArticleItem:self]; |
| 71 } | 80 } |
| 72 cell.titleLabel.text = self.title; | 81 cell.titleLabel.text = self.title; |
| 73 cell.subtitleLabel.text = self.subtitle; | 82 cell.subtitleLabel.text = self.subtitle; |
| 74 cell.imageView.image = self.image; | 83 if (self.image) |
|
jif
2017/03/28 09:04:58
we are already checking in |setContentImage| if it
gambard
2017/03/28 09:40:29
Done.
| |
| 84 [cell setContentImage:self.image]; | |
| 75 [cell setPublisherName:self.publisher date:self.publishDate]; | 85 [cell setPublisherName:self.publisher date:self.publishDate]; |
| 76 } | 86 } |
| 77 | 87 |
| 78 #pragma mark - Private | |
| 79 | |
| 80 - (UIImage*)emptyImageBackground { | |
| 81 // TODO(crbug.com/698171): Remove this function once we have real background | |
| 82 // image. | |
| 83 UIColor* color = [UIColor lightGrayColor]; | |
| 84 CGRect rect = CGRectMake(0, 0, 1, 1); | |
| 85 UIGraphicsBeginImageContext(rect.size); | |
| 86 [color setFill]; | |
| 87 UIRectFill(rect); | |
| 88 UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); | |
| 89 UIGraphicsEndImageContext(); | |
| 90 return image; | |
| 91 } | |
| 92 | |
| 93 @end | 88 @end |
| 94 | 89 |
| 95 #pragma mark - ContentSuggestionsArticleCell | 90 #pragma mark - ContentSuggestionsArticleCell |
| 96 | 91 |
| 97 @interface ContentSuggestionsArticleCell () | 92 @interface ContentSuggestionsArticleCell () |
| 98 | 93 |
| 99 @property(nonatomic, strong) UILabel* publisherLabel; | 94 @property(nonatomic, strong) UILabel* publisherLabel; |
| 95 @property(nonatomic, strong) UIView* imageContainer; | |
| 96 @property(nonatomic, strong) UIImageView* noImageIcon; | |
| 97 @property(nonatomic, strong) UIImageView* contentImageView; | |
| 100 | 98 |
| 101 // Applies the constraints on the elements. Called in the init. | 99 // Applies the constraints on the elements. Called in the init. |
| 102 - (void)applyConstraints; | 100 - (void)applyConstraints; |
| 103 | 101 |
| 104 @end | 102 @end |
| 105 | 103 |
| 106 @implementation ContentSuggestionsArticleCell | 104 @implementation ContentSuggestionsArticleCell |
| 107 | 105 |
| 108 @synthesize titleLabel = _titleLabel; | 106 @synthesize titleLabel = _titleLabel; |
| 109 @synthesize subtitleLabel = _subtitleLabel; | 107 @synthesize subtitleLabel = _subtitleLabel; |
| 110 @synthesize imageView = _imageView; | 108 @synthesize imageContainer = _imageContainer; |
| 109 @synthesize noImageIcon = _noImageIcon; | |
| 111 @synthesize publisherLabel = _publisherLabel; | 110 @synthesize publisherLabel = _publisherLabel; |
| 111 @synthesize contentImageView = _contentImageView; | |
| 112 | 112 |
| 113 - (instancetype)initWithFrame:(CGRect)frame { | 113 - (instancetype)initWithFrame:(CGRect)frame { |
| 114 self = [super initWithFrame:frame]; | 114 self = [super initWithFrame:frame]; |
| 115 if (self) { | 115 if (self) { |
| 116 _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | 116 _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
| 117 _subtitleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | 117 _subtitleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
| 118 _imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; | 118 _imageContainer = [[UIView alloc] initWithFrame:CGRectZero]; |
| 119 _noImageIcon = [[UIImageView alloc] initWithFrame:CGRectZero]; | |
| 119 _publisherLabel = [[UILabel alloc] initWithFrame:CGRectZero]; | 120 _publisherLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
| 120 | 121 |
| 121 _titleLabel.numberOfLines = 2; | 122 _titleLabel.numberOfLines = 2; |
| 122 _subtitleLabel.numberOfLines = 2; | 123 _subtitleLabel.numberOfLines = 2; |
| 123 [_subtitleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh | 124 [_subtitleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh |
| 124 forAxis:UILayoutConstraintAxisVertical]; | 125 forAxis:UILayoutConstraintAxisVertical]; |
| 125 [_titleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh | 126 [_titleLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh |
| 126 forAxis:UILayoutConstraintAxisVertical]; | 127 forAxis:UILayoutConstraintAxisVertical]; |
| 127 _imageView.contentMode = UIViewContentModeScaleAspectFill; | |
| 128 _imageView.clipsToBounds = YES; | |
| 129 | 128 |
| 130 _imageView.translatesAutoresizingMaskIntoConstraints = NO; | 129 _imageContainer.translatesAutoresizingMaskIntoConstraints = NO; |
| 130 _noImageIcon.translatesAutoresizingMaskIntoConstraints = NO; | |
| 131 _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; | 131 _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; |
| 132 _subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; | 132 _subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO; |
| 133 _publisherLabel.translatesAutoresizingMaskIntoConstraints = NO; | 133 _publisherLabel.translatesAutoresizingMaskIntoConstraints = NO; |
| 134 | 134 |
| 135 [self.contentView addSubview:_imageView]; | 135 [self.contentView addSubview:_imageContainer]; |
| 136 [self.contentView addSubview:_titleLabel]; | 136 [self.contentView addSubview:_titleLabel]; |
| 137 [self.contentView addSubview:_subtitleLabel]; | 137 [self.contentView addSubview:_subtitleLabel]; |
| 138 [self.contentView addSubview:_publisherLabel]; | 138 [self.contentView addSubview:_publisherLabel]; |
| 139 | 139 |
| 140 [_imageContainer addSubview:_noImageIcon]; | |
| 141 | |
| 142 _imageContainer.backgroundColor = | |
| 143 [UIColor colorWithWhite:kBackgroundWhite alpha:1]; | |
| 144 _noImageIcon.image = [[UIImage imageNamed:kImageName] | |
| 145 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; | |
| 146 [_noImageIcon setTintColor:[UIColor colorWithWhite:kImageWhite alpha:1]]; | |
| 147 | |
| 140 _titleLabel.font = [MDCTypography subheadFont]; | 148 _titleLabel.font = [MDCTypography subheadFont]; |
| 141 _subtitleLabel.font = [MDCTypography body1Font]; | 149 _subtitleLabel.font = [MDCTypography body1Font]; |
| 142 _publisherLabel.font = [MDCTypography captionFont]; | 150 _publisherLabel.font = [MDCTypography captionFont]; |
| 143 | 151 |
| 144 _subtitleLabel.textColor = [[MDCPalette greyPalette] tint700]; | 152 _subtitleLabel.textColor = [[MDCPalette greyPalette] tint700]; |
| 145 _publisherLabel.textColor = [[MDCPalette greyPalette] tint700]; | 153 _publisherLabel.textColor = [[MDCPalette greyPalette] tint700]; |
| 146 | 154 |
| 147 [self applyConstraints]; | 155 [self applyConstraints]; |
| 148 } | 156 } |
| 149 return self; | 157 return self; |
| 150 } | 158 } |
| 151 | 159 |
| 160 - (void)setContentImage:(UIImage*)image { | |
| 161 if (!image) | |
| 162 return; | |
| 163 self.contentImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; | |
| 164 self.contentImageView.translatesAutoresizingMaskIntoConstraints = NO; | |
| 165 self.contentImageView.contentMode = UIViewContentModeScaleAspectFill; | |
| 166 self.contentImageView.clipsToBounds = YES; | |
| 167 self.contentImageView.image = image; | |
| 168 | |
| 169 self.contentImageView.alpha = 0; | |
| 170 [self.imageContainer addSubview:self.contentImageView]; | |
| 171 AddSameSizeConstraint(self.contentImageView, self.imageContainer); | |
| 172 | |
| 173 [UIView animateWithDuration:kAnimationDuration | |
| 174 animations:^{ | |
| 175 self.contentImageView.alpha = 1; | |
| 176 }]; | |
| 177 } | |
| 178 | |
| 152 - (void)setPublisherName:(NSString*)publisherName date:(base::Time)publishDate { | 179 - (void)setPublisherName:(NSString*)publisherName date:(base::Time)publishDate { |
| 153 NSDate* date = [NSDate dateWithTimeIntervalSince1970:publishDate.ToDoubleT()]; | 180 NSDate* date = [NSDate dateWithTimeIntervalSince1970:publishDate.ToDoubleT()]; |
| 154 NSString* dateString = | 181 NSString* dateString = |
| 155 [NSDateFormatter localizedStringFromDate:date | 182 [NSDateFormatter localizedStringFromDate:date |
| 156 dateStyle:NSDateFormatterMediumStyle | 183 dateStyle:NSDateFormatterMediumStyle |
| 157 timeStyle:NSDateFormatterNoStyle]; | 184 timeStyle:NSDateFormatterNoStyle]; |
| 158 | 185 |
| 159 self.publisherLabel.text = AdjustStringForLocaleDirection( | 186 self.publisherLabel.text = AdjustStringForLocaleDirection( |
| 160 [NSString stringWithFormat:@"%@ - %@.", publisherName, dateString]); | 187 [NSString stringWithFormat:@"%@ - %@.", publisherName, dateString]); |
| 161 } | 188 } |
| 162 | 189 |
| 190 - (void)prepareForReuse { | |
| 191 [self.contentImageView removeFromSuperview]; | |
| 192 } | |
| 193 | |
| 163 #pragma mark - UIView | 194 #pragma mark - UIView |
| 164 | 195 |
| 165 // Implements -layoutSubviews as per instructions in documentation for | 196 // Implements -layoutSubviews as per instructions in documentation for |
| 166 // +[MDCCollectionViewCell cr_preferredHeightForWidth:forItem:]. | 197 // +[MDCCollectionViewCell cr_preferredHeightForWidth:forItem:]. |
| 167 - (void)layoutSubviews { | 198 - (void)layoutSubviews { |
| 168 [super layoutSubviews]; | 199 [super layoutSubviews]; |
| 169 | 200 |
| 170 // Adjust the text label preferredMaxLayoutWidth when the parent's width | 201 // Adjust the text label preferredMaxLayoutWidth when the parent's width |
| 171 // changes, for instance on screen rotation. | 202 // changes, for instance on screen rotation. |
| 172 CGFloat parentWidth = CGRectGetWidth(self.contentView.bounds); | 203 CGFloat parentWidth = CGRectGetWidth(self.contentView.bounds); |
| 173 | 204 |
| 174 self.titleLabel.preferredMaxLayoutWidth = | 205 self.titleLabel.preferredMaxLayoutWidth = |
| 175 parentWidth - kImageSize - 3 * kStandardSpacing; | 206 parentWidth - kImageSize - 3 * kStandardSpacing; |
| 176 self.subtitleLabel.preferredMaxLayoutWidth = | 207 self.subtitleLabel.preferredMaxLayoutWidth = |
| 177 parentWidth - kImageSize - 3 * kStandardSpacing; | 208 parentWidth - kImageSize - 3 * kStandardSpacing; |
| 178 | 209 |
| 179 // Re-layout with the new preferred width to allow the label to adjust its | 210 // Re-layout with the new preferred width to allow the label to adjust its |
| 180 // height. | 211 // height. |
| 181 [super layoutSubviews]; | 212 [super layoutSubviews]; |
| 182 } | 213 } |
| 183 | 214 |
| 184 #pragma mark - Private | 215 #pragma mark - Private |
| 185 | 216 |
| 186 - (void)applyConstraints { | 217 - (void)applyConstraints { |
| 187 [NSLayoutConstraint activateConstraints:@[ | 218 [NSLayoutConstraint activateConstraints:@[ |
| 188 [_imageView.widthAnchor constraintEqualToConstant:kImageSize], | 219 [_imageContainer.widthAnchor constraintEqualToConstant:kImageSize], |
| 189 [_imageView.heightAnchor constraintEqualToAnchor:_imageView.widthAnchor], | 220 [_imageContainer.heightAnchor |
| 190 [_imageView.topAnchor constraintEqualToAnchor:_titleLabel.topAnchor], | 221 constraintEqualToAnchor:_imageContainer.widthAnchor], |
| 222 [_imageContainer.topAnchor constraintEqualToAnchor:_titleLabel.topAnchor], | |
| 191 [_publisherLabel.topAnchor | 223 [_publisherLabel.topAnchor |
| 192 constraintGreaterThanOrEqualToAnchor:_imageView.bottomAnchor | 224 constraintGreaterThanOrEqualToAnchor:_imageContainer.bottomAnchor |
| 193 constant:kStandardSpacing], | 225 constant:kStandardSpacing], |
| 194 [_publisherLabel.topAnchor | 226 [_publisherLabel.topAnchor |
| 195 constraintGreaterThanOrEqualToAnchor:_subtitleLabel.bottomAnchor | 227 constraintGreaterThanOrEqualToAnchor:_subtitleLabel.bottomAnchor |
| 196 constant:kStandardSpacing], | 228 constant:kStandardSpacing], |
| 229 | |
| 230 // No image icon. | |
| 231 [_noImageIcon.centerXAnchor | |
| 232 constraintEqualToAnchor:_imageContainer.centerXAnchor], | |
| 233 [_noImageIcon.centerYAnchor | |
| 234 constraintEqualToAnchor:_imageContainer.centerYAnchor], | |
| 235 [_noImageIcon.widthAnchor constraintEqualToConstant:kIconSize], | |
| 236 [_noImageIcon.heightAnchor constraintEqualToAnchor:_noImageIcon.widthAnchor] | |
| 197 ]]; | 237 ]]; |
| 198 | 238 |
| 199 ApplyVisualConstraintsWithMetrics( | 239 ApplyVisualConstraintsWithMetrics( |
| 200 @[ | 240 @[ |
| 201 @"H:|-(space)-[title]-(space)-[image]-(space)-|", | 241 @"H:|-(space)-[title]-(space)-[image]-(space)-|", |
| 202 @"H:|-(space)-[text]-(space)-[image]", | 242 @"H:|-(space)-[text]-(space)-[image]", |
| 203 @"V:|-(space)-[title]-[text]", | 243 @"V:|-(space)-[title]-[text]", |
| 204 @"H:|-(space)-[publish]-(space)-|", | 244 @"H:|-(space)-[publish]-(space)-|", |
| 205 @"V:[publish]-|", | 245 @"V:[publish]-|", |
| 206 ], | 246 ], |
| 207 @{ | 247 @{ |
| 208 @"image" : _imageView, | 248 @"image" : _imageContainer, |
| 209 @"title" : _titleLabel, | 249 @"title" : _titleLabel, |
| 210 @"text" : _subtitleLabel, | 250 @"text" : _subtitleLabel, |
| 211 @"publish" : _publisherLabel, | 251 @"publish" : _publisherLabel, |
| 212 }, | 252 }, |
| 213 @{ @"space" : @(kStandardSpacing) }); | 253 @{ @"space" : @(kStandardSpacing) }); |
| 214 } | 254 } |
| 215 | 255 |
| 216 @end | 256 @end |
| OLD | NEW |