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 */,