From 00ee6cea81b0fcdccb2b0cb0b66a116d5ebe2250 Mon Sep 17 00:00:00 2001 From: kean Date: Mon, 20 Mar 2023 14:31:25 -0400 Subject: [PATCH 1/9] Add Personalize Home Tab screen --- .../BuildInformation/FeatureFlag.swift | 5 + .../BlogDashboardPersonalizeCardCell.swift | 90 ++++++++++++++++++ .../Blog/Blog Dashboard/DashboardCard.swift | 16 +++- .../BlogDashboardPersonalizationService.swift | 2 +- .../BlogDashboardPersonalizationView.swift | 58 +++++++++++ ...logDashboardPersonalizationViewModel.swift | 51 ++++++++++ .../personalize.imageset/Contents.json | 12 +++ .../personalize.imageset/personalize.pdf | Bin 0 -> 1548 bytes WordPress/WordPress.xcodeproj/project.pbxproj | 26 +++++ 9 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift create mode 100644 WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift create mode 100644 WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift create mode 100644 WordPress/Resources/AppImages.xcassets/personalize.imageset/Contents.json create mode 100644 WordPress/Resources/AppImages.xcassets/personalize.imageset/personalize.pdf diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index 4798923c797b..eaea1b19dfbd 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -39,6 +39,7 @@ enum FeatureFlag: Int, CaseIterable { case jetpackIndividualPluginSupport case siteCreationDomainPurchasing case readerUserBlocking + case personalizeHomeTab /// Returns a boolean indicating if the feature is enabled var enabled: Bool { @@ -125,6 +126,8 @@ enum FeatureFlag: Int, CaseIterable { return false case .readerUserBlocking: return true + case .personalizeHomeTab: + return false } } @@ -221,6 +224,8 @@ extension FeatureFlag { return "Site Creation Domain Purchasing" case .readerUserBlocking: return "Reader User Blocking" + case .personalizeHomeTab: + return "Personalize Home Tab" } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift new file mode 100644 index 000000000000..939a275a8f5a --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift @@ -0,0 +1,90 @@ +import UIKit +import SwiftUI + +final class BlogDashboardPersonalizeCardCell: DashboardCollectionViewCell { + private var blog: Blog? + private weak var presentingViewController: BlogDashboardViewController? + + private let personalizeButton = UIButton(type: .system) + + // MARK: - Initializers + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View setup + + private func setupView() { + let titleLabel = UILabel() + titleLabel.text = Strings.buttonTitle + titleLabel.font = WPStyleGuide.fontForTextStyle(.subheadline, fontWeight: .semibold) + titleLabel.adjustsFontForContentSizeCategory = true + + let imageView = UIImageView(image: UIImage(named: "personalize")?.withRenderingMode(.alwaysTemplate)) + imageView.tintColor = .label + + let spacer = UIView() + spacer.translatesAutoresizingMaskIntoConstraints = false + spacer.widthAnchor.constraint(greaterThanOrEqualToConstant: 8).isActive = true + + let contents = UIStackView(arrangedSubviews: [titleLabel, spacer, imageView]) + contents.alignment = .center + contents.isUserInteractionEnabled = false + + personalizeButton.accessibilityLabel = Strings.buttonTitle + personalizeButton.setBackgroundImage(.renderBackgroundImage(fill: .tertiarySystemFill), for: .normal) + personalizeButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) + + let container = UIView() + container.layer.cornerRadius = 10 + container.addSubview(personalizeButton) + container.addSubview(contents) + + personalizeButton.translatesAutoresizingMaskIntoConstraints = false + container.pinSubviewToAllEdges(personalizeButton) + + contents.translatesAutoresizingMaskIntoConstraints = false + container.pinSubviewToAllEdges(contents, insets: .init(allEdges: 16)) + + contentView.addSubview(container) + container.translatesAutoresizingMaskIntoConstraints = false + contentView.pinSubviewToAllEdges(container) + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + personalizeButton.setBackgroundImage(.renderBackgroundImage(fill: .tertiarySystemFill), for: .normal) + } + + // MARK: - BlogDashboardCardConfigurable + + func configure(blog: Blog, viewController: BlogDashboardViewController?, apiResponse: BlogDashboardRemoteEntity?) { + self.blog = blog + self.presentingViewController = viewController + } + + // MARK: - Actions + + @objc private func buttonTapped() { + guard let siteID = blog?.dotComID?.intValue else { + return assertionFailure("SiteID is missing") + } + let viewController = UIHostingController(rootView: NavigationView { + BlogDashboardPersonalizationView(viewModel: .init(service: .init(siteID: siteID))) + }) + presentingViewController?.present(viewController, animated: true) + } +} + +private extension BlogDashboardPersonalizeCardCell { + struct Strings { + static let buttonTitle = NSLocalizedString("dasboard.personalizeHomeButtonTitle", value: "Personalize your home tab", comment: "Personialize home tab button title") + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift index 397b738e2d8d..e175b855b93a 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift @@ -18,10 +18,10 @@ enum DashboardCard: String, CaseIterable { case nextPost = "create_next" case createPost = "create_first" case jetpackBadge - - // Card placeholder for when loading data + /// Card placeholder for when loading data case ghost case failure + case personalize var cell: DashboardCollectionViewCell.Type { switch self { @@ -52,6 +52,8 @@ enum DashboardCard: String, CaseIterable { case .domainsDashboardCard: /// TODO return DashboardFailureCardCell.self + case .personalize: + return BlogDashboardPersonalizeCardCell.self } } @@ -88,6 +90,8 @@ enum DashboardCard: String, CaseIterable { return BlazeHelper.shouldShowCard(for: blog) case .domainsDashboardCard: return DomainsDashboardCardHelper.shouldShowCard(for: blog) + case .personalize: + return AppConfiguration.isJetpack && FeatureFlag.personalizeHomeTab.enabled } } @@ -111,6 +115,14 @@ enum DashboardCard: String, CaseIterable { } } + static let personalizableCards: [DashboardCard] = [ + .todaysStats, + .draftPosts, + .scheduledPosts, + .blaze, + .prompts + ] + /// Includes all cards that should be fetched from the backend /// The `String` should match its identifier on the backend. enum RemoteDashboardCard: String, CaseIterable { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift index 40801e59b7e8..c278df348a10 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift @@ -52,7 +52,7 @@ private func makeKey(for card: DashboardCard) -> String? { return "prompts-enabled-site-settings" case .domainsDashboardCard: return "domains-dashboard-enabled-site-settings" - case .quickStart, .jetpackBadge, .jetpackInstall, .nextPost, .createPost, .failure, .ghost: + case .quickStart, .jetpackBadge, .jetpackInstall, .nextPost, .createPost, .failure, .ghost, .personalize: return nil } } diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift new file mode 100644 index 000000000000..44369faf7869 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift @@ -0,0 +1,58 @@ +import SwiftUI + +struct BlogDashboardPersonalizationView: View { + @StateObject var viewModel: BlogDashboardPersonalizationViewModel + + @SwiftUI.Environment(\.presentationMode) var presentationMode + + var body: some View { + List { + Section(content: { + ForEach(viewModel.cards, content: BlogDashboardPersonalizationCardCell.init) + }, header: { + Text(Strings.sectionHeader) + }, footer: { + Text(Strings.sectionFooter) + }) + } + .navigationTitle(Strings.title) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { closeButton } + } + } + + private var closeButton: some View { + Button(action: { presentationMode.wrappedValue.dismiss() }) { + Image(systemName: "xmark") + .font(.body.weight(.medium)) + .foregroundColor(Color.primary) + } + } +} + +private struct BlogDashboardPersonalizationCardCell: View { + @ObservedObject var viewModel: BlogDashboardPersonalizationCardCellViewModel + + var body: some View { + Toggle(viewModel.title, isOn: $viewModel.isOn) + } +} + +private extension BlogDashboardPersonalizationView { + struct Strings { + static let title = NSLocalizedString("personalizeHome.cardsSectionFooter", value: "Personalize Home Tab", comment: "Page title") + static let sectionHeader = NSLocalizedString("personalizeHome.cardsSectionHeader", value: "Add or hide cards", comment: "Section header") + static let sectionFooter = NSLocalizedString("personalizeHome.cardsSectionFooter", value: "Cards may show different content depending on what's happening on your site. We're working on on more cards and controls.", comment: "Section footer displayed below the list of toggles") + } +} + +#if DEBUG +struct BlogDashboardPersonalizationView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + BlogDashboardPersonalizationView(viewModel: .init(service: .init(repository: UserDefaults.standard, siteID: 1))) + } + } +} +#endif diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift new file mode 100644 index 000000000000..a24511f64968 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift @@ -0,0 +1,51 @@ +import SwiftUI + +final class BlogDashboardPersonalizationViewModel: ObservableObject { + let cards: [BlogDashboardPersonalizationCardCellViewModel] + + init(service: BlogDashboardPersonalizationService) { + self.cards = DashboardCard.personalizableCards.map { + BlogDashboardPersonalizationCardCellViewModel(card: $0, service: service) + } + } +} + +final class BlogDashboardPersonalizationCardCellViewModel: ObservableObject, Identifiable { + private let card: DashboardCard + private let service: BlogDashboardPersonalizationService + + var id: String { card.rawValue } + var title: String { card.localizedTitle } + + var isOn: Bool { + get { service.isEnabled(card) } + set { + objectWillChange.send() + service.setEnabled(newValue, for: card) + } + } + + init(card: DashboardCard, service: BlogDashboardPersonalizationService) { + self.card = card + self.service = service + } +} + +private extension DashboardCard { + var localizedTitle: String { + switch self { + case .prompts: + return NSLocalizedString("personalizeHome.dashboardCard.prompts", value: "Blogging prompts", comment: "Card title for the pesonalization menu") + case .blaze: + return NSLocalizedString("personalizeHome.dashboardCard.blaze", value: "Blaze", comment: "Card title for the pesonalization menu") + case .todaysStats: + return NSLocalizedString("personalizeHome.dashboardCard.todaysStats", value: "Today's stats", comment: "Card title for the pesonalization menu") + case .draftPosts: + return NSLocalizedString("personalizeHome.dashboardCard.draftPosts", value: "Draft posts", comment: "Card title for the pesonalization menu") + case .scheduledPosts: + return NSLocalizedString("personalizeHome.dashboardCard.scheduledPosts", value: "Scheduled posts", comment: "Card title for the pesonalization menu") + case .quickStart, .nextPost, .createPost, .ghost, .failure, .personalize, .jetpackBadge, .jetpackInstall, .domainsDashboardCard: + return "" // These cards don't appear in the personalization menus + } + } +} diff --git a/WordPress/Resources/AppImages.xcassets/personalize.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/personalize.imageset/Contents.json new file mode 100644 index 000000000000..7937f37d66b4 --- /dev/null +++ b/WordPress/Resources/AppImages.xcassets/personalize.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "personalize.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WordPress/Resources/AppImages.xcassets/personalize.imageset/personalize.pdf b/WordPress/Resources/AppImages.xcassets/personalize.imageset/personalize.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e7c6eb47bbd791f3c6f7197a4346c4ebfe903aed GIT binary patch literal 1548 zcmZXUOK+So5PVwcp)e32y@+Fu?BV7a(3=(p!BZ9elHH^J18t`}WZ&k|t z;~j1v=6~e?e?WM_bTF_VSNwH;$5&+o{C)m0lr4zBi?!ypm#LFt1DUse$5KrMuDwvd zNa3g;64#E~i2OILP=ga+K+{E;eaOC0JL#IR%S=k( z$e3~XneMd=U6$D3qLxhO#hi!X6wG2f!85e*9n|ESv9vR+##PUD7Nk7cYo;{u{%zZK z#lfBgF`(cc$GX~0)`sEWJp6(8LkyL`G%M2#PS-FdF-! zs$~zVdY^Wbj*Fpd Date: Wed, 22 Mar 2023 16:05:14 -0400 Subject: [PATCH 2/9] Fix BlogDashboardPersonalizationView design on iOS 14 --- .../BlogPersonalization/BlogDashboardPersonalizationView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift index 44369faf7869..689f647a2088 100644 --- a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift @@ -15,6 +15,7 @@ struct BlogDashboardPersonalizationView: View { Text(Strings.sectionFooter) }) } + .listStyle(.insetGrouped) .navigationTitle(Strings.title) .navigationBarTitleDisplayMode(.inline) .toolbar { From 99b7f221c99c572ea952adee7c319f4c829cd950 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 22 Mar 2023 16:10:06 -0400 Subject: [PATCH 3/9] Add analyticts to new personalization flow on dashboard --- .../Cards/BlogDashboardPersonalizeCardCell.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift index 939a275a8f5a..abef77d24a4d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift @@ -73,9 +73,10 @@ final class BlogDashboardPersonalizeCardCell: DashboardCollectionViewCell { // MARK: - Actions @objc private func buttonTapped() { - guard let siteID = blog?.dotComID?.intValue else { - return assertionFailure("SiteID is missing") + guard let blog = blog, let siteID = blog.dotComID?.intValue else { + return DDLogError("Failed to show dashboard personalization screen: siteID is missing") } + WPAnalytics.track(.dashboardCardItemTapped, properties: ["type": DashboardCard.personalize.rawValue], blog: blog) let viewController = UIHostingController(rootView: NavigationView { BlogDashboardPersonalizationView(viewModel: .init(service: .init(siteID: siteID))) }) From da4f69909d0ba918d397dd7a43a56474f9972512 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 22 Mar 2023 17:02:16 -0400 Subject: [PATCH 4/9] Add BlogDashboardPersonalizationView iPad support --- .../Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift index abef77d24a4d..45a4ec6df12f 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift @@ -79,7 +79,7 @@ final class BlogDashboardPersonalizeCardCell: DashboardCollectionViewCell { WPAnalytics.track(.dashboardCardItemTapped, properties: ["type": DashboardCard.personalize.rawValue], blog: blog) let viewController = UIHostingController(rootView: NavigationView { BlogDashboardPersonalizationView(viewModel: .init(service: .init(siteID: siteID))) - }) + }.navigationViewStyle(.stack)) // .stack is required for iPad presentingViewController?.present(viewController, animated: true) } } From 2b9a0822bbba6c931a72f9449958702077a0d908 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 22 Mar 2023 17:20:44 -0400 Subject: [PATCH 5/9] Add BlogDashboardPersonalizationViewModel tests and docs --- .../Blog/Blog Dashboard/DashboardCard.swift | 2 ++ .../BlogDashboardPersonalizationView.swift | 2 +- ...logDashboardPersonalizationViewModel.swift | 3 +- WordPress/WordPress.xcodeproj/project.pbxproj | 4 +++ ...shboardPersonalizationViewModelTests.swift | 33 +++++++++++++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 WordPress/WordPressTest/Dashboard/BlogDashboardPersonalizationViewModelTests.swift diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift index e175b855b93a..5d8f61700ec9 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift @@ -21,6 +21,7 @@ enum DashboardCard: String, CaseIterable { /// Card placeholder for when loading data case ghost case failure + /// A "Personalize Home Tab" button case personalize var cell: DashboardCollectionViewCell.Type { @@ -115,6 +116,7 @@ enum DashboardCard: String, CaseIterable { } } + /// A list of cards that can be shown/hidden on a "Personalize Home Tab" screen. static let personalizableCards: [DashboardCard] = [ .todaysStats, .draftPosts, diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift index 689f647a2088..c325fb0f969a 100644 --- a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift @@ -42,7 +42,7 @@ private struct BlogDashboardPersonalizationCardCell: View { private extension BlogDashboardPersonalizationView { struct Strings { - static let title = NSLocalizedString("personalizeHome.cardsSectionFooter", value: "Personalize Home Tab", comment: "Page title") + static let title = NSLocalizedString("personalizeHome.title", value: "Personalize Home Tab", comment: "Page title") static let sectionHeader = NSLocalizedString("personalizeHome.cardsSectionHeader", value: "Add or hide cards", comment: "Section header") static let sectionFooter = NSLocalizedString("personalizeHome.cardsSectionFooter", value: "Cards may show different content depending on what's happening on your site. We're working on on more cards and controls.", comment: "Section footer displayed below the list of toggles") } diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift index a24511f64968..78453e339c26 100644 --- a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift @@ -14,7 +14,7 @@ final class BlogDashboardPersonalizationCardCellViewModel: ObservableObject, Ide private let card: DashboardCard private let service: BlogDashboardPersonalizationService - var id: String { card.rawValue } + var id: DashboardCard { card } var title: String { card.localizedTitle } var isOn: Bool { @@ -45,6 +45,7 @@ private extension DashboardCard { case .scheduledPosts: return NSLocalizedString("personalizeHome.dashboardCard.scheduledPosts", value: "Scheduled posts", comment: "Card title for the pesonalization menu") case .quickStart, .nextPost, .createPost, .ghost, .failure, .personalize, .jetpackBadge, .jetpackInstall, .domainsDashboardCard: + assertionFailure("\(self) card should not appear in the personalization menus") return "" // These cards don't appear in the personalization menus } } diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a3d0d50f80e6..a3f6cc684c75 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -303,6 +303,7 @@ 0A9610F928B2E56300076EBA /* UserSuggestion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */; }; 0A9610FA28B2E56300076EBA /* UserSuggestion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */; }; 0A9687BC28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */; }; + 0C35FFF429CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */; }; 0CB4056B29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056C29C78F06008EED0A /* BlogDashboardPersonalizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */; }; 0CB4056E29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */; }; @@ -5917,6 +5918,7 @@ 0A69300A28B5AA5E00E98DE1 /* FullScreenCommentReplyViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCommentReplyViewModelTests.swift; sourceTree = ""; }; 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSuggestion+Comparable.swift"; sourceTree = ""; }; 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCommentReplyViewModelMock.swift; sourceTree = ""; }; + 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModelTests.swift; sourceTree = ""; }; 0CB4056A29C78F06008EED0A /* BlogDashboardPersonalizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationService.swift; sourceTree = ""; }; 0CB4056D29C7BA63008EED0A /* BlogDashboardPersonalizationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationServiceTests.swift; sourceTree = ""; }; 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationViewModel.swift; sourceTree = ""; }; @@ -13457,6 +13459,7 @@ 80B016CE27FEBDC900D15566 /* DashboardCardTests.swift */, 80EF9283280CFEB60064A971 /* DashboardPostsSyncManagerTests.swift */, 0118969029D1F2FE00D34BA9 /* DomainsDashboardCardHelperTests.swift */, + 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */, ); path = Dashboard; sourceTree = ""; @@ -23118,6 +23121,7 @@ 732A473D218787500015DA74 /* WPRichTextFormatterTests.swift in Sources */, 1ABA150822AE5F870039311A /* WordPressUIBundleTests.swift in Sources */, FEA312842987FB0100FFD405 /* BlogJetpackTests.swift in Sources */, + 0C35FFF429CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift in Sources */, 57569CF2230485680052EE14 /* PostAutoUploadInteractorTests.swift in Sources */, 7E8980B922E73F4000C567B0 /* EditorSettingsServiceTests.swift in Sources */, 1797373720EBAA4100377B4E /* RouteMatcherTests.swift in Sources */, diff --git a/WordPress/WordPressTest/Dashboard/BlogDashboardPersonalizationViewModelTests.swift b/WordPress/WordPressTest/Dashboard/BlogDashboardPersonalizationViewModelTests.swift new file mode 100644 index 000000000000..4c8ea5bb8735 --- /dev/null +++ b/WordPress/WordPressTest/Dashboard/BlogDashboardPersonalizationViewModelTests.swift @@ -0,0 +1,33 @@ +import XCTest +import Nimble + +@testable import WordPress + +final class BlogDashboardPersonalizationViewModelTests: XCTestCase { + private let repository = InMemoryUserDefaults() + private lazy var service = BlogDashboardPersonalizationService(repository: repository, siteID: 1) + + var viewModel: BlogDashboardPersonalizationViewModel! + + override func setUp() { + super.setUp() + + viewModel = BlogDashboardPersonalizationViewModel(service: service) + } + + func testThatCardStateIsToggled() throws { + // Given + let cardViewModel = try XCTUnwrap(viewModel.cards.first) + let card = cardViewModel.id + XCTAssertEqual(card, .todaysStats) + XCTAssertTrue(cardViewModel.isOn, "By default, the cards are enabled") + XCTAssertTrue(service.isEnabled(card)) + + // When + cardViewModel.isOn.toggle() + + // Then + XCTAssertFalse(cardViewModel.isOn) + XCTAssertFalse(service.isEnabled(card), "Service wasn't updated") + } +} From d5b167646ad550af1ea3ce8a5946e48734cfe342 Mon Sep 17 00:00:00 2001 From: kean Date: Thu, 23 Mar 2023 17:31:20 -0400 Subject: [PATCH 6/9] Update to support latest BlogDashboardPersonalizationService changes --- .../Service/BlogDashboardPersonalizationService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift index c278df348a10..6a56fd0a083d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift @@ -51,7 +51,7 @@ private func makeKey(for card: DashboardCard) -> String? { // avoid having to migrate data. return "prompts-enabled-site-settings" case .domainsDashboardCard: - return "domains-dashboard-enabled-site-settings" + return "domains-dashboard-card-enabled-site-settings" case .quickStart, .jetpackBadge, .jetpackInstall, .nextPost, .createPost, .failure, .ghost, .personalize: return nil } From e23baec31feaa5f13947492b50a0301f9ef91721 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Fri, 24 Mar 2023 22:05:11 -0400 Subject: [PATCH 7/9] Fix localizable string value Co-authored-by: Paul Von Schrottky --- .../BlogPersonalization/BlogDashboardPersonalizationView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift index c325fb0f969a..27101c564dcf 100644 --- a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift @@ -44,7 +44,7 @@ private extension BlogDashboardPersonalizationView { struct Strings { static let title = NSLocalizedString("personalizeHome.title", value: "Personalize Home Tab", comment: "Page title") static let sectionHeader = NSLocalizedString("personalizeHome.cardsSectionHeader", value: "Add or hide cards", comment: "Section header") - static let sectionFooter = NSLocalizedString("personalizeHome.cardsSectionFooter", value: "Cards may show different content depending on what's happening on your site. We're working on on more cards and controls.", comment: "Section footer displayed below the list of toggles") + static let sectionFooter = NSLocalizedString("personalizeHome.cardsSectionFooter", value: "Cards may show different content depending on what's happening on your site. We're working on more cards and controls.", comment: "Section footer displayed below the list of toggles") } } From e7ef530e09e62dda7776a48c3510b2348dd22ba8 Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Mon, 27 Mar 2023 15:51:15 -0400 Subject: [PATCH 8/9] Update personalize screen size on iPad --- .../Cards/BlogDashboardPersonalizeCardCell.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift index 45a4ec6df12f..ed11b9aa3543 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift @@ -80,6 +80,9 @@ final class BlogDashboardPersonalizeCardCell: DashboardCollectionViewCell { let viewController = UIHostingController(rootView: NavigationView { BlogDashboardPersonalizationView(viewModel: .init(service: .init(siteID: siteID))) }.navigationViewStyle(.stack)) // .stack is required for iPad + if UIDevice.isPad() { + viewController.modalPresentationStyle = .formSheet + } presentingViewController?.present(viewController, animated: true) } } From fb7d8d63fb607a570dd3b27d6c32a53895f0956a Mon Sep 17 00:00:00 2001 From: Alex Grebenyuk Date: Wed, 29 Mar 2023 08:16:31 -0400 Subject: [PATCH 9/9] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 6d65acc75246..a03569c0e25f 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ * [**] [internal] Refactor updating account related Core Data operations, which ususally happens during log in and out of the app. [#20394] * [***] [internal] Refactor uploading photos (from the device photo, the Free Photo library, and other sources) to the WordPress Media Library. Affected areas are where you can choose a photo and upload, including the "Media" screen, adding images to a post, updating site icon, etc. [#20322] * [**] [WordPress-only] Warns user about sites with only individual plugins not supporting core app features and offers the option to switch to the Jetpack app. [#20408] +* [**] Add a "Personalize Home Tab" button to the bottom of the Home tab that allows changing cards visibility. [#20369] 22.0 -----