From e3cf8762a6e983eb5594c760f4f40401a84e95b1 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 13 Dec 2024 10:35:06 -0500 Subject: [PATCH 1/2] Rework PostCompactCell --- .../Posts/DashboardPostsListCardCell.swift | 3 +- .../Cards/Posts/PostsCardViewModel.swift | 3 +- .../Post/Views/PostCompactCell.swift | 207 ++++-------------- .../Post/Views/PostCompactCell.xib | 172 --------------- 4 files changed, 45 insertions(+), 340 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.xib diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift index 0d77b9bff0f4..cb07a51ff778 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/DashboardPostsListCardCell.swift @@ -23,8 +23,7 @@ class DashboardPostsListCardCell: UICollectionViewCell, Reusable { tableView.translatesAutoresizingMaskIntoConstraints = false tableView.isScrollEnabled = false tableView.backgroundColor = nil - let postCompactCellNib = PostCompactCell.defaultNib - tableView.register(postCompactCellNib, forCellReuseIdentifier: PostCompactCell.defaultReuseID) + tableView.register(PostCompactCell.self, forCellReuseIdentifier: PostCompactCell.defaultReuseID) let ghostCellNib = BlogDashboardPostCardGhostCell.defaultNib tableView.register(ghostCellNib, forCellReuseIdentifier: BlogDashboardPostCardGhostCell.defaultReuseID) tableView.register(DashboardPostListErrorCell.self, forCellReuseIdentifier: DashboardPostListErrorCell.defaultReuseID) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift index b7b7507033e3..35900c7e0edf 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift @@ -127,8 +127,7 @@ private extension PostsCardViewModel { let cell = tableView.dequeueReusableCell(withIdentifier: PostCompactCell.defaultReuseID, for: indexPath) as? PostCompactCell - cell?.accessoryType = .none - cell?.configureForDashboard(with: post) + cell?.configure(with: post) return cell ?? UITableViewCell() } diff --git a/WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.swift b/WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.swift index 97fd0045d947..3ea6a301a827 100644 --- a/WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.swift +++ b/WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.swift @@ -1,107 +1,79 @@ import AutomatticTracks import UIKit -import Gridicons import WordPressShared import WordPressUI -class PostCompactCell: UITableViewCell { - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var timestampLabel: UILabel! - @IBOutlet weak var badgesLabel: UILabel! - @IBOutlet weak var menuButton: UIButton! - @IBOutlet weak var featuredImageView: CachedAnimatedImageView! - @IBOutlet weak var headerStackView: UIStackView! - @IBOutlet weak var innerView: UIView! - @IBOutlet weak var contentStackView: UIStackView! - @IBOutlet weak var ghostView: UIView! - @IBOutlet weak var separator: UIView! - - @IBOutlet weak var trailingContentConstraint: NSLayoutConstraint! - - private var iPadReadableLeadingAnchor: NSLayoutConstraint? - private var iPadReadableTrailingAnchor: NSLayoutConstraint? - - lazy var imageLoader: ImageLoader = { - return ImageLoader(imageView: featuredImageView, gifStrategy: .mediumGIFs) - }() +final class PostCompactCell: UITableViewCell, Reusable { + private let titleLabel = UILabel() + private let detailsLabel = UILabel() + private let featuredImageView = AsyncImageView() private var post: Post? { didSet { - guard let post, post != oldValue else { - return - } - + guard let post, post != oldValue else { return } viewModel = PostCardStatusViewModel(post: post) } } private var viewModel: PostCardStatusViewModel? - func configure(with post: Post) { - self.post = post + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) - resetGhostStyles() - configureTitle() - configureDate() - configureStatus() - configureFeaturedImage() - configureMenuInteraction() + setupStyles() + setupLayout() } - @IBAction func more(_ sender: Any) { - // Do nothing. The compact cell is only shown in the dashboard, where the more button is hidden. + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - override func awakeFromNib() { - super.awakeFromNib() - applyStyles() - setupReadableGuideForiPad() - setupSeparator() - setupAccessibility() - } + func configure(with post: Post) { + self.post = post - private func resetGhostStyles() { - toggleGhost(visible: false) - menuButton.layer.opacity = Constants.opacity + titleLabel.text = post.titleForDisplay() + detailsLabel.text = post.contentPreviewForDisplay() + configureFeaturedImage() } - private func applyStyles() { + private func setupStyles() { WPStyleGuide.configureTableViewCell(self) WPStyleGuide.applyPostCardStyle(self) - WPStyleGuide.configureLabel(timestampLabel, textStyle: .subheadline) - WPStyleGuide.configureLabel(badgesLabel, textStyle: .subheadline) - titleLabel.font = AppStyleGuide.prominentFont(textStyle: .headline, weight: .bold) + titleLabel.font = .preferredFont(forTextStyle: .headline) titleLabel.adjustsFontForContentSizeCategory = true - titleLabel.textColor = .label - timestampLabel.textColor = .secondaryLabel - menuButton.tintColor = .secondaryLabel + titleLabel.numberOfLines = 1 - menuButton.setImage(.gridicon(.ellipsis), for: .normal) + detailsLabel.font = .preferredFont(forTextStyle: .subheadline) + detailsLabel.textColor = .secondaryLabel + detailsLabel.numberOfLines = 1 featuredImageView.layer.cornerRadius = Constants.imageRadius + featuredImageView.layer.masksToBounds = true - innerView.backgroundColor = .secondarySystemGroupedBackground - backgroundColor = .secondarySystemGroupedBackground - contentView.backgroundColor = .secondarySystemGroupedBackground - } - - private func setupSeparator() { - WPStyleGuide.applyBorderStyle(separator) + contentView.backgroundColor = .systemBackground } - private func setupReadableGuideForiPad() { - guard WPDeviceIdentification.isiPad() else { return } - - iPadReadableLeadingAnchor = innerView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor) - iPadReadableTrailingAnchor = innerView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor) + private func setupLayout() { + let stackView = UIStackView(alignment: .top, spacing: 8, [ + UIStackView(axis: .vertical, alignment: .leading, spacing: 2, [ + titleLabel, detailsLabel + ]), + featuredImageView + ]) + contentView.addSubview(stackView) + stackView.pinEdges(insets: UIEdgeInsets(horizontal: 16, vertical: 8)) - iPadReadableLeadingAnchor?.isActive = true - iPadReadableTrailingAnchor?.isActive = true + NSLayoutConstraint.activate([ + featuredImageView.widthAnchor.constraint(equalToConstant: Constants.imageSize.width), + featuredImageView.heightAnchor.constraint(equalToConstant: Constants.imageSize.height), + ]) } private func configureFeaturedImage() { + featuredImageView.prepareForReuse() + if let post, let url = post.featuredImageURL { featuredImageView.isHidden = false @@ -110,108 +82,15 @@ class PostCompactCell: UITableViewCell { WordPressAppDelegate.crashLogging?.logError(error) }) - imageLoader.loadImage(with: url, from: host, preferredSize: CGSize(width: featuredImageView.frame.width, height: featuredImageView.frame.height)) + let targetSize = Constants.imageSize.scaled(by: traitCollection.displayScale) + featuredImageView.setImage(with: url, host: host, size: targetSize) } else { featuredImageView.isHidden = true } } - private func configureTitle() { - titleLabel.text = post?.titleForDisplay() - } - - private func configureDate() { - guard let post else { - return - } - - timestampLabel.text = post.latest().dateStringForDisplay() - timestampLabel.isHidden = false - } - - private func configureExcerpt() { - guard let post else { - return - } - - timestampLabel.text = post.contentPreviewForDisplay() - timestampLabel.isHidden = false - } - - private func configureStatus() { - guard let viewModel else { - return - } - badgesLabel.textColor = viewModel.statusColor - badgesLabel.text = viewModel.statusAndBadges(separatedBy: Constants.separator) - if badgesLabel.text?.isEmpty ?? true { - badgesLabel.isHidden = true - } else { - badgesLabel.isHidden = false - } - } - - private func configureMenuInteraction() { - menuButton.isEnabled = true - menuButton.alpha = 1.0 - } - - private func setupAccessibility() { - menuButton.accessibilityLabel = - NSLocalizedString("More", comment: "Accessibility label for the More button in Post List (compact view).") - } - private enum Constants { - static let separator = " · " - static let imageRadius: CGFloat = 2 - static let opacity: Float = 1 - static let margin: CGFloat = 16 - } -} - -extension PostCompactCell: GhostableView { - func ghostAnimationWillStart() { - toggleGhost(visible: true) - menuButton.layer.opacity = GhostConstants.opacity - } - - private func toggleGhost(visible: Bool) { - isUserInteractionEnabled = !visible - menuButton.isGhostableDisabled = true - separator.isGhostableDisabled = true - ghostView.isHidden = !visible - ghostView.backgroundColor = .secondarySystemGroupedBackground - contentStackView.isHidden = visible - } - - private enum GhostConstants { - static let opacity: Float = 0.5 - } -} - -extension PostCompactCell: NibReusable { } - -// MARK: - For display on the Posts Card (Dashboard) - -extension PostCompactCell { - /// Configure the cell to be displayed in the Posts Card - /// No "more" button and show a description, instead of a date - func configureForDashboard(with post: Post) { - configure(with: post) - separator.isHidden = true - menuButton.isHidden = true - trailingContentConstraint.constant = Constants.margin - headerStackView.spacing = Constants.margin - - disableiPadReadableMargin() - - if !post.isScheduled() { - configureExcerpt() - } - } - - func disableiPadReadableMargin() { - iPadReadableLeadingAnchor?.isActive = false - iPadReadableTrailingAnchor?.isActive = false + static let imageRadius: CGFloat = 4 + static let imageSize = CGSize(width: 40, height: 40) } } diff --git a/WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.xib b/WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.xib deleted file mode 100644 index aacee20ed9ce..000000000000 --- a/WordPress/Classes/ViewRelated/Post/Views/PostCompactCell.xib +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - - - NotoSerif-Bold - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From ad329f61752bb1450a2bcfb2cd1b569aa3173268 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 13 Dec 2024 13:59:33 -0500 Subject: [PATCH 2/2] Remove obsolete tests --- WordPress/WordPress.xcodeproj/project.pbxproj | 8 -- .../PostCompactCellGhostableTests.swift | 54 ------------- .../WordPressTest/PostCompactCellTests.swift | 76 ------------------- 3 files changed, 138 deletions(-) delete mode 100644 WordPress/WordPressTest/PostCompactCellGhostableTests.swift delete mode 100644 WordPress/WordPressTest/PostCompactCellTests.swift diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 2b22d99ace40..03bd379d4b60 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -683,10 +683,8 @@ 570BFD902282418A007859A8 /* PostBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 570BFD8F2282418A007859A8 /* PostBuilder.swift */; }; 572FB401223A806000933C76 /* NoticeStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572FB400223A806000933C76 /* NoticeStoreTests.swift */; }; 575802132357C41200E4C63C /* MediaCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575802122357C41200E4C63C /* MediaCoordinatorTests.swift */; }; - 575E126322973EBB0041B3EB /* PostCompactCellGhostableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575E126222973EBB0041B3EB /* PostCompactCellGhostableTests.swift */; }; 57889AB823589DF100DAE56D /* PageBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57889AB723589DF100DAE56D /* PageBuilder.swift */; }; 57B71D4E230DB5F200789A68 /* BlogBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57B71D4D230DB5F200789A68 /* BlogBuilder.swift */; }; - 57D6C83E22945A10003DDC7E /* PostCompactCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D6C83D22945A10003DDC7E /* PostCompactCellTests.swift */; }; 5948AD111AB73D19006E8882 /* WPAppAnalyticsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5948AD101AB73D19006E8882 /* WPAppAnalyticsTests.m */; }; 5960967F1CF7959300848496 /* PostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5960967E1CF7959300848496 /* PostTests.swift */; }; 5981FE051AB8A89A0009E080 /* WPUserAgentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5981FE041AB8A89A0009E080 /* WPUserAgentTests.m */; }; @@ -2574,10 +2572,8 @@ 570BFD8F2282418A007859A8 /* PostBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBuilder.swift; sourceTree = ""; }; 572FB400223A806000933C76 /* NoticeStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeStoreTests.swift; sourceTree = ""; }; 575802122357C41200E4C63C /* MediaCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaCoordinatorTests.swift; path = Services/MediaCoordinatorTests.swift; sourceTree = ""; }; - 575E126222973EBB0041B3EB /* PostCompactCellGhostableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCompactCellGhostableTests.swift; sourceTree = ""; }; 57889AB723589DF100DAE56D /* PageBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PageBuilder.swift; path = TestUtilities/PageBuilder.swift; sourceTree = ""; }; 57B71D4D230DB5F200789A68 /* BlogBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogBuilder.swift; sourceTree = ""; }; - 57D6C83D22945A10003DDC7E /* PostCompactCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCompactCellTests.swift; sourceTree = ""; }; 57E15BC2269B6B7419464B6F /* Pods_Apps_Jetpack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Apps_Jetpack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5948AD101AB73D19006E8882 /* WPAppAnalyticsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = WPAppAnalyticsTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 5960967E1CF7959300848496 /* PostTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostTests.swift; sourceTree = ""; }; @@ -6166,8 +6162,6 @@ 57E3C98223835A57004741DB /* Controllers */, 57DF04BF2314895E00CC93D6 /* Views */, E10F3DA01E5C2CE0008FAADA /* PostListFilterTests.swift */, - 57D6C83D22945A10003DDC7E /* PostCompactCellTests.swift */, - 575E126222973EBB0041B3EB /* PostCompactCellGhostableTests.swift */, ); name = Post; sourceTree = ""; @@ -10174,7 +10168,6 @@ 8B69F100255C4870006B1CEF /* ActivityStoreTests.swift in Sources */, B5C0CF3F204DB92F00DB0362 /* NotificationReplyStoreTests.swift in Sources */, 4A5598852B05AC180083C220 /* PagesListTests.swift in Sources */, - 575E126322973EBB0041B3EB /* PostCompactCellGhostableTests.swift in Sources */, 2481B20C260D8FED00AE59DB /* WPAccount+ObjCLookupTests.m in Sources */, FEFA6AC82A88D5FC004EE5E6 /* Post+JetpackSocialTests.swift in Sources */, 40ACCF14224E167900190713 /* FlagsTest.swift in Sources */, @@ -10323,7 +10316,6 @@ 805CC0B9296680F7002941DC /* RemoteConfigStoreMock.swift in Sources */, 0CD382862A4B6FCF00612173 /* DashboardBlazeCardCellViewModelTest.swift in Sources */, 0147D651294B6EA600AA6410 /* StatsRevampStoreTests.swift in Sources */, - 57D6C83E22945A10003DDC7E /* PostCompactCellTests.swift in Sources */, B030FE0A27EBF0BC000F6F5E /* SiteCreationIntentTracksEventTests.swift in Sources */, 3F56F55C2AEA2F67006BDCEA /* ReaderPostBuilder.swift in Sources */, D81C2F5C20F872C2002AE1F1 /* ReplyToCommentActionTests.swift in Sources */, diff --git a/WordPress/WordPressTest/PostCompactCellGhostableTests.swift b/WordPress/WordPressTest/PostCompactCellGhostableTests.swift deleted file mode 100644 index 8df13ce865a5..000000000000 --- a/WordPress/WordPressTest/PostCompactCellGhostableTests.swift +++ /dev/null @@ -1,54 +0,0 @@ -import UIKit -import XCTest - -@testable import WordPress - -class PostCompactCellGhostableTests: CoreDataTestCase { - - var postCell: PostCompactCell! - - override func setUp() { - postCell = postCellFromNib() - postCell.ghostAnimationWillStart() - } - - func testIsNotInteractive() { - XCTAssertFalse(postCell.isUserInteractionEnabled) - } - - func testShowGhost() { - XCTAssertFalse(postCell.ghostView.isHidden) - XCTAssertTrue(postCell.contentStackView.isHidden) - } - - func testChangesMenuButtonOpacity() { - XCTAssertEqual(postCell.menuButton.layer.opacity, 0.5) - } - - func testIsInteractiveAfterAConfigure() { - let post = PostBuilder(mainContext).build() - - postCell.configure(with: post) - - XCTAssertTrue(postCell.isUserInteractionEnabled) - } - - func testHideGhostAfterConfigure() { - let post = PostBuilder(mainContext).build() - - postCell.configure(with: post) - - XCTAssertTrue(postCell.ghostView.isHidden) - XCTAssertFalse(postCell.contentStackView.isHidden) - } - - private func postCellFromNib() -> PostCompactCell { - let bundle = Bundle(for: PostCompactCell.self) - guard let postCell = bundle.loadNibNamed("PostCompactCell", owner: nil)?.first as? PostCompactCell else { - fatalError("PostCell does not exist") - } - - return postCell - } - -} diff --git a/WordPress/WordPressTest/PostCompactCellTests.swift b/WordPress/WordPressTest/PostCompactCellTests.swift deleted file mode 100644 index ce26fe7cc61b..000000000000 --- a/WordPress/WordPressTest/PostCompactCellTests.swift +++ /dev/null @@ -1,76 +0,0 @@ -import UIKit -import XCTest - -@testable import WordPress - -class PostCompactCellTests: CoreDataTestCase { - - var postCell: PostCompactCell! - - override func setUp() { - postCell = postCellFromNib() - } - - func testShowImageWhenAvailable() { - let post = PostBuilder(mainContext).withImage().build() - - postCell.configure(with: post) - - XCTAssertFalse(postCell.featuredImageView.isHidden) - } - - func testHideImageWhenNotAvailable() { - let post = PostBuilder(mainContext).build() - - postCell.configure(with: post) - - XCTAssertTrue(postCell.featuredImageView.isHidden) - } - - func testShowPostTitle() { - let post = PostBuilder(mainContext).with(title: "Foo bar").build() - - postCell.configure(with: post) - - XCTAssertEqual(postCell.titleLabel.text, "Foo bar") - } - - func testShowDate() { - let post = PostBuilder(mainContext).with(remoteStatus: .sync) - .with(dateCreated: Date()).build() - - postCell.configure(with: post) - - XCTAssertEqual(postCell.timestampLabel.text, "now") - } - - func testStatusAndBadgeLabels() { - let post = PostBuilder(mainContext).with(remoteStatus: .sync) - .with(dateCreated: Date()).is(sticked: true).build() - - postCell.configure(with: post) - - XCTAssertEqual(postCell.badgesLabel.text, "Sticky") - } - - func testShowBadgesWhenNotEmpty() { - let post = PostBuilder(mainContext) - .with(remoteStatus: .sync) - .build() - - postCell.configure(with: post) - - XCTAssertEqual(postCell.badgesLabel.text, "") - XCTAssertTrue(postCell.badgesLabel.isHidden) - } - - private func postCellFromNib() -> PostCompactCell { - let bundle = Bundle(for: PostCompactCell.self) - guard let postCell = bundle.loadNibNamed("PostCompactCell", owner: nil)?.first as? PostCompactCell else { - fatalError("PostCompactCell does not exist") - } - - return postCell - } - -}