diff --git a/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift b/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift index 899be011057c..a14862d0d751 100644 --- a/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift +++ b/WordPress/Classes/Models/BloggingPromptSettings+CoreDataClass.swift @@ -27,17 +27,6 @@ public class BloggingPromptSettings: NSManagedObject { return try context.fetch(fetchRequest).first } - func configure(with remoteSettings: RemoteBloggingPromptsSettings, siteID: Int32, context: NSManagedObjectContext) { - self.siteID = siteID - self.promptCardEnabled = remoteSettings.promptCardEnabled - self.reminderTime = remoteSettings.reminderTime - self.promptRemindersEnabled = remoteSettings.promptRemindersEnabled - self.isPotentialBloggingSite = remoteSettings.isPotentialBloggingSite - updatePromptSettingsIfNecessary(siteID: Int(siteID), enabled: isPotentialBloggingSite) - self.reminderDays = reminderDays ?? BloggingPromptSettingsReminderDays(context: context) - reminderDays?.configure(with: remoteSettings.reminderDays) - } - func reminderTimeDate() -> Date? { guard let reminderTime else { return nil @@ -46,15 +35,6 @@ public class BloggingPromptSettings: NSManagedObject { dateFormatter.dateFormat = "HH.mm" return dateFormatter.date(from: reminderTime) } - - private func updatePromptSettingsIfNecessary(siteID: Int, enabled: Bool) { - DispatchQueue.main.async { - let service = BlogDashboardPersonalizationService(siteID: siteID) - if !service.hasPreference(for: .prompts) { - service.setEnabled(enabled, for: .prompts) - } - } - } } extension RemoteBloggingPromptsSettings { diff --git a/WordPress/Classes/Models/BloggingPromptSettingsReminderDays+CoreDataClass.swift b/WordPress/Classes/Models/BloggingPromptSettingsReminderDays+CoreDataClass.swift index bb883f1798f0..65470be74594 100644 --- a/WordPress/Classes/Models/BloggingPromptSettingsReminderDays+CoreDataClass.swift +++ b/WordPress/Classes/Models/BloggingPromptSettingsReminderDays+CoreDataClass.swift @@ -13,22 +13,4 @@ public class BloggingPromptSettingsReminderDays: NSManagedObject { self.saturday = remoteReminderDays.saturday self.sunday = remoteReminderDays.sunday } - - func getActiveWeekdays() -> [BloggingRemindersScheduler.Weekday] { - return [ - sunday, - monday, - tuesday, - wednesday, - thursday, - friday, - saturday - ].enumerated().compactMap { (index: Int, isReminderActive: Bool) in - guard isReminderActive else { - return nil - } - return BloggingRemindersScheduler.Weekday(rawValue: index) - } - } - } diff --git a/WordPress/Classes/Models/PostVisibility.swift b/WordPress/Classes/Models/PostVisibility.swift new file mode 100644 index 000000000000..0637529443d1 --- /dev/null +++ b/WordPress/Classes/Models/PostVisibility.swift @@ -0,0 +1,37 @@ +public enum PostVisibility: Identifiable, CaseIterable { + case `public` + case `private` + case protected + + public init(post: AbstractPost) { + self.init(status: post.status ?? .draft, password: post.password) + } + + init(status: AbstractPost.Status, password: String?) { + if let password, !password.isEmpty { + self = .protected + } else if status == .publishPrivate { + self = .private + } else { + self = .public + } + } + + public var id: PostVisibility { self } + + public var localizedTitle: String { + switch self { + case .public: NSLocalizedString("postVisibility.public.title", value: "Public", comment: "Title for a 'Public' (default) privacy setting") + case .protected: NSLocalizedString("postVisibility.protected.title", value: "Password protected", comment: "Title for a 'Password Protected' privacy setting") + case .private: NSLocalizedString("postVisibility.private.title", value: "Private", comment: "Title for a 'Private' privacy setting") + } + } + + public var localizedDetails: String { + switch self { + case .public: NSLocalizedString("postVisibility.public.details", value: "Visible to everyone", comment: "Details for a 'Public' (default) privacy setting") + case .protected: NSLocalizedString("postVisibility.protected.details", value: "Visibile to everyone but requires a password", comment: "Details for a 'Password Protected' privacy setting") + case .private: NSLocalizedString("postVisibility.private.details", value: "Only visible to site admins and editors", comment: "Details for a 'Private' privacy setting") + } + } +} diff --git a/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift b/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift index 66b244b15159..a3f711672adb 100644 --- a/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift +++ b/WordPress/Classes/Utility/Blogging Reminders/BloggingRemindersScheduler.swift @@ -434,3 +434,23 @@ class BloggingRemindersScheduler { static let notificationBody = NSLocalizedString("This is your reminder to blog today ✍️", comment: "The body of a notification displayed to the user prompting them to create a new blog post. The emoji should ideally remain, as part of the text.") } } + +extension BloggingPromptSettingsReminderDays { + + func getActiveWeekdays() -> [BloggingRemindersScheduler.Weekday] { + return [ + sunday, + monday, + tuesday, + wednesday, + thursday, + friday, + saturday + ].enumerated().compactMap { (index: Int, isReminderActive: Bool) in + guard isReminderActive else { + return nil + } + return BloggingRemindersScheduler.Weekday(rawValue: index) + } + } +} 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 new file mode 100644 index 000000000000..a994fd30dc35 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution+Strings.swift @@ -0,0 +1,83 @@ +import UIKit +import WordPressUI +import Gridicons + +extension BloggingPromptsAttribution { + + var attributedText: NSAttributedString { + let baseText = String(format: Strings.fromTextFormat, source) + let attributedText = NSMutableAttributedString(string: baseText, attributes: Constants.baseAttributes) + guard let range = baseText.range(of: source) else { + return attributedText + } + + let nsRange = NSRange(range, in: baseText) + attributedText.addAttributes(Constants.sourceAttributes, range: nsRange) + + return attributedText + } + + 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 + } + } + + var trailingImage: UIImage? { + guard let _ = externalURL else { + return nil + } + + return Constants.linkIcon + } + + private struct Strings { + 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, + ] + static let sourceAttributes: [NSAttributedString.Key: Any] = [ + .font: WPStyleGuide.fontForTextStyle(.caption1, fontWeight: .medium), + .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) + } +} diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift index ca73bf0ea434..2f580a809ed5 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Prompts/BloggingPromptsAttribution.swift @@ -1,85 +1,4 @@ -import UIKit -import WordPressUI -import Gridicons - -enum BloggingPromptsAttribution: String { +public enum BloggingPromptsAttribution: String { case dayone case bloganuary - - var attributedText: NSAttributedString { - let baseText = String(format: Strings.fromTextFormat, source) - let attributedText = NSMutableAttributedString(string: baseText, attributes: Constants.baseAttributes) - guard let range = baseText.range(of: source) else { - return attributedText - } - - let nsRange = NSRange(range, in: baseText) - attributedText.addAttributes(Constants.sourceAttributes, range: nsRange) - - return attributedText - } - - 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 - } - } - - var trailingImage: UIImage? { - guard let _ = externalURL else { - return nil - } - - return Constants.linkIcon - } - - private struct Strings { - 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, - ] - static let sourceAttributes: [NSAttributedString.Key: Any] = [ - .font: WPStyleGuide.fontForTextStyle(.caption1, fontWeight: .medium), - .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) - } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift index d955c5effb87..704ac97d8832 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Service/BlogDashboardPersonalizationService.swift @@ -1,4 +1,5 @@ import Foundation +import WordPressKit import WordPressShared /// `BlogDashboardPersonalizable` is a protocol that defines the requirements for personalizing blog dashboard items. @@ -142,3 +143,26 @@ extension NSNotification.Name { /// are changed. static let blogDashboardPersonalizationSettingsChanged = NSNotification.Name("BlogDashboardPersonalizationSettingsChanged") } + +extension BloggingPromptSettings { + + func configure(with remoteSettings: RemoteBloggingPromptsSettings, siteID: Int32, context: NSManagedObjectContext) { + self.siteID = siteID + self.promptCardEnabled = remoteSettings.promptCardEnabled + self.reminderTime = remoteSettings.reminderTime + self.promptRemindersEnabled = remoteSettings.promptRemindersEnabled + self.isPotentialBloggingSite = remoteSettings.isPotentialBloggingSite + updatePromptSettingsIfNecessary(siteID: Int(siteID), enabled: isPotentialBloggingSite) + self.reminderDays = reminderDays ?? BloggingPromptSettingsReminderDays(context: context) + reminderDays?.configure(with: remoteSettings.reminderDays) + } + + private func updatePromptSettingsIfNecessary(siteID: Int, enabled: Bool) { + DispatchQueue.main.async { + let service = BlogDashboardPersonalizationService(siteID: siteID) + if !service.hasPreference(for: .prompts) { + service.setEnabled(enabled, for: .prompts) + } + } + } +} diff --git a/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift b/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift index 72abaefe8650..ed3a8adb70a8 100644 --- a/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift +++ b/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift @@ -149,44 +149,6 @@ private struct PasswordField: View { } } -enum PostVisibility: Identifiable, CaseIterable { - case `public` - case `private` - case protected - - init(post: AbstractPost) { - self.init(status: post.status ?? .draft, password: post.password) - } - - init(status: AbstractPost.Status, password: String?) { - if let password, !password.isEmpty { - self = .protected - } else if status == .publishPrivate { - self = .private - } else { - self = .public - } - } - - var id: PostVisibility { self } - - var localizedTitle: String { - switch self { - case .public: NSLocalizedString("postVisibility.public.title", value: "Public", comment: "Title for a 'Public' (default) privacy setting") - case .protected: NSLocalizedString("postVisibility.protected.title", value: "Password protected", comment: "Title for a 'Password Protected' privacy setting") - case .private: NSLocalizedString("postVisibility.private.title", value: "Private", comment: "Title for a 'Private' privacy setting") - } - } - - var localizedDetails: String { - switch self { - case .public: NSLocalizedString("postVisibility.public.details", value: "Visible to everyone", comment: "Details for a 'Public' (default) privacy setting") - case .protected: NSLocalizedString("postVisibility.protected.details", value: "Visibile to everyone but requires a password", comment: "Details for a 'Password Protected' privacy setting") - case .private: NSLocalizedString("postVisibility.private.details", value: "Only visible to site admins and editors", comment: "Details for a 'Private' privacy setting") - } - } -} - private enum Strings { static let title = NSLocalizedString("postVisibilityPicker.navigationTitle", value: "Visibility", comment: "Navigation bar title for the Post Visibility picker") static let cancel = NSLocalizedString("postVisibilityPicker.cancel", value: "Cancel", comment: "Button cancel") diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 5094d53b0516..33927830bbd8 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -931,7 +931,6 @@ D821C817210036D9002ED995 /* ActivityContentFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D821C816210036D9002ED995 /* ActivityContentFactoryTests.swift */; }; D821C819210037F8002ED995 /* activity-log-activity-content.json in Resources */ = {isa = PBXBuildFile; fileRef = D821C818210037F8002ED995 /* activity-log-activity-content.json */; }; D821C81B21003AE9002ED995 /* FormattableContentGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D821C81A21003AE9002ED995 /* FormattableContentGroupTests.swift */; }; - D826D67F211D21C700A5D8FE /* NullMockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D826D67E211D21C700A5D8FE /* NullMockUserDefaults.swift */; }; D82E087529EEB0B00098F500 /* DashboardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82E087429EEB0B00098F500 /* DashboardTests.swift */; }; D82E087629EEB0B00098F500 /* DashboardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82E087429EEB0B00098F500 /* DashboardTests.swift */; }; D842EA4021FABB1800210E96 /* SiteSegmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D842EA3F21FABB1700210E96 /* SiteSegmentTests.swift */; }; @@ -2513,7 +2512,6 @@ D821C816210036D9002ED995 /* ActivityContentFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityContentFactoryTests.swift; sourceTree = ""; }; D821C818210037F8002ED995 /* activity-log-activity-content.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "activity-log-activity-content.json"; sourceTree = ""; }; D821C81A21003AE9002ED995 /* FormattableContentGroupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattableContentGroupTests.swift; sourceTree = ""; }; - D826D67E211D21C700A5D8FE /* NullMockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NullMockUserDefaults.swift; sourceTree = ""; }; D82E087429EEB0B00098F500 /* DashboardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardTests.swift; sourceTree = ""; }; D842EA3F21FABB1700210E96 /* SiteSegmentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteSegmentTests.swift; sourceTree = ""; }; D848CBF620FEEE7F00A9038F /* notifications-text-content.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "notifications-text-content.json"; sourceTree = ""; }; @@ -4398,7 +4396,6 @@ 0CF7D6C22ABB753A006D1E89 /* MediaImageServiceTests.swift */, C38C5D8027F61D2C002F517E /* MenuItemTests.swift */, D848CC1420FF33FC00A9038F /* NotificationContentRangeTests.swift */, - D826D67E211D21C700A5D8FE /* NullMockUserDefaults.swift */, 5960967E1CF7959300848496 /* PostTests.swift */, FEFA6AC72A88D5FC004EE5E6 /* Post+JetpackSocialTests.swift */, 8BBBEBB124B8F8C0005E358E /* ReaderCardTests.swift */, @@ -8077,7 +8074,6 @@ D81C2F5E20F88CE5002AE1F1 /* MarkAsSpamActionTests.swift in Sources */, D848CC1520FF33FC00A9038F /* NotificationContentRangeTests.swift in Sources */, 084FC3B729913B1B00A17BCF /* JetpackPluginOverlayViewModelTests.swift in Sources */, - D826D67F211D21C700A5D8FE /* NullMockUserDefaults.swift in Sources */, 7E987F58210811CC00CAFB88 /* NotificationContentRouterTests.swift in Sources */, E1C9AA561C10427100732665 /* MathTest.swift in Sources */, 93A379EC19FFBF7900415023 /* KeychainTest.m in Sources */, diff --git a/WordPress/WordPressTest/NullMockUserDefaults.swift b/WordPress/WordPressTest/NullMockUserDefaults.swift deleted file mode 100644 index 3abe06f5658e..000000000000 --- a/WordPress/WordPressTest/NullMockUserDefaults.swift +++ /dev/null @@ -1,17 +0,0 @@ -@testable import WordPress -import WordPressShared - -/// A null mock implementation of the KeyValueDatabase protocol -class NullMockUserDefaults: KeyValueDatabase { - func object(forKey defaultName: String) -> Any? { - return nil - } - - func set(_ value: Any?, forKey defaultName: String) { - // - } - - func removeObject(forKey defaultName: String) { - // - } -}