From f5426ee0f571ea0f730233f8e00a50f069c0a71f Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 16 Oct 2024 09:01:10 -0400 Subject: [PATCH 01/30] Remove unused changeLayoutMargins --- .../Extensions/UIView+Helpers.swift | 9 ---- .../UIView+ChangeLayoutMarginsTests.swift | 50 ------------------- 2 files changed, 59 deletions(-) delete mode 100644 Modules/Tests/WordPressUITests/Extensions/UIView+ChangeLayoutMarginsTests.swift diff --git a/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift b/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift index 8e31fdb0753a..a39479326530 100644 --- a/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift +++ b/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift @@ -89,13 +89,4 @@ extension UIView { @objc public func userInterfaceLayoutDirection() -> UIUserInterfaceLayoutDirection { return UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) } - - public func changeLayoutMargins(top: CGFloat? = nil, left: CGFloat? = nil, bottom: CGFloat? = nil, right: CGFloat? = nil) { - let top = top ?? layoutMargins.top - let left = left ?? layoutMargins.left - let bottom = bottom ?? layoutMargins.bottom - let right = right ?? layoutMargins.right - - layoutMargins = UIEdgeInsets(top: top, left: left, bottom: bottom, right: right) - } } diff --git a/Modules/Tests/WordPressUITests/Extensions/UIView+ChangeLayoutMarginsTests.swift b/Modules/Tests/WordPressUITests/Extensions/UIView+ChangeLayoutMarginsTests.swift deleted file mode 100644 index 53acacc1e879..000000000000 --- a/Modules/Tests/WordPressUITests/Extensions/UIView+ChangeLayoutMarginsTests.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Foundation -import XCTest - -@testable import WordPressUI - -class UIViewChangeLayoutMarginSTests: XCTestCase { - - var view: UIView! - - override func setUp() { - view = UIView() - view.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) - } - - func testChangeOnlyTopLayoutMargin() { - view.changeLayoutMargins(top: 10) - - XCTAssertEqual(view.layoutMargins.top, 10) - XCTAssertEqual(view.layoutMargins.left, 5) - XCTAssertEqual(view.layoutMargins.bottom, 5) - XCTAssertEqual(view.layoutMargins.right, 5) - } - - func testChangeOnlyLeftLayoutMargin() { - view.changeLayoutMargins(left: 10) - - XCTAssertEqual(view.layoutMargins.top, 5) - XCTAssertEqual(view.layoutMargins.left, 10) - XCTAssertEqual(view.layoutMargins.bottom, 5) - XCTAssertEqual(view.layoutMargins.right, 5) - } - - func testChangeOnlyBottomLayoutMargin() { - view.changeLayoutMargins(bottom: 10) - - XCTAssertEqual(view.layoutMargins.top, 5) - XCTAssertEqual(view.layoutMargins.left, 5) - XCTAssertEqual(view.layoutMargins.bottom, 10) - XCTAssertEqual(view.layoutMargins.right, 5) - } - - func testChangeOnlyRightLayoutMargin() { - view.changeLayoutMargins(right: 10) - - XCTAssertEqual(view.layoutMargins.top, 5) - XCTAssertEqual(view.layoutMargins.left, 5) - XCTAssertEqual(view.layoutMargins.bottom, 5) - XCTAssertEqual(view.layoutMargins.right, 10) - } -} From d5c16e3755ceb44dacc768368db73aa1889563eb Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 16 Oct 2024 09:08:07 -0400 Subject: [PATCH 02/30] Add new pinEdges method --- .../Extensions/UIView+AutoLayout.swift | 71 +++++++++++++++++++ .../Extensions/UIView+Helpers.swift | 4 +- .../Extensions/Font/UIFont+Weight.swift | 3 +- 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift diff --git a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift new file mode 100644 index 000000000000..e16630a1121a --- /dev/null +++ b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift @@ -0,0 +1,71 @@ +import UIKit +import SwiftUI + +extension UIView { + /// Pins edges of the view to the edges of the given container. By default, + /// pins to the nearest superview. + @discardableResult + public func pinEdges( + _ edges: Edge.Set = .all, + to container: AutoLayoutItem? = nil, + insets: UIEdgeInsets = .zero, + relation: AutoLayoutPinEdgesRelation = .equal, + priority: UILayoutPriority? = nil + ) -> [NSLayoutConstraint] { + guard let container = container ?? superview else { + assertionFailure("view has to be installed in the view hierarchy") + return [] + } + translatesAutoresizingMaskIntoConstraints = false + + var constraints: [NSLayoutConstraint] = [] + + func pin(_ edge: Edge.Set, _ closure: @autoclosure () -> NSLayoutConstraint) { + guard edges.contains(edge) else { + return + } + let constraint = closure() + if let priority { + constraint.priority = priority + } + constraints.append(constraint) + } + + switch relation { + case .equal: + pin(.top, topAnchor.constraint(equalTo: container.topAnchor, constant: insets.top)) + pin(.trailing, trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -insets.right)) + pin(.bottom, bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -insets.bottom)) + pin(.leading, leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: insets.left)) + case .lessThanOrEqual: + pin(.top, topAnchor.constraint(lessThanOrEqualTo: container.topAnchor, constant: insets.top)) + pin(.trailing, trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -insets.right)) + pin(.bottom, bottomAnchor.constraint(greaterThanOrEqualTo: container.bottomAnchor, constant: -insets.bottom)) + pin(.leading, leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: insets.left)) + } + + NSLayoutConstraint.activate(constraints) + return constraints + } +} + +public protocol AutoLayoutItem { + var leadingAnchor: NSLayoutXAxisAnchor { get } + var trailingAnchor: NSLayoutXAxisAnchor { get } + var leftAnchor: NSLayoutXAxisAnchor { get } + var rightAnchor: NSLayoutXAxisAnchor { get } + var topAnchor: NSLayoutYAxisAnchor { get } + var bottomAnchor: NSLayoutYAxisAnchor { get } + var widthAnchor: NSLayoutDimension { get } + var heightAnchor: NSLayoutDimension { get } + var centerXAnchor: NSLayoutXAxisAnchor { get } + var centerYAnchor: NSLayoutYAxisAnchor { get } +} + +public enum AutoLayoutPinEdgesRelation { + case equal + case lessThanOrEqual +} + +extension UIView: AutoLayoutItem {} +extension UILayoutGuide: AutoLayoutItem {} diff --git a/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift b/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift index a39479326530..f4e0f1ae98a3 100644 --- a/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift +++ b/Modules/Sources/WordPressUI/Extensions/UIView+Helpers.swift @@ -1,8 +1,8 @@ import Foundation import UIKit -// MARK: - UIView Helpers -// +// MARK: - UIView (Soft-Deprecated) + extension UIView { @objc public func pinSubviewAtCenter(_ subview: UIView) { diff --git a/WordPress/Classes/Extensions/Font/UIFont+Weight.swift b/WordPress/Classes/Extensions/Font/UIFont+Weight.swift index a8df631c9e16..392edc098891 100644 --- a/WordPress/Classes/Extensions/Font/UIFont+Weight.swift +++ b/WordPress/Classes/Extensions/Font/UIFont+Weight.swift @@ -22,9 +22,8 @@ extension UIFont { return UIFont(descriptor: descriptor, size: 0) } - private func withWeight(_ weight: UIFont.Weight) -> UIFont { + func withWeight(_ weight: UIFont.Weight) -> UIFont { let descriptor = fontDescriptor.addingAttributes([.traits: [UIFontDescriptor.TraitKey.weight: weight]]) - return UIFont(descriptor: descriptor, size: 0) } } From bfcd2195185579394cf6cd17479d2acae8d32449 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 16 Oct 2024 13:16:35 -0400 Subject: [PATCH 03/30] Add pinCenter --- .../Extensions/UIView+AutoLayout.swift | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift index e16630a1121a..b206a1435354 100644 --- a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift +++ b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift @@ -3,7 +3,7 @@ import SwiftUI extension UIView { /// Pins edges of the view to the edges of the given container. By default, - /// pins to the nearest superview. + /// pins to the superview. @discardableResult public func pinEdges( _ edges: Edge.Set = .all, @@ -21,14 +21,8 @@ extension UIView { var constraints: [NSLayoutConstraint] = [] func pin(_ edge: Edge.Set, _ closure: @autoclosure () -> NSLayoutConstraint) { - guard edges.contains(edge) else { - return - } - let constraint = closure() - if let priority { - constraint.priority = priority - } - constraints.append(constraint) + guard edges.contains(edge) else { return } + constraints.append(closure()) } switch relation { @@ -38,12 +32,47 @@ extension UIView { pin(.bottom, bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -insets.bottom)) pin(.leading, leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: insets.left)) case .lessThanOrEqual: - pin(.top, topAnchor.constraint(lessThanOrEqualTo: container.topAnchor, constant: insets.top)) + pin(.top, topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor, constant: insets.top)) pin(.trailing, trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -insets.right)) - pin(.bottom, bottomAnchor.constraint(greaterThanOrEqualTo: container.bottomAnchor, constant: -insets.bottom)) + pin(.bottom, bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor, constant: -insets.bottom)) pin(.leading, leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: insets.left)) } + if let priority { + for constraint in constraints { + constraint.priority = priority + } + } + + NSLayoutConstraint.activate(constraints) + return constraints + } + + /// Pins the view to the center of the given container. By default, + /// pins to the superview. + @discardableResult + public func pinCenter( + to container: AutoLayoutItem? = nil, + offset: UIOffset = .zero, + priority: UILayoutPriority? = nil + ) -> [NSLayoutConstraint] { + guard let container = container ?? superview else { + assertionFailure("view has to be installed in the view hierarchy") + return [] + } + translatesAutoresizingMaskIntoConstraints = false + + let constraints = [ + centerXAnchor.constraint(equalTo: container.centerXAnchor, constant: offset.horizontal), + centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: offset.vertical), + ] + + if let priority { + for constraint in constraints { + constraint.priority = priority + } + } + NSLayoutConstraint.activate(constraints) return constraints } From 58368a8e0a8dd1540e3890cab52524496bc5700a Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 16 Oct 2024 13:51:09 -0400 Subject: [PATCH 04/30] Add initial ReaderDiscoverHeaderView implementation --- .../Reader/ReaderDiscoverHeaderView.swift | 160 ++++++++++++++++++ .../ReaderStreamViewController+Helper.swift | 4 + WordPress/WordPress.xcodeproj/project.pbxproj | 6 + 3 files changed, 170 insertions(+) create mode 100644 WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift new file mode 100644 index 000000000000..c29ddbf675c5 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -0,0 +1,160 @@ +import UIKit + +final class ReaderDiscoverHeaderView: UIView, ReaderStreamHeader { + private let titleView = ReaderStreamTitleView() + private let tagsStackView = UIStackView(spacing: 8, []) + private var tagViews: [ReaderTagView] = [] + + weak var delegate: ReaderStreamHeaderDelegate? + + override init(frame: CGRect) { + super.init(frame: frame) + + let scrollView = UIScrollView() + scrollView.addSubview(tagsStackView) + scrollView.showsHorizontalScrollIndicator = false + scrollView.clipsToBounds = false + tagsStackView.pinEdges() + scrollView.heightAnchor.constraint(equalTo: tagsStackView.heightAnchor).isActive = true + + let stackView = UIStackView(axis: .vertical, spacing: 8, [titleView, scrollView]) + addSubview(stackView) + stackView.pinEdges(insets: UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16)) + + titleView.titleLabel.text = Strings.title + titleView.detailsLabel.text = Strings.details + + // TODO: (reader) configure dynamically. where does these come from? + configure(tags: ["Recommended", "First posts", "Latest", "Daily prompts", "Food"]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(tags: [String]) { + for view in tagViews { + view.removeFromSuperview() + } + tagViews = tags.map(makeTagView) + for view in tagViews { + tagsStackView.addArrangedSubview(view) + } + } + + private func makeTagView(_ title: String) -> ReaderTagView { + let view = ReaderTagView(title: title) + view.button.addAction(UIAction { [weak self, weak view] _ in + guard let self, let view else { return } + self.readerTagViewTapped(view) + }, for: .primaryActionTriggered) + return view + } + + @objc private func readerTagViewTapped(_ view: ReaderTagView) { + didSelectTag(view.title) + } + + private func didSelectTag(_ tag: String) { + for view in self.tagViews { + view.isSelected = view.title == tag + } + } + + // MARK: - ReaderStreamHeader + + func enableLoggedInFeatures(_ enable: Bool) { + // Do nothing + } + + func configureHeader(_ topic: ReaderAbstractTopic) { + // Do nothing + } +} + +private final class ReaderTagView: UIView { + private let textLabel = UILabel() + private let backgroundView = UIView() + let button = UIButton(type: .system) + let title: String + + var isSelected: Bool = false { + didSet { + guard oldValue != isSelected else { return } + configure(isSelected: isSelected) + } + } + + init(title: String) { + self.title = title + + super.init(frame: .zero) + + textLabel.font = UIFont.preferredFont(forTextStyle: .subheadline).withWeight(.medium) + textLabel.text = title + + backgroundView.clipsToBounds = true + + addSubview(backgroundView) + addSubview(textLabel) + addSubview(button) + + textLabel.pinEdges(to: backgroundView, insets: UIEdgeInsets(horizontal: 10, vertical: 6)) + backgroundView.pinEdges(insets: UIEdgeInsets(.vertical, 8)) + button.pinEdges() + + configure(isSelected: isSelected) + } + + private func configure(isSelected: Bool) { + if isSelected { + backgroundView.backgroundColor = UIColor.label + textLabel.textColor = UIColor.systemBackground + } else { + backgroundView.backgroundColor = UIColor.opaqueSeparator.withAlphaComponent(0.33) + textLabel.textColor = UIColor.label + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + backgroundView.layer.cornerRadius = backgroundView.bounds.height / 2 + } +} + +final class ReaderStreamTitleView: UIView { + let titleLabel = UILabel() + let detailsLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + titleLabel.font = UIFont.preferredFont(forTextStyle: .largeTitle).withWeight(.bold) + detailsLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) + detailsLabel.textColor = .secondaryLabel + detailsLabel.numberOfLines = 0 + + let stackView = UIStackView(axis: .vertical, alignment: .leading, [titleLabel, detailsLabel]) + addSubview(stackView) + stackView.pinEdges() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private enum Strings { + static let title = NSLocalizedString("reader.discover.header.title", value: "Discover", comment: "Header view title") + static let details = NSLocalizedString("reader.discover.header.title", value: "Explore popular blogs that inspire, educate, and entertain.", comment: "Header view details") +} + +@available(iOS 17, *) +#Preview { + ReaderDiscoverHeaderView() +} diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift index e76c03cb003b..62ea12b1e75e 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift @@ -36,6 +36,10 @@ extension ReaderStreamViewController { func headerForStream(_ topic: ReaderAbstractTopic) -> ReaderHeader? { + if FeatureFlag.readerReset.enabled && self is ReaderCardsStreamViewController { + return ReaderDiscoverHeaderView() + } + if ReaderHelpers.isTopicTag(topic) && !isContentFiltered { guard let nibViews = Bundle.main.loadNibNamed("ReaderTagStreamHeader", owner: nil, options: nil) as? [ReaderTagStreamHeader] else { return nil diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 45781644222c..19562f9793ba 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -433,6 +433,8 @@ 0C0AD10B2B0CCFA400EC06E6 /* MediaPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0AD1092B0CCFA400EC06E6 /* MediaPreviewController.swift */; }; 0C0AE7592A8FAD6A007D9D6C /* MediaPickerMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0AE7582A8FAD6A007D9D6C /* MediaPickerMenu.swift */; }; 0C0AE75A2A8FAD6A007D9D6C /* MediaPickerMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0AE7582A8FAD6A007D9D6C /* MediaPickerMenu.swift */; }; + 0C0BEEEB2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0BEEEA2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift */; }; + 0C0BEEEC2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0BEEEA2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift */; }; 0C0D3B0D2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; 0C0D3B0E2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; 0C0DF8942C2DF14600011B7D /* LoginFacadeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C0DF8932C2DF12A00011B7D /* LoginFacadeTests.m */; }; @@ -6441,6 +6443,7 @@ 0C0AD1052B0C483F00EC06E6 /* ExternalMediaSelectionTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalMediaSelectionTitleView.swift; sourceTree = ""; }; 0C0AD1092B0CCFA400EC06E6 /* MediaPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewController.swift; sourceTree = ""; }; 0C0AE7582A8FAD6A007D9D6C /* MediaPickerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerMenu.swift; sourceTree = ""; }; + 0C0BEEEA2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderDiscoverHeaderView.swift; sourceTree = ""; }; 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsStream.swift; sourceTree = ""; }; 0C0DF8932C2DF12A00011B7D /* LoginFacadeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LoginFacadeTests.m; sourceTree = ""; }; 0C0F05522C6290670040390D /* ReaderFeedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderFeedCell.swift; sourceTree = ""; }; @@ -17888,6 +17891,7 @@ E6D2E1641B8AAD7E0000ED14 /* ReaderSiteStreamHeader.swift */, E6D2E15E1B8A9C830000ED14 /* ReaderSiteStreamHeader.xib */, E6D2E16B1B8B423B0000ED14 /* ReaderStreamHeader.swift */, + 0C0BEEEA2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift */, E6D2E1661B8AAD8C0000ED14 /* ReaderTagStreamHeader.swift */, E6D2E1601B8AA4410000ED14 /* ReaderTagStreamHeader.xib */, ); @@ -22177,6 +22181,7 @@ 17C1D7DC26735631006C8970 /* EmojiRenderer.swift in Sources */, C81CCD7B243BF7A600A83E27 /* TenorStrings.swift in Sources */, 91DCE84621A6A7F50062F134 /* PostEditor+MoreOptions.swift in Sources */, + 0C0BEEEC2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift in Sources */, 3FB1929526C79EC6000F5AA3 /* Date+Formats.swift in Sources */, FA4EBCC12A98F00200BA3DFB /* NoSitesView.swift in Sources */, 0C7762232AAFD39700E07A88 /* SiteMediaAddMediaMenuController.swift in Sources */, @@ -24496,6 +24501,7 @@ 0C1C083F2B9BF9A000E52F8C /* PostRepository+Helpers.swift in Sources */, FABB21082602FC2C00C8785C /* SiteCreationHeaderData.swift in Sources */, FABB21092602FC2C00C8785C /* WPAuthTokenIssueSolver.m in Sources */, + 0C0BEEEB2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift in Sources */, FABB210B2602FC2C00C8785C /* ReaderDetailViewController.swift in Sources */, FABB210C2602FC2C00C8785C /* HomeWidgetThisWeekData.swift in Sources */, FABB210D2602FC2C00C8785C /* PersonHeaderCell.swift in Sources */, From 36f9ff9d1619b23e5df02cf33c21d6f0f11df1d3 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 16 Oct 2024 18:43:51 -0400 Subject: [PATCH 05/30] Remove readerDiscoverEndpoint FF --- .../Classes/Services/ReaderCardService.swift | 33 +++++-------------- .../BuildInformation/RemoteFeatureFlag.swift | 7 ---- .../ReaderCardServiceTests.swift | 9 ----- 3 files changed, 9 insertions(+), 40 deletions(-) diff --git a/WordPress/Classes/Services/ReaderCardService.swift b/WordPress/Classes/Services/ReaderCardService.swift index 2fb1dd55c235..21c434bce8bb 100644 --- a/WordPress/Classes/Services/ReaderCardService.swift +++ b/WordPress/Classes/Services/ReaderCardService.swift @@ -1,7 +1,6 @@ import Foundation protocol ReaderCardServiceRemote { - func fetchStreamCards(for topics: [String], page: String?, sortingOption: ReaderSortingOption, @@ -10,13 +9,6 @@ protocol ReaderCardServiceRemote { success: @escaping ([RemoteReaderCard], String?) -> Void, failure: @escaping (Error) -> Void) - func fetchCards(for topics: [String], - page: String?, - sortingOption: ReaderSortingOption, - refreshCount: Int?, - success: @escaping ([RemoteReaderCard], String?) -> Void, - failure: @escaping (Error) -> Void) - } extension ReaderPostServiceRemote: ReaderCardServiceRemote { } @@ -114,22 +106,15 @@ class ReaderCardService { failure(error) } - if RemoteFeatureFlag.readerDiscoverEndpoint.enabled() { - self.service.fetchStreamCards(for: slugs, - page: self.pageHandle(isFirstPage: isFirstPage), - sortingOption: .noSorting, - refreshCount: refreshCount, - count: nil, - success: success, - failure: failure) - } else { - self.service.fetchCards(for: slugs, - page: self.pageHandle(isFirstPage: isFirstPage), - sortingOption: .noSorting, - refreshCount: refreshCount, - success: success, - failure: failure) - } + self.service.fetchStreamCards( + for: slugs, + page: self.pageHandle(isFirstPage: isFirstPage), + sortingOption: .noSorting, + refreshCount: refreshCount, + count: nil, + success: success, + failure: failure + ) } } diff --git a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift index 4ec98147bd8f..fbb2123628d2 100644 --- a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift @@ -26,7 +26,6 @@ enum RemoteFeatureFlag: Int, CaseIterable { case wordPressSotWCard case inAppRating case siteMonitoring - case readerDiscoverEndpoint case readingPreferences case readingPreferencesFeedback case readerAnnouncementCard @@ -84,8 +83,6 @@ enum RemoteFeatureFlag: Int, CaseIterable { return false case .siteMonitoring: return false - case .readerDiscoverEndpoint: - return true case .readingPreferences: return true case .readingPreferencesFeedback: @@ -152,8 +149,6 @@ enum RemoteFeatureFlag: Int, CaseIterable { return "in_app_rating_and_feedback" case .siteMonitoring: return "site_monitoring" - case .readerDiscoverEndpoint: - return "reader_discover_new_endpoint" case .readingPreferences: return "reading_preferences" case .readingPreferencesFeedback: @@ -219,8 +214,6 @@ enum RemoteFeatureFlag: Int, CaseIterable { return "In-App Rating and Feedback" case .siteMonitoring: return "Site Monitoring" - case .readerDiscoverEndpoint: - return "Reader Discover New Endpoint" case .readingPreferences: return "Reading Preferences" case .readingPreferencesFeedback: diff --git a/WordPress/WordPressTest/ReaderCardServiceTests.swift b/WordPress/WordPressTest/ReaderCardServiceTests.swift index 3ee06e9df02f..9bf219ca11cb 100644 --- a/WordPress/WordPressTest/ReaderCardServiceTests.swift +++ b/WordPress/WordPressTest/ReaderCardServiceTests.swift @@ -115,15 +115,6 @@ final class ReaderPostServiceRemoteMock: ReaderCardServiceRemote { mockFetch(success: success, failure: failure) } - func fetchCards(for topics: [String], - page: String?, - sortingOption: WordPressKit.ReaderSortingOption, - refreshCount: Int?, - success: @escaping ([WordPressKit.RemoteReaderCard], String?) -> Void, - failure: @escaping (any Error) -> Void) { - mockFetch(success: success, failure: failure) - } - func mockFetch(success: @escaping ([WordPressKit.RemoteReaderCard], String?) -> Void, failure: @escaping (any Error) -> Void) { guard !shouldCallFailure else { From 3ea97f77e521dc719c9fcf512972475f00c700fc Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 11:13:40 -0400 Subject: [PATCH 06/30] Rename ReaderDiscoverViewController --- WordPress/Classes/System/ReaderPresenter.swift | 2 +- ...ller.swift => ReaderDiscoverViewController.swift} | 12 ++++++------ .../Reader/ReaderStreamViewController+Helper.swift | 2 +- .../WPTabBarController+ReaderTabNavigation.swift | 2 +- WordPress/WordPress.xcodeproj/project.pbxproj | 12 ++++++------ 5 files changed, 15 insertions(+), 15 deletions(-) rename WordPress/Classes/ViewRelated/Reader/{ReaderCardsStreamViewController.swift => ReaderDiscoverViewController.swift} (95%) diff --git a/WordPress/Classes/System/ReaderPresenter.swift b/WordPress/Classes/System/ReaderPresenter.swift index c0d59c8050e5..3996e86a493d 100644 --- a/WordPress/Classes/System/ReaderPresenter.swift +++ b/WordPress/Classes/System/ReaderPresenter.swift @@ -111,7 +111,7 @@ final class ReaderPresenter: NSObject, SplitViewDisplayable { case .recent, .discover, .likes: if let topic = screen.topicType.flatMap(sidebarViewModel.getTopic) { if screen == .discover { - return ReaderCardsStreamViewController.controller(topic: topic) + return ReaderDiscoverViewController.controller(topic: topic) } else { return ReaderStreamViewController.controllerWithTopic(topic) } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderCardsStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift similarity index 95% rename from WordPress/Classes/ViewRelated/Reader/ReaderCardsStreamViewController.swift rename to WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index c1329c91f427..9b417cba6dbd 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderCardsStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -1,6 +1,6 @@ import Foundation -class ReaderCardsStreamViewController: ReaderStreamViewController { +class ReaderDiscoverViewController: ReaderStreamViewController { private let readerCardTopicsIdentifier = "ReaderTopicsCell" private let readerCardSitesIdentifier = "ReaderSitesCell" @@ -193,8 +193,8 @@ class ReaderCardsStreamViewController: ReaderStreamViewController { /// /// - Returns: An instance of the controller /// - class func controller(topic: ReaderAbstractTopic) -> ReaderCardsStreamViewController { - let controller = ReaderCardsStreamViewController() + class func controller(topic: ReaderAbstractTopic) -> ReaderDiscoverViewController { + let controller = ReaderDiscoverViewController() controller.readerTopic = topic return controller } @@ -236,7 +236,7 @@ class ReaderCardsStreamViewController: ReaderStreamViewController { } // MARK: - Select Interests Display -private extension ReaderCardsStreamViewController { +private extension ReaderDiscoverViewController { func displaySelectInterestsIfNeeded() { selectInterestsViewController.userIsFollowingTopics { [weak self] isFollowing in guard let self else { @@ -253,7 +253,7 @@ private extension ReaderCardsStreamViewController { // MARK: - ReaderTopicsTableCardCellDelegate -extension ReaderCardsStreamViewController: ReaderTopicsTableCardCellDelegate { +extension ReaderDiscoverViewController: ReaderTopicsTableCardCellDelegate { func didSelect(topic: ReaderAbstractTopic) { if topic as? ReaderTagTopic != nil { WPAnalytics.trackReader(.readerDiscoverTopicTapped) @@ -273,7 +273,7 @@ extension ReaderCardsStreamViewController: ReaderTopicsTableCardCellDelegate { // MARK: - ReaderSitesCardCellDelegate -extension ReaderCardsStreamViewController: ReaderSitesCardCellDelegate { +extension ReaderDiscoverViewController: ReaderSitesCardCellDelegate { func handleFollowActionForTopic(_ topic: ReaderAbstractTopic, for cell: ReaderSitesCardCell) { toggleFollowingForTopic(topic) { success in cell.didToggleFollowing(topic, with: success) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift index 62ea12b1e75e..611c96eaf61d 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift @@ -36,7 +36,7 @@ extension ReaderStreamViewController { func headerForStream(_ topic: ReaderAbstractTopic) -> ReaderHeader? { - if FeatureFlag.readerReset.enabled && self is ReaderCardsStreamViewController { + if FeatureFlag.readerReset.enabled && self is ReaderDiscoverViewController { return ReaderDiscoverHeaderView() } diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift index 72ca7c6bf8cc..41dda4e7c5e0 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift @@ -18,7 +18,7 @@ extension WPTabBarController { let viewModel = ReaderTabViewModel( readerContentFactory: { content in if content.topicType == .discover, let topic = content.topic { - return ReaderCardsStreamViewController.controller(topic: topic) + return ReaderDiscoverViewController.controller(topic: topic) } else if let topic = content.topic { return ReaderStreamViewController.controllerWithTopic(topic) } else { diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 19562f9793ba..a0506b1f1186 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -2596,7 +2596,7 @@ 8BB185CC24B6058600A4CCE8 /* reader-cards.json in Resources */ = {isa = PBXBuildFile; fileRef = 8BB185CB24B6058600A4CCE8 /* reader-cards.json */; }; 8BB185CE24B62CE100A4CCE8 /* ReaderCardServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB185CD24B62CE100A4CCE8 /* ReaderCardServiceTests.swift */; }; 8BB185CF24B62D7600A4CCE8 /* reader-cards.json in Resources */ = {isa = PBXBuildFile; fileRef = 8BB185CB24B6058600A4CCE8 /* reader-cards.json */; }; - 8BB185D224B63D5F00A4CCE8 /* ReaderCardsStreamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB185D024B63D1600A4CCE8 /* ReaderCardsStreamViewController.swift */; }; + 8BB185D224B63D5F00A4CCE8 /* ReaderDiscoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB185D024B63D1600A4CCE8 /* ReaderDiscoverViewController.swift */; }; 8BB185D524B66FE600A4CCE8 /* ReaderCard+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB185D324B66FE500A4CCE8 /* ReaderCard+CoreDataProperties.swift */; }; 8BB185D624B66FE600A4CCE8 /* ReaderCard+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB185D424B66FE600A4CCE8 /* ReaderCard+CoreDataClass.swift */; }; 8BBBCE702717651200B277AC /* JetpackModuleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BBBCE6F2717651200B277AC /* JetpackModuleHelper.swift */; }; @@ -4931,7 +4931,7 @@ FABB23572602FC2C00C8785C /* PostTagPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E155EC711E9B7DCE009D7F63 /* PostTagPickerViewController.swift */; }; FABB23582602FC2C00C8785C /* SignupEpilogueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A25BD0203CB25F006A5807 /* SignupEpilogueCell.swift */; }; FABB23592602FC2C00C8785C /* NoteBlockActionsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57AF5F91ACDC73D0075A7D2 /* NoteBlockActionsTableViewCell.swift */; }; - FABB235A2602FC2C00C8785C /* ReaderCardsStreamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB185D024B63D1600A4CCE8 /* ReaderCardsStreamViewController.swift */; }; + FABB235A2602FC2C00C8785C /* ReaderDiscoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB185D024B63D1600A4CCE8 /* ReaderDiscoverViewController.swift */; }; FABB235B2602FC2C00C8785C /* DeleteSiteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742C79971E5F511C00DB1608 /* DeleteSiteViewController.swift */; }; FABB235C2602FC2C00C8785C /* WPAnalyticsTrackerWPCom.m in Sources */ = {isa = PBXBuildFile; fileRef = 85DA8C4318F3F29A0074C8A4 /* WPAnalyticsTrackerWPCom.m */; }; FABB235D2602FC2C00C8785C /* AztecAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C0C5C1EF42A4A00372C65 /* AztecAttachmentViewController.swift */; }; @@ -8137,7 +8137,7 @@ 8BB185C524B5FB8500A4CCE8 /* ReaderCardService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCardService.swift; sourceTree = ""; }; 8BB185CB24B6058600A4CCE8 /* reader-cards.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "reader-cards.json"; sourceTree = ""; }; 8BB185CD24B62CE100A4CCE8 /* ReaderCardServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCardServiceTests.swift; sourceTree = ""; }; - 8BB185D024B63D1600A4CCE8 /* ReaderCardsStreamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCardsStreamViewController.swift; sourceTree = ""; }; + 8BB185D024B63D1600A4CCE8 /* ReaderDiscoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderDiscoverViewController.swift; sourceTree = ""; }; 8BB185D324B66FE500A4CCE8 /* ReaderCard+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReaderCard+CoreDataProperties.swift"; sourceTree = ""; }; 8BB185D424B66FE600A4CCE8 /* ReaderCard+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReaderCard+CoreDataClass.swift"; sourceTree = ""; }; 8BBBCE6F2717651200B277AC /* JetpackModuleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackModuleHelper.swift; sourceTree = ""; }; @@ -13349,7 +13349,7 @@ 0C0F05522C6290670040390D /* ReaderFeedCell.swift */, 5D1D04741B7A50B100CDE646 /* ReaderStreamViewController.swift */, 8BCB83D024C21063001581BD /* ReaderStreamViewController+Ghost.swift */, - 8BB185D024B63D1600A4CCE8 /* ReaderCardsStreamViewController.swift */, + 8BB185D024B63D1600A4CCE8 /* ReaderDiscoverViewController.swift */, 3234BB162530DFCA0068DA40 /* ReaderTableCardCell.swift */, 8BCF957924C6044000712056 /* ReaderTopicsCardCell.swift */, 3234B8E6252FA0930068DA40 /* ReaderSitesCardCell.swift */, @@ -22374,7 +22374,7 @@ B57AF5FA1ACDC73D0075A7D2 /* NoteBlockActionsTableViewCell.swift in Sources */, 8BA125EB27D8F5E4008B779F /* UIView+PinSubviewPriority.swift in Sources */, FEAC916E28001FC4005026E7 /* AvatarTrainView.swift in Sources */, - 8BB185D224B63D5F00A4CCE8 /* ReaderCardsStreamViewController.swift in Sources */, + 8BB185D224B63D5F00A4CCE8 /* ReaderDiscoverViewController.swift in Sources */, 742C79981E5F511C00DB1608 /* DeleteSiteViewController.swift in Sources */, 0C748B4B2A9D71A100809E1A /* SiteMediaCollectionViewController.swift in Sources */, 85DA8C4418F3F29A0074C8A4 /* WPAnalyticsTrackerWPCom.m in Sources */, @@ -25329,7 +25329,7 @@ FABB23572602FC2C00C8785C /* PostTagPickerViewController.swift in Sources */, FABB23582602FC2C00C8785C /* SignupEpilogueCell.swift in Sources */, FABB23592602FC2C00C8785C /* NoteBlockActionsTableViewCell.swift in Sources */, - FABB235A2602FC2C00C8785C /* ReaderCardsStreamViewController.swift in Sources */, + FABB235A2602FC2C00C8785C /* ReaderDiscoverViewController.swift in Sources */, 0C14F9722C8A48BA0084E5C0 /* ReaderSubscriptionCell.swift in Sources */, FABB235B2602FC2C00C8785C /* DeleteSiteViewController.swift in Sources */, FABB235C2602FC2C00C8785C /* WPAnalyticsTrackerWPCom.m in Sources */, From 751b7fc405f529c360a76ca8b750dbf869c2cec1 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 11:31:03 -0400 Subject: [PATCH 07/30] Move ReaderDiscoverHeaderView --- .../Reader/ReaderDiscoverHeaderView.swift | 14 +---------- .../Reader/ReaderDiscoverViewController.swift | 11 ++++++++- .../ReaderStreamViewController+Helper.swift | 23 ------------------- .../Reader/ReaderStreamViewController.swift | 9 ++++++++ 4 files changed, 20 insertions(+), 37 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index c29ddbf675c5..b447638e8ed9 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -1,12 +1,10 @@ import UIKit -final class ReaderDiscoverHeaderView: UIView, ReaderStreamHeader { +final class ReaderDiscoverHeaderView: UIView { private let titleView = ReaderStreamTitleView() private let tagsStackView = UIStackView(spacing: 8, []) private var tagViews: [ReaderTagView] = [] - weak var delegate: ReaderStreamHeaderDelegate? - override init(frame: CGRect) { super.init(frame: frame) @@ -60,16 +58,6 @@ final class ReaderDiscoverHeaderView: UIView, ReaderStreamHeader { view.isSelected = view.title == tag } } - - // MARK: - ReaderStreamHeader - - func enableLoggedInFeatures(_ enable: Bool) { - // Do nothing - } - - func configureHeader(_ topic: ReaderAbstractTopic) { - // Do nothing - } } private final class ReaderTagView: UIView { diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 9b417cba6dbd..2ad99f90617b 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -48,7 +48,16 @@ class ReaderDiscoverViewController: ReaderStreamViewController { displaySelectInterestsIfNeeded() } - // MARK: - TableView Related + // MARK: - Header + + override func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? { + if FeatureFlag.readerReset.enabled { + return ReaderDiscoverHeaderView() + } + return nil + } + + // MARK: - UITableView override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let card = cards?[indexPath.row] else { diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift index 611c96eaf61d..5091cfbed8e9 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift @@ -10,24 +10,6 @@ extension ReaderStreamViewController { var message: String } - /// Returns the ReaderStreamHeader appropriate for a particular ReaderTopic. - /// The header is returned already configured - /// - /// - Parameter topic: A ReaderTopic - /// - Parameter isLoggedIn: A boolean flag indicating if the user is logged in - /// - Parameter delegate: The header delegate - /// - /// - Returns: A configured instance of UIView. - /// - func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? { - if let topic, - let header = headerForStream(topic) { - configure(header, topic: topic, isLoggedIn: isLoggedIn, delegate: self) - return header - } - return nil - } - func configure(_ header: ReaderHeader?, topic: ReaderAbstractTopic, isLoggedIn: Bool, delegate: ReaderStreamHeaderDelegate) { header?.configureHeader(topic) header?.enableLoggedInFeatures(isLoggedIn) @@ -35,11 +17,6 @@ extension ReaderStreamViewController { } func headerForStream(_ topic: ReaderAbstractTopic) -> ReaderHeader? { - - if FeatureFlag.readerReset.enabled && self is ReaderDiscoverViewController { - return ReaderDiscoverHeaderView() - } - if ReaderHelpers.isTopicTag(topic) && !isContentFiltered { guard let nibViews = Bundle.main.loadNibNamed("ReaderTagStreamHeader", owner: nil, options: nil) as? [ReaderTagStreamHeader] else { return nil diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift index 72c0b6221722..59d0a68e3ddc 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift @@ -569,6 +569,15 @@ import AutomatticTracks tableView.tableHeaderView = tableView.tableHeaderView } + func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? { + if let topic, + let header = headerForStream(topic) { + configure(header, topic: topic, isLoggedIn: isLoggedIn, delegate: self) + return header + } + return nil + } + /// Updates the content based on the values of `readerTopic` and `contentType` private func updateContent(synchronize: Bool = true) { // if the view has not been loaded yet, this will be called in viewDidLoad From 5bebf9ae99694c9f9cd727ef09b671326ffa1f2e Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 11:35:03 -0400 Subject: [PATCH 08/30] Fix separator insets in ReaderPostCell --- WordPress/Classes/ViewRelated/Reader/ReaderPostCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPostCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPostCell.swift index 20f42ad93882..c967bcfc77a7 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPostCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPostCell.swift @@ -61,7 +61,7 @@ final class ReaderPostCell: UITableViewCell { } private func updateSeparatorsInsets() { - separatorInset = UIEdgeInsets(.leading, isSeparatorHidden ? 9999 : view.insets.left + contentView.readableContentGuide.layoutFrame.minX) + separatorInset = UIEdgeInsets(.leading, isSeparatorHidden ? 9999 : view.insets.left + (isCompact ? 0 : contentView.readableContentGuide.layoutFrame.minX)) } override func updateConstraints() { From 53b5a02bb84ad76915d4850634d9a78202898fe4 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 11:50:39 -0400 Subject: [PATCH 09/30] Extract ReaderStreamTitleView --- .../Reader/ReaderDiscoverHeaderView.swift | 22 ----------------- .../Reader/ReaderStreamTitleView.swift | 24 +++++++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 6 +++++ 3 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index b447638e8ed9..ccce0d4648ef 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -115,28 +115,6 @@ private final class ReaderTagView: UIView { } } -final class ReaderStreamTitleView: UIView { - let titleLabel = UILabel() - let detailsLabel = UILabel() - - override init(frame: CGRect) { - super.init(frame: frame) - - titleLabel.font = UIFont.preferredFont(forTextStyle: .largeTitle).withWeight(.bold) - detailsLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) - detailsLabel.textColor = .secondaryLabel - detailsLabel.numberOfLines = 0 - - let stackView = UIStackView(axis: .vertical, alignment: .leading, [titleLabel, detailsLabel]) - addSubview(stackView) - stackView.pinEdges() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - private enum Strings { static let title = NSLocalizedString("reader.discover.header.title", value: "Discover", comment: "Header view title") static let details = NSLocalizedString("reader.discover.header.title", value: "Explore popular blogs that inspire, educate, and entertain.", comment: "Header view details") diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift new file mode 100644 index 000000000000..8cfe8bc2fe99 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift @@ -0,0 +1,24 @@ +import UIKit + +/// A Reader stream header with a large title and a description. +final class ReaderStreamTitleView: UIView { + let titleLabel = UILabel() + let detailsLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + titleLabel.font = UIFont.preferredFont(forTextStyle: .largeTitle).withWeight(.bold) + detailsLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) + detailsLabel.textColor = .secondaryLabel + detailsLabel.numberOfLines = 0 + + let stackView = UIStackView(axis: .vertical, alignment: .leading, [titleLabel, detailsLabel]) + addSubview(stackView) + stackView.pinEdges() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a0506b1f1186..c98d0923b042 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -435,6 +435,8 @@ 0C0AE75A2A8FAD6A007D9D6C /* MediaPickerMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0AE7582A8FAD6A007D9D6C /* MediaPickerMenu.swift */; }; 0C0BEEEB2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0BEEEA2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift */; }; 0C0BEEEC2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0BEEEA2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift */; }; + 0C0BEEEE2CC169A60073F4E0 /* ReaderStreamTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0BEEED2CC169A40073F4E0 /* ReaderStreamTitleView.swift */; }; + 0C0BEEEF2CC169A60073F4E0 /* ReaderStreamTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0BEEED2CC169A40073F4E0 /* ReaderStreamTitleView.swift */; }; 0C0D3B0D2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; 0C0D3B0E2A4C79DE0050A00D /* BlazeCampaignsStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */; }; 0C0DF8942C2DF14600011B7D /* LoginFacadeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0C0DF8932C2DF12A00011B7D /* LoginFacadeTests.m */; }; @@ -6444,6 +6446,7 @@ 0C0AD1092B0CCFA400EC06E6 /* MediaPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewController.swift; sourceTree = ""; }; 0C0AE7582A8FAD6A007D9D6C /* MediaPickerMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerMenu.swift; sourceTree = ""; }; 0C0BEEEA2CC02D2C0073F4E0 /* ReaderDiscoverHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderDiscoverHeaderView.swift; sourceTree = ""; }; + 0C0BEEED2CC169A40073F4E0 /* ReaderStreamTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderStreamTitleView.swift; sourceTree = ""; }; 0C0D3B0C2A4C79DE0050A00D /* BlazeCampaignsStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaignsStream.swift; sourceTree = ""; }; 0C0DF8932C2DF12A00011B7D /* LoginFacadeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LoginFacadeTests.m; sourceTree = ""; }; 0C0F05522C6290670040390D /* ReaderFeedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderFeedCell.swift; sourceTree = ""; }; @@ -17885,6 +17888,7 @@ E6D2E16A1B8B41AC0000ED14 /* Headers */ = { isa = PBXGroup; children = ( + 0C0BEEED2CC169A40073F4E0 /* ReaderStreamTitleView.swift */, E6D2E1681B8AAD9B0000ED14 /* ReaderListStreamHeader.swift */, E6D2E1621B8AAA340000ED14 /* ReaderListStreamHeader.xib */, 83A337A02A9FA525009ED60C /* ReaderSiteHeaderView.swift */, @@ -22366,6 +22370,7 @@ FF70A3231FD5840500BC270D /* UIImage+Export.swift in Sources */, D81322B32050F9110067714D /* NotificationName+Names.swift in Sources */, F4BECD1B288EE5220078391A /* SuggestionsViewModelType.swift in Sources */, + 0C0BEEEE2CC169A60073F4E0 /* ReaderStreamTitleView.swift in Sources */, B5DD04741CD3DAB00003DF89 /* NSFetchedResultsController+Helpers.swift in Sources */, B55F1AA81C10936600FD04D4 /* BlogSettings+Discussion.swift in Sources */, E155EC721E9B7DCE009D7F63 /* PostTagPickerViewController.swift in Sources */, @@ -26080,6 +26085,7 @@ FABB25772602FC2C00C8785C /* NotificationMediaDownloader.swift in Sources */, FABB25782602FC2C00C8785C /* WizardDelegate.swift in Sources */, 0CDDCA092C8F4126005AACA3 /* ReaderTagsAddTagView.swift in Sources */, + 0C0BEEEF2CC169A60073F4E0 /* ReaderStreamTitleView.swift in Sources */, FABB25792602FC2C00C8785C /* Blog+Files.swift in Sources */, FABB257A2602FC2C00C8785C /* RevisionDiffsBrowserViewController.swift in Sources */, FA4FE0B42BEB6EF700A635D3 /* PostHelper+Metadata.swift in Sources */, From ab39fedb28f97d363ffed62f0aa367de8497ea54 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 12:26:28 -0400 Subject: [PATCH 10/30] Implement selection in ReaderDiscoverHeaderView --- .../Reader/ReaderDiscoverHeaderView.swift | 70 +++++++++++++------ .../Reader/ReaderDiscoverViewController.swift | 26 +++++-- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index ccce0d4648ef..26de66063181 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -1,9 +1,17 @@ import UIKit +protocol ReaderDiscoverHeaderViewDelegate: AnyObject { + func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverTag) +} + final class ReaderDiscoverHeaderView: UIView { private let titleView = ReaderStreamTitleView() private let tagsStackView = UIStackView(spacing: 8, []) - private var tagViews: [ReaderTagView] = [] + private var tagViews: [ReaderDiscoverTagView] = [] + + private var selectedTag: ReaderDiscoverTag? + + weak var delegate: ReaderDiscoverHeaderViewDelegate? override init(frame: CGRect) { super.init(frame: frame) @@ -21,16 +29,13 @@ final class ReaderDiscoverHeaderView: UIView { titleView.titleLabel.text = Strings.title titleView.detailsLabel.text = Strings.details - - // TODO: (reader) configure dynamically. where does these come from? - configure(tags: ["Recommended", "First posts", "Latest", "Daily prompts", "Food"]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - func configure(tags: [String]) { + func configure(tags: [ReaderDiscoverTag]) { for view in tagViews { view.removeFromSuperview() } @@ -40,31 +45,37 @@ final class ReaderDiscoverHeaderView: UIView { } } - private func makeTagView(_ title: String) -> ReaderTagView { - let view = ReaderTagView(title: title) - view.button.addAction(UIAction { [weak self, weak view] _ in - guard let self, let view else { return } - self.readerTagViewTapped(view) + func setSelectedTag(_ tag: ReaderDiscoverTag) { + selectedTag = tag + refreshTagViews() + } + + private func makeTagView(_ tag: ReaderDiscoverTag) -> ReaderDiscoverTagView { + let view = ReaderDiscoverTagView(tag: tag) + view.button.addAction(UIAction { [weak self] _ in + self?.didSelectTag(tag) }, for: .primaryActionTriggered) return view } - @objc private func readerTagViewTapped(_ view: ReaderTagView) { - didSelectTag(view.title) + private func didSelectTag(_ tag: ReaderDiscoverTag) { + selectedTag = tag + delegate?.readerDiscoverHeaderView(self, didChangeSelection: tag) + refreshTagViews() } - private func didSelectTag(_ tag: String) { - for view in self.tagViews { - view.isSelected = view.title == tag + private func refreshTagViews() { + for view in tagViews { + view.isSelected = view.discoverTag == selectedTag } } } -private final class ReaderTagView: UIView { +private final class ReaderDiscoverTagView: UIView { private let textLabel = UILabel() private let backgroundView = UIView() let button = UIButton(type: .system) - let title: String + let discoverTag: ReaderDiscoverTag var isSelected: Bool = false { didSet { @@ -73,13 +84,13 @@ private final class ReaderTagView: UIView { } } - init(title: String) { - self.title = title + init(tag: ReaderDiscoverTag) { + self.discoverTag = tag super.init(frame: .zero) textLabel.font = UIFont.preferredFont(forTextStyle: .subheadline).withWeight(.medium) - textLabel.text = title + textLabel.text = discoverTag.localizedTitle backgroundView.clipsToBounds = true @@ -115,6 +126,25 @@ private final class ReaderTagView: UIView { } } +enum ReaderDiscoverTag: Hashable { + /// The default channel showing your selected tags. + case recommended + + // case firstPosts + + /// Latest post from your selected tags. + case latest + + var localizedTitle: String { + switch self { + case .recommended: + NSLocalizedString("reader.discover.header.tag.recommended", value: "Recommended", comment: "Header view tag (filter)") + case .latest: + NSLocalizedString("reader.discover.header.tag.latest", value: "Latest", comment: "Header view tag (filter)") + } + } +} + private enum Strings { static let title = NSLocalizedString("reader.discover.header.title", value: "Discover", comment: "Header view title") static let details = NSLocalizedString("reader.discover.header.title", value: "Explore popular blogs that inspire, educate, and entertain.", comment: "Header view details") diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 2ad99f90617b..7e714aff6bc2 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -1,6 +1,10 @@ import Foundation +import UIKit +import Combine + +class ReaderDiscoverViewController: ReaderStreamViewController, ReaderDiscoverHeaderViewDelegate { + private let headerView = ReaderDiscoverHeaderView() -class ReaderDiscoverViewController: ReaderStreamViewController { private let readerCardTopicsIdentifier = "ReaderTopicsCell" private let readerCardSitesIdentifier = "ReaderSitesCell" @@ -14,9 +18,7 @@ class ReaderDiscoverViewController: ReaderStreamViewController { content.content as? [ReaderCard] } - lazy var cardsService: ReaderCardService = { - return ReaderCardService() - }() + private lazy var cardsService = ReaderCardService() /// Whether the current view controller is visible private var isVisible: Bool { @@ -40,7 +42,9 @@ class ReaderDiscoverViewController: ReaderStreamViewController { override func viewDidLoad() { super.viewDidLoad() + addObservers() + setupHeaderView() } override func viewWillAppear(_ animated: Bool) { @@ -48,11 +52,17 @@ class ReaderDiscoverViewController: ReaderStreamViewController { displaySelectInterestsIfNeeded() } + func setupHeaderView() { + headerView.configure(tags: [.recommended, .latest]) + headerView.setSelectedTag(.recommended) + headerView.delegate = self + } + // MARK: - Header override func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? { if FeatureFlag.readerReset.enabled { - return ReaderDiscoverHeaderView() + return headerView } return nil } @@ -242,6 +252,12 @@ class ReaderDiscoverViewController: ReaderStreamViewController { super.syncIfAppropriate(forceSync: true) tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.fade) } + + // MARK: - ReaderDiscoverHeaderViewDelegate + + func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverTag) { + print("sel:", selection) + } } // MARK: - Select Interests Display From 90a600396c5d9ea7860718ff2f52ef67d4ab3ba5 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 13:02:14 -0400 Subject: [PATCH 11/30] Extract ReaderDiscoverStreamViewController to allow switching between streams --- .../Classes/System/ReaderPresenter.swift | 2 +- .../Reader/ReaderDiscoverViewController.swift | 92 +++++++++++++------ .../ReaderStreamViewController+Helper.swift | 9 ++ .../Reader/ReaderStreamViewController.swift | 12 +-- ...TabBarController+ReaderTabNavigation.swift | 2 +- 5 files changed, 78 insertions(+), 39 deletions(-) diff --git a/WordPress/Classes/System/ReaderPresenter.swift b/WordPress/Classes/System/ReaderPresenter.swift index 3996e86a493d..967d99cb40bd 100644 --- a/WordPress/Classes/System/ReaderPresenter.swift +++ b/WordPress/Classes/System/ReaderPresenter.swift @@ -111,7 +111,7 @@ final class ReaderPresenter: NSObject, SplitViewDisplayable { case .recent, .discover, .likes: if let topic = screen.topicType.flatMap(sidebarViewModel.getTopic) { if screen == .discover { - return ReaderDiscoverViewController.controller(topic: topic) + return ReaderDiscoverViewController(topic: topic) } else { return ReaderStreamViewController.controllerWithTopic(topic) } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 7e714aff6bc2..04413600c84f 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -2,9 +2,67 @@ import Foundation import UIKit import Combine -class ReaderDiscoverViewController: ReaderStreamViewController, ReaderDiscoverHeaderViewDelegate { +class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDelegate, ReaderContentViewController { private let headerView = ReaderDiscoverHeaderView() + private var selectedTag: ReaderDiscoverTag = .recommended + private let topic: ReaderAbstractTopic + private var streamVC: ReaderStreamViewController? + init(topic: ReaderAbstractTopic) { + wpAssert(ReaderHelpers.topicIsDiscover(topic)) + self.topic = topic + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupNavigation() + setupHeaderView() + configureStream(ReaderDiscoverStreamViewController.controller(topic: topic)) + } + + private func setupNavigation() { + navigationItem.largeTitleDisplayMode = .never + } + + private func setupHeaderView() { + headerView.configure(tags: [.recommended, .latest]) + headerView.setSelectedTag(selectedTag) + headerView.delegate = self + } + + private func configureStream(_ streamVC: ReaderStreamViewController) { + self.streamVC = streamVC + + addChild(streamVC) + view.addSubview(streamVC.view) + streamVC.view.pinEdges() + streamVC.didMove(toParent: self) + + if FeatureFlag.readerReset.enabled { + streamVC.setHeaderView(headerView) + } + } + + // MARK: - ReaderContentViewController (Deprecated) + + func setContent(_ content: ReaderContent) { + streamVC?.setContent(content) + } + + // MARK: - ReaderDiscoverHeaderViewDelegate + + func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverTag) { + print("sel:", selection) + } +} + +private class ReaderDiscoverStreamViewController: ReaderStreamViewController { private let readerCardTopicsIdentifier = "ReaderTopicsCell" private let readerCardSitesIdentifier = "ReaderSitesCell" @@ -44,7 +102,6 @@ class ReaderDiscoverViewController: ReaderStreamViewController, ReaderDiscoverHe super.viewDidLoad() addObservers() - setupHeaderView() } override func viewWillAppear(_ animated: Bool) { @@ -52,21 +109,6 @@ class ReaderDiscoverViewController: ReaderStreamViewController, ReaderDiscoverHe displaySelectInterestsIfNeeded() } - func setupHeaderView() { - headerView.configure(tags: [.recommended, .latest]) - headerView.setSelectedTag(.recommended) - headerView.delegate = self - } - - // MARK: - Header - - override func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? { - if FeatureFlag.readerReset.enabled { - return headerView - } - return nil - } - // MARK: - UITableView override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -212,8 +254,8 @@ class ReaderDiscoverViewController: ReaderStreamViewController, ReaderDiscoverHe /// /// - Returns: An instance of the controller /// - class func controller(topic: ReaderAbstractTopic) -> ReaderDiscoverViewController { - let controller = ReaderDiscoverViewController() + class func controller(topic: ReaderAbstractTopic) -> ReaderDiscoverStreamViewController { + let controller = ReaderDiscoverStreamViewController() controller.readerTopic = topic return controller } @@ -252,16 +294,10 @@ class ReaderDiscoverViewController: ReaderStreamViewController, ReaderDiscoverHe super.syncIfAppropriate(forceSync: true) tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.fade) } - - // MARK: - ReaderDiscoverHeaderViewDelegate - - func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverTag) { - print("sel:", selection) - } } // MARK: - Select Interests Display -private extension ReaderDiscoverViewController { +private extension ReaderDiscoverStreamViewController { func displaySelectInterestsIfNeeded() { selectInterestsViewController.userIsFollowingTopics { [weak self] isFollowing in guard let self else { @@ -278,7 +314,7 @@ private extension ReaderDiscoverViewController { // MARK: - ReaderTopicsTableCardCellDelegate -extension ReaderDiscoverViewController: ReaderTopicsTableCardCellDelegate { +extension ReaderDiscoverStreamViewController: ReaderTopicsTableCardCellDelegate { func didSelect(topic: ReaderAbstractTopic) { if topic as? ReaderTagTopic != nil { WPAnalytics.trackReader(.readerDiscoverTopicTapped) @@ -298,7 +334,7 @@ extension ReaderDiscoverViewController: ReaderTopicsTableCardCellDelegate { // MARK: - ReaderSitesCardCellDelegate -extension ReaderDiscoverViewController: ReaderSitesCardCellDelegate { +extension ReaderDiscoverStreamViewController: ReaderSitesCardCellDelegate { func handleFollowActionForTopic(_ topic: ReaderAbstractTopic, for cell: ReaderSitesCardCell) { toggleFollowingForTopic(topic) { success in cell.didToggleFollowing(topic, with: success) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift index 5091cfbed8e9..d371fef73fde 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Helper.swift @@ -10,6 +10,15 @@ extension ReaderStreamViewController { var message: String } + func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? { + if let topic, + let header = headerForStream(topic) { + configure(header, topic: topic, isLoggedIn: isLoggedIn, delegate: self) + return header + } + return nil + } + func configure(_ header: ReaderHeader?, topic: ReaderAbstractTopic, isLoggedIn: Bool, delegate: ReaderStreamHeaderDelegate) { header?.configureHeader(topic) header?.enableLoggedInFeatures(isLoggedIn) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift index 59d0a68e3ddc..ce0fa572e1a5 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift @@ -544,7 +544,10 @@ import AutomatticTracks tableView.tableHeaderView = nil return } + setHeaderView(headerView) + } + func setHeaderView(_ headerView: UIView) { if let tableHeaderView = tableView.tableHeaderView { headerView.isHidden = tableHeaderView.isHidden } @@ -569,15 +572,6 @@ import AutomatticTracks tableView.tableHeaderView = tableView.tableHeaderView } - func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? { - if let topic, - let header = headerForStream(topic) { - configure(header, topic: topic, isLoggedIn: isLoggedIn, delegate: self) - return header - } - return nil - } - /// Updates the content based on the values of `readerTopic` and `contentType` private func updateContent(synchronize: Bool = true) { // if the view has not been loaded yet, this will be called in viewDidLoad diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift index 41dda4e7c5e0..a91fbab243a2 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/WPTabBarController+ReaderTabNavigation.swift @@ -18,7 +18,7 @@ extension WPTabBarController { let viewModel = ReaderTabViewModel( readerContentFactory: { content in if content.topicType == .discover, let topic = content.topic { - return ReaderDiscoverViewController.controller(topic: topic) + return ReaderDiscoverViewController(topic: topic) } else if let topic = content.topic { return ReaderStreamViewController.controllerWithTopic(topic) } else { From 85a74ab0143f0a86c39c68b4a67b93e146efcb77 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 14:28:56 -0400 Subject: [PATCH 12/30] Add initial implementation of Latest Posts --- .../Classes/Services/ReaderCardService.swift | 4 +- .../Classes/Utility/ContextManager.swift | 7 +++ .../Reader/ReaderDiscoverHeaderView.swift | 3 + .../Reader/ReaderDiscoverViewController.swift | 59 +++++++++++++------ 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/WordPress/Classes/Services/ReaderCardService.swift b/WordPress/Classes/Services/ReaderCardService.swift index 21c434bce8bb..1145a59c9bbe 100644 --- a/WordPress/Classes/Services/ReaderCardService.swift +++ b/WordPress/Classes/Services/ReaderCardService.swift @@ -27,6 +27,8 @@ class ReaderCardService { /// Used only internally to order the cards private var pageNumber = 1 + var sorting: ReaderSortingOption = .noSorting + init(service: ReaderCardServiceRemote = ReaderPostServiceRemote.withDefaultApi(), coreDataStack: CoreDataStack = ContextManager.shared, followedInterestsService: ReaderFollowedInterestsService? = nil, @@ -109,7 +111,7 @@ class ReaderCardService { self.service.fetchStreamCards( for: slugs, page: self.pageHandle(isFirstPage: isFirstPage), - sortingOption: .noSorting, + sortingOption: self.sorting, refreshCount: refreshCount, count: nil, success: success, diff --git a/WordPress/Classes/Utility/ContextManager.swift b/WordPress/Classes/Utility/ContextManager.swift index 0385684e2fb7..dcac8518097a 100644 --- a/WordPress/Classes/Utility/ContextManager.swift +++ b/WordPress/Classes/Utility/ContextManager.swift @@ -176,6 +176,13 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift { } } + func batchDelete(entity: T.Type) throws { + let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: ReaderCard.entityName()) + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + let coordinator = persistentContainer.persistentStoreCoordinator + try coordinator.execute(deleteRequest, with: mainContext) + } + static func migrateDataModelsIfNecessary(storeURL: URL, objectModel: NSManagedObjectModel) throws { guard FileManager.default.fileExists(atPath: storeURL.path) else { DDLogInfo("No store exists at \(storeURL). Skipping migration.") diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index 26de66063181..f77eb431ae61 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -59,6 +59,9 @@ final class ReaderDiscoverHeaderView: UIView { } private func didSelectTag(_ tag: ReaderDiscoverTag) { + guard selectedTag != tag else { + return + } selectedTag = tag delegate?.readerDiscoverHeaderView(self, didChangeSelection: tag) refreshTagViews() diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 04413600c84f..9865119740fc 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -23,7 +23,8 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe setupNavigation() setupHeaderView() - configureStream(ReaderDiscoverStreamViewController.controller(topic: topic)) + + configureStream(for: selectedTag) } private func setupNavigation() { @@ -36,7 +37,30 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe headerView.delegate = self } - private func configureStream(_ streamVC: ReaderStreamViewController) { + // MARK: - Selected Stream + + private func configureStream(for tag: ReaderDiscoverTag) { + showStreamViewController(makeViewController(for: tag)) + } + + private func makeViewController(for tag: ReaderDiscoverTag) -> ReaderStreamViewController { + switch tag { + case .recommended: + ReaderDiscoverStreamViewController(topic: topic) + case .latest: + ReaderDiscoverStreamViewController(topic: topic, sorting: .date) + } + } + + private func showStreamViewController(_ streamVC: ReaderStreamViewController) { + if let currentVC = self.streamVC { + deleteCachedReaderCards() + + currentVC.willMove(toParent: nil) + currentVC.view.removeFromSuperview() + currentVC.removeFromParent() + } + self.streamVC = streamVC addChild(streamVC) @@ -49,6 +73,15 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe } } + /// TODO: (tech-debt) the app currently stores the responses from the `/discover` + /// entpoint (cards) in Core Data with no way to distinguish between the + /// requests with different parameters like different sort. In order to + /// address it, the app currently drops the previously cached responses + /// when you change the streams. + private func deleteCachedReaderCards() { + try? ContextManager.shared.batchDelete(entity: ReaderCard.self) + } + // MARK: - ReaderContentViewController (Deprecated) func setContent(_ content: ReaderContent) { @@ -58,7 +91,8 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe // MARK: - ReaderDiscoverHeaderViewDelegate func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverTag) { - print("sel:", selection) + self.selectedTag = selection + configureStream(for: selection) } } @@ -83,9 +117,12 @@ private class ReaderDiscoverStreamViewController: ReaderStreamViewController { return isViewLoaded && view.window != nil } - init() { + init(topic: ReaderAbstractTopic, sorting: ReaderSortingOption = .noSorting) { super.init(nibName: nil, bundle: nil) + self.readerTopic = topic + self.cardsService.sorting = sorting + // register table view cells specific to this controller as early as possible. // the superclass might trigger `layoutIfNeeded` from its `viewDidLoad`, and we want to make sure that // all the cell types have been registered by that time. @@ -246,20 +283,6 @@ private class ReaderDiscoverStreamViewController: ReaderStreamViewController { return NSPredicate(format: "post != NULL OR topics.@count != 0 OR sites.@count != 0") } - /// Convenience method for instantiating an instance of ReaderCardsStreamViewController - /// for a existing topic. - /// - /// - Parameters: - /// - topic: Any subclass of ReaderAbstractTopic - /// - /// - Returns: An instance of the controller - /// - class func controller(topic: ReaderAbstractTopic) -> ReaderDiscoverStreamViewController { - let controller = ReaderDiscoverStreamViewController() - controller.readerTopic = topic - return controller - } - private func addObservers() { // Listens for when the reader manage view controller is dismissed From d54405705d00ab340029f71c8fa2982bf4e98f3a Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 16:36:42 -0400 Subject: [PATCH 13/30] Fix how ghost cells are shown --- .../Reader/ReaderDiscoverViewController.swift | 10 +++++---- .../ReaderStreamViewController+Ghost.swift | 22 ++++++++++++++----- .../Reader/ReaderStreamViewController.swift | 14 +++++++++--- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 9865119740fc..ab6bd8a91d23 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -63,14 +63,16 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe self.streamVC = streamVC + if FeatureFlag.readerReset.enabled { + // Important to set before `viewDidLoad` + streamVC.isReaderResetDiscoverEnabled = true + streamVC.setHeaderView(headerView) + } + addChild(streamVC) view.addSubview(streamVC.view) streamVC.view.pinEdges() streamVC.didMove(toParent: self) - - if FeatureFlag.readerReset.enabled { - streamVC.setHeaderView(headerView) - } } /// TODO: (tech-debt) the app currently stores the responses from the `/discover` diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift index 2c5e659a155d..7a66c783a796 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController+Ghost.swift @@ -11,12 +11,22 @@ extension ReaderStreamViewController { ghostableTableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(ghostableTableView) - NSLayoutConstraint.activate([ - ghostableTableView.widthAnchor.constraint(equalTo: tableView.widthAnchor, multiplier: 1), - ghostableTableView.heightAnchor.constraint(equalTo: tableView.heightAnchor, multiplier: 1), - ghostableTableView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor), - ghostableTableView.centerYAnchor.constraint(equalTo: tableView.centerYAnchor) - ]) + if isReaderResetDiscoverEnabled { + NSLayoutConstraint.activate([ + ghostableTableView.topAnchor.constraint(equalTo: tableView.tableHeaderView?.bottomAnchor ?? view.topAnchor), + ghostableTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + ghostableTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ghostableTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } else { + view.addSubview(ghostableTableView) + NSLayoutConstraint.activate([ + ghostableTableView.widthAnchor.constraint(equalTo: tableView.widthAnchor, multiplier: 1), + ghostableTableView.heightAnchor.constraint(equalTo: tableView.heightAnchor, multiplier: 1), + ghostableTableView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor), + ghostableTableView.centerYAnchor.constraint(equalTo: tableView.centerYAnchor) + ]) + } ghostableTableView.accessibilityIdentifier = "Reader Ghost Loading" ghostableTableView.cellLayoutMarginsFollowReadableWidth = true diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift index ce0fa572e1a5..08379eddf285 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamViewController.swift @@ -260,6 +260,8 @@ import AutomatticTracks lazy var isSidebarModeEnabled = splitViewController?.isCollapsed == false + var isReaderResetDiscoverEnabled = false + // MARK: - Factory Methods /// Convenience method for instantiating an instance of ReaderStreamViewController @@ -540,6 +542,9 @@ import AutomatticTracks // MARK: - Configuration / Topic Presentation @objc private func configureStreamHeader() { + guard !isReaderResetDiscoverEnabled else { + return + } guard let headerView = headerForStream(readerTopic, isLoggedIn: isLoggedIn, container: tableViewController) else { tableView.tableHeaderView = nil return @@ -1718,8 +1723,9 @@ extension ReaderStreamViewController { if content.contentCount > 0 { return } - - tableView.tableHeaderView?.isHidden = true + if !isReaderResetDiscoverEnabled { + tableView.tableHeaderView?.isHidden = true + } configureResultsStatus(title: ResultsStatusText.fetchingPostsTitle, accessoryView: NoResultsViewController.loadingAccessoryView()) displayResultsStatus() showGhost() @@ -1739,7 +1745,9 @@ extension ReaderStreamViewController { return } - tableView.tableHeaderView?.isHidden = true + if !isReaderResetDiscoverEnabled { + tableView.tableHeaderView?.isHidden = true + } guard connectionAvailable() else { displayNoConnectionView() From c8871c36cea2ef031364791232c99a303db8d186 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 16:55:30 -0400 Subject: [PATCH 14/30] Add support for tags --- .../Reader/ReaderDiscoverHeaderView.swift | 5 +++++ .../Reader/ReaderDiscoverViewController.swift | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index f77eb431ae61..b3d0fbf3b2e4 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -138,12 +138,17 @@ enum ReaderDiscoverTag: Hashable { /// Latest post from your selected tags. case latest + /// A quick access for your tags. + case tag(ReaderTagTopic) + var localizedTitle: String { switch self { case .recommended: NSLocalizedString("reader.discover.header.tag.recommended", value: "Recommended", comment: "Header view tag (filter)") case .latest: NSLocalizedString("reader.discover.header.tag.latest", value: "Latest", comment: "Header view tag (filter)") + case .tag(let tag): + tag.title.localizedCapitalized } } } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index ab6bd8a91d23..e2708033a963 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -7,6 +7,9 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe private var selectedTag: ReaderDiscoverTag = .recommended private let topic: ReaderAbstractTopic private var streamVC: ReaderStreamViewController? + private var viewContext: NSManagedObjectContext { + ContextManager.shared.mainContext + } init(topic: ReaderAbstractTopic) { wpAssert(ReaderHelpers.topicIsDiscover(topic)) @@ -32,11 +35,21 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe } private func setupHeaderView() { - headerView.configure(tags: [.recommended, .latest]) + let tags = fetchTags().map(ReaderDiscoverTag.tag) + + headerView.configure(tags: [.recommended, .latest] + tags) headerView.setSelectedTag(selectedTag) headerView.delegate = self } + private func fetchTags() -> [ReaderTagTopic] { + viewContext.allObjects( + ofType: ReaderTagTopic.self, + matching: ReaderSidebarTagsSection.predicate, + sortedBy: [NSSortDescriptor(key: "title", ascending: true)] + ) + } + // MARK: - Selected Stream private func configureStream(for tag: ReaderDiscoverTag) { @@ -49,6 +62,8 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe ReaderDiscoverStreamViewController(topic: topic) case .latest: ReaderDiscoverStreamViewController(topic: topic, sorting: .date) + case .tag(let tag): + ReaderStreamViewController.controllerWithTopic(tag) } } From f8f4ed366abf9098c3cde03e60cd2a716b8a4e5f Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 17 Oct 2024 17:09:21 -0400 Subject: [PATCH 15/30] Rename tags to channels --- .../Reader/ReaderDiscoverHeaderView.swift | 66 +++++++++---------- .../Reader/ReaderDiscoverViewController.swift | 22 +++---- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index b3d0fbf3b2e4..6ef112847344 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -1,15 +1,15 @@ import UIKit protocol ReaderDiscoverHeaderViewDelegate: AnyObject { - func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverTag) + func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverChannel) } final class ReaderDiscoverHeaderView: UIView { private let titleView = ReaderStreamTitleView() - private let tagsStackView = UIStackView(spacing: 8, []) - private var tagViews: [ReaderDiscoverTagView] = [] + private let channelsStackView = UIStackView(spacing: 8, []) + private var channelViews: [ReaderDiscoverChannelView] = [] - private var selectedTag: ReaderDiscoverTag? + private var selectedChannel: ReaderDiscoverChannel? weak var delegate: ReaderDiscoverHeaderViewDelegate? @@ -17,11 +17,11 @@ final class ReaderDiscoverHeaderView: UIView { super.init(frame: frame) let scrollView = UIScrollView() - scrollView.addSubview(tagsStackView) + scrollView.addSubview(channelsStackView) scrollView.showsHorizontalScrollIndicator = false scrollView.clipsToBounds = false - tagsStackView.pinEdges() - scrollView.heightAnchor.constraint(equalTo: tagsStackView.heightAnchor).isActive = true + channelsStackView.pinEdges() + scrollView.heightAnchor.constraint(equalTo: channelsStackView.heightAnchor).isActive = true let stackView = UIStackView(axis: .vertical, spacing: 8, [titleView, scrollView]) addSubview(stackView) @@ -35,50 +35,50 @@ final class ReaderDiscoverHeaderView: UIView { fatalError("init(coder:) has not been implemented") } - func configure(tags: [ReaderDiscoverTag]) { - for view in tagViews { + func configure(channels: [ReaderDiscoverChannel]) { + for view in channelViews { view.removeFromSuperview() } - tagViews = tags.map(makeTagView) - for view in tagViews { - tagsStackView.addArrangedSubview(view) + channelViews = channels.map(makeChannelView) + for view in channelViews { + channelsStackView.addArrangedSubview(view) } } - func setSelectedTag(_ tag: ReaderDiscoverTag) { - selectedTag = tag - refreshTagViews() + func setSelectedChannel(_ channel: ReaderDiscoverChannel) { + selectedChannel = channel + refreshChannelViews() } - private func makeTagView(_ tag: ReaderDiscoverTag) -> ReaderDiscoverTagView { - let view = ReaderDiscoverTagView(tag: tag) + private func makeChannelView(_ channel: ReaderDiscoverChannel) -> ReaderDiscoverChannelView { + let view = ReaderDiscoverChannelView(channel: channel) view.button.addAction(UIAction { [weak self] _ in - self?.didSelectTag(tag) + self?.didSelectChannel(channel) }, for: .primaryActionTriggered) return view } - private func didSelectTag(_ tag: ReaderDiscoverTag) { - guard selectedTag != tag else { + private func didSelectChannel(_ channel: ReaderDiscoverChannel) { + guard selectedChannel != channel else { return } - selectedTag = tag - delegate?.readerDiscoverHeaderView(self, didChangeSelection: tag) - refreshTagViews() + selectedChannel = channel + delegate?.readerDiscoverHeaderView(self, didChangeSelection: channel) + refreshChannelViews() } - private func refreshTagViews() { - for view in tagViews { - view.isSelected = view.discoverTag == selectedTag + private func refreshChannelViews() { + for view in channelViews { + view.isSelected = view.channel == selectedChannel } } } -private final class ReaderDiscoverTagView: UIView { +private final class ReaderDiscoverChannelView: UIView { private let textLabel = UILabel() private let backgroundView = UIView() let button = UIButton(type: .system) - let discoverTag: ReaderDiscoverTag + let channel: ReaderDiscoverChannel var isSelected: Bool = false { didSet { @@ -87,13 +87,13 @@ private final class ReaderDiscoverTagView: UIView { } } - init(tag: ReaderDiscoverTag) { - self.discoverTag = tag + init(channel: ReaderDiscoverChannel) { + self.channel = channel super.init(frame: .zero) textLabel.font = UIFont.preferredFont(forTextStyle: .subheadline).withWeight(.medium) - textLabel.text = discoverTag.localizedTitle + textLabel.text = channel.localizedTitle backgroundView.clipsToBounds = true @@ -129,12 +129,10 @@ private final class ReaderDiscoverTagView: UIView { } } -enum ReaderDiscoverTag: Hashable { +enum ReaderDiscoverChannel: Hashable { /// The default channel showing your selected tags. case recommended - // case firstPosts - /// Latest post from your selected tags. case latest diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index e2708033a963..eb3f7e7e399b 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -4,7 +4,7 @@ import Combine class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDelegate, ReaderContentViewController { private let headerView = ReaderDiscoverHeaderView() - private var selectedTag: ReaderDiscoverTag = .recommended + private var selectedChannel: ReaderDiscoverChannel = .recommended private let topic: ReaderAbstractTopic private var streamVC: ReaderStreamViewController? private var viewContext: NSManagedObjectContext { @@ -27,7 +27,7 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe setupNavigation() setupHeaderView() - configureStream(for: selectedTag) + configureStream(for: selectedChannel) } private func setupNavigation() { @@ -35,10 +35,10 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe } private func setupHeaderView() { - let tags = fetchTags().map(ReaderDiscoverTag.tag) + let tags = fetchTags().map(ReaderDiscoverChannel.tag) - headerView.configure(tags: [.recommended, .latest] + tags) - headerView.setSelectedTag(selectedTag) + headerView.configure(channels: [.recommended, .latest] + tags) + headerView.setSelectedChannel(selectedChannel) headerView.delegate = self } @@ -52,12 +52,12 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe // MARK: - Selected Stream - private func configureStream(for tag: ReaderDiscoverTag) { - showStreamViewController(makeViewController(for: tag)) + private func configureStream(for channel: ReaderDiscoverChannel) { + showStreamViewController(makeViewController(for: channel)) } - private func makeViewController(for tag: ReaderDiscoverTag) -> ReaderStreamViewController { - switch tag { + private func makeViewController(for channel: ReaderDiscoverChannel) -> ReaderStreamViewController { + switch channel { case .recommended: ReaderDiscoverStreamViewController(topic: topic) case .latest: @@ -107,8 +107,8 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe // MARK: - ReaderDiscoverHeaderViewDelegate - func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverTag) { - self.selectedTag = selection + func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverChannel) { + self.selectedChannel = selection configureStream(for: selection) } } From 14e7dfa43fa532cfa8e4b532bbdf9a5ead8a8e6d Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 09:26:30 -0400 Subject: [PATCH 16/30] Integarte WPKit changes with code that loads avatars --- Modules/Package.swift | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../Classes/Services/ReaderCardService.swift | 15 +++++++++++---- .../Reader/ReaderDiscoverViewController.swift | 8 +++++--- .../Tab Navigation/ReaderTabViewController.swift | 2 +- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index 80d4ba337bf8..6407f9793dae 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -43,7 +43,7 @@ let package = Package( .package(url: "https://github.com/wordpress-mobile/MediaEditor-iOS", branch: "task/spm-support"), .package(url: "https://github.com/wordpress-mobile/NSObject-SafeExpectations", from: "0.0.6"), .package(url: "https://github.com/wordpress-mobile/NSURL-IDN", branch: "trunk"), - .package(url: "https://github.com/wordpress-mobile/WordPressKit-iOS", branch: "wpios-edition"), + .package(url: "https://github.com/wordpress-mobile/WordPressKit-iOS", branch: "task/reader-discover"), .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-swift-20240813"), diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index e4572bea8db4..5b43b8f0078e 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -381,8 +381,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/WordPressKit-iOS", "state" : { - "branch" : "wpios-edition", - "revision" : "c3eeb90e7a4f3664f85ff53f1fef009cda17d5b6" + "branch" : "task/reader-discover", + "revision" : "a5dfe35cd92e4df8bdd971edd162ecf02b7d0a29" } }, { diff --git a/WordPress/Classes/Services/ReaderCardService.swift b/WordPress/Classes/Services/ReaderCardService.swift index 1145a59c9bbe..cdf1bfc073bb 100644 --- a/WordPress/Classes/Services/ReaderCardService.swift +++ b/WordPress/Classes/Services/ReaderCardService.swift @@ -1,7 +1,9 @@ import Foundation +import WordPressKit protocol ReaderCardServiceRemote { - func fetchStreamCards(for topics: [String], + func fetchStreamCards(stream: ReaderStream, + for topics: [String], page: String?, sortingOption: ReaderSortingOption, refreshCount: Int?, @@ -14,6 +16,8 @@ protocol ReaderCardServiceRemote { extension ReaderPostServiceRemote: ReaderCardServiceRemote { } class ReaderCardService { + private let stream: ReaderStream + private let sorting: ReaderSortingOption private let service: ReaderCardServiceRemote private let coreDataStack: CoreDataStack @@ -27,12 +31,14 @@ class ReaderCardService { /// Used only internally to order the cards private var pageNumber = 1 - var sorting: ReaderSortingOption = .noSorting - - init(service: ReaderCardServiceRemote = ReaderPostServiceRemote.withDefaultApi(), + init(stream: ReaderStream, + sorting: ReaderSortingOption, + service: ReaderCardServiceRemote = ReaderPostServiceRemote.withDefaultApi(), coreDataStack: CoreDataStack = ContextManager.shared, followedInterestsService: ReaderFollowedInterestsService? = nil, siteInfoService: ReaderSiteInfoService? = nil) { + self.stream = stream + self.sorting = sorting self.service = service self.coreDataStack = coreDataStack self.followedInterestsService = followedInterestsService ?? ReaderTopicService(coreDataStack: coreDataStack) @@ -109,6 +115,7 @@ class ReaderCardService { } self.service.fetchStreamCards( + stream: self.stream, for: slugs, page: self.pageHandle(isFirstPage: isFirstPage), sortingOption: self.sorting, diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index eb3f7e7e399b..6e8792b322af 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import Combine +import WordPressKit class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDelegate, ReaderContentViewController { private let headerView = ReaderDiscoverHeaderView() @@ -127,18 +128,19 @@ private class ReaderDiscoverStreamViewController: ReaderStreamViewController { content.content as? [ReaderCard] } - private lazy var cardsService = ReaderCardService() + private let cardsService: ReaderCardService /// Whether the current view controller is visible private var isVisible: Bool { return isViewLoaded && view.window != nil } - init(topic: ReaderAbstractTopic, sorting: ReaderSortingOption = .noSorting) { + init(topic: ReaderAbstractTopic, stream: ReaderStream = .discover, sorting: ReaderSortingOption = .noSorting) { + self.cardsService = ReaderCardService(stream: stream, sorting: sorting) + super.init(nibName: nil, bundle: nil) self.readerTopic = topic - self.cardsService.sorting = sorting // register table view cells specific to this controller as early as possible. // the superclass might trigger `layoutIfNeeded` from its `viewDidLoad`, and we want to make sure that diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift index 2b88f2f4105d..6fa7a4b8e7bd 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift @@ -22,7 +22,7 @@ class ReaderTabViewController: UIViewController { title = ReaderTabConstants.title - ReaderCardService().clean() + ReaderCardService(stream: .discover, sorting: .popularity).clean() viewModel.filterTapped = { [weak self] (filter, fromView, completion) in guard let self = self else { From 1c30416862151dff1104baadd99118881b8eb1eb Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 09:31:27 -0400 Subject: [PATCH 17/30] Add first posts --- .../Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift | 4 ++++ .../ViewRelated/Reader/ReaderDiscoverViewController.swift | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index 6ef112847344..ddaff06f43c2 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -133,6 +133,8 @@ enum ReaderDiscoverChannel: Hashable { /// The default channel showing your selected tags. case recommended + case firstPosts + /// Latest post from your selected tags. case latest @@ -143,6 +145,8 @@ enum ReaderDiscoverChannel: Hashable { switch self { case .recommended: NSLocalizedString("reader.discover.header.tag.recommended", value: "Recommended", comment: "Header view tag (filter)") + case .firstPosts: + NSLocalizedString("reader.discover.header.tag.firstPost", value: "First Posts", comment: "Header view tag (filter)") case .latest: NSLocalizedString("reader.discover.header.tag.latest", value: "Latest", comment: "Header view tag (filter)") case .tag(let tag): diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 6e8792b322af..2db1675190d5 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -38,7 +38,7 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe private func setupHeaderView() { let tags = fetchTags().map(ReaderDiscoverChannel.tag) - headerView.configure(channels: [.recommended, .latest] + tags) + headerView.configure(channels: [.recommended, .firstPosts, .latest] + tags) headerView.setSelectedChannel(selectedChannel) headerView.delegate = self } @@ -61,6 +61,8 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe switch channel { case .recommended: ReaderDiscoverStreamViewController(topic: topic) + case .firstPosts: + ReaderDiscoverStreamViewController(topic: topic, stream: .firstPosts, sorting: .date) case .latest: ReaderDiscoverStreamViewController(topic: topic, sorting: .date) case .tag(let tag): From 1aee40d3bcd7d8a14137f72f2ad969c1343e3d0d Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 14:35:54 -0400 Subject: [PATCH 18/30] Add daily prompts --- .../ViewRelated/Reader/ReaderDiscoverHeaderView.swift | 11 ++++++++--- .../Reader/ReaderDiscoverViewController.swift | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index ddaff06f43c2..3c7c3745d1f2 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -133,22 +133,27 @@ enum ReaderDiscoverChannel: Hashable { /// The default channel showing your selected tags. case recommended + /// First posts in the selected tags. case firstPosts /// Latest post from your selected tags. case latest + case dailyPrompts + /// A quick access for your tags. case tag(ReaderTagTopic) var localizedTitle: String { switch self { case .recommended: - NSLocalizedString("reader.discover.header.tag.recommended", value: "Recommended", comment: "Header view tag (filter)") + NSLocalizedString("reader.discover.channel.recommended", value: "Recommended", comment: "Header view channel (filter)") case .firstPosts: - NSLocalizedString("reader.discover.header.tag.firstPost", value: "First Posts", comment: "Header view tag (filter)") + NSLocalizedString("reader.discover.channel.firstPost", value: "First Posts", comment: "Header view channel (filter)") case .latest: - NSLocalizedString("reader.discover.header.tag.latest", value: "Latest", comment: "Header view tag (filter)") + NSLocalizedString("reader.discover.channel.latest", value: "Latest", comment: "Header view channel (filter)") + case .dailyPrompts: + NSLocalizedString("reader.discover.channel.dailyPrompts", value: "Daily Prompts", comment: "Header view channel (filter)") case .tag(let tag): tag.title.localizedCapitalized } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 2db1675190d5..745218a278f1 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -36,9 +36,11 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe } private func setupHeaderView() { - let tags = fetchTags().map(ReaderDiscoverChannel.tag) + let tags = fetchTags() + .filter { $0.slug != "dailyprompts" } + .map(ReaderDiscoverChannel.tag) - headerView.configure(channels: [.recommended, .firstPosts, .latest] + tags) + headerView.configure(channels: [.recommended, .firstPosts, .latest, .dailyPrompts] + tags) headerView.setSelectedChannel(selectedChannel) headerView.delegate = self } @@ -65,6 +67,8 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe ReaderDiscoverStreamViewController(topic: topic, stream: .firstPosts, sorting: .date) case .latest: ReaderDiscoverStreamViewController(topic: topic, sorting: .date) + case .dailyPrompts: + ReaderStreamViewController.controllerWithTagSlug("dailyprompt") case .tag(let tag): ReaderStreamViewController.controllerWithTopic(tag) } From f8954dfaf923f3f425e07d998c55bd9ef2f0e6cf Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 14:41:21 -0400 Subject: [PATCH 19/30] Remove batchDelete --- WordPress/Classes/Services/ReaderCardService.swift | 12 ++++++++---- WordPress/Classes/Utility/ContextManager.swift | 7 ------- .../Reader/ReaderDiscoverViewController.swift | 2 +- .../Tab Navigation/ReaderTabViewController.swift | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/WordPress/Classes/Services/ReaderCardService.swift b/WordPress/Classes/Services/ReaderCardService.swift index cdf1bfc073bb..b536ab333d2f 100644 --- a/WordPress/Classes/Services/ReaderCardService.swift +++ b/WordPress/Classes/Services/ReaderCardService.swift @@ -72,7 +72,7 @@ class ReaderCardService { self.coreDataStack.performAndSave({ context in if isFirstPage { self.pageNumber = 1 - self.removeAllCards(in: context) + ReaderCardService.removeAllCards(in: context) } else { self.pageNumber += 1 } @@ -129,12 +129,16 @@ class ReaderCardService { /// Remove all cards and saves the context func clean() { - coreDataStack.performAndSave { context in - self.removeAllCards(in: context) + ReaderCardService.removeAllCards() + } + + static func removeAllCards(on stack: CoreDataStackSwift = ContextManager.shared) { + stack.performAndSave { context in + removeAllCards(in: context) } } - private func removeAllCards(in context: NSManagedObjectContext) { + private static func removeAllCards(in context: NSManagedObjectContext) { let fetchRequest = NSFetchRequest(entityName: ReaderCard.classNameWithoutNamespaces()) fetchRequest.returnsObjectsAsFaults = false diff --git a/WordPress/Classes/Utility/ContextManager.swift b/WordPress/Classes/Utility/ContextManager.swift index dcac8518097a..0385684e2fb7 100644 --- a/WordPress/Classes/Utility/ContextManager.swift +++ b/WordPress/Classes/Utility/ContextManager.swift @@ -176,13 +176,6 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift { } } - func batchDelete(entity: T.Type) throws { - let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: ReaderCard.entityName()) - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - let coordinator = persistentContainer.persistentStoreCoordinator - try coordinator.execute(deleteRequest, with: mainContext) - } - static func migrateDataModelsIfNecessary(storeURL: URL, objectModel: NSManagedObjectModel) throws { guard FileManager.default.fileExists(atPath: storeURL.path) else { DDLogInfo("No store exists at \(storeURL). Skipping migration.") diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 745218a278f1..1e977956a6c9 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -103,7 +103,7 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe /// address it, the app currently drops the previously cached responses /// when you change the streams. private func deleteCachedReaderCards() { - try? ContextManager.shared.batchDelete(entity: ReaderCard.self) + ReaderCardService.removeAllCards() } // MARK: - ReaderContentViewController (Deprecated) diff --git a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift index 6fa7a4b8e7bd..43c9ede06538 100644 --- a/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Tab Navigation/ReaderTabViewController.swift @@ -22,7 +22,7 @@ class ReaderTabViewController: UIViewController { title = ReaderTabConstants.title - ReaderCardService(stream: .discover, sorting: .popularity).clean() + ReaderCardService.removeAllCards() viewModel.filterTapped = { [weak self] (filter, fromView, completion) in guard let self = self else { From ca2381f376aec638c6f7065ff81df93c60b99fbc Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 14:43:58 -0400 Subject: [PATCH 20/30] Add a constant for dailyprompt --- WordPress/Classes/Models/ReaderTagTopic.swift | 4 ++++ .../Cards/Prompts/DashboardPromptsCardCell.swift | 3 +-- .../ViewRelated/Reader/ReaderDiscoverViewController.swift | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Models/ReaderTagTopic.swift b/WordPress/Classes/Models/ReaderTagTopic.swift index b9cc1974b08a..9151484d576d 100644 --- a/WordPress/Classes/Models/ReaderTagTopic.swift +++ b/WordPress/Classes/Models/ReaderTagTopic.swift @@ -58,3 +58,7 @@ import Foundation showInMenu = (following || isRecommended) } } + +extension ReaderTagTopic { + static let dailyPromptTag = "dailyprompt" +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift index 27df0c822ca6..88242cfcec3a 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardPromptsCardCell.swift @@ -179,7 +179,7 @@ class DashboardPromptsCardCell: UICollectionViewCell, Reusable { let promptID = prompt?.promptID else { return } - let tagName = "\(Constants.dailyPromptTag)-\(promptID)" + let tagName = "\(ReaderTagTopic.dailyPromptTag)-\(promptID)" RootViewCoordinator.sharedPresenter.showReader(path: .makeWithTagName(tagName)) WPAnalytics.track(.promptsOtherAnswersTapped) } @@ -582,7 +582,6 @@ private extension DashboardPromptsCardCell { static let exampleAnswerCount = 19 static let cardFrameConstraintPriority = UILayoutPriority(999) static let skippedPromptsUDKey = "wp_skipped_blogging_prompts" - static let dailyPromptTag = "dailyprompt" } // MARK: Contextual Menu diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index 1e977956a6c9..ca7851d40161 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -37,7 +37,7 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe private func setupHeaderView() { let tags = fetchTags() - .filter { $0.slug != "dailyprompts" } + .filter { $0.slug != ReaderTagTopic.dailyPromptTag } .map(ReaderDiscoverChannel.tag) headerView.configure(channels: [.recommended, .firstPosts, .latest, .dailyPrompts] + tags) @@ -68,7 +68,7 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe case .latest: ReaderDiscoverStreamViewController(topic: topic, sorting: .date) case .dailyPrompts: - ReaderStreamViewController.controllerWithTagSlug("dailyprompt") + ReaderStreamViewController.controllerWithTagSlug(ReaderTagTopic.dailyPromptTag) case .tag(let tag): ReaderStreamViewController.controllerWithTopic(tag) } From 8cec93bf413ddc6a1125a8f5ba6fbf2c5ef47539 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 14:49:01 -0400 Subject: [PATCH 21/30] Add ManagedObjectsObserver --- .../Utility/ManagedObjectsObserver.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift diff --git a/Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift b/Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift new file mode 100644 index 000000000000..22e43786dd8a --- /dev/null +++ b/Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift @@ -0,0 +1,31 @@ +import Foundation +import CoreData +import Combine + +public final class ManagedObjectsObserver: NSObject, NSFetchedResultsControllerDelegate { + @Published private(set) public var objects: [T] = [] + + private let controller: NSFetchedResultsController + + public convenience init(predicate: NSPredicate, context: NSManagedObjectContext) { + let request = NSFetchRequest(entityName: T.entity().name ?? "") + request.predicate = predicate + self.init(request: request, context: context) + } + + public init(request: NSFetchRequest, + context: NSManagedObjectContext, + cacheName: String? = nil) { + self.controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: cacheName) + super.init() + + try? controller.performFetch() + objects = controller.fetchedObjects ?? [] + + controller.delegate = self + } + + public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + objects = self.controller.fetchedObjects ?? [] + } +} From 19730a6622502acb4e2dc797825ceede759d4672 Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 15:17:37 -0400 Subject: [PATCH 22/30] Observe tags --- .../Utility/ManagedObjectsObserver.swift | 15 +++++--- .../Reader/ReaderDiscoverViewController.swift | 34 +++++++++++-------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift b/Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift index 22e43786dd8a..27d2ac48b862 100644 --- a/Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift +++ b/Modules/Sources/WordPressShared/Utility/ManagedObjectsObserver.swift @@ -7,15 +7,22 @@ public final class ManagedObjectsObserver: NSObject, NSFetch private let controller: NSFetchedResultsController - public convenience init(predicate: NSPredicate, context: NSManagedObjectContext) { + public convenience init( + predicate: NSPredicate, + sortDescriptors: [SortDescriptor], + context: NSManagedObjectContext + ) { let request = NSFetchRequest(entityName: T.entity().name ?? "") request.predicate = predicate + request.sortDescriptors = sortDescriptors.map(NSSortDescriptor.init) self.init(request: request, context: context) } - public init(request: NSFetchRequest, - context: NSManagedObjectContext, - cacheName: String? = nil) { + public init( + request: NSFetchRequest, + context: NSManagedObjectContext, + cacheName: String? = nil + ) { self.controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: cacheName) super.init() diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index ca7851d40161..af9eda30d547 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -2,19 +2,26 @@ import Foundation import UIKit import Combine import WordPressKit +import WordPressShared class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDelegate, ReaderContentViewController { private let headerView = ReaderDiscoverHeaderView() private var selectedChannel: ReaderDiscoverChannel = .recommended private let topic: ReaderAbstractTopic private var streamVC: ReaderStreamViewController? - private var viewContext: NSManagedObjectContext { - ContextManager.shared.mainContext - } + private let tags: ManagedObjectsObserver + private let viewContext: NSManagedObjectContext + private var cancellables: [AnyCancellable] = [] init(topic: ReaderAbstractTopic) { wpAssert(ReaderHelpers.topicIsDiscover(topic)) + self.viewContext = ContextManager.shared.mainContext self.topic = topic + self.tags = ManagedObjectsObserver( + predicate: ReaderSidebarTagsSection.predicate, + sortDescriptors: [SortDescriptor(\.title, order: .forward)], + context: viewContext + ) super.init(nibName: nil, bundle: nil) } @@ -36,21 +43,20 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe } private func setupHeaderView() { - let tags = fetchTags() - .filter { $0.slug != ReaderTagTopic.dailyPromptTag } - .map(ReaderDiscoverChannel.tag) + tags.$objects.sink { [weak self] tags in + self?.configureHeader(tags: tags) + }.store(in: &cancellables) - headerView.configure(channels: [.recommended, .firstPosts, .latest, .dailyPrompts] + tags) - headerView.setSelectedChannel(selectedChannel) headerView.delegate = self } - private func fetchTags() -> [ReaderTagTopic] { - viewContext.allObjects( - ofType: ReaderTagTopic.self, - matching: ReaderSidebarTagsSection.predicate, - sortedBy: [NSSortDescriptor(key: "title", ascending: true)] - ) + private func configureHeader(tags: [ReaderTagTopic]) { + let channels = tags + .filter { $0.slug != ReaderTagTopic.dailyPromptTag } + .map(ReaderDiscoverChannel.tag) + + headerView.configure(channels: [.recommended, .firstPosts, .latest, .dailyPrompts] + channels) + headerView.setSelectedChannel(selectedChannel) } // MARK: - Selected Stream From 8259a863dc37749d83141b03d80d172d21b01e1f Mon Sep 17 00:00:00 2001 From: kean Date: Fri, 18 Oct 2024 15:24:33 -0400 Subject: [PATCH 23/30] Rename container to target --- .../Extensions/UIView+AutoLayout.swift | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift index b206a1435354..302e44305011 100644 --- a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift +++ b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift @@ -7,12 +7,12 @@ extension UIView { @discardableResult public func pinEdges( _ edges: Edge.Set = .all, - to container: AutoLayoutItem? = nil, + to target: AutoLayoutItem? = nil, insets: UIEdgeInsets = .zero, relation: AutoLayoutPinEdgesRelation = .equal, priority: UILayoutPriority? = nil ) -> [NSLayoutConstraint] { - guard let container = container ?? superview else { + guard let target = target ?? superview else { assertionFailure("view has to be installed in the view hierarchy") return [] } @@ -27,15 +27,15 @@ extension UIView { switch relation { case .equal: - pin(.top, topAnchor.constraint(equalTo: container.topAnchor, constant: insets.top)) - pin(.trailing, trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -insets.right)) - pin(.bottom, bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -insets.bottom)) - pin(.leading, leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: insets.left)) + pin(.top, topAnchor.constraint(equalTo: target.topAnchor, constant: insets.top)) + pin(.trailing, trailingAnchor.constraint(equalTo: target.trailingAnchor, constant: -insets.right)) + pin(.bottom, bottomAnchor.constraint(equalTo: target.bottomAnchor, constant: -insets.bottom)) + pin(.leading, leadingAnchor.constraint(equalTo: target.leadingAnchor, constant: insets.left)) case .lessThanOrEqual: - pin(.top, topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor, constant: insets.top)) - pin(.trailing, trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -insets.right)) - pin(.bottom, bottomAnchor.constraint(lessThanOrEqualTo: container.bottomAnchor, constant: -insets.bottom)) - pin(.leading, leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: insets.left)) + pin(.top, topAnchor.constraint(greaterThanOrEqualTo: target.topAnchor, constant: insets.top)) + pin(.trailing, trailingAnchor.constraint(lessThanOrEqualTo: target.trailingAnchor, constant: -insets.right)) + pin(.bottom, bottomAnchor.constraint(lessThanOrEqualTo: target.bottomAnchor, constant: -insets.bottom)) + pin(.leading, leadingAnchor.constraint(greaterThanOrEqualTo: target.leadingAnchor, constant: insets.left)) } if let priority { @@ -52,19 +52,19 @@ extension UIView { /// pins to the superview. @discardableResult public func pinCenter( - to container: AutoLayoutItem? = nil, + to target: AutoLayoutItem? = nil, offset: UIOffset = .zero, priority: UILayoutPriority? = nil ) -> [NSLayoutConstraint] { - guard let container = container ?? superview else { + guard let target = target ?? superview else { assertionFailure("view has to be installed in the view hierarchy") return [] } translatesAutoresizingMaskIntoConstraints = false let constraints = [ - centerXAnchor.constraint(equalTo: container.centerXAnchor, constant: offset.horizontal), - centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: offset.vertical), + centerXAnchor.constraint(equalTo: target.centerXAnchor, constant: offset.horizontal), + centerYAnchor.constraint(equalTo: target.centerYAnchor, constant: offset.vertical), ] if let priority { From 391871b1c9c882616a4f90fa40fd748e59434ade Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 21 Oct 2024 09:55:17 -0400 Subject: [PATCH 24/30] Add some docs --- .../WordPressUI/Extensions/UIView+AutoLayout.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift index 302e44305011..1bb93abb687b 100644 --- a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift +++ b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift @@ -2,8 +2,18 @@ import UIKit import SwiftUI extension UIView { - /// Pins edges of the view to the edges of the given container. By default, - /// pins to the superview. + /// Pins edges of the view to the edges of the given target view or layout + /// guide. By default, pins to the superview. + /// + /// The view also gets enabled for Auto Layout by setting + /// `translatesAutoresizingMaskIntoConstraints` to `false`. + /// + /// Example uage: + /// + /// ```swift + /// subview.pinEdges() // to superview + /// subview.pinEdges(to: superview.safeAreaLayoutGuide) + /// ``` @discardableResult public func pinEdges( _ edges: Edge.Set = .all, From 8a75e49be668b218a04e84780e21812617f5a427 Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 21 Oct 2024 13:21:32 -0400 Subject: [PATCH 25/30] Add a link to edit your interests to the Reader --- .../Extensions/UITextView+Extensions.swift | 14 +++++++++ .../Manage/ReaderTagsTableViewModel.swift | 8 +++++ .../Reader/ReaderDiscoverHeaderView.swift | 30 +++++++++++++++++-- .../Reader/ReaderStreamTitleView.swift | 10 +++---- 4 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 Modules/Sources/WordPressUI/Extensions/UITextView+Extensions.swift diff --git a/Modules/Sources/WordPressUI/Extensions/UITextView+Extensions.swift b/Modules/Sources/WordPressUI/Extensions/UITextView+Extensions.swift new file mode 100644 index 000000000000..e9bb2867837a --- /dev/null +++ b/Modules/Sources/WordPressUI/Extensions/UITextView+Extensions.swift @@ -0,0 +1,14 @@ +import UIKit + +extension UITextView { + /// Creates a text view that behaves like a non-editable multiline label + /// but supports interaction and other text view features. + public static func makeLabel() -> UITextView { + let textView = UITextView() + textView.isScrollEnabled = false + textView.isEditable = false + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + return textView + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift b/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift index 419a598e8333..deae1d254ad8 100644 --- a/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift +++ b/WordPress/Classes/ViewRelated/Reader/Manage/ReaderTagsTableViewModel.swift @@ -54,6 +54,14 @@ extension ReaderTagsTableViewModel: WPTableViewHandlerDelegate { // MARK: - Discover more topics footer + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + nil + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + CGFloat.leastNormalMagnitude + } + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { guard section == 0 else { return CGFloat.leastNormalMagnitude diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index 3c7c3745d1f2..c69d51426bd9 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -4,7 +4,7 @@ protocol ReaderDiscoverHeaderViewDelegate: AnyObject { func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverChannel) } -final class ReaderDiscoverHeaderView: UIView { +final class ReaderDiscoverHeaderView: UIView, UITextViewDelegate { private let titleView = ReaderStreamTitleView() private let channelsStackView = UIStackView(spacing: 8, []) private var channelViews: [ReaderDiscoverChannelView] = [] @@ -28,7 +28,17 @@ final class ReaderDiscoverHeaderView: UIView { stackView.pinEdges(insets: UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16)) titleView.titleLabel.text = Strings.title - titleView.detailsLabel.text = Strings.details + titleView.detailsTextView.attributedText = { + guard let details = try? NSMutableAttributedString(markdown: Strings.details) else { + return nil + } + details.addAttributes([ + .font: UIFont.preferredFont(forTextStyle: .subheadline), + .foregroundColor: UIColor.secondaryLabel, + ], range: NSRange(location: 0, length: details.length)) + return details + }() + titleView.detailsTextView.delegate = self } required init?(coder: NSCoder) { @@ -72,6 +82,19 @@ final class ReaderDiscoverHeaderView: UIView { view.isSelected = view.channel == selectedChannel } } + + // MARK: UITextViewDelegate + + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { + let tagsVC = ReaderTagsTableViewController(style: .plain) + tagsVC.title = Strings.editInterests + tagsVC.navigationItem.rightBarButtonItem = UIBarButtonItem(title: SharedStrings.Button.done, primaryAction: UIAction { [weak tagsVC] _ in + tagsVC?.presentingViewController?.dismiss(animated: true) + }) + let navVC = UINavigationController(rootViewController: tagsVC) + UIViewController.topViewController?.present(navVC, animated: true) + return false + } } private final class ReaderDiscoverChannelView: UIView { @@ -162,7 +185,8 @@ enum ReaderDiscoverChannel: Hashable { private enum Strings { static let title = NSLocalizedString("reader.discover.header.title", value: "Discover", comment: "Header view title") - static let details = NSLocalizedString("reader.discover.header.title", value: "Explore popular blogs that inspire, educate, and entertain.", comment: "Header view details") + static let details = NSLocalizedString("reader.discover.header.title", value: "Explore popular blogs that inspire, educate, and entertain based on your [interests](/interests).", comment: "Reader Discover header view details label. The text has a Markdown URL: [interests](/interests). Only the text in the square brackets needs to be translated: [](/interests).") + static let editInterests = NSLocalizedString("reader.editInterests.title", value: "Edit Interests", comment: "Screen title") } @available(iOS 17, *) diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift index 8cfe8bc2fe99..8764f24665af 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderStreamTitleView.swift @@ -1,19 +1,19 @@ import UIKit +import WordPressUI /// A Reader stream header with a large title and a description. final class ReaderStreamTitleView: UIView { let titleLabel = UILabel() - let detailsLabel = UILabel() + let detailsTextView = UITextView.makeLabel() override init(frame: CGRect) { super.init(frame: frame) titleLabel.font = UIFont.preferredFont(forTextStyle: .largeTitle).withWeight(.bold) - detailsLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) - detailsLabel.textColor = .secondaryLabel - detailsLabel.numberOfLines = 0 + detailsTextView.font = UIFont.preferredFont(forTextStyle: .subheadline) + detailsTextView.textColor = .secondaryLabel - let stackView = UIStackView(axis: .vertical, alignment: .leading, [titleLabel, detailsLabel]) + let stackView = UIStackView(axis: .vertical, alignment: .leading, [titleLabel, detailsTextView]) addSubview(stackView) stackView.pinEdges() } From a57fdb1239cd090305ff1c998b77f7d6fae3560d Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 24 Oct 2024 12:18:12 -0400 Subject: [PATCH 26/30] Add runtime check for pinEdges --- .../Sources/WordPressUI/Extensions/UIView+AutoLayout.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift index 1bb93abb687b..ee82e9a6d2e4 100644 --- a/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift +++ b/Modules/Sources/WordPressUI/Extensions/UIView+AutoLayout.swift @@ -28,6 +28,12 @@ extension UIView { } translatesAutoresizingMaskIntoConstraints = false +#if DEBUG + if let target = target as? UIView { + assert(!target.isDescendant(of: self), "The target view can't be a descendant for the view") + } +#endif + var constraints: [NSLayoutConstraint] = [] func pin(_ edge: Edge.Set, _ closure: @autoclosure () -> NSLayoutConstraint) { From 93d96641f5eb0d9ca42b50f7fde0d77ad699feeb Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 24 Oct 2024 15:59:14 -0400 Subject: [PATCH 27/30] Add .readerDiscoverChannelSelected --- .../Utility/Analytics/WPAnalyticsEvent.swift | 7 +++++++ .../Reader/ReaderDiscoverHeaderView.swift | 18 ++++++++++++++++++ .../Reader/ReaderDiscoverViewController.swift | 1 + 3 files changed, 26 insertions(+) diff --git a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift index 7ffc738c2003..c11a7351d958 100644 --- a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift +++ b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift @@ -320,6 +320,9 @@ import Foundation case postRepositoryConflictEncountered case postRepositoryPostsFetchFailed + // Reader: Discover + case readerDiscoverChannelSelected + // Reader: Filter Sheet case readerFilterSheetDisplayed case readerFilterSheetDismissed @@ -1198,6 +1201,10 @@ import Foundation case .postRepositoryPostsFetchFailed: return "post_repository_posts_fetch_failed" + // Reader: Discover + case .readerDiscoverChannelSelected: + return "reader_discover_channel_selected" + // Reader: Filter Sheet case .readerFilterSheetDisplayed: return "reader_filter_sheet_displayed" diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index c69d51426bd9..297b2beee6ff 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -181,6 +181,24 @@ enum ReaderDiscoverChannel: Hashable { tag.title.localizedCapitalized } } + + var analyticsProperties: [String: String] { + var properties = ["channel": analyticsID] + if case let .tag(tag) = self { + properties["tag"] = tag.slug + } + return properties + } + + private var analyticsID: String { + switch self { + case .recommended: "recommended" + case .firstPosts: "first_posts" + case .latest: "latest" + case .dailyPrompts: "daily_prompts" + case .tag: "tag" + } + } } private enum Strings { diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift index af9eda30d547..c94d59b753ab 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverViewController.swift @@ -123,6 +123,7 @@ class ReaderDiscoverViewController: UIViewController, ReaderDiscoverHeaderViewDe func readerDiscoverHeaderView(_ view: ReaderDiscoverHeaderView, didChangeSelection selection: ReaderDiscoverChannel) { self.selectedChannel = selection configureStream(for: selection) + WPAnalytics.track(.readerDiscoverChannelSelected, properties: selection.analyticsProperties) } } From 0e9f4c31cf56f1eed37e8f7b2dc2f5f4d4c1015b Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 24 Oct 2024 16:00:19 -0400 Subject: [PATCH 28/30] Add readerDiscoverEditInterestsTapped --- WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift | 3 +++ .../Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift | 2 ++ 2 files changed, 5 insertions(+) diff --git a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift index c11a7351d958..c8e43ad4a768 100644 --- a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift +++ b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift @@ -322,6 +322,7 @@ import Foundation // Reader: Discover case readerDiscoverChannelSelected + case readerDiscoverEditInterestsTapped // Reader: Filter Sheet case readerFilterSheetDisplayed @@ -1204,6 +1205,8 @@ import Foundation // Reader: Discover case .readerDiscoverChannelSelected: return "reader_discover_channel_selected" + case .readerDiscoverEditInterestsTapped: + return "reader_discover_edit_interests_tapped" // Reader: Filter Sheet case .readerFilterSheetDisplayed: diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift index 297b2beee6ff..cae921d57e8c 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderDiscoverHeaderView.swift @@ -86,6 +86,8 @@ final class ReaderDiscoverHeaderView: UIView, UITextViewDelegate { // MARK: UITextViewDelegate func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { + WPAnalytics.track(.readerDiscoverEditInterestsTapped) + let tagsVC = ReaderTagsTableViewController(style: .plain) tagsVC.title = Strings.editInterests tagsVC.navigationItem.rightBarButtonItem = UIBarButtonItem(title: SharedStrings.Button.done, primaryAction: UIAction { [weak tagsVC] _ in From 32f2da8500f5b8281b73eda9c080889ff34a361f Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 29 Oct 2024 09:41:26 -0400 Subject: [PATCH 29/30] Update tests --- WordPress/Classes/Services/ReaderCardService.swift | 4 ++-- WordPress/WordPressTest/ReaderCardServiceTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Services/ReaderCardService.swift b/WordPress/Classes/Services/ReaderCardService.swift index b536ab333d2f..e184b640a71a 100644 --- a/WordPress/Classes/Services/ReaderCardService.swift +++ b/WordPress/Classes/Services/ReaderCardService.swift @@ -31,8 +31,8 @@ class ReaderCardService { /// Used only internally to order the cards private var pageNumber = 1 - init(stream: ReaderStream, - sorting: ReaderSortingOption, + init(stream: ReaderStream = .discover, + sorting: ReaderSortingOption = .noSorting, service: ReaderCardServiceRemote = ReaderPostServiceRemote.withDefaultApi(), coreDataStack: CoreDataStack = ContextManager.shared, followedInterestsService: ReaderFollowedInterestsService? = nil, diff --git a/WordPress/WordPressTest/ReaderCardServiceTests.swift b/WordPress/WordPressTest/ReaderCardServiceTests.swift index 9bf219ca11cb..4ea5f9dadc34 100644 --- a/WordPress/WordPressTest/ReaderCardServiceTests.swift +++ b/WordPress/WordPressTest/ReaderCardServiceTests.swift @@ -102,10 +102,10 @@ class ReaderCardServiceTests: CoreDataTestCase { } final class ReaderPostServiceRemoteMock: ReaderCardServiceRemote { - var shouldCallFailure = false - func fetchStreamCards(for topics: [String], + func fetchStreamCards(stream: WordPressKit.ReaderStream, + for topics: [String], page: String?, sortingOption: WordPressKit.ReaderSortingOption, refreshCount: Int?, From 24d5826f82f3247d342fdd6c5cb3686bb1641802 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 29 Oct 2024 19:21:15 -0400 Subject: [PATCH 30/30] Update unit tests --- WordPress/Classes/Services/ReaderCardService.swift | 6 +++--- WordPress/WordPressTest/ReaderTabViewModelTests.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Services/ReaderCardService.swift b/WordPress/Classes/Services/ReaderCardService.swift index e184b640a71a..47b2c016b3ed 100644 --- a/WordPress/Classes/Services/ReaderCardService.swift +++ b/WordPress/Classes/Services/ReaderCardService.swift @@ -33,7 +33,7 @@ class ReaderCardService { init(stream: ReaderStream = .discover, sorting: ReaderSortingOption = .noSorting, - service: ReaderCardServiceRemote = ReaderPostServiceRemote.withDefaultApi(), + service: ReaderCardServiceRemote = ReaderPostServiceRemote.withDefaultApi(), coreDataStack: CoreDataStack = ContextManager.shared, followedInterestsService: ReaderFollowedInterestsService? = nil, siteInfoService: ReaderSiteInfoService? = nil) { @@ -129,10 +129,10 @@ class ReaderCardService { /// Remove all cards and saves the context func clean() { - ReaderCardService.removeAllCards() + ReaderCardService.removeAllCards(on: coreDataStack) } - static func removeAllCards(on stack: CoreDataStackSwift = ContextManager.shared) { + static func removeAllCards(on stack: CoreDataStack = ContextManager.shared) { stack.performAndSave { context in removeAllCards(in: context) } diff --git a/WordPress/WordPressTest/ReaderTabViewModelTests.swift b/WordPress/WordPressTest/ReaderTabViewModelTests.swift index 84dd29289f22..39eafec9670e 100644 --- a/WordPress/WordPressTest/ReaderTabViewModelTests.swift +++ b/WordPress/WordPressTest/ReaderTabViewModelTests.swift @@ -67,7 +67,7 @@ class ReaderTabViewModelTests: CoreDataTestCase { viewModel.filterTapped = { filter, view, completion in filterTappedExpectation.fulfill() } - let viewController = UIViewController() + // When viewModel.didTapStreamFilterButton(with: filter) // Then