diff --git a/Modules/Sources/WordPressKit/BloggingPromptsServiceRemote.swift b/Modules/Sources/WordPressKit/BloggingPromptsServiceRemote.swift index 8968a26301f5..96136291fe46 100644 --- a/Modules/Sources/WordPressKit/BloggingPromptsServiceRemote.swift +++ b/Modules/Sources/WordPressKit/BloggingPromptsServiceRemote.swift @@ -17,55 +17,6 @@ open class BloggingPromptsServiceRemote: ServiceRemoteWordPressComREST { case encodingFailure } - /// Fetches a number of blogging prompts for the specified site. - /// Note that this method hits wpcom/v2, which means the `WordPressComRestAPI` needs to be initialized with `LocaleKeyV2`. - /// - /// - Parameters: - /// - siteID: Used to check which prompts have been answered for the site with given `siteID`. - /// - number: The number of prompts to query. When not specified, this will default to remote implementation. - /// - fromDate: When specified, this will fetch prompts from the given date. When not specified, this will default to remote implementation. - /// - completion: A closure that will be called when the fetch request completes. - open func fetchPrompts(for siteID: NSNumber, - number: Int? = nil, - fromDate: Date? = nil, - completion: @escaping (Result<[RemoteBloggingPrompt], Error>) -> Void) { - let path = path(forEndpoint: "sites/\(siteID)/blogging-prompts", withVersion: ._2_0) - let requestParameter: [String: AnyHashable] = { - var params = [String: AnyHashable]() - - if let number, number > 0 { - params["number"] = number - } - - if let fromDate { - // convert to yyyy-MM-dd format, excluding the timezone information. - // the date parameter doesn't need to be timezone-accurate since prompts are grouped by date. - params["from"] = Self.dateFormatter.string(from: fromDate) - } - - return params - }() - - let decoder = JSONDecoder.apiDecoder - // our API decoder assumes that we're converting from snake case. - // revert it to default so the CodingKeys match the actual response keys. - decoder.keyDecodingStrategy = .useDefaultKeys - - Task { @MainActor in - await self.wordPressComRestApi - .perform( - .get, - URLString: path, - parameters: requestParameter as [String: AnyObject], - jsonDecoder: decoder, - type: [String: [RemoteBloggingPrompt]].self - ) - .map { $0.body.values.first ?? [] } - .mapError { error -> Error in error.asNSError() } - .execute(completion) - } - } - /// Fetches the blogging prompts settings for a given site. /// /// - Parameters: diff --git a/Sources/WordPressData/Swift/BloggingPrompt+CoreDataClass.swift b/Sources/WordPressData/Swift/BloggingPrompt+CoreDataClass.swift index 8a103f8df53b..4f443f4a18b8 100644 --- a/Sources/WordPressData/Swift/BloggingPrompt+CoreDataClass.swift +++ b/Sources/WordPressData/Swift/BloggingPrompt+CoreDataClass.swift @@ -5,11 +5,11 @@ import CoreData public class BloggingPrompt: NSManagedObject { @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: Self.classNameWithoutNamespaces()) + NSFetchRequest(entityName: Self.classNameWithoutNamespaces()) } @nonobjc public class func newObject(in context: NSManagedObjectContext) -> BloggingPrompt? { - return NSEntityDescription.insertNewObject(forEntityName: Self.entityName(), into: context) as? BloggingPrompt + NSEntityDescription.insertNewObject(forEntityName: Self.entityName(), into: context) as? BloggingPrompt } public override func awakeFromInsert() { @@ -36,14 +36,10 @@ public class BloggingPrompt: NSManagedObject { self.answerCount = Int32(remotePrompt.answeredUsersCount) self.displayAvatarURLs = remotePrompt.answeredUserAvatarURLs self.additionalPostTags = [String]() // reset previously additional tags. - - if let brandContext = BrandContext(with: remotePrompt) { - brandContext.configure(self) - } } public func textForDisplay() -> String { - return text.stringByDecodingXMLCharacters().trim() + text.stringByDecodingXMLCharacters().trim() } /// Convenience method that checks if the given date is within the same day of the prompt's date without considering the timezone information. @@ -55,7 +51,7 @@ public class BloggingPrompt: NSManagedObject { /// - localDate: The date to compare against in local timezone. /// - Returns: True if the year, month, and day components of the `localDate` matches the prompt's localized date. public func inSameDay(as dateToCompare: Date) -> Bool { - return DateFormatters.utc.string(from: date) == DateFormatters.local.string(from: dateToCompare) + DateFormatters.utc.string(from: date) == DateFormatters.local.string(from: dateToCompare) } /// Used for comparison on upsert – there can't be two `BloggingPrompt` objects with the same date, so we can use it as a unique identifier @@ -79,36 +75,6 @@ public extension BloggingPrompt { private extension BloggingPrompt { - enum Constants { - static let bloganuaryTag = "bloganuary" - } - - enum BrandContext { - case bloganuary(String) - - init?(with remotePrompt: BloggingPromptRemoteObject) { - // Bloganuary context - if let bloganuaryId = remotePrompt.bloganuaryId, - bloganuaryId.contains(Constants.bloganuaryTag) { - self = .bloganuary(bloganuaryId) - return - } - - return nil - } - - /// Configures the given prompt with additional data based on the brand context. - /// - /// - Parameter prompt: The `BloggingPrompt` instance to configure. - func configure(_ prompt: BloggingPrompt) { - switch self { - case .bloganuary(let id): - prompt.additionalPostTags = [Constants.bloganuaryTag, id] - prompt.attribution = BloggingPromptsAttribution.bloganuary.rawValue - } - } - } - struct DateFormatters { static let local: DateFormatter = { let formatter = DateFormatter() diff --git a/Sources/WordPressData/Swift/BloggingPromptRemoteObject.swift b/Sources/WordPressData/Swift/BloggingPromptRemoteObject.swift index d0c43500efc7..211e847c525e 100644 --- a/Sources/WordPressData/Swift/BloggingPromptRemoteObject.swift +++ b/Sources/WordPressData/Swift/BloggingPromptRemoteObject.swift @@ -11,7 +11,6 @@ public struct BloggingPromptRemoteObject { let answeredUserAvatarURLs: [URL] let answeredLink: URL? let answeredLinkText: String - let bloganuaryId: String? /// Used for comparison on import public var dateString: String { @@ -32,7 +31,6 @@ extension BloggingPromptRemoteObject: Decodable { case answeredUserAvatarURLs = "answered_users_sample" case answeredLink = "answered_link" case answeredLinkText = "answered_link_text" - case bloganuaryId = "bloganuary_id" } /// meta structure to simplify decoding logic for user avatar objects. @@ -65,20 +63,13 @@ extension BloggingPromptRemoteObject: Decodable { self.answeredLink = { guard let linkURLString = try? container.decode(String.self, forKey: .answeredLink), - let answeredLinkURL = URL(string: linkURLString) else { + let answeredLinkURL = URL(string: linkURLString) + else { return nil } return answeredLinkURL }() self.answeredLinkText = try container.decode(String.self, forKey: .answeredLinkText) - - self.bloganuaryId = { - guard let remoteBloganuaryId = try? container.decode(String.self, forKey: .bloganuaryId), - !remoteBloganuaryId.isEmpty else { - return nil - } - return remoteBloganuaryId - }() } } diff --git a/Sources/WordPressData/Swift/BloggingPromptsAttribution.swift b/Sources/WordPressData/Swift/BloggingPromptsAttribution.swift index 2f580a809ed5..c4c0d48634a7 100644 --- a/Sources/WordPressData/Swift/BloggingPromptsAttribution.swift +++ b/Sources/WordPressData/Swift/BloggingPromptsAttribution.swift @@ -1,4 +1,3 @@ public enum BloggingPromptsAttribution: String { case dayone - case bloganuary } diff --git a/Tests/KeystoneTests/Resources/Mocks/Misc/blogging-prompts-bloganuary.json b/Tests/KeystoneTests/Resources/Mocks/Misc/blogging-prompts-bloganuary.json deleted file mode 100644 index 5c1dd4637e50..000000000000 --- a/Tests/KeystoneTests/Resources/Mocks/Misc/blogging-prompts-bloganuary.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "id": 239, - "date": "2023-12-31", - "label": "Daily writing prompt", - "text": "Was there a toy or thing you always wanted as a child, during the holidays or on your birthday, but never received? Tell us about it.", - "attribution": "dayone", - "answered": false, - "answered_users_count": 0, - "answered_users_sample": [], - "answered_link": "https:\/\/wordpress.com\/tag\/dailyprompt-239", - "answered_link_text": "View all responses", - "bloganuary_id": "" - }, - { - "id": 248, - "date": "2024-01-01", - "label": "Daily writing prompt", - "text": "Tell us about a time when you felt out of place.", - "attribution": "", - "answered": true, - "answered_users_count": 1, - "answered_users_sample": [ - { - "avatar": "https://0.gravatar.com/avatar/example?s=96&d=identicon&r=G" - } - ], - "answered_link": "https:\/\/wordpress.com\/tag\/dailyprompt-248", - "answered_link_text": "View all responses", - "bloganuary_id": "bloganuary-2024-01" - } -] diff --git a/Tests/KeystoneTests/Tests/Features/Dashboard/DashboardBloganuaryCardCellTests.swift b/Tests/KeystoneTests/Tests/Features/Dashboard/DashboardBloganuaryCardCellTests.swift deleted file mode 100644 index 4ddd97c9647a..000000000000 --- a/Tests/KeystoneTests/Tests/Features/Dashboard/DashboardBloganuaryCardCellTests.swift +++ /dev/null @@ -1,162 +0,0 @@ -import XCTest -@testable import WordPress -@testable import WordPressData - -final class DashboardBloganuaryCardCellTests: CoreDataTestCase { - - private static var calendar = { - Calendar(identifier: .gregorian) - }() - private let blogID = 100 - private let featureFlags = FeatureFlagOverrideStore() - - override func setUp() { - super.setUp() - featureFlags.override(RemoteFeatureFlag.bloganuaryDashboardNudge, withValue: true) - } - - override func tearDown() { - super.tearDown() - featureFlags.override(RemoteFeatureFlag.bloganuaryDashboardNudge, withValue: RemoteFeatureFlag.bloganuaryDashboardNudge.defaultValue) - } - - // MARK: - `shouldShowCard` tests - - func testCardIsNotShownWhenFlagIsDisabled() throws { - // Given - let blog = makeBlog() - makeBloggingPromptSettings() - try mainContext.save() - featureFlags.override(RemoteFeatureFlag.bloganuaryDashboardNudge, withValue: false) - - // When - let result = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInDecember) - - // Then - XCTAssertFalse(result) - } - - func testCardIsNotShownWhenSiteIsNotMarkedAsBloggingSite() throws { - // Given - let blog = makeBlog() - makeBloggingPromptSettings(markAsBloggingSite: false) - try mainContext.save() - - // When - let result = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInDecember) - - // Then - XCTAssertFalse(result) - } - - func testCardIsNotShownForEligibleSitesOutsideEligibleMonths() throws { - // Given - let blog = makeBlog() - makeBloggingPromptSettings() - try mainContext.save() - - // When - let result = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInFebruary) - - // Then - XCTAssertFalse(result) - } - - func testCardIsShownWhenSiteIsEligible() throws { - // Given - let blog = makeBlog() - makeBloggingPromptSettings() - try mainContext.save() - - // When - let resultForDecember = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInDecember) - let resultForJanuary = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInJanuary) - - // Then - XCTAssertTrue(resultForDecember) - XCTAssertTrue(resultForJanuary) - } - - func testCardIsShownForEligibleSitesThatHavePromptsDisabled() throws { - // Given - let blog = makeBlog() - makeBloggingPromptSettings(promptCardEnabled: false) - try mainContext.save() - - // When - let result = DashboardBloganuaryCardCell.shouldShowCard(for: blog, date: sometimeInDecember) - - // Then - XCTAssertTrue(result) - } -} - -// MARK: - Helpers - -private extension DashboardBloganuaryCardCellTests { - - var sometimeInDecember: Date { - let date = Date() - var components = Self.calendar.dateComponents([.year, .month, .day], from: date) - components.month = 12 - components.year = 2023 - components.day = 10 - - return Self.calendar.date(from: components) ?? date - } - - var sometimeInJanuary: Date { - let date = Date() - var components = Self.calendar.dateComponents([.year, .month, .day], from: date) - components.month = 1 - components.year = 2024 - components.day = 10 - - return Self.calendar.date(from: components) ?? date - } - - var sometimeInFebruary: Date { - let date = Date() - var components = Self.calendar.dateComponents([.year, .month, .day], from: date) - components.month = 2 - components.year = 2024 - components.day = 10 - - return Self.calendar.date(from: components) ?? date - } - - func prepareData() -> (Blog, BloggingPromptSettings) { - return (makeBlog(), makeBloggingPromptSettings()) - } - - func makeBlog() -> Blog { - let builder = BlogBuilder(mainContext) - .withAnAccount() - .with(dotComID: blogID) - - return builder.build() - } - - @discardableResult - func makeBloggingPromptSettings(markAsBloggingSite: Bool = true, promptCardEnabled: Bool = true) -> BloggingPromptSettings { - let settings = NSEntityDescription.insertNewObject(forEntityName: BloggingPromptSettings.entityName(), - into: mainContext) as! WordPress.BloggingPromptSettings - - let reminderDays = NSEntityDescription.insertNewObject(forEntityName: BloggingPromptSettingsReminderDays.entityName(), - into: mainContext) as! WordPress.BloggingPromptSettingsReminderDays - reminderDays.monday = false - reminderDays.tuesday = false - reminderDays.wednesday = false - reminderDays.thursday = false - reminderDays.friday = false - reminderDays.saturday = false - reminderDays.sunday = false - - settings.isPotentialBloggingSite = markAsBloggingSite - settings.promptCardEnabled = promptCardEnabled - settings.reminderDays = reminderDays - settings.siteID = Int32(blogID) - - return settings - } -} diff --git a/Tests/KeystoneTests/Tests/Services/BloggingPromptsServiceTests.swift b/Tests/KeystoneTests/Tests/Services/BloggingPromptsServiceTests.swift index ad60f30a9fbe..67655d4a58cc 100644 --- a/Tests/KeystoneTests/Tests/Services/BloggingPromptsServiceTests.swift +++ b/Tests/KeystoneTests/Tests/Services/BloggingPromptsServiceTests.swift @@ -9,7 +9,6 @@ final class BloggingPromptsServiceTests: CoreDataTestCase { private let siteID = 1 private let timeout: TimeInterval = 2 private let fetchPromptsResponseFileName = "blogging-prompts-fetch-success" - private let bloganuaryPromptsResponseFileName = "blogging-prompts-bloganuary" private var dateFormatter: DateFormatter = { let formatter = DateFormatter() @@ -157,10 +156,10 @@ final class BloggingPromptsServiceTests: CoreDataTestCase { let currentYear = Calendar(identifier: .gregorian).component(.year, from: Date()) XCTAssertEqual("\(currentYear)-01-02", try passedDate()) } -func test_fetchPrompts_alwaysPassesDescendingOrder() throws { - service.fetchPrompts(success: { _ in }, failure: { _ in }) - XCTAssertEqual(try passedParameter("order") as? String, "desc") -} + func test_fetchPrompts_alwaysPassesDescendingOrder() throws { + service.fetchPrompts(success: { _ in }, failure: { _ in }) + XCTAssertEqual(try passedParameter("order") as? String, "desc") + } // MARK: - Upsert Tests // new prompts should overwrite any existing prompts. @@ -285,50 +284,6 @@ func test_fetchPrompts_alwaysPassesDescendingOrder() throws { wait(for: [expectation], timeout: timeout) } - - // MARK: Bloganuary Tests - - func test_fetchPrompt_shouldParseBloganuaryPromptsCorrectly() { - // use actual remote object so the request can be intercepted by HTTPStubs. - service = BloggingPromptsService(contextManager: contextManager, blog: blog) - testPrompts = loadTestPrompts(from: bloganuaryPromptsResponseFileName) - stubFetchPromptsResponse(with: bloganuaryPromptsResponseFileName) - - let expectation = expectation(description: "Fetch prompts should succeed") - service.fetchPrompts(from: .distantPast) { [testPrompts] prompts in - XCTAssertEqual(prompts.count, testPrompts.count) - - prompts.forEach { prompt in - guard let expected = testPrompts.first(where: { $0.promptID == prompt.promptID }) else { - XCTFail("Prompt with ID: \(prompt.promptID) not found in the test data.") - return - } - - // check for Bloganuary prompts. - if let bloganuaryId = expected.bloganuaryId, !bloganuaryId.isEmpty { - // the attribution should be added client-side. - XCTAssertEqual(prompt.attribution, "bloganuary") - XCTAssertNotNil(prompt.additionalPostTags) - - let tags = prompt.additionalPostTags! - XCTAssertTrue(tags.contains("bloganuary")) - XCTAssertTrue(tags.contains(bloganuaryId)) - } else { - // otherwise, normal cards shouldn't have the bloganuary attributions. - // no additional tags should be added here. - XCTAssertNotEqual(prompt.attribution, "bloganuary") - XCTAssertTrue(prompt.additionalPostTags!.isEmpty) - } - } - - expectation.fulfill() - } failure: { _ in - XCTFail("This closure shouldn't be called.") - expectation.fulfill() - } - - wait(for: [expectation], timeout: timeout) - } } // MARK: - Helpers diff --git a/Tests/WordPressKitTests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift b/Tests/WordPressKitTests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift index 05f9b3b5fb16..dda49ea5d224 100644 --- a/Tests/WordPressKitTests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift +++ b/Tests/WordPressKitTests/WordPressKitTests/Tests/BloggingPromptsServiceRemoteTests.swift @@ -36,84 +36,6 @@ class BloggingPromptsServiceRemoteTests: RemoteTestCase, RESTTestable { // MARK: Tests - func test_fetchPrompts_returnsRemotePrompts() { - let expectedAvatarURLString = "https://0.gravatar.com/avatar/example?s=96&d=identicon&r=G" - stubRemoteResponse(.bloggingPromptsEndpoint, filename: .mockFileName, contentType: .ApplicationJSON) - - let expect = expectation(description: "Fetch blogging prompts succeeded") - service.fetchPrompts(for: siteID) { result in - guard case .success(let prompts) = result else { - XCTFail("Expected success result type") - return - } - - XCTAssertEqual(prompts.count, 2) - - let firstPrompt = prompts.first! - XCTAssertEqual(firstPrompt.promptID, 239) - XCTAssertEqual(firstPrompt.text, "Was there a toy or thing you always wanted as a child, during the holidays or on your birthday, but never received? Tell us about it.") - XCTAssertEqual(firstPrompt.title, "Prompt number 1") - XCTAssertEqual(firstPrompt.content, "\n

Was there a toy or thing you always wanted as a child, during the holidays or on your birthday, but never received? Tell us about it.

(courtesy of plinky.com)
\n") - XCTAssertEqual(firstPrompt.attribution, "dayone") - - let firstDateComponents = Calendar.current.dateComponents(in: self.utcTimeZone, from: firstPrompt.date) - XCTAssertEqual(firstDateComponents.year!, 2022) - XCTAssertEqual(firstDateComponents.month!, 5) - XCTAssertEqual(firstDateComponents.day!, 3) - - XCTAssertFalse(firstPrompt.answered) - XCTAssertEqual(firstPrompt.answeredUsersCount, 0) - - let secondPrompt = prompts.last! - XCTAssertEqual(secondPrompt.answeredUsersCount, 1) - XCTAssertEqual(secondPrompt.answeredUserAvatarURLs.count, 1) - XCTAssertTrue(secondPrompt.attribution.isEmpty) - - let secondDateComponents = Calendar.current.dateComponents(in: self.utcTimeZone, from: secondPrompt.date) - XCTAssertEqual(secondDateComponents.year!, 2021) - XCTAssertEqual(secondDateComponents.month!, 9) - XCTAssertEqual(secondDateComponents.day!, 12) - - let avatarURL = secondPrompt.answeredUserAvatarURLs.first! - XCTAssertEqual(avatarURL.absoluteString, expectedAvatarURLString) - - expect.fulfill() - } - - wait(for: [expect], timeout: timeout) - } - - func test_fetchPrompts_correctlyAddsParametersToRequest() throws { - let requestReceived = expectation(description: "HTTP request is received") - var request: URLRequest? - stub(condition: isHost("public-api.wordpress.com")) { - request = $0 - requestReceived.fulfill() - return HTTPStubsResponse(error: URLError(.networkConnectionLost)) - } - - let expectedNumber = 10 - let expectedDateString = "2022-01-02" - let expectedDate = dateFormatter.date(from: expectedDateString) - service = BloggingPromptsServiceRemote(wordPressComRestApi: WordPressComRestApi()) - - // no-op; we just need to check the passed params. - service.fetchPrompts(for: siteID, number: expectedNumber, fromDate: expectedDate, completion: { _ in }) - wait(for: [requestReceived], timeout: 0.3) - - let url = try XCTUnwrap(request?.url) - let queryItems = try XCTUnwrap(URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems) - let params = queryItems.reduce(into: [String: String]()) { result, query in - result[query.name] = query.value - } - - XCTAssertNotNil(params[.numberKey]) - XCTAssertEqual(params[.numberKey], expectedNumber.description) - - XCTAssertNotNil(params[.dateKey]) - XCTAssertEqual(params[.dateKey], expectedDateString) - } - func test_fetchSettings_returnsRemoteSettings() { stubRemoteResponse(.bloggingPromptsEndpoint, filename: .mockFetchSettingsFilename, contentType: .ApplicationJSON) diff --git a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift index ed90a13240e2..a8557cefc55b 100644 --- a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift +++ b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift @@ -480,12 +480,6 @@ import WordPressShared case promptsOtherAnswersTapped case promptsSettingsShowPromptsTapped - // Bloganuary Nudges - case bloganuaryNudgeCardLearnMoreTapped - case bloganuaryNudgeModalShown - case bloganuaryNudgeModalDismissed - case bloganuaryNudgeModalActionTapped - // Jetpack branding case jetpackPoweredBadgeTapped case jetpackPoweredBannerTapped @@ -742,7 +736,7 @@ import WordPressShared return "media_library_photo_added" case .editorAddedPhotoViaTenor: return "editor_photo_added" - // Media + // Media case .siteMediaShareTapped: return "site_media_shared_tapped" case .mediaStorageDetailsViewed: @@ -1543,16 +1537,6 @@ import WordPressShared case .promptsSettingsShowPromptsTapped: return "blogging_prompts_settings_show_prompts_tapped" - // Bloganuary Nudges - case .bloganuaryNudgeCardLearnMoreTapped: - return "bloganuary_nudge_my_site_card_learn_more_tapped" - case .bloganuaryNudgeModalShown: - return "bloganuary_nudge_learn_more_modal_shown" - case .bloganuaryNudgeModalDismissed: - return "bloganuary_nudge_learn_more_modal_dismissed" - case .bloganuaryNudgeModalActionTapped: - return "bloganuary_nudge_learn_more_modal_action_tapped" - // Jetpack branding case .jetpackPoweredBadgeTapped: return "jetpack_powered_badge_tapped" @@ -1909,7 +1893,7 @@ import WordPressShared case .jetpackConnectStepRetried: return "jetpack_rest_connect_step_retried" - // Intelligence + // Intelligence case .intelligenceExcerptGeneratorOpened: return "intelligence_excerpt_generator_opened" case .intelligenceExcerptSelected: @@ -2009,7 +1993,8 @@ extension WPAnalytics { static func track(_ event: WPAnalyticsEvent, properties: [AnyHashable: Any], blog: Blog) { var props = properties props[WPAppAnalyticsKeyBlogID] = blog.dotComID - props[WPAppAnalyticsKeySiteType] = blog.isWPForTeams ? WPAppAnalyticsValueSiteTypeP2 : WPAppAnalyticsValueSiteTypeBlog + props[WPAppAnalyticsKeySiteType] = + blog.isWPForTeams ? WPAppAnalyticsValueSiteTypeP2 : WPAppAnalyticsValueSiteTypeBlog WPAnalytics.track(event, properties: props) } @@ -2105,7 +2090,9 @@ extension WPAnalytics { } if event == nil { - print("🟡 Not Tracked: \"\(eventName)\" Block Editor event ignored as it was not found in the `trackBlockEditorEvent` conversion cases.") + print( + "🟡 Not Tracked: \"\(eventName)\" Block Editor event ignored as it was not found in the `trackBlockEditorEvent` conversion cases." + ) } else { WPAnalytics.track(event!, properties: properties, blog: blog) } diff --git a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift index c151374c2a97..68d0e5c1431c 100644 --- a/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift @@ -23,7 +23,6 @@ public enum RemoteFeatureFlag: Int, CaseIterable { case domainManagement case dynamicDashboardCards case plansInSiteCreation - case bloganuaryDashboardNudge // pcdRpT-4FE-p2 case inAppRating case siteMonitoring case inAppUpdates @@ -77,8 +76,6 @@ public enum RemoteFeatureFlag: Int, CaseIterable { return false case .plansInSiteCreation: return false - case .bloganuaryDashboardNudge: - return AppConfiguration.isJetpack case .inAppRating: return false case .siteMonitoring: @@ -141,8 +138,6 @@ public enum RemoteFeatureFlag: Int, CaseIterable { return "dynamic_dashboard_cards" case .plansInSiteCreation: return "plans_in_site_creation" - case .bloganuaryDashboardNudge: - return "bloganuary_dashboard_nudge" case .inAppRating: return "in_app_rating_and_feedback" case .siteMonitoring: @@ -204,8 +199,6 @@ public enum RemoteFeatureFlag: Int, CaseIterable { return "Dynamic Dashboard Cards" case .plansInSiteCreation: return "Plans in Site Creation" - case .bloganuaryDashboardNudge: - return "Bloganuary Dashboard Nudge" case .inAppRating: return "In-App Rating and Feedback" case .siteMonitoring: @@ -228,8 +221,10 @@ public enum RemoteFeatureFlag: Int, CaseIterable { /// If the flag is overridden, the overridden value is returned. /// If the flag exists in the local cache, the current value will be returned. /// If the flag is not overridden and does not exist in the local cache, the compile-time default will be returned. - func enabled(using remoteStore: RemoteFeatureFlagStore = RemoteFeatureFlagStore(), - overrideStore: FeatureFlagOverrideStore = FeatureFlagOverrideStore()) -> Bool { + func enabled( + using remoteStore: RemoteFeatureFlagStore = RemoteFeatureFlagStore(), + overrideStore: FeatureFlagOverrideStore = FeatureFlagOverrideStore() + ) -> Bool { if let overriddenValue = overrideStore.overriddenValue(for: self) { return overriddenValue } @@ -243,11 +238,11 @@ public enum RemoteFeatureFlag: Int, CaseIterable { extension RemoteFeatureFlag: OverridableFlag { var key: String { - return "ff-override-\(String(describing: self))" + "ff-override-\(String(describing: self))" } var originalValue: Bool { - return enabled() + enabled() } var canOverride: Bool { diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution+Strings.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution+Strings.swift index 0bbbafbbea7e..9c51ea377f2d 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution+Strings.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution+Strings.swift @@ -21,21 +21,18 @@ extension BloggingPromptsAttribution { var source: String { switch self { case .dayone: return Strings.dayOne - case .bloganuary: return Strings.bloganuary } } var iconImage: UIImage? { switch self { case .dayone: return Constants.dayOneIcon - case .bloganuary: return Constants.bloganuaryIcon } } var externalURL: URL? { switch self { case .dayone: return Constants.dayOneURL - case .bloganuary: return nil } } @@ -48,37 +45,30 @@ extension BloggingPromptsAttribution { } private struct Strings { - static let fromTextFormat = NSLocalizedString("From %1$@", comment: "Format for blogging prompts attribution. %1$@ is the attribution source.") + static let fromTextFormat = NSLocalizedString( + "From %1$@", + comment: "Format for blogging prompts attribution. %1$@ is the attribution source." + ) static let dayOne = "Day One" - static let bloganuary = "Bloganuary" } private struct Constants { static let baseAttributes: [NSAttributedString.Key: Any] = [ .font: WPStyleGuide.fontForTextStyle(.caption1), - .foregroundColor: UIColor.secondaryLabel, + .foregroundColor: UIColor.secondaryLabel ] static let sourceAttributes: [NSAttributedString.Key: Any] = [ .font: WPStyleGuide.fontForTextStyle(.caption1, fontWeight: .medium), - .foregroundColor: UIColor.label, + .foregroundColor: UIColor.label ] static let dayOneIconSize = CGSize(width: 18, height: 18) static let dayOneIcon = UIImage(named: "logo-dayone")?.resized(to: Constants.dayOneIconSize) static let dayOneURL = URL(string: "https://dayoneapp.com/?utm_source=jetpack&utm_medium=prompts") static let linkIconSize = CGFloat(10) - static let linkIcon = UIImage(systemName: "link", withConfiguration: UIImage.SymbolConfiguration(pointSize: linkIconSize)) - - /// This is computed so it can react accordingly on color scheme changes. - static var bloganuaryIcon: UIImage? { - UIImage(named: "logo-bloganuary")? - .withRenderingMode(.alwaysTemplate) - .resized(to: Constants.bloganuaryIconSize) - .withAlignmentRectInsets(UIEdgeInsets(.all, -6.0)) - .withTintColor(.label) - } - - /// Unlike the dayOne icon, the bloganuary icon has no implicit 6px padding surrounding the icon. - static let bloganuaryIconSize = CGSize(width: 12, height: 12) + static let linkIcon = UIImage( + systemName: "link", + withConfiguration: UIImage.SymbolConfiguration(pointSize: linkIconSize) + ) } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardBloganuaryCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardBloganuaryCardCell.swift deleted file mode 100644 index 6b7d1746d1cc..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/DashboardBloganuaryCardCell.swift +++ /dev/null @@ -1,199 +0,0 @@ -import SwiftUI -import WordPressData -import WordPressShared - -class DashboardBloganuaryCardCell: DashboardCollectionViewCell { - - private var blog: Blog? { - didSet { - updateUI() - } - } - - private weak var presenterViewController: BlogDashboardViewController? - - /// Checks whether the Bloganuary nudge card should be shown on the dashboard. - /// - /// The card is only going to be shown in December, and will be hidden in January. - /// It's also going to be shown for blogs that are marked as potential blogs by the backend, regardless - /// of whether the user has manually disabled the blogging prompts. - /// - /// - Parameters: - /// - blog: The current `Blog` instance. - /// - date: The date to check. Defaults to today. - /// - Returns: `true` if the Bloganuary card should be shown. `false` otherwise. - static func shouldShowCard(for blog: Blog, date: Date = Date()) -> Bool { - guard RemoteFeatureFlag.bloganuaryDashboardNudge.enabled(), - let context = blog.managedObjectContext else { - return false - } - - // Check for date eligibility. - let isDateWithinEligibleMonths: Bool = { - let components = date.dateAndTimeComponents() - guard let month = components.month else { - return false - } - - // NOTE: For simplicity, we're going to hardcode the date check if the date is within December or January. - return Constants.eligibleMonths.contains(month) - }() - - // Check if the blog is marked as a potential blogging site. - let isPotentialBloggingSite: Bool = context.performAndWait { - return (try? BloggingPromptSettings.of(blog))?.isPotentialBloggingSite ?? false - } - - return isDateWithinEligibleMonths && isPotentialBloggingSite - } - - func configure(blog: Blog, viewController: BlogDashboardViewController?, apiResponse: BlogDashboardRemoteEntity?) { - self.blog = blog - self.presenterViewController = viewController - - BlogDashboardAnalytics.shared.track(.dashboardCardShown, - properties: [ - "type": DashboardCard.bloganuaryNudge.rawValue, - "subtype": DashboardCard.bloganuaryNudge.rawValue - ]) - } - - // MARK: Private methods - - @MainActor - private func updateUI() { - guard let blog, - let blogID = blog.dotComID?.intValue else { - return - } - - contentView.subviews.forEach { $0.removeFromSuperview() } - - let cardView = BloganuaryNudgeCardView(onLearnMoreTapped: { [weak self] in - // check if the prompts card is enabled in the dashboard. - let promptsCardEnabled = BlogDashboardPersonalizationService(siteID: blogID).isEnabled(.prompts) - let overlayView = BloganuaryOverlayViewController(blogID: blogID, promptsEnabled: promptsCardEnabled) - - let navigationController = UINavigationController(rootViewController: overlayView) - navigationController.modalPresentationStyle = .formSheet - if let sheet = navigationController.sheetPresentationController { - sheet.prefersGrabberVisible = WPDeviceIdentification.isiPhone() - } - - BloganuaryTracker.trackCardLearnMoreTapped(promptsEnabled: promptsCardEnabled) - - self?.presenterViewController?.present(navigationController, animated: true) - }) - - let hostView = UIView.embedSwiftUIView(cardView) - let frameView = makeCardFrameView() - frameView.add(subview: hostView) - - contentView.addSubview(frameView) - contentView.pinSubviewToAllEdges(frameView) - } - - private func makeCardFrameView() -> BlogDashboardCardFrameView { - let frameView = BlogDashboardCardFrameView() - frameView.translatesAutoresizingMaskIntoConstraints = false - frameView.configureButtonContainerStackView() - - // NOTE: this is intentionally called *before* configuring the ellipsis button action, - // to avoid additional trailing padding. - frameView.hideHeader() - - if let blog { - frameView.onEllipsisButtonTap = { - BlogDashboardAnalytics.trackContextualMenuAccessed(for: .bloganuaryNudge) - } - frameView.ellipsisButton.showsMenuAsPrimaryAction = true - let action = BlogDashboardHelpers.makeHideCardAction(for: .bloganuaryNudge, blog: blog) - frameView.ellipsisButton.menu = UIMenu(title: String(), options: .displayInline, children: [action]) - } - - return frameView - } - - struct Constants { - // Only show the card in December and January. - static let eligibleMonths = [1, 12] - } -} - -// MARK: - SwiftUI - -private struct BloganuaryNudgeCardView: View { - let onLearnMoreTapped: (() -> Void)? - - var body: some View { - VStack(alignment: .leading, spacing: 12.0) { - bloganuaryImage - .resizable() - .frame(width: 24.0, height: 24.0) - textContainer - Button { - onLearnMoreTapped?() - } label: { - Text(Strings.cta) - .font(.subheadline) - } - } - .padding(.top, 12.0) - .padding([.horizontal, .bottom], 16.0) - } - - var bloganuaryImage: Image { - if let uiImage = UIImage(named: "logo-bloganuary")?.withRenderingMode(.alwaysTemplate).withTintColor(.label) { - return Image(uiImage: uiImage) - } - return Image("logo-bloganuary", bundle: .main) - } - - var textContainer: some View { - VStack(alignment: .leading, spacing: 8.0) { - Text(cardTitle) - .font(.headline) - .fontWeight(.semibold) - Text(Strings.description) - .font(.subheadline) - .foregroundStyle(.secondary) - } - } - - var cardTitle: String { - let components = Date().dateAndTimeComponents() - guard let month = components.month, - DashboardBloganuaryCardCell.Constants.eligibleMonths.contains(month) else { - return Strings.title - } - - return month == 1 ? Strings.runningTitle : Strings.title - } - - struct Strings { - static let title = NSLocalizedString( - "bloganuary.dashboard.card.title", - value: "Bloganuary is coming!", - comment: "Title for the Bloganuary dashboard card." - ) - - // The card title string to be shown while Bloganuary is running - static let runningTitle = NSLocalizedString( - "bloganuary.dashboard.card.runningTitle", - value: "Bloganuary is here!", - comment: "Title for the Bloganuary dashboard card while Bloganuary is running." - ) - - static let description = NSLocalizedString( - "bloganuary.dashboard.card.description", - value: "For the month of January, blogging prompts will come from Bloganuary — our community challenge to build a blogging habit for the new year.", - comment: "Short description for the Bloganuary event, shown right below the title." - ) - - static let cta = NSLocalizedString( - "bloganuary.dashboard.card.button.learnMore", - value: "Learn more", - comment: "Title for a button that, when tapped, shows more info about participating in Bloganuary." - ) - } -} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard+Personalization.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard+Personalization.swift index 9585b544b82f..b9e1e93f9903 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard+Personalization.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard+Personalization.swift @@ -12,8 +12,6 @@ extension DashboardCard: BlogDashboardPersonalizable { return "scheduled-posts-card-enabled-site-settings" case .blaze: return "blaze-card-enabled-site-settings" - case .bloganuaryNudge: - return "bloganuary-nudge-card-enabled-site-settings" case .prompts: // Warning: there is an irregularity with the prompts key that doesn't // have a "-card" component in the key name. Keeping it like this to @@ -29,7 +27,8 @@ extension DashboardCard: BlogDashboardPersonalizable { return "activity-log-card-enabled-site-settings" case .pages: return "pages-card-enabled-site-settings" - case .dynamic, .jetpackBadge, .jetpackInstall, .jetpackSocial, .failure, .ghost, .personalize, .empty, .extensiveLogging: + case .dynamic, .jetpackBadge, .jetpackInstall, .jetpackSocial, .failure, .ghost, .personalize, .empty, + .extensiveLogging: return nil } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard.swift index f77859b2bc19..7d2c725e4eb3 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Models/DashboardCard.swift @@ -11,7 +11,6 @@ import Support enum DashboardCard: String, CaseIterable, Sendable { case dynamic case jetpackInstall - case bloganuaryNudge = "bloganuary_nudge" case prompts case extensiveLogging case googleDomains @@ -47,8 +46,6 @@ enum DashboardCard: String, CaseIterable, Sendable { return DashboardScheduledPostsCardCell.self case .todaysStats: return DashboardStatsCardCell.self - case .bloganuaryNudge: - return DashboardBloganuaryCardCell.self case .prompts: return DashboardPromptsCardCell.self case .ghost: @@ -110,8 +107,6 @@ enum DashboardCard: String, CaseIterable, Sendable { return shouldShowRemoteCard(apiResponse: apiResponse) case .todaysStats: return DashboardStatsCardCell.shouldShowCard(for: blog) && shouldShowRemoteCard(apiResponse: apiResponse) - case .bloganuaryNudge: - return DashboardBloganuaryCardCell.shouldShowCard(for: blog) case .prompts: return DashboardPromptsCardCell.shouldShowCard(for: blog) case .extensiveLogging: @@ -137,9 +132,11 @@ enum DashboardCard: String, CaseIterable, Sendable { case .personalize: return true case .pages: - return DashboardPagesListCardCell.shouldShowCard(for: blog) && shouldShowRemoteCard(apiResponse: apiResponse) + return DashboardPagesListCardCell.shouldShowCard(for: blog) + && shouldShowRemoteCard(apiResponse: apiResponse) case .activityLog: - return DashboardActivityLogCardCell.shouldShowCard(for: blog) && shouldShowRemoteCard(apiResponse: apiResponse) + return DashboardActivityLogCardCell.shouldShowCard(for: blog) + && shouldShowRemoteCard(apiResponse: apiResponse) case .jetpackSocial: return DashboardJetpackSocialCardCell.shouldShowCard(for: blog) case .googleDomains: @@ -212,31 +209,31 @@ enum DashboardCard: String, CaseIterable, Sendable { private extension BlogDashboardRemoteEntity { var hasDrafts: Bool { - return (self.posts?.value?.draft?.count ?? 0) > 0 + (self.posts?.value?.draft?.count ?? 0) > 0 } var hasScheduled: Bool { - return (self.posts?.value?.scheduled?.count ?? 0) > 0 + (self.posts?.value?.scheduled?.count ?? 0) > 0 } var hasPages: Bool { - return self.pages?.value != nil + self.pages?.value != nil } var hasStats: Bool { - return self.todaysStats?.value != nil + self.todaysStats?.value != nil } var hasActivities: Bool { - return (self.activity?.value?.current?.orderedItems?.count ?? 0) > 0 + (self.activity?.value?.current?.orderedItems?.count ?? 0) > 0 } - } +} // MARK: - BlogDashboardAnalyticPropertiesProviding Protocol Conformance extension DashboardCard: BlogDashboardAnalyticPropertiesProviding { var analyticProperties: [AnyHashable: Any] { - return ["card": rawValue] + ["card": rawValue] } } diff --git a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift index 6ef561297a12..3bb1714b0e49 100644 --- a/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift +++ b/WordPress/Classes/ViewRelated/Blog/BlogPersonalization/BlogDashboardPersonalizationViewModel.swift @@ -71,24 +71,52 @@ private extension DashboardCard { func getLocalizedTitle() -> String { switch self { case .prompts: - return NSLocalizedString("personalizeHome.dashboardCard.prompts", value: "Blogging prompts", comment: "Card title for the pesonalization menu") + 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") + 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") + 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") + 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") + return NSLocalizedString( + "personalizeHome.dashboardCard.scheduledPosts", + value: "Scheduled posts", + comment: "Card title for the pesonalization menu" + ) case .activityLog: - return NSLocalizedString("personalizeHome.dashboardCard.activityLog", value: "Recent activity", comment: "Card title for the pesonalization menu") + return NSLocalizedString( + "personalizeHome.dashboardCard.activityLog", + value: "Recent activity", + comment: "Card title for the pesonalization menu" + ) case .pages: - return NSLocalizedString("personalizeHome.dashboardCard.pages", value: "Pages", comment: "Card title for the pesonalization menu") + return NSLocalizedString( + "personalizeHome.dashboardCard.pages", + value: "Pages", + comment: "Card title for the pesonalization menu" + ) case .dynamic, .ghost, - .failure, .personalize, .jetpackBadge, - .jetpackInstall, .empty, .freeToPaidPlansDashboardCard, - .domainRegistration, .jetpackSocial, .bloganuaryNudge, - .googleDomains, .extensiveLogging: + .failure, .personalize, .jetpackBadge, + .jetpackInstall, .empty, .freeToPaidPlansDashboardCard, + .domainRegistration, .jetpackSocial, + .googleDomains, .extensiveLogging: assertionFailure("\(self) card should not appear in the personalization menus") return "" // These cards don't appear in the personalization menus } diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/Bloganuary/BloganuaryOverlayViewController.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/Bloganuary/BloganuaryOverlayViewController.swift deleted file mode 100644 index 91a548eb3121..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/Bloganuary/BloganuaryOverlayViewController.swift +++ /dev/null @@ -1,327 +0,0 @@ -import SwiftUI -import WordPressShared - -class BloganuaryOverlayViewController: UIViewController { - - private let blogID: Int - - private let promptsEnabled: Bool - - private lazy var viewModel: BloganuaryOverlayViewModel = { - return BloganuaryOverlayViewModel(promptsEnabled: promptsEnabled, orientation: UIDevice.current.orientation) - }() - - // MARK: Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - setupNavigationBar() - - // Make sure we only track this once, regardless of redraws from orientation change, etc. - BloganuaryTracker.trackModalShown(promptsEnabled: promptsEnabled) - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - - // update view model state after the device has finished the orientation change animation. - coordinator.animate(alongsideTransition: nil) { [weak self] _ in - self?.viewModel.orientation = UIDevice.current.orientation - } - } - - init(blogID: Int, promptsEnabled: Bool) { - self.blogID = blogID - self.promptsEnabled = promptsEnabled - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: Private Methods - - private func setupViews() { - view.backgroundColor = .systemBackground - - let overlayView = BloganuaryOverlayView(viewModel: viewModel, onPrimaryButtonTapped: { [weak self] in - guard let self else { - return - } - - BloganuaryTracker.trackModalActionTapped(self.promptsEnabled ? .dismiss : .turnPromptsOn) - - self.dismiss(completion: { - if self.promptsEnabled { - return - } - - Task { @MainActor in - // enable prompts card on the dashboard. - BlogDashboardPersonalizationService(siteID: self.blogID).setEnabled(true, for: .prompts) - } - }) - }) - - let swiftUIView = UIView.embedSwiftUIView(overlayView) - view.addSubview(swiftUIView) - view.pinSubviewToAllEdges(swiftUIView) - } - - private func setupNavigationBar() { - // Set up the close button in the navigation bar. - let dismissAction = UIAction { [weak self] _ in - BloganuaryTracker.trackModalDismissed() - self?.dismiss() - } - navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .close, primaryAction: dismissAction) - } - - private func dismiss(completion: (() -> Void)? = nil) { - navigationController?.dismiss(animated: true, completion: completion) - } -} - -// MARK: - SwiftUI - -class BloganuaryOverlayViewModel: ObservableObject { - let promptsEnabled: Bool - @Published var orientation: UIDeviceOrientation - - init(promptsEnabled: Bool, orientation: UIDeviceOrientation) { - self.promptsEnabled = promptsEnabled - self.orientation = orientation - } -} - -private struct BloganuaryOverlayView: View { - - @ObservedObject var viewModel: BloganuaryOverlayViewModel - - @State var scrollViewHeight: CGFloat = 0.0 - - var onPrimaryButtonTapped: (() -> Void)? - - @ScaledMetric(relativeTo: Constants.descriptionTextStyle) - private var descriptionIconSize = 24.0 - - @ScaledMetric(relativeTo: Constants.descriptionTextStyle) - private var descriptionItemHSpacing = 16.0 - - @ScaledMetric(relativeTo: Constants.descriptionTextStyle) - private var descriptionItemVSpacing = 24.0 - - var body: some View { - VStack(spacing: .zero) { - contentScrollView - footerContainer - } - } - - var contentScrollView: some View { - ScrollView { - VStack { - content - Spacer(minLength: 32.0) - Text(stringForFooter) - .font(.footnote) - .foregroundStyle(.secondary) - .padding(.horizontal, Constants.horizontalPadding) - .multilineTextAlignment(.center) - } - .padding(.vertical, 18.0) - .frame(minHeight: scrollViewHeight, maxHeight: .infinity) - } - .layoutPriority(1) // force the scroll view to fill most of the screen space. - .background { - // try to get the scrollView height and use it as the ideal height for its content view. - GeometryReader { geo in - Color.clear - .onAppear { - scrollViewHeight = geo.size.height - } - .onChange(of: viewModel.orientation) { - // since onAppear is only called once, assign the value again every time the orientation changes. - scrollViewHeight = geo.size.height - } - } - } - } - - var content: some View { - VStack(alignment: .center, spacing: 24.0) { - Image(Constants.bloganuaryImageName, bundle: nil) - .resizable() - .renderingMode(.template) - .foregroundStyle(.primary) - .scaledToFit() - .frame(width: Constants.preferredLogoWidth, height: Constants.preferredLogoHeight) - descriptionContainer - } - .padding(.horizontal, Constants.horizontalPadding) - .frame(maxWidth: .infinity, alignment: .topLeading) - } - - var descriptionContainer: some View { - VStack(alignment: .leading, spacing: 32.0) { - Text(Strings.headline) - .font(.largeTitle) - .fontWeight(.bold) - .multilineTextAlignment(.center) - .lineLimit(nil) - .fixedSize(horizontal: false, vertical: true) - .frame(maxWidth: .infinity, alignment: .center) - descriptionList - } - } - - var descriptionList: some View { - HStack(spacing: .zero) { - Spacer(minLength: .zero) - VStack(alignment: .leading, spacing: descriptionItemVSpacing) { - descriptionEntry(iconName: Constants.firstDescriptionIconName, text: Strings.firstDescriptionLine) - descriptionEntry(iconName: Constants.secondDescriptionIconName, text: Strings.secondDescriptionLine) - descriptionEntry(iconName: Constants.thirdDescriptionIconName, text: Strings.thirdDescriptionLine) - } - .padding(.horizontal, 16.0) - .frame(maxWidth: 420.0) - .layoutPriority(1) - Spacer(minLength: .zero) - } - } - - func descriptionEntry(iconName: String, text: String) -> some View { - HStack(alignment: .center, spacing: descriptionItemHSpacing) { - descriptionIconView(for: iconName) - Text(text) - .font(.headline) - .fontWeight(.semibold) - .foregroundStyle(Constants.descriptionItemTextColor) - .lineLimit(nil) - .fixedSize(horizontal: false, vertical: true) - } - } - - var footerContainer: some View { - VStack(spacing: .zero) { - Divider() - .frame(maxWidth: .infinity) - Group { - ctaButton - } - .padding(WPDeviceIdentification.isiPad() ? .vertical : .top, 24.0) - .padding(.horizontal, Constants.horizontalPadding) - } - } - - var ctaButton: some View { - Button { - onPrimaryButtonTapped?() - } label: { - Text(viewModel.promptsEnabled ? Strings.buttonTitleForEnabledPrompts : Strings.buttonTitleForDisabledPrompts) - .multilineTextAlignment(.center) - .lineLimit(nil) - .frame(maxWidth: .infinity) - .fixedSize(horizontal: false, vertical: true) - } - .padding(.vertical, 14.0) - .padding(.horizontal, 20.0) - .frame(maxWidth: .infinity, alignment: .center) - .foregroundStyle(Color(.systemBackground)) - .background(Color(.label)) - .clipShape(RoundedRectangle(cornerRadius: 12.0)) - } - - var stringForFooter: String { - guard viewModel.promptsEnabled else { - return "\(Strings.footer) \(Strings.footerAddition)" - } - return Strings.footer - } - - func descriptionIconView(for iconName: String) -> some View { - Image(iconName, bundle: nil) - .resizable() - .renderingMode(.template) - .foregroundStyle(Constants.descriptionIconColor) - .flipsForRightToLeftLayoutDirection(true) - .frame(width: descriptionIconSize, height: descriptionIconSize) - .padding(12.0) - .background { - Circle().fill(Constants.descriptionIconBackgroundColor) - } - } - - // MARK: Constants - - struct Constants { - static let horizontalPadding: CGFloat = 32.0 - static let preferredLogoWidth: CGFloat = 180.0 - static let preferredLogoHeight: CGFloat = 42.0 - static let bloganuaryImageName = "logo-bloganuary-large" - static let descriptionTextStyle: Font.TextStyle = .footnote - - static let firstDescriptionIconName = "bloganuary-icon-page" - static let secondDescriptionIconName = "bloganuary-icon-verse" - static let thirdDescriptionIconName = "bloganuary-icon-people" - - static let descriptionItemTextColor = Color(.init(light: .label, dark: .secondaryLabel)) - static let descriptionIconColor = Color(.init(light: .systemBackground, dark: .label)) - static let descriptionIconBackgroundColor = Color(.init(light: .label, dark: .tertiarySystemBackground)) - } - - struct Strings { - static let headline = NSLocalizedString( - "bloganuary.learnMore.modal.headline", - value: "Join our month-long writing challenge", - comment: "The headline text of the Bloganuary modal sheet." - ) - - static let firstDescriptionLine = NSLocalizedString( - "bloganuary.learnMore.modal.descriptions.first", - value: "Receive a new prompt to inspire you each day.", - comment: "The first line of the description shown in the Bloganuary modal sheet." - ) - - static let secondDescriptionLine = NSLocalizedString( - "bloganuary.learnMore.modal.description.second", - value: "Publish your response.", - comment: "The second line of the description shown in the Bloganuary modal sheet." - ) - - static let thirdDescriptionLine = NSLocalizedString( - "bloganuary.learnMore.modal.description.third", - value: "Read other bloggers’ responses to get inspiration and make new connections.", - comment: "The third line of the description shown in the Bloganuary modal sheet." - ) - - static let footer = NSLocalizedString( - "bloganuary.learnMore.modal.footer.text", - value: "Bloganuary will use Daily Blogging Prompts to send you topics for the month of January.", - comment: "An informative excerpt shown in a subtler tone." - ) - - static let footerAddition = NSLocalizedString( - "bloganuary.learnMore.modal.footer.addition", - value: "To join Bloganuary you need to enable Blogging Prompts.", - comment: "An additional piece of information shown in case the user has the Blogging Prompts feature disabled." - ) - - static let buttonTitleForDisabledPrompts = NSLocalizedString( - "bloganuary.learnMore.modal.button.promptsDisabled", - value: "Turn on blogging prompts", - comment: "Title of a button that calls the user to enable the Blogging Prompts feature." - ) - - static let buttonTitleForEnabledPrompts = NSLocalizedString( - "bloganuary.learnMore.modal.button.promptsEnabled", - value: "Let’s go!", - comment: """ - Title of a button that will dismiss the Bloganuary modal when tapped. - Note that the word 'go' here should have a closer meaning to 'start' rather than 'move forward'. - """ - ) - } -} diff --git a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/Bloganuary/BloganuaryTracker.swift b/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/Bloganuary/BloganuaryTracker.swift deleted file mode 100644 index 05ca83b953be..000000000000 --- a/WordPress/Classes/ViewRelated/Blog/Blogging Prompts/Bloganuary/BloganuaryTracker.swift +++ /dev/null @@ -1,25 +0,0 @@ -import WordPressShared - -struct BloganuaryTracker { - - enum ModalAction: String { - case turnPromptsOn = "turn_prompts_on" - case dismiss - } - - static func trackCardLearnMoreTapped(promptsEnabled: Bool) { - WPAnalytics.track(.bloganuaryNudgeCardLearnMoreTapped, properties: ["prompts_enabled": promptsEnabled]) - } - - static func trackModalShown(promptsEnabled: Bool) { - WPAnalytics.track(.bloganuaryNudgeModalShown, properties: ["prompts_enabled": promptsEnabled]) - } - - static func trackModalDismissed() { - WPAnalytics.track(.bloganuaryNudgeModalDismissed) - } - - static func trackModalActionTapped(_ action: ModalAction) { - WPAnalytics.track(.bloganuaryNudgeModalActionTapped, properties: ["action": action.rawValue]) - } -} diff --git a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-page.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/bloganuary-icon-page.imageset/Contents.json deleted file mode 100644 index b948e1f83a2e..000000000000 --- a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-page.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "bloganuary-icon-page.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-page.imageset/bloganuary-icon-page.pdf b/WordPress/Resources/AppImages.xcassets/bloganuary-icon-page.imageset/bloganuary-icon-page.pdf deleted file mode 100644 index de83d3086a4e..000000000000 Binary files a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-page.imageset/bloganuary-icon-page.pdf and /dev/null differ diff --git a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-people.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/bloganuary-icon-people.imageset/Contents.json deleted file mode 100644 index 34fb916a0055..000000000000 --- a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-people.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "blognuary-icon-people.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-people.imageset/blognuary-icon-people.pdf b/WordPress/Resources/AppImages.xcassets/bloganuary-icon-people.imageset/blognuary-icon-people.pdf deleted file mode 100644 index bd3dbfb06a95..000000000000 Binary files a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-people.imageset/blognuary-icon-people.pdf and /dev/null differ diff --git a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-verse.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/bloganuary-icon-verse.imageset/Contents.json deleted file mode 100644 index 849eed1d57f2..000000000000 --- a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-verse.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "bloganuary-icon-verse.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-verse.imageset/bloganuary-icon-verse.pdf b/WordPress/Resources/AppImages.xcassets/bloganuary-icon-verse.imageset/bloganuary-icon-verse.pdf deleted file mode 100644 index 28cfb92eb067..000000000000 Binary files a/WordPress/Resources/AppImages.xcassets/bloganuary-icon-verse.imageset/bloganuary-icon-verse.pdf and /dev/null differ diff --git a/WordPress/Resources/AppImages.xcassets/logo-bloganuary-large.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/logo-bloganuary-large.imageset/Contents.json deleted file mode 100644 index 30a38a27b61a..000000000000 --- a/WordPress/Resources/AppImages.xcassets/logo-bloganuary-large.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "logo-bloganuary-large.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/WordPress/Resources/AppImages.xcassets/logo-bloganuary-large.imageset/logo-bloganuary-large.pdf b/WordPress/Resources/AppImages.xcassets/logo-bloganuary-large.imageset/logo-bloganuary-large.pdf deleted file mode 100644 index d083793dc79b..000000000000 Binary files a/WordPress/Resources/AppImages.xcassets/logo-bloganuary-large.imageset/logo-bloganuary-large.pdf and /dev/null differ diff --git a/WordPress/Resources/AppImages.xcassets/logo-bloganuary.imageset/Contents.json b/WordPress/Resources/AppImages.xcassets/logo-bloganuary.imageset/Contents.json deleted file mode 100644 index 4d3ca8dcd5a0..000000000000 --- a/WordPress/Resources/AppImages.xcassets/logo-bloganuary.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "logo-bloganuary.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/WordPress/Resources/AppImages.xcassets/logo-bloganuary.imageset/logo-bloganuary.svg b/WordPress/Resources/AppImages.xcassets/logo-bloganuary.imageset/logo-bloganuary.svg deleted file mode 100644 index 87debb9a3124..000000000000 --- a/WordPress/Resources/AppImages.xcassets/logo-bloganuary.imageset/logo-bloganuary.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -