diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 9eef518266f9..06d6073cb3ab 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 ----- 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..ed11b9aa3543 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/BlogDashboardPersonalizeCardCell.swift @@ -0,0 +1,94 @@ +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 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))) + }.navigationViewStyle(.stack)) // .stack is required for iPad + if UIDevice.isPad() { + viewController.modalPresentationStyle = .formSheet + } + 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..5d8f61700ec9 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/DashboardCard.swift @@ -18,10 +18,11 @@ 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 + /// A "Personalize Home Tab" button + case personalize var cell: DashboardCollectionViewCell.Type { switch self { @@ -52,6 +53,8 @@ enum DashboardCard: String, CaseIterable { case .domainsDashboardCard: /// TODO return DashboardFailureCardCell.self + case .personalize: + return BlogDashboardPersonalizeCardCell.self } } @@ -88,6 +91,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 +116,15 @@ 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, + .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..6a56fd0a083d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift @@ -51,8 +51,8 @@ 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" - case .quickStart, .jetpackBadge, .jetpackInstall, .nextPost, .createPost, .failure, .ghost: + return "domains-dashboard-card-enabled-site-settings" + 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..27101c564dcf --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationView.swift @@ -0,0 +1,59 @@ +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) + }) + } + .listStyle(.insetGrouped) + .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.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 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..78453e339c26 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift @@ -0,0 +1,52 @@ +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: DashboardCard { card } + 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: + assertionFailure("\(self) card should not appear in the personalization menus") + 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 000000000000..e7c6eb47bbd7 Binary files /dev/null and b/WordPress/Resources/AppImages.xcassets/personalize.imageset/personalize.pdf differ diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index d7e38ea324d8..a3f6cc684c75 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -303,9 +303,16 @@ 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 */; }; + 0CB4057629C8DDE5008EED0A /* BlogDashboardPersonalizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057229C8DD01008EED0A /* BlogDashboardPersonalizationView.swift */; }; + 0CB4057729C8DDE8008EED0A /* BlogDashboardPersonalizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057229C8DD01008EED0A /* BlogDashboardPersonalizationView.swift */; }; + 0CB4057929C8DDEC008EED0A /* BlogDashboardPersonalizationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */; }; + 0CB4057A29C8DDEE008EED0A /* BlogDashboardPersonalizationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */; }; + 0CB4057D29C8DF83008EED0A /* BlogDashboardPersonalizeCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */; }; + 0CB4057E29C8DF84008EED0A /* BlogDashboardPersonalizeCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */; }; 1702BBDC1CEDEA6B00766A33 /* BadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1702BBDB1CEDEA6B00766A33 /* BadgeLabel.swift */; }; 1702BBE01CF3034E00766A33 /* DomainsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1702BBDF1CF3034E00766A33 /* DomainsService.swift */; }; 17039225282E6D2800F602E9 /* ViewsVisitorsLineChartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC772B0728201F5300664C02 /* ViewsVisitorsLineChartCell.swift */; }; @@ -5911,8 +5918,12 @@ 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 = ""; }; + 0CB4057229C8DD01008EED0A /* BlogDashboardPersonalizationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizationView.swift; sourceTree = ""; }; + 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDashboardPersonalizeCardCell.swift; sourceTree = ""; }; 131D0EE49695795ECEDAA446 /* Pods-WordPressTest.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressTest.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressTest/Pods-WordPressTest.release-alpha.xcconfig"; sourceTree = ""; }; 150B6590614A28DF9AD25491 /* Pods-Apps-Jetpack.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-Jetpack.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-Jetpack/Pods-Apps-Jetpack.release-alpha.xcconfig"; sourceTree = ""; }; 152F25D5C232985E30F56CAC /* Pods-Apps-Jetpack.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Apps-Jetpack.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-Apps-Jetpack/Pods-Apps-Jetpack.debug.xcconfig"; sourceTree = ""; }; @@ -9819,6 +9830,15 @@ path = Mocks; sourceTree = ""; }; + 0CB4056F29C8DCD7008EED0A /* BlogPersonalization */ = { + isa = PBXGroup; + children = ( + 0CB4057229C8DD01008EED0A /* BlogDashboardPersonalizationView.swift */, + 0CB4057029C8DCF4008EED0A /* BlogDashboardPersonalizationViewModel.swift */, + ); + path = BlogPersonalization; + sourceTree = ""; + }; 1705CEE6260A57F900F23763 /* ContentViews */ = { isa = PBXGroup; children = ( @@ -13398,6 +13418,7 @@ 8BF9E03627B1AD2100915B27 /* Stats */, 8B4DDF24278F3AF60022494D /* Posts */, 83796698299C048E004A92B9 /* DashboardJetpackInstallCardCell.swift */, + 0CB4057B29C8DEE1008EED0A /* BlogDashboardPersonalizeCardCell.swift */, ); path = Cards; sourceTree = ""; @@ -13438,6 +13459,7 @@ 80B016CE27FEBDC900D15566 /* DashboardCardTests.swift */, 80EF9283280CFEB60064A971 /* DashboardPostsSyncManagerTests.swift */, 0118969029D1F2FE00D34BA9 /* DomainsDashboardCardHelperTests.swift */, + 0C35FFF329CBA6DA00D224EB /* BlogDashboardPersonalizationViewModelTests.swift */, ); path = Dashboard; sourceTree = ""; @@ -14467,6 +14489,7 @@ F1863714253E49B8003D4BEF /* Add Site */, 3F37609B23FD803300F0D87F /* Blog + Me */, FA73D7D7278D9E6300DF24B3 /* Blog Dashboard */, + 0CB4056F29C8DCD7008EED0A /* BlogPersonalization */, 3F43603423F368BF001DEE70 /* Blog Details */, 3F43603523F368CA001DEE70 /* Blog List */, 3F43603623F36962001DEE70 /* Blog Selector */, @@ -20622,6 +20645,7 @@ F15272FF243B28B700C8DC7A /* RequestAuthenticator.swift in Sources */, E66E2A651FE4311300788F22 /* SettingsTitleSubtitleController.swift in Sources */, E1222B671F878E4700D23173 /* WebViewControllerFactory.swift in Sources */, + 0CB4057729C8DDE8008EED0A /* BlogDashboardPersonalizationView.swift in Sources */, E1D86E691B2B414300DD2192 /* WordPress-32-33.xcmappingmodel in Sources */, 9A2B28E8219046ED00458F2A /* ShowRevisionsListManger.swift in Sources */, FAA4012D27B405DB009E1137 /* DashboardQuickActionsCardCell.swift in Sources */, @@ -20639,6 +20663,7 @@ 738B9A5A21B85CF20005062B /* SiteCreationHeaderData.swift in Sources */, 594399931B45091000539E21 /* WPAuthTokenIssueSolver.m in Sources */, F16C35D623F33DE400C81331 /* PageAutoUploadMessageProvider.swift in Sources */, + 0CB4057929C8DDEC008EED0A /* BlogDashboardPersonalizationViewModel.swift in Sources */, 8BDA5A6B247C2EAF00AB124C /* ReaderDetailViewController.swift in Sources */, 3F8B313025D1D652005A2903 /* HomeWidgetThisWeekData.swift in Sources */, 17ABD3522811A48900B1E9CB /* StatsMostPopularTimeInsightsCell.swift in Sources */, @@ -21790,6 +21815,7 @@ 3FA62FD326FE2E4B0020793A /* ShapeWithTextView.swift in Sources */, F10465142554260600655194 /* BindableTapGestureRecognizer.swift in Sources */, E62079DF1CF79FC200F5CD46 /* ReaderSearchSuggestion.swift in Sources */, + 0CB4057E29C8DF84008EED0A /* BlogDashboardPersonalizeCardCell.swift in Sources */, E64ECA4D1CE62041000188A0 /* ReaderSearchViewController.swift in Sources */, 803BB9792959543D00B3F6D6 /* RootViewCoordinator.swift in Sources */, 98F537A722496CF300B334F9 /* SiteStatsTableHeaderView.swift in Sources */, @@ -23095,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 */, @@ -23852,6 +23879,7 @@ 8B74A9A9268E3C68003511CE /* RewindStatus+multiSite.swift in Sources */, FABB224C2602FC2C00C8785C /* GravatarUploader.swift in Sources */, FABB224D2602FC2C00C8785C /* NotificationSettingDetailsViewController.swift in Sources */, + 0CB4057D29C8DF83008EED0A /* BlogDashboardPersonalizeCardCell.swift in Sources */, FABB224E2602FC2C00C8785C /* AztecMediaPickingCoordinator.swift in Sources */, FABB224F2602FC2C00C8785C /* SiteSuggestion+CoreDataClass.swift in Sources */, FABB22502602FC2C00C8785C /* NibReusable.swift in Sources */, @@ -24060,6 +24088,7 @@ 3F2ABE1C277118C9005D8916 /* VideoLimitsAlertPresenter.swift in Sources */, C7F7BE0726262B9A00CE547F /* AuthenticationHandler.swift in Sources */, FABB22E52602FC2C00C8785C /* MediaHost.swift in Sources */, + 0CB4057629C8DDE5008EED0A /* BlogDashboardPersonalizationView.swift in Sources */, FABB22E62602FC2C00C8785C /* MenuItemAbstractPostsViewController.m in Sources */, FABB22E72602FC2C00C8785C /* AtomicAuthenticationService.swift in Sources */, FABB22E82602FC2C00C8785C /* WPStyleGuide+Posts.swift in Sources */, @@ -24318,6 +24347,7 @@ C324D7AC28C2F73F00310DEF /* SplashPrologueViewController.swift in Sources */, FABB23AA2602FC2C00C8785C /* AuthorFilterButton.swift in Sources */, FABB23AB2602FC2C00C8785C /* MenuItemTagsViewController.m in Sources */, + 0CB4057A29C8DDEE008EED0A /* BlogDashboardPersonalizationViewModel.swift in Sources */, FABB23AC2602FC2C00C8785C /* ReachabilityUtils.m in Sources */, FABB23AD2602FC2C00C8785C /* TenorDataSource.swift in Sources */, FABB23AE2602FC2C00C8785C /* ReaderTopicService+Subscriptions.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") + } +}