diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift index 56bb07ec9f58..de7884ac049c 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift @@ -47,7 +47,7 @@ class ReaderTagCardCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() - registerTagCell() + registerCells() setupButtonStyles() accessibilityElements = [tagButton, collectionView].compactMap { $0 } collectionViewHeightConstraint.constant = cellSize.height @@ -64,6 +64,10 @@ class ReaderTagCardCell: UITableViewCell { } func configure(parent: UIViewController, tag: ReaderTagTopic, isLoggedIn: Bool, shouldSyncRemotely: Bool = false) { + if viewModel?.slug == tag.slug { + return + } + resetScrollPosition() weak var weakSelf = self viewModel = ReaderTagCardCellViewModel(parent: parent, tag: tag, @@ -116,9 +120,13 @@ private extension ReaderTagCardCell { tagButton.configuration = buttonConfig } - func registerTagCell() { - let nib = UINib(nibName: ReaderTagCell.classNameWithoutNamespaces(), bundle: nil) - collectionView.register(nib, forCellWithReuseIdentifier: ReaderTagCell.classNameWithoutNamespaces()) + func registerCells() { + let tagCell = UINib(nibName: ReaderTagCell.classNameWithoutNamespaces(), bundle: nil) + let footerView = UINib(nibName: ReaderTagFooterView.classNameWithoutNamespaces(), bundle: nil) + collectionView.register(tagCell, forCellWithReuseIdentifier: ReaderTagCell.classNameWithoutNamespaces()) + collectionView.register(footerView, + forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, + withReuseIdentifier: ReaderTagFooterView.classNameWithoutNamespaces()) } /// Injects a "fake" UICollectionView for the loading state animation. @@ -166,4 +174,13 @@ private extension ReaderTagCardCell { ghostableCollectionView.removeFromSuperview() } + func resetScrollPosition() { + let isRTL = UIView.userInterfaceLayoutDirection(for: .unspecified) == .rightToLeft + if isRTL { + collectionView.scrollToEnd(animated: false) + } else { + collectionView.scrollToStart(animated: false) + } + } + } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib index a157831b3d78..ed4a207514d1 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib @@ -33,7 +33,7 @@ - + diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift index ef8b1f04a0b0..1c6db21d25ff 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift @@ -9,11 +9,11 @@ class ReaderTagCardCellViewModel: NSObject { private typealias DataSource = UICollectionViewDiffableDataSource private typealias Snapshot = NSDiffableDataSourceSnapshot + let slug: String weak var viewDelegate: ReaderTagCardCellViewModelDelegate? = nil private let coreDataStack: CoreDataStackSwift private weak var parentViewController: UIViewController? - private let slug: String private weak var collectionView: UICollectionView? private let isLoggedIn: Bool private let cellSize: () -> CGSize? @@ -26,7 +26,7 @@ class ReaderTagCardCellViewModel: NSObject { guard let collectionView else { return nil } - return DataSource(collectionView: collectionView) { [weak self] collectionView, indexPath, objectID in + let dataSource = DataSource(collectionView: collectionView) { [weak self] collectionView, indexPath, objectID in guard let post = try? ContextManager.shared.mainContext.existingObject(with: objectID) as? ReaderPost, let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ReaderTagCell.classNameWithoutNamespaces(), for: indexPath) as? ReaderTagCell else { return UICollectionViewCell() @@ -36,6 +36,20 @@ class ReaderTagCardCellViewModel: NSObject { isLoggedIn: self?.isLoggedIn ?? AccountHelper.isLoggedIn) return cell } + dataSource.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in + guard let slug = self?.slug, + kind == UICollectionView.elementKindSectionFooter, + let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, + withReuseIdentifier: ReaderTagFooterView.classNameWithoutNamespaces(), + for: indexPath) as? ReaderTagFooterView else { + return nil + } + view.configure(with: slug) { [weak self] in + self?.onTagButtonTapped() + } + return view + } + return dataSource }() private lazy var resultsController: NSFetchedResultsController = { @@ -103,6 +117,7 @@ class ReaderTagCardCellViewModel: NSObject { struct Constants { static let displayPostLimit = 10 + static let footerWidth: CGFloat = 200 } } @@ -144,4 +159,10 @@ extension ReaderTagCardCellViewModel: UICollectionViewDelegateFlowLayout { return cellSize() ?? .zero } + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { + var viewSize = cellSize() ?? .zero + viewSize.width = Constants.footerWidth + return viewSize + } + } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.swift new file mode 100644 index 000000000000..eafdfd17b493 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.swift @@ -0,0 +1,42 @@ + +class ReaderTagFooterView: UICollectionReusableView { + + @IBOutlet private weak var contentStackView: UIStackView! + @IBOutlet private weak var arrowButton: UIButton! + @IBOutlet private weak var moreLabel: UILabel! + + private var onTapped: (() -> Void)? + + override func awakeFromNib() { + super.awakeFromNib() + setupStyles() + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onViewTapped)) + addGestureRecognizer(tapGesture) + } + + func configure(with slug: String, onTapped: @escaping () -> Void) { + moreLabel.setText(String(format: Constants.moreText, slug)) + self.onTapped = onTapped + } + + @objc func onViewTapped() { + onTapped?() + } + + @IBAction func onArrowButtonTapped(_ sender: Any) { + onTapped?() + } + + private func setupStyles() { + moreLabel.font = WPStyleGuide.fontForTextStyle(.subheadline, fontWeight: .semibold) + arrowButton.configuration?.background.backgroundColor = UIColor(light: .secondarySystemBackground, + dark: .tertiarySystemBackground) + } + + private struct Constants { + static let moreText = NSLocalizedString("reader.tags.footer.more", + value: "More from %1$@", + comment: "Label for an action to open more content from a specified Reader tag. %1$@ is the Reader tag.") + } + +} diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.xib b/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.xib new file mode 100644 index 000000000000..7c374b796aad --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WordPress/Jetpack/AppImages.xcassets/Reader/reader-tag-arrow.imageset/Contents.json b/WordPress/Jetpack/AppImages.xcassets/Reader/reader-tag-arrow.imageset/Contents.json new file mode 100644 index 000000000000..b01acb7c7d4b --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Reader/reader-tag-arrow.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "reader-tag-arrow-right.svg", + "idiom" : "universal", + "language-direction" : "left-to-right" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/WordPress/Jetpack/AppImages.xcassets/Reader/reader-tag-arrow.imageset/reader-tag-arrow-right.svg b/WordPress/Jetpack/AppImages.xcassets/Reader/reader-tag-arrow.imageset/reader-tag-arrow-right.svg new file mode 100644 index 000000000000..52d8f0c6124c --- /dev/null +++ b/WordPress/Jetpack/AppImages.xcassets/Reader/reader-tag-arrow.imageset/reader-tag-arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 8e0f1fe438c3..16ec4c868263 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2282,6 +2282,10 @@ 83A1B1A328AFE89F00E737AC /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D690141F828FF000200E30 /* BuildConfiguration.swift */; }; 83A337A12A9FA525009ED60C /* ReaderSiteHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A337A02A9FA525009ED60C /* ReaderSiteHeaderView.swift */; }; 83A337A22A9FA525009ED60C /* ReaderSiteHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A337A02A9FA525009ED60C /* ReaderSiteHeaderView.swift */; }; + 83A8A2942BDC557F001F9133 /* ReaderTagFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A8A2922BDC557E001F9133 /* ReaderTagFooterView.swift */; }; + 83A8A2952BDC557F001F9133 /* ReaderTagFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A8A2922BDC557E001F9133 /* ReaderTagFooterView.swift */; }; + 83A8A2962BDC557F001F9133 /* ReaderTagFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83A8A2932BDC557E001F9133 /* ReaderTagFooterView.xib */; }; + 83A8A2972BDC557F001F9133 /* ReaderTagFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83A8A2932BDC557E001F9133 /* ReaderTagFooterView.xib */; }; 83B1D037282C62620061D911 /* BloggingPromptsAttribution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B1D036282C62620061D911 /* BloggingPromptsAttribution.swift */; }; 83B1D038282C62620061D911 /* BloggingPromptsAttribution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B1D036282C62620061D911 /* BloggingPromptsAttribution.swift */; }; 83BF48BB2BD6F03000C0E1A1 /* ReaderTagCardCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83BF48BA2BD6F03000C0E1A1 /* ReaderTagCardCellViewModel.swift */; }; @@ -7699,6 +7703,8 @@ 839435922847F2200019A94F /* WordPress 143.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 143.xcdatamodel"; sourceTree = ""; }; 839B150A2795DEE0009F5E77 /* UIView+Margins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Margins.swift"; sourceTree = ""; }; 83A337A02A9FA525009ED60C /* ReaderSiteHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderSiteHeaderView.swift; sourceTree = ""; }; + 83A8A2922BDC557E001F9133 /* ReaderTagFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReaderTagFooterView.swift; sourceTree = ""; }; + 83A8A2932BDC557E001F9133 /* ReaderTagFooterView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReaderTagFooterView.xib; sourceTree = ""; }; 83B1D036282C62620061D911 /* BloggingPromptsAttribution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloggingPromptsAttribution.swift; sourceTree = ""; }; 83BF48BA2BD6F03000C0E1A1 /* ReaderTagCardCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderTagCardCellViewModel.swift; sourceTree = ""; }; 83BF48BD2BD6FA3000C0E1A1 /* ReaderTagCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderTagCellViewModel.swift; sourceTree = ""; }; @@ -12792,6 +12798,8 @@ 83317EDB2BC72DB7001AD2F4 /* ReaderTagCell.swift */, 83317EDE2BC72DED001AD2F4 /* ReaderTagCell.xib */, 83BF48BD2BD6FA3000C0E1A1 /* ReaderTagCellViewModel.swift */, + 83A8A2922BDC557E001F9133 /* ReaderTagFooterView.swift */, + 83A8A2932BDC557E001F9133 /* ReaderTagFooterView.xib */, ); name = Cards; sourceTree = ""; @@ -19637,6 +19645,7 @@ 9A9E3FB0230EA7A300909BC4 /* StatsGhostTopCell.xib in Resources */, 985793C922F23D7000643DBF /* CustomizeInsightsCell.xib in Resources */, 1761F17926209AEE000815EF /* wordpress-dark-icon-app-60x60@2x.png in Resources */, + 83A8A2962BDC557F001F9133 /* ReaderTagFooterView.xib in Resources */, F5A34D0F25DF2F7F00C9654B /* oswald_upper.ttf in Resources */, 2FAE970D0E33B21600CA8540 /* xhtmlValidatorTemplate.xhtml in Resources */, 9826AE8821B5C73400C851FA /* PostingActivityDay.xib in Resources */, @@ -20280,6 +20289,7 @@ FABB202F2602FC2C00C8785C /* CountriesMapView.xib in Resources */, F465978528E65E1800D5F49A /* blue-on-white-icon-app-60@2x.png in Resources */, FABB20302602FC2C00C8785C /* OverviewCell.xib in Resources */, + 83A8A2972BDC557F001F9133 /* ReaderTagFooterView.xib in Resources */, F46597E928E6698D00D5F49A /* spectrum-on-black-icon-app-83.5@2x.png in Resources */, FABB20322602FC2C00C8785C /* JetpackRemoteInstallStateView.xib in Resources */, FABB20352602FC2C00C8785C /* TemplatePreviewViewController.xib in Resources */, @@ -22473,6 +22483,7 @@ 931215F2267FE162008C3B69 /* ReferrerDetailsSpamActionRow.swift in Sources */, E66EB6F91C1B7A76003DABC5 /* ReaderSpacerView.swift in Sources */, FAFF7A222B695801006A7CB2 /* WebServerLogsView.swift in Sources */, + 83A8A2942BDC557F001F9133 /* ReaderTagFooterView.swift in Sources */, AE2F3125270B6DA000B2A9C2 /* Scanner+QuotedText.swift in Sources */, B560914C208A671F00399AE4 /* WPStyleGuide+SiteCreation.swift in Sources */, 014ACD142A1E5034008A706C /* WebKitViewController+SandboxStore.swift in Sources */, @@ -26154,6 +26165,7 @@ 8BE6F92D27EE27DB0008BDC7 /* BlogDashboardPostCardGhostCell.swift in Sources */, 3FF15A5C291ED21100E1B4E5 /* MigrationNotificationsCenterView.swift in Sources */, FABB25B32602FC2C00C8785C /* ReaderVisitSiteAction.swift in Sources */, + 83A8A2952BDC557F001F9133 /* ReaderTagFooterView.swift in Sources */, FABB25B42602FC2C00C8785C /* ReaderCard+CoreDataProperties.swift in Sources */, FABB25B52602FC2C00C8785C /* GutenbergViewController+MoreActions.swift in Sources */, FABB25B62602FC2C00C8785C /* Product.swift in Sources */,