diff --git a/WordPress/Classes/Models/ReaderPost+Searchable.swift b/WordPress/Classes/Models/ReaderPost+Searchable.swift index dffd8223cf9a..296270892fa9 100644 --- a/WordPress/Classes/Models/ReaderPost+Searchable.swift +++ b/WordPress/Classes/Models/ReaderPost+Searchable.swift @@ -34,7 +34,7 @@ extension ReaderPost: SearchableItemConvertable { var searchDescription: String? { guard let readerPostPreview = contentPreviewForDisplay(), !readerPostPreview.isEmpty else { - return siteURLForDisplay() ?? contentForDisplay() + return blogURL ?? contentForDisplay() } return readerPostPreview } diff --git a/WordPress/Classes/Models/ReaderPost.h b/WordPress/Classes/Models/ReaderPost.h index 38700221e77e..8de9a3d82ba3 100644 --- a/WordPress/Classes/Models/ReaderPost.h +++ b/WordPress/Classes/Models/ReaderPost.h @@ -1,7 +1,12 @@ #import #import #import "BasePost.h" -#import "ReaderPostContentProvider.h" + +typedef NS_ENUM(NSUInteger, SourceAttributionStyle) { + SourceAttributionStyleNone, + SourceAttributionStylePost, + SourceAttributionStyleSite, +}; @class ReaderAbstractTopic; @class ReaderCrossPostMeta; @@ -13,7 +18,7 @@ extern NSString * const ReaderPostStoredCommentIDKey; extern NSString * const ReaderPostStoredCommentTextKey; -@interface ReaderPost : BasePost +@interface ReaderPost : BasePost @property (nonatomic, strong) NSString *authorDisplayName; @property (nonatomic, strong) NSString *authorEmail; @@ -78,10 +83,14 @@ extern NSString * const ReaderPostStoredCommentTextKey; + (instancetype)createOrReplaceFromRemotePost:(RemoteReaderPost *)remotePost forTopic:(ReaderAbstractTopic *)topic context:(NSManagedObjectContext *) managedObjectContext; - (BOOL)isCrossPost; -- (BOOL)isPrivate; - (BOOL)isP2Type; - (NSString *)authorString; - (BOOL)contentIncludesFeaturedImage; +- (NSURL *)siteIconForDisplayOfSize:(NSInteger)size; +- (SourceAttributionStyle)sourceAttributionStyle; +- (NSString *)sourceAuthorNameForDisplay; +- (NSURL *)sourceAvatarURLForDisplay; +- (NSString *)sourceBlogNameForDisplay; - (BOOL)isSourceAttributionWPCom; - (NSDictionary *)railcarDictionary; diff --git a/WordPress/Classes/Models/ReaderPost.m b/WordPress/Classes/Models/ReaderPost.m index 9edd2897b04a..8b9f2a216ffe 100644 --- a/WordPress/Classes/Models/ReaderPost.m +++ b/WordPress/Classes/Models/ReaderPost.m @@ -237,16 +237,6 @@ - (BOOL)isCrossPost return self.crossPostMeta != nil; } -- (BOOL)isAtomic -{ - return self.isBlogAtomic; -} - -- (BOOL)isPrivate -{ - return self.isBlogPrivate; -} - - (BOOL)isP2Type { NSInteger orgID = [self.organizationID intValue]; @@ -393,14 +383,6 @@ - (NSString *)sourceAuthorNameForDisplay return self.sourceAttribution.authorName; } -- (NSURL *)sourceAuthorURLForDisplay -{ - if (!self.sourceAttribution) { - return nil; - } - return [NSURL URLWithString:self.sourceAttribution.authorURL]; -} - - (NSURL *)sourceAvatarURLForDisplay { if (!self.sourceAttribution) { @@ -414,14 +396,6 @@ - (NSString *)sourceBlogNameForDisplay return self.sourceAttribution.blogName; } -- (NSURL *)sourceBlogURLForDisplay -{ - if (!self.sourceAttribution) { - return nil; - } - return [NSURL URLWithString:self.sourceAttribution.blogURL]; -} - - (BOOL)isSourceAttributionWPCom { return (self.sourceAttribution.blogID) ? YES : NO; @@ -432,26 +406,6 @@ - (NSURL *)avatarURLForDisplay return [NSURL URLWithString:self.authorAvatarURL]; } -- (NSString *)siteURLForDisplay -{ - return self.blogURL; -} - -- (NSString *)siteHostNameForDisplay -{ - return self.blogURL.hostname; -} - -- (NSString *)crossPostOriginSiteURLForDisplay -{ - return self.crossPostMeta.siteURL; -} - -- (BOOL)isCommentCrossPost -{ - return self.crossPostMeta.commentURL.length > 0; -} - - (NSDictionary *)railcarDictionary { if (!self.railcar) { @@ -467,7 +421,7 @@ - (NSDictionary *)railcarDictionary return nil; } -- (void) didSave { +- (void)didSave { [super didSave]; // A ReaderCard can have either a post, or a list of topics, but not both. diff --git a/WordPress/Classes/Models/ReaderPostContentProvider.h b/WordPress/Classes/Models/ReaderPostContentProvider.h deleted file mode 100644 index cfd8891374db..000000000000 --- a/WordPress/Classes/Models/ReaderPostContentProvider.h +++ /dev/null @@ -1,42 +0,0 @@ -#import -#import "PostContentProvider.h" - -typedef NS_ENUM(NSUInteger, SourceAttributionStyle) { - SourceAttributionStyleNone, - SourceAttributionStylePost, - SourceAttributionStyleSite, -}; - -@protocol ReaderPostContentProvider -- (NSNumber *)siteID; -- (NSURL *)siteIconForDisplayOfSize:(NSInteger)size; -- (SourceAttributionStyle)sourceAttributionStyle; -- (NSString *)sourceAuthorNameForDisplay; -- (NSURL *)sourceAuthorURLForDisplay; -- (NSURL *)sourceAvatarURLForDisplay; -- (NSString *)sourceBlogNameForDisplay; -- (NSURL *)sourceBlogURLForDisplay; - -- (NSString *)likeCountForDisplay; -- (NSNumber *)commentCount; -- (NSNumber *)likeCount; -- (BOOL)commentsOpen; -- (BOOL)isFollowing; -- (BOOL)isLikesEnabled; -- (BOOL)isAtomic; -- (BOOL)isPrivate; -- (BOOL)isLiked; -- (BOOL)isExternal; -- (BOOL)isJetpack; -- (BOOL)isWPCom; -- (BOOL)isSavedForLater; -- (NSString *)primaryTag; -- (NSNumber *)readingTime; -- (NSNumber *)wordCount; - -- (NSString *)siteURLForDisplay; -- (NSString *)siteHostNameForDisplay; -- (NSString *)crossPostOriginSiteURLForDisplay; -- (BOOL)isCommentCrossPost; - -@end diff --git a/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift b/WordPress/Classes/Networking/MediaHost+ReaderPost.swift similarity index 58% rename from WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift rename to WordPress/Classes/Networking/MediaHost+ReaderPost.swift index 7421acdf9fd9..ab5457464844 100644 --- a/WordPress/Classes/Networking/MediaHost+ReaderPostContentProvider.swift +++ b/WordPress/Classes/Networking/MediaHost+ReaderPost.swift @@ -4,12 +4,12 @@ import Foundation /// initialize it from a given `Blog`. /// extension MediaHost { - enum ReaderPostContentProviderError: Swift.Error { + enum ReaderPostError: Swift.Error { case baseInitializerError(error: Error) } - init(with readerPostContentProvider: ReaderPostContentProvider, failure: (ReaderPostContentProviderError) -> ()) { - let isAccessibleThroughWPCom = readerPostContentProvider.isWPCom() || readerPostContentProvider.isJetpack() + init(with post: ReaderPost, failure: (ReaderPostError) -> ()) { + let isAccessibleThroughWPCom = post.isWPCom || post.isJetpack // This is the only way in which we can obtain the username and authToken here. // It'd be nice if all data was associated with an account instead, for transparency @@ -22,14 +22,13 @@ extension MediaHost { self.init( isAccessibleThroughWPCom: isAccessibleThroughWPCom, - isPrivate: readerPostContentProvider.isPrivate(), - isAtomic: readerPostContentProvider.isAtomic(), - siteID: readerPostContentProvider.siteID()?.intValue, + isPrivate: post.isBlogPrivate, + isAtomic: post.isBlogAtomic, + siteID: post.siteID?.intValue, username: username, authToken: authToken, failure: { error in - // We just associate a ReaderPostContentProvider with the underlying error for simpler debugging. - failure(ReaderPostContentProviderError.baseInitializerError(error: error)) + failure(ReaderPostError.baseInitializerError(error: error)) } ) } diff --git a/WordPress/Classes/Services/CommentService.m b/WordPress/Classes/Services/CommentService.m index 578a14b9a0b5..11b2aac727d7 100644 --- a/WordPress/Classes/Services/CommentService.m +++ b/WordPress/Classes/Services/CommentService.m @@ -684,7 +684,7 @@ - (void)replyToPost:(ReaderPost *)post { // Create and optimistically save a comment, based on the current wpcom acct // post and content provided. - BOOL isPrivateSite = post.isPrivate; + BOOL isPrivateSite = post.isBlogPrivate; [self createHierarchicalCommentWithContent:content withParent:nil postObjectID:post.objectID siteID:post.siteID completion:^(NSManagedObjectID *commentID) { if (!commentID) { NSError *error = [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:@{NSDebugDescriptionErrorKey: @"Failed to create a comment for a post"}]; @@ -737,7 +737,7 @@ - (void)replyToHierarchicalCommentWithID:(NSNumber *)commentID { // Create and optimistically save a comment, based on the current wpcom acct // post and content provided. - BOOL isPrivateSite = post.isPrivate; + BOOL isPrivateSite = post.isBlogPrivate; [self createHierarchicalCommentWithContent:content withParent:nil postObjectID:post.objectID siteID:post.siteID completion:^(NSManagedObjectID *commentObjectID) { if (!commentObjectID) { NSError *error = [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:@{NSDebugDescriptionErrorKey: @"Failed to create a comment for a post"}]; @@ -1204,7 +1204,7 @@ - (BOOL)mergeHierarchicalComments:(NSArray *)comments forPage:(NSUInteger)page f comment.depth = ancestors.count; comment.post = post; - comment.content = [self sanitizeCommentContent:comment.content isPrivateSite:post.isPrivate]; + comment.content = [self sanitizeCommentContent:comment.content isPrivateSite:post.isBlogPrivate]; [commentsToKeep addObject:comment]; } diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index 4b2beb4c3dfb..87e08f402be7 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -50,7 +50,6 @@ #import "ReaderCommentsViewController.h" #import "ReaderGapMarker.h" #import "ReaderPost.h" -#import "ReaderPostContentProvider.h" #import "ReaderPostService.h" #import "ReaderSiteService.h" #import "ReaderSiteService_Internal.h" diff --git a/WordPress/Classes/Utility/Media/ImageView.swift b/WordPress/Classes/Utility/Media/ImageView.swift index ed0f8215946e..c1286f88b649 100644 --- a/WordPress/Classes/Utility/Media/ImageView.swift +++ b/WordPress/Classes/Utility/Media/ImageView.swift @@ -19,9 +19,6 @@ final class ImageView: UIView { var isErrorViewEnabled = true var loadingStyle = LoadingStyle.background - /// The background color to use when the image is loaded. - var successBackgroundColor = UIColor.clear - override init(frame: CGRect) { super.init(frame: frame) @@ -72,7 +69,7 @@ final class ImageView: UIView { case .success(let image): imageView.configure(image: image) imageView.isHidden = false - backgroundColor = successBackgroundColor + backgroundColor = .clear case .failure: if isErrorViewEnabled { makeErrorView().isHidden = false diff --git a/WordPress/Classes/ViewRelated/Comments/Views/Detail/ContentRenderer/RichCommentContentRenderer.swift b/WordPress/Classes/ViewRelated/Comments/Views/Detail/ContentRenderer/RichCommentContentRenderer.swift index ad75caad25ef..b220d279e421 100644 --- a/WordPress/Classes/ViewRelated/Comments/Views/Detail/ContentRenderer/RichCommentContentRenderer.swift +++ b/WordPress/Classes/ViewRelated/Comments/Views/Detail/ContentRenderer/RichCommentContentRenderer.swift @@ -75,7 +75,7 @@ private extension RichCommentContentRenderer { // We'll log the error, so we know it's there, but we won't halt execution. WordPressAppDelegate.crashLogging?.logError(error) }) - } else if let post = comment.post as? ReaderPost, post.isPrivate() { + } else if let post = comment.post as? ReaderPost, post.isBlogPrivate { return MediaHost(with: post, failure: { error in // We'll log the error, so we know it's there, but we won't halt execution. WordPressAppDelegate.crashLogging?.logError(error) diff --git a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderBlockedSiteCell.swift b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderBlockedSiteCell.swift index c0877bd1f6c9..3923fe5468f4 100644 --- a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderBlockedSiteCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderBlockedSiteCell.swift @@ -12,7 +12,7 @@ open class ReaderBlockedSiteCell: UITableViewCell { fileprivate func applyStyles() { contentView.backgroundColor = .systemGroupedBackground - borderedContentView.layer.borderColor = WPStyleGuide.readerCardCellBorderColor().cgColor + borderedContentView.layer.borderColor = UIColor.separator.cgColor borderedContentView.layer.borderWidth = .hairlineBorderWidth label.font = WPStyleGuide.subtitleFont() label.textColor = .secondaryLabel diff --git a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCardDiscoverAttributionView.swift b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCardDiscoverAttributionView.swift index 478f32e50c76..ba7e45267b31 100644 --- a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCardDiscoverAttributionView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCardDiscoverAttributionView.swift @@ -78,19 +78,19 @@ private enum ReaderCardDiscoverAttribution: Int { textLabel?.backgroundColor = backgroundColor } - @objc open func configureView(_ contentProvider: ReaderPostContentProvider?) { - if contentProvider?.sourceAttributionStyle() == SourceAttributionStyle.post { - configurePostAttribution(contentProvider!) - } else if contentProvider?.sourceAttributionStyle() == SourceAttributionStyle.site { - configureSiteAttribution(contentProvider!, verboseAttribution: false) + @objc open func configureView(_ post: ReaderPost?) { + if post?.sourceAttributionStyle() == SourceAttributionStyle.post { + configurePostAttribution(post!) + } else if post?.sourceAttributionStyle() == SourceAttributionStyle.site { + configureSiteAttribution(post!, verboseAttribution: false) } else { reset() } } - @objc open func configureViewWithVerboseSiteAttribution(_ contentProvider: ReaderPostContentProvider?) { - if let contentProvider = contentProvider { - configureSiteAttribution(contentProvider, verboseAttribution: true) + @objc open func configureViewWithVerboseSiteAttribution(_ post: ReaderPost?) { + if let post { + configureSiteAttribution(post, verboseAttribution: true) } else { reset() } @@ -102,26 +102,26 @@ private enum ReaderCardDiscoverAttribution: Int { attributionAction = .none } - fileprivate func configurePostAttribution(_ contentProvider: ReaderPostContentProvider) { - let url = contentProvider.sourceAvatarURLForDisplay() + fileprivate func configurePostAttribution(_ post: ReaderPost) { + let url = post.sourceAvatarURLForDisplay() let placeholder = UIImage(named: gravatarImageName) imageView.downloadImage(from: url, placeholderImage: placeholder) imageView.shouldRoundCorners = true - let str = stringForPostAttribution(contentProvider.sourceAuthorNameForDisplay(), - blogName: contentProvider.sourceBlogNameForDisplay()) + let str = stringForPostAttribution(post.sourceAuthorNameForDisplay(), + blogName: post.sourceBlogNameForDisplay()) let attributes = originalAttributionParagraphAttributes textLabel.attributedText = NSAttributedString(string: str, attributes: attributes) attributionAction = .none } - fileprivate func configureSiteAttribution(_ contentProvider: ReaderPostContentProvider, verboseAttribution verbose: Bool) { - let url = contentProvider.sourceAvatarURLForDisplay() + fileprivate func configureSiteAttribution(_ post: ReaderPost, verboseAttribution verbose: Bool) { + let url = post.sourceAvatarURLForDisplay() let placeholder = UIImage(named: blavatarImageName) imageView.downloadImage(from: url, placeholderImage: placeholder) imageView.shouldRoundCorners = false - let blogName = contentProvider.sourceBlogNameForDisplay() + let blogName = post.sourceBlogNameForDisplay() let pattern = patternForSiteAttribution(verbose) let str = String(format: pattern, blogName!) diff --git a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCrossPostCell.swift b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCrossPostCell.swift index 8247d70059ef..7dd1a5314e68 100644 --- a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCrossPostCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCrossPostCell.swift @@ -1,211 +1,143 @@ -import AlamofireImage import Foundation import AutomatticTracks import WordPressShared -open class ReaderCrossPostCell: UITableViewCell { +final class ReaderCrossPostCell: ReaderStreamBaseCell { + private let avatarView = ReaderAvatarView() + private let iconView = ReaderAvatarView() + private let headerLabel = UILabel() + private let postTitleLabel = UILabel() - // MARK: - Properties + private let insets = ReaderStreamBaseCell.insets + private let avatarSize: CGFloat = 28 + private let crossPostIconSize: CGFloat = 18 - @IBOutlet private weak var blavatarImageView: UIImageView! - @IBOutlet private weak var avatarImageView: UIImageView! - @IBOutlet private weak var titleLabel: UILabel! - @IBOutlet private weak var label: UILabel! - @IBOutlet private weak var borderView: UIView! - @IBOutlet private weak var topViewConstraint: NSLayoutConstraint! - @IBOutlet private weak var separatorViewHeightConstraint: NSLayoutConstraint! - @IBOutlet private weak var imageSpacingConstraint: NSLayoutConstraint! + private let postTitleAttributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.preferredFont(forTextStyle: .subheadline).semibold(), + .foregroundColor: UIColor.label + ] - private weak var contentProvider: ReaderPostContentProvider? + private let subtitleAttributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.preferredFont(forTextStyle: .footnote), + .foregroundColor: UIColor.secondaryLabel + ] - // MARK: - Accessors + private let boldSubtitleAttributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.preferredFont(forTextStyle: .footnote).semibold(), + .foregroundColor: UIColor.secondaryLabel + ] - private lazy var readerCrossPostTitleAttributes: [NSAttributedString.Key: Any] = { - return WPStyleGuide.readerCrossPostTitleAttributes() - }() + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) - private lazy var readerCrossPostSubtitleAttributes: [NSAttributedString.Key: Any] = { - return WPStyleGuide.readerCrossPostSubtitleAttributes() - }() + setupStyle() + setupLayout() - private lazy var readerCrossPostBoldSubtitleAttributes: [NSAttributedString.Key: Any] = { - return WPStyleGuide.readerCrossPostBoldSubtitleAttributes() - }() - - @objc open var enableLoggedInFeatures: Bool = true - - open override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - setHighlighted(selected, animated: animated) + selectedBackgroundView = ReaderPostCell.makeSelectedBackgroundView() } - open override func setHighlighted(_ highlighted: Bool, animated: Bool) { - let previouslyHighlighted = self.isHighlighted - super.setHighlighted(highlighted, animated: animated) - - if previouslyHighlighted == highlighted { - return - } - applyHighlightedEffect(highlighted, animated: animated) + required init?(coder: NSCoder) { + fatalError("Not implemented") } - // MARK: - Lifecycle Methods + override func prepareForReuse() { + super.prepareForReuse() - open override func awakeFromNib() { - super.awakeFromNib() - applyStyles() + avatarView.prepareForReuse() } - // MARK: - Configuration - - @objc open func configureCell(_ contentProvider: ReaderPostContentProvider) { - self.contentProvider = contentProvider - - configureTitleLabel() - configureLabel() - configureBlavatarImage() - configureAvatarImageView() - } - -} - -// MARK: - Private Methods + // MARK: Setup -private extension ReaderCrossPostCell { + private func setupStyle() { + headerLabel.numberOfLines = 2 + headerLabel.adjustsFontForContentSizeCategory = true + headerLabel.maximumContentSizeCategory = .accessibilityExtraLarge - struct Constants { - static let blavatarPlaceholderImage: UIImage? = UIImage(named: "post-blavatar-placeholder") - static let avatarPlaceholderImage: UIImage? = UIImage(named: "gravatar") - static let imageBorderWidth: CGFloat = 1 - static let xPostTitlePrefix = "X-post: " - static let commentTemplate = "%@ left a comment on %@, cross-posted to %@" - static let siteTemplate = "%@ cross-posted from %@ to %@" - } + postTitleLabel.numberOfLines = 2 + postTitleLabel.adjustsFontForContentSizeCategory = true + postTitleLabel.maximumContentSizeCategory = .accessibilityExtraLarge - // MARK: - Appearance - - func applyStyles() { - backgroundColor = .clear - contentView.backgroundColor = .systemBackground - borderView?.backgroundColor = .systemBackground - label?.backgroundColor = .systemBackground - titleLabel?.backgroundColor = .systemBackground - topViewConstraint.constant = 0.0 - separatorViewHeightConstraint.constant = 0.5 - imageSpacingConstraint.priority = .required + iconView.setStaticIcon(crossPostIcon, tintColor: .secondaryLabel) } - func applyHighlightedEffect(_ highlighted: Bool, animated: Bool) { - func updateBorder() { - label.alpha = highlighted ? 0.50 : 1 - titleLabel.alpha = highlighted ? 0.50 : 1 - } - guard animated else { - updateBorder() - return - } - UIView.animate(withDuration: 0.25, - delay: 0, - options: UIView.AnimationOptions(), - animations: updateBorder) - } - - // MARK: - Configuration - - func configureBlavatarImage() { - configureAvatarBorder(blavatarImageView) - let placeholder = Constants.blavatarPlaceholderImage - let size = blavatarImageView.frame.size.width * UIScreen.main.scale - - // Always reset - blavatarImageView.image = placeholder - - guard let contentProvider = contentProvider, - let url = contentProvider.siteIconForDisplay(ofSize: Int(size)) else { - return - } - - let host = MediaHost(with: contentProvider) { error in - WordPressAppDelegate.crashLogging?.logError(error) - } - - let mediaAuthenticator = MediaRequestAuthenticator() - mediaAuthenticator.authenticatedRequest(for: url, from: host, onComplete: { [weak self] request in - self?.blavatarImageView.af.setImage(withURLRequest: request, placeholderImage: placeholder) - }) { [weak self] error in - WordPressAppDelegate.crashLogging?.logError(error) - self?.blavatarImageView.image = placeholder + private func setupLayout() { + for view in [avatarView, iconView, headerLabel, postTitleLabel] { + addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false } + NSLayoutConstraint.activate([ + avatarView.widthAnchor.constraint(equalToConstant: avatarSize), + avatarView.heightAnchor.constraint(equalToConstant: avatarSize), + avatarView.centerYAnchor.constraint(equalTo: headerLabel.centerYAnchor), + avatarView.trailingAnchor.constraint(equalTo: headerLabel.leadingAnchor, constant: -8), + + iconView.widthAnchor.constraint(equalToConstant: crossPostIconSize), + iconView.heightAnchor.constraint(equalToConstant: crossPostIconSize), + iconView.centerXAnchor.constraint(equalTo: avatarView.centerXAnchor, constant: crossPostIconSize / 2 - 1), + iconView.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor, constant: crossPostIconSize / 2 + 3), + + headerLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12), + headerLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.left), + headerLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor), + + postTitleLabel.topAnchor.constraint(equalTo: headerLabel.bottomAnchor, constant: 6), + postTitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.left), + postTitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -insets.right), + postTitleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12), + ]) } - func configureAvatarImageView() { - configureAvatarBorder(avatarImageView) - let placeholder = Constants.avatarPlaceholderImage - - // Always reset - avatarImageView.image = placeholder + // MARK: Configuration - if let url = contentProvider?.avatarURLForDisplay() { - avatarImageView.downloadImage(from: url, placeholderImage: placeholder) - } - } - - func configureAvatarBorder(_ imageView: UIImageView) { - imageView.layer.borderColor = WPStyleGuide.readerCardBlogIconBorderColor().cgColor - imageView.layer.borderWidth = Constants.imageBorderWidth - imageView.layer.masksToBounds = true - } + func configure(with post: ReaderPost) { + headerLabel.attributedText = makeHeaderString(for: post) + postTitleLabel.attributedText = NSAttributedString(string: post.titleForDisplay() ?? "", attributes: postTitleAttributes) - func configureTitleLabel() { - if var title = contentProvider?.titleForDisplay(), !title.isEmpty() { - if let prefixRange = title.range(of: Constants.xPostTitlePrefix) { - title.removeSubrange(prefixRange) - } - - titleLabel.attributedText = NSAttributedString(string: title, attributes: readerCrossPostTitleAttributes) - titleLabel.isHidden = false - } else { - titleLabel.attributedText = nil - titleLabel.isHidden = true + avatarView.setPlaceholder(UIImage(named: "post-blavatar-placeholder")) + if let avatarURL = post.avatarURLForDisplay() { + let avatarSize = CGSize(width: avatarSize, height: avatarSize) + .scaled(by: UITraitCollection.current.displayScale) + avatarView.setImage(with: avatarURL, size: avatarSize) } } - func configureLabel() { - guard let contentProvider = contentProvider else { - return + private func makeHeaderString(for post: ReaderPost) -> NSAttributedString? { + guard let meta = post.crossPostMeta else { + return nil } + let template = meta.commentURL.isEmpty ? Strings.siteTemplate : Strings.commentTemplate - // Compose the subtitle - // These templates are deliberately not localized (for now) given the intended audience. - let template = contentProvider.isCommentCrossPost() ? Constants.commentTemplate : Constants.siteTemplate - - let authorName: NSString = contentProvider.authorForDisplay() as NSString - let siteName = subDomainNameFromPath(contentProvider.siteURLForDisplay()) - let originName = subDomainNameFromPath(contentProvider.crossPostOriginSiteURLForDisplay()) + let authorName: NSString = post.authorForDisplay() as NSString + let siteName = subdomainNameFromPath(post.blogURL) + let originName = subdomainNameFromPath(meta.siteURL) let subtitle = NSString(format: template as NSString, authorName, originName, siteName) as String - let attrSubtitle = NSMutableAttributedString(string: subtitle, attributes: readerCrossPostSubtitleAttributes) - - attrSubtitle.setAttributes(readerCrossPostBoldSubtitleAttributes, range: NSRange(location: 0, length: authorName.length)) + let string = NSMutableAttributedString(string: subtitle, attributes: subtitleAttributes) + string.setAttributes(boldSubtitleAttributes, range: NSRange(location: 0, length: authorName.length)) if let siteRange = subtitle.nsRange(of: siteName) { - attrSubtitle.setAttributes(readerCrossPostBoldSubtitleAttributes, range: siteRange) + string.setAttributes(boldSubtitleAttributes, range: siteRange) } - if let originRange = subtitle.nsRange(of: originName) { - attrSubtitle.setAttributes(readerCrossPostBoldSubtitleAttributes, range: originRange) + string.setAttributes(boldSubtitleAttributes, range: originRange) } - - label.attributedText = attrSubtitle + return string } +} - func subDomainNameFromPath(_ path: String) -> String { - guard let url = URL(string: path), - let host = url.host else { - return "" - } - - return host.components(separatedBy: ".").first ?? "" +private func subdomainNameFromPath(_ path: String) -> String { + guard let url = URL(string: path), let host = url.host else { + return "" } + return host.components(separatedBy: ".").first ?? "" +} + +private let crossPostIcon = UIImage(named: "wpl-shuffle")? + .resized(to: CGSize(width: 16, height: 16)) + .withRenderingMode(.alwaysTemplate) +private struct Strings { + // TODO: add localization but make sure to update ranges in makeHeaderString! + static let commentTemplate = "%1$@ left a comment on %2$@, cross-posted to %3$@" + static let siteTemplate = "%1$@ cross-posted from %2$@ to %3$@" } diff --git a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCrossPostCell.xib b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCrossPostCell.xib deleted file mode 100644 index 6d5c0e499dc8..000000000000 --- a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderCrossPostCell.xib +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCell.swift b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCell.swift index 54feb9bf4d02..2c92dd9a5a1c 100644 --- a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCell.swift @@ -2,15 +2,9 @@ import SwiftUI import UIKit import Combine -final class ReaderPostCell: UITableViewCell { +final class ReaderPostCell: ReaderStreamBaseCell { private let view = ReaderPostCellView() - private var isSeparatorHidden = false - private var isCompact: Bool = true { - didSet { - guard oldValue != isCompact else { return } - setNeedsUpdateConstraints() - } - } + private var contentViewConstraints: [NSLayoutConstraint] = [] static let avatarSize: CGFloat = 28 @@ -24,9 +18,12 @@ final class ReaderPostCell: UITableViewCell { view.topAnchor.constraint(equalTo: contentView.topAnchor), view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).withPriority(999), ]) + } - selectedBackgroundView = UIView() - selectedBackgroundView?.backgroundColor = UIColor.opaqueSeparator.withAlphaComponent(0.2) + static func makeSelectedBackgroundView() -> UIView { + let view = UIView() + view.backgroundColor = UIColor.opaqueSeparator.withAlphaComponent(0.2) + return view } required init?(coder: NSCoder) { @@ -39,29 +36,17 @@ final class ReaderPostCell: UITableViewCell { view.prepareForReuse() } - func configure( - with viewModel: ReaderPostCellViewModel, - isCompact: Bool, - isSeparatorHidden: Bool - ) { - self.isSeparatorHidden = isSeparatorHidden + func configure(with viewModel: ReaderPostCellViewModel, isCompact: Bool) { self.isCompact = isCompact view.isCompact = isCompact - updateSeparatorsInsets() view.configure(with: viewModel) accessibilityLabel = "\(viewModel.author). \(viewModel.title). \(viewModel.details)" } - override func layoutSubviews() { - super.layoutSubviews() - - updateSeparatorsInsets() - } - - private func updateSeparatorsInsets() { - separatorInset = UIEdgeInsets(.leading, isSeparatorHidden ? 9999 : view.insets.left + (isCompact ? 0 : contentView.readableContentGuide.layoutFrame.minX)) + override func didUpdateCompact(_ isCompact: Bool) { + setNeedsUpdateConstraints() } override func updateConstraints() { @@ -78,7 +63,7 @@ final class ReaderPostCell: UITableViewCell { private final class ReaderPostCellView: UIView { // Header - let avatarView = ImageView() + let avatarView = ReaderAvatarView() let buttonAuthor = makeAuthorButton() let timeLabel = UILabel() let buttonMore = makeButton(systemImage: "ellipsis", font: .systemFont(ofSize: 13)) @@ -103,7 +88,7 @@ private final class ReaderPostCellView: UIView { } } - let insets = UIEdgeInsets(top: 0, left: 44, bottom: 0, right: 16) + let insets = ReaderStreamBaseCell.insets private var viewModel: ReaderPostCellViewModel? // important: has to retain private let coverAspectRatio: CGFloat = 239.0 / 358.0 @@ -130,13 +115,6 @@ private final class ReaderPostCellView: UIView { } private func setupStyle() { - avatarView.layer.cornerRadius = ReaderPostCell.avatarSize / 2 - avatarView.layer.masksToBounds = true - avatarView.successBackgroundColor = UIColor.white - avatarView.layer.borderWidth = 0.5 - avatarView.layer.borderColor = UIColor.opaqueSeparator.withAlphaComponent(0.5).cgColor - avatarView.isErrorViewEnabled = false - buttonAuthor.maximumContentSizeCategory = .accessibilityLarge setupTimeLabel(timeLabel) timeLabel.setContentCompressionResistancePriority(.init(800), for: .horizontal) @@ -331,7 +309,7 @@ private final class ReaderPostCellView: UIView { } private func setAvatar(with viewModel: ReaderPostCellViewModel) { - avatarView.imageView.image = UIImage(named: "post-blavatar-placeholder") + avatarView.setPlaceholder(UIImage(named: "post-blavatar-placeholder")) let avatarSize = CGSize(width: ReaderPostCell.avatarSize, height: ReaderPostCell.avatarSize) .scaled(by: UITraitCollection.current.displayScale) if let avatarURL = viewModel.avatarURL { diff --git a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCellViewModel.swift b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCellViewModel.swift index 455ae6737bd7..eeb6f091e7ce 100644 --- a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCellViewModel.swift @@ -115,9 +115,9 @@ struct ReaderPostToolbarViewModel { isBookmarked: post.isSavedForLater, isCommentsEnabled: post.isCommentsEnabled, commentCount: post.commentCount?.intValue ?? 0, - isLikesEnabled: post.isLikesEnabled(), + isLikesEnabled: post.isLikesEnabled, likeCount: post.likeCount?.intValue ?? 0, - isLiked: post.isLiked() + isLiked: post.isLiked ) } } diff --git a/WordPress/Classes/ViewRelated/Reader/Cards/ReaderStreamBaseCell.swift b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderStreamBaseCell.swift new file mode 100644 index 000000000000..931aa1a20032 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/Cards/ReaderStreamBaseCell.swift @@ -0,0 +1,44 @@ +import UIKit + +class ReaderStreamBaseCell: UITableViewCell { + static let insets = UIEdgeInsets(top: 0, left: 44, bottom: 0, right: 16) + + var isCompact: Bool = true { + didSet { + guard oldValue != isCompact else { return } + didUpdateCompact(isCompact) + } + } + + var isSeparatorHidden = false { + didSet { + guard oldValue != isSeparatorHidden else { return } + updateSeparatorsInsets() + } + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + selectedBackgroundView = UIView() + selectedBackgroundView?.backgroundColor = UIColor.opaqueSeparator.withAlphaComponent(0.2) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + updateSeparatorsInsets() + } + + private func updateSeparatorsInsets() { + separatorInset = UIEdgeInsets(.leading, isSeparatorHidden ? 9999 : Self.insets.left + (isCompact ? 0 : contentView.readableContentGuide.layoutFrame.minX)) + } + + func didUpdateCompact(_ isCompact: Bool) { + // Do nothing + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderCellConfiguration.swift b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderCellConfiguration.swift index 503e8ba61abf..988113687a78 100644 --- a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderCellConfiguration.swift +++ b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderCellConfiguration.swift @@ -2,21 +2,6 @@ import UIKit /// Configuration and population of cells in Reader final class ReaderCellConfiguration { - func configureCrossPostCell(_ cell: ReaderCrossPostCell, withContent content: ReaderTableContent, atIndexPath indexPath: IndexPath) { - if content.isNull { - return - } - cell.accessoryType = .none - cell.selectionStyle = .none - - guard let posts = content.content as? [ReaderPost] else { - return - } - - let post = posts[indexPath.row] - cell.configureCell(post) - } - func configureBlockedCell(_ cell: ReaderBlockedSiteCell, withContent content: ReaderTableContent, atIndexPath indexPath: IndexPath) { if content.isNull { return diff --git a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderHelpers.swift b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderHelpers.swift index 4a3973c92ca9..94e41a18c149 100644 --- a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderHelpers.swift +++ b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderHelpers.swift @@ -226,7 +226,7 @@ struct ReaderNotificationKeys { // If the user is an admin on the post's site do not bump the page view unless // the the post is private. - if !post.isPrivate() && isUserAdminOnSiteWithID(siteID) { + if !post.isBlogPrivate && isUserAdminOnSiteWithID(siteID) { return } diff --git a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderStreamViewController.swift index 42ddb2a70bef..d9df9a51d6dd 100644 --- a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderStreamViewController.swift @@ -1398,17 +1398,22 @@ extension ReaderStreamViewController: WPTableViewHandlerDelegate { return cell } + let isCompact = traitCollection.horizontalSizeClass == .compact + if post.isCross() { let cell = tableConfiguration.crossPostCell(tableView) - cellConfiguration.configureCrossPostCell(cell, withContent: content, atIndexPath: indexPath) - hideSeparator(for: cell) + cell.isCompact = isCompact + cell.isSeparatorHidden = !showsSeparator + cell.configure(with: post) return cell } - let cell = tableConfiguration.postCell(in: tableView, for: indexPath) let viewModel = ReaderPostCellViewModel(post: post, topic: readerTopic) viewModel.viewController = self - cell.configure(with: viewModel, isCompact: traitCollection.horizontalSizeClass == .compact, isSeparatorHidden: !showsSeparator) + + let cell = tableConfiguration.postCell(in: tableView, for: indexPath) + cell.configure(with: viewModel, isCompact: isCompact) + cell.isSeparatorHidden = !showsSeparator return cell } diff --git a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderTableConfiguration.swift b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderTableConfiguration.swift index 65f516f3a0a9..6034aa67f38c 100644 --- a/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderTableConfiguration.swift +++ b/WordPress/Classes/ViewRelated/Reader/Controllers/ReaderTableConfiguration.swift @@ -1,13 +1,14 @@ +import UIKit + /// Registration and dequeuing of cells for table views in Reader final class ReaderTableConfiguration { private let footerViewNibName = "PostListFooterView" - private let readerPostCellReuseIdentifier = "ReaderPostCellReuseIdentifier" - private let readerBlockedCellNibName = "ReaderBlockedSiteCell" - private let readerBlockedCellReuseIdentifier = "ReaderBlockedCellReuseIdentifier" - private let readerGapMarkerCellNibName = "ReaderGapMarkerCell" - private let readerGapMarkerCellReuseIdentifier = "ReaderGapMarkerCellReuseIdentifier" - private let readerCrossPostCellNibName = "ReaderCrossPostCell" - private let readerCrossPostCellReuseIdentifier = "ReaderCrossPostCellReuseIdentifier" + private let postCellReuseIdentifier = "ReaderPostCellReuseIdentifier" + private let crossPostCellReuseIdentifier = "ReaderCrossPostCellReuseIdentifier" + private let blockedCellNibName = "ReaderBlockedSiteCell" + private let blockedCellReuseIdentifier = "ReaderBlockedCellReuseIdentifier" + private let gapMarkerCellNibName = "ReaderGapMarkerCell" + private let gapMarkerCellReuseIdentifier = "ReaderGapMarkerCellReuseIdentifier" private let rowHeight = CGFloat(415.0) @@ -15,9 +16,9 @@ final class ReaderTableConfiguration { setupAccessibility(tableView) setUpBlockerCell(tableView) setUpGapMarkerCell(tableView) - setUpCrossPostCell(tableView) - tableView.register(ReaderPostCell.self, forCellReuseIdentifier: readerPostCellReuseIdentifier) + tableView.register(ReaderPostCell.self, forCellReuseIdentifier: postCellReuseIdentifier) + tableView.register(ReaderCrossPostCell.self, forCellReuseIdentifier: crossPostCellReuseIdentifier) } private func setupAccessibility(_ tableView: UITableView) { @@ -25,18 +26,13 @@ final class ReaderTableConfiguration { } private func setUpBlockerCell(_ tableView: UITableView) { - let nib = UINib(nibName: readerBlockedCellNibName, bundle: nil) - tableView.register(nib, forCellReuseIdentifier: readerBlockedCellReuseIdentifier) + let nib = UINib(nibName: blockedCellNibName, bundle: nil) + tableView.register(nib, forCellReuseIdentifier: blockedCellReuseIdentifier) } private func setUpGapMarkerCell(_ tableView: UITableView) { - let nib = UINib(nibName: readerGapMarkerCellNibName, bundle: nil) - tableView.register(nib, forCellReuseIdentifier: readerGapMarkerCellReuseIdentifier) - } - - private func setUpCrossPostCell(_ tableView: UITableView) { - let nib = UINib(nibName: readerCrossPostCellNibName, bundle: nil) - tableView.register(nib, forCellReuseIdentifier: readerCrossPostCellReuseIdentifier) + let nib = UINib(nibName: gapMarkerCellNibName, bundle: nil) + tableView.register(nib, forCellReuseIdentifier: gapMarkerCellReuseIdentifier) } func footer() -> PostListFooterView { @@ -52,18 +48,18 @@ final class ReaderTableConfiguration { } func crossPostCell(_ tableView: UITableView) -> ReaderCrossPostCell { - return tableView.dequeueReusableCell(withIdentifier: readerCrossPostCellReuseIdentifier) as! ReaderCrossPostCell + tableView.dequeueReusableCell(withIdentifier: crossPostCellReuseIdentifier) as! ReaderCrossPostCell } func postCell(in tableView: UITableView, for indexPath: IndexPath) -> ReaderPostCell { - tableView.dequeueReusableCell(withIdentifier: readerPostCellReuseIdentifier, for: indexPath) as! ReaderPostCell + tableView.dequeueReusableCell(withIdentifier: postCellReuseIdentifier, for: indexPath) as! ReaderPostCell } func gapMarkerCell(_ tableView: UITableView) -> ReaderGapMarkerCell { - return tableView.dequeueReusableCell(withIdentifier: readerGapMarkerCellReuseIdentifier) as! ReaderGapMarkerCell + tableView.dequeueReusableCell(withIdentifier: gapMarkerCellReuseIdentifier) as! ReaderGapMarkerCell } func blockedSiteCell(_ tableView: UITableView) -> ReaderBlockedSiteCell { - return tableView.dequeueReusableCell(withIdentifier: readerBlockedCellReuseIdentifier) as! ReaderBlockedSiteCell + tableView.dequeueReusableCell(withIdentifier: blockedCellReuseIdentifier) as! ReaderBlockedSiteCell } } diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift index d242bde4c20f..1371ff20a10d 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailToolbar.swift @@ -259,7 +259,7 @@ class ReaderDetailToolbar: UIView, NibLoadable { return } - reblogButton.isEnabled = ReaderHelpers.isLoggedIn() && !post.isPrivate() + reblogButton.isEnabled = ReaderHelpers.isLoggedIn() && !post.isBlogPrivate WPStyleGuide.applyReaderReblogActionButtonStyle(reblogButton, showTitle: false) configureActionButtonStyle(reblogButton) diff --git a/WordPress/Classes/ViewRelated/Reader/Headers/ReaderListStreamHeader.swift b/WordPress/Classes/ViewRelated/Reader/Headers/ReaderListStreamHeader.swift index 68bfd8e7eaa7..117e839ac132 100644 --- a/WordPress/Classes/ViewRelated/Reader/Headers/ReaderListStreamHeader.swift +++ b/WordPress/Classes/ViewRelated/Reader/Headers/ReaderListStreamHeader.swift @@ -20,7 +20,7 @@ import WordPressShared @objc func applyStyles() { backgroundColor = .systemGroupedBackground borderedView.backgroundColor = .secondarySystemGroupedBackground - borderedView.layer.borderColor = WPStyleGuide.readerCardCellBorderColor().cgColor + borderedView.layer.borderColor = UIColor.separator.cgColor borderedView.layer.borderWidth = .hairlineBorderWidth WPStyleGuide.applyReaderStreamHeaderTitleStyle(titleLabel) WPStyleGuide.applyReaderStreamHeaderDetailStyle(detailLabel) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift index 1c38880941dc..2669bf1947dd 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift @@ -3,10 +3,10 @@ import Foundation extension ReaderPost { var isCommentsEnabled: Bool { - let usesWPComAPI = isWPCom() || isJetpack() - let commentCount = commentCount()?.intValue ?? 0 + let usesWPComAPI = isWPCom || isJetpack + let commentCount = commentCount?.intValue ?? 0 let hasComments = commentCount > 0 - return usesWPComAPI && (commentsOpen() || hasComments) + return usesWPComAPI && (commentsOpen || hasComments) } } diff --git a/WordPress/Classes/ViewRelated/Reader/Style/WPStyleGuide+Reader.swift b/WordPress/Classes/ViewRelated/Reader/Style/WPStyleGuide+Reader.swift index 44a7e62c5d42..707ce8807e39 100644 --- a/WordPress/Classes/ViewRelated/Reader/Style/WPStyleGuide+Reader.swift +++ b/WordPress/Classes/ViewRelated/Reader/Style/WPStyleGuide+Reader.swift @@ -5,7 +5,6 @@ import Gridicons /// A WPStyleGuide extension with styles and methods specific to the Reader feature. /// extension WPStyleGuide { - // MARK: - Original Post/Site Attribution Styles. @objc public class func originalAttributionParagraphAttributes() -> [NSAttributedString.Key: Any] { @@ -24,41 +23,6 @@ extension WPStyleGuide { return Cards.contentTextStyle } - // MARK: - Custom Colors - @objc public class func readerCardCellBorderColor() -> UIColor { - .separator - } - - public class func readerCardBlogIconBorderColor() -> UIColor { - return UIColor(light: UIAppColor.gray(.shade0), dark: .systemGray5) - } - - // MARK: - Card Attributed Text Attributes - - @objc public class func readerCrossPostTitleAttributes() -> [NSAttributedString.Key: Any] { - let font = UIFont.preferredFont(forTextStyle: .subheadline).semibold() - return [ - .font: font, - .foregroundColor: UIColor.label - ] - } - - @objc public class func readerCrossPostBoldSubtitleAttributes() -> [NSAttributedString.Key: Any] { - let font = UIFont.preferredFont(forTextStyle: .footnote).semibold() - return [ - .font: font, - .foregroundColor: UIColor.secondaryLabel - ] - } - - @objc public class func readerCrossPostSubtitleAttributes() -> [NSAttributedString.Key: Any] { - let font = UIFont.preferredFont(forTextStyle: .footnote) - return [ - .font: font, - .foregroundColor: UIColor.secondaryLabel - ] - } - // MARK: - No Followed Sites Error Text Attributes @objc public class func noFollowedSitesErrorTitleAttributes() -> [NSAttributedString.Key: Any] { let paragraphStyle = NSMutableParagraphStyle() diff --git a/WordPress/Classes/ViewRelated/Reader/Views/ReaderAvatarView.swift b/WordPress/Classes/ViewRelated/Reader/Views/ReaderAvatarView.swift new file mode 100644 index 000000000000..93df7a99e0ef --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/Views/ReaderAvatarView.swift @@ -0,0 +1,48 @@ +import UIKit + +final class ReaderAvatarView: UIView { + private let asyncImageView = ImageView() + + override init(frame: CGRect) { + super.init(frame: frame) + + layer.masksToBounds = true + backgroundColor = UIColor.white + + layer.borderWidth = 0.5 + layer.borderColor = UIColor.opaqueSeparator.withAlphaComponent(0.5).cgColor + + asyncImageView.isErrorViewEnabled = false + + addSubview(asyncImageView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + asyncImageView.frame = bounds + layer.cornerRadius = bounds.width / 2 + } + + func prepareForReuse() { + asyncImageView.prepareForReuse() + } + + func setStaticIcon(_ image: UIImage?, tintColor: UIColor) { + asyncImageView.imageView.tintColor = .secondaryLabel + asyncImageView.imageView.contentMode = .center + asyncImageView.imageView.image = image + } + + func setPlaceholder(_ image: UIImage?) { + asyncImageView.imageView.image = image + } + + func setImage(with imageURL: URL, size: CGSize? = nil) { + asyncImageView.setImage(with: imageURL, size: size) + } +} diff --git a/WordPress/Resources/AppImages.xcassets/_WordPressDesignLibrary/wpl-shuffle.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/_WordPressDesignLibrary/wpl-shuffle.imageset/Contents.json new file mode 100644 index 000000000000..0c895df80f95 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/_WordPressDesignLibrary/wpl-shuffle.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "wpl-shuffle.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/_WordPressDesignLibrary/wpl-shuffle.imageset/wpl-shuffle.pdf b/WordPress/Resources/AppImages.xcassets/_WordPressDesignLibrary/wpl-shuffle.imageset/wpl-shuffle.pdf new file mode 100644 index 000000000000..28aabc0e7833 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/_WordPressDesignLibrary/wpl-shuffle.imageset/wpl-shuffle.pdf differ