Skip to content
33 changes: 30 additions & 3 deletions WordPress/Classes/Services/BloggingPromptsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,44 @@ class BloggingPromptsService {

// MARK: - Init

/// Initializes a service for blogging prompts.
///
/// - Parameters:
/// - contextManager: The CoreDataStack instance.
/// - remote: When supplied, the service will use the specified remote service.
/// Otherwise, a remote service with the default account's credentials will be used.
/// - blog: When supplied, the service will perform blogging prompts requests for this specified blog.
/// Otherwise, this falls back to the default account's primary blog.
required init?(contextManager: CoreDataStackSwift = ContextManager.shared,
remote: BloggingPromptsServiceRemote? = nil,
blog: Blog? = nil) {
guard let account = try? WPAccount.lookupDefaultWordPressComAccount(in: contextManager.mainContext),
let siteID = blog?.dotComID ?? account.primaryBlogID else {
let blogObjectID = blog?.objectID
let (siteID, remoteInstance) = contextManager.performQuery { mainContext in
// if a blog exists, then try to use the blog's ID.
var blogInContext: Blog? = nil
if let blogObjectID {
blogInContext = (try? mainContext.existingObject(with: blogObjectID)) as? Blog
}

// fetch the default account and fall back to default values as needed.
guard let account = try? WPAccount.lookupDefaultWordPressComAccount(in: mainContext) else {
return (blogInContext?.dotComID, remote)
}

return (
blogInContext?.dotComID ?? account.primaryBlogID,
remote ?? .init(wordPressComRestApi: account.wordPressComRestV2Api)
)
}

guard let siteID,
let remoteInstance else {
return nil
}

self.contextManager = contextManager
self.siteID = siteID
self.remote = remote ?? .init(wordPressComRestApi: account.wordPressComRestV2Api)
self.remote = remoteInstance
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,16 @@ class PromptRemindersScheduler {
/// - time: The user's preferred time to be notified.
/// - completion: Closure called after the process completes.
func schedule(_ schedule: BloggingRemindersScheduler.Schedule, for blog: Blog, time: Date? = nil, completion: @escaping (Result<Void, Error>) -> Void) {
guard let context = blog.managedObjectContext else {
completion(.failure(Errors.unknown))
return
}

guard schedule != .none else {
// If there's no schedule, then we don't need to request authorization
processSchedule(schedule, blog: blog, time: time, completion: completion)
context.performAndWait {
// If there's no schedule, then we don't need to request authorization
processSchedule(schedule, blog: blog, time: time, completion: completion)
}
return
}

Expand All @@ -59,7 +66,9 @@ class PromptRemindersScheduler {
return
}

self.processSchedule(schedule, blog: blog, time: time, completion: completion)
context.performAndWait {
self.processSchedule(schedule, blog: blog, time: time, completion: completion)
}
}
}

Expand Down Expand Up @@ -149,6 +158,7 @@ private extension PromptRemindersScheduler {
return
}

let title = blog.title
let reminderTime = Time(from: time) ?? Constants.defaultTime
let currentDate = currentDateProvider.date()
promptsService.fetchPrompts(from: currentDate, number: Constants.promptsToFetch) { [weak self] prompts in
Expand Down Expand Up @@ -177,7 +187,7 @@ private extension PromptRemindersScheduler {
var lastScheduledPrompt: BloggingPrompt? = nil
var notificationIds = [String]()
promptsToSchedule.forEach { prompt in
guard let identifier = self.addLocalNotification(for: prompt, blog: blog, at: reminderTime) else {
guard let identifier = self.addLocalNotification(for: prompt, siteID: siteID, siteTitle: title, at: reminderTime) else {
return
}
notificationIds.append(identifier)
Expand All @@ -197,7 +207,10 @@ private extension PromptRemindersScheduler {
return lastReminderDate
}()

if let staticNotificationIds = self.addStaticNotifications(after: lastReminderDate, with: schedule, time: reminderTime, blog: blog) {
if let staticNotificationIds = self.addStaticNotifications(after: lastReminderDate,
with: schedule,
time: reminderTime,
siteID: siteID) {
notificationIds.append(contentsOf: staticNotificationIds)
}

Expand All @@ -224,17 +237,13 @@ private extension PromptRemindersScheduler {
/// - blog: The user's blog.
/// - time: The preferred reminder time for the notification.
/// - Returns: String representing the notification identifier.
func addLocalNotification(for prompt: BloggingPrompt, blog: Blog, at time: Time) -> String? {
guard blog.dotComID != nil else {
return nil
}

func addLocalNotification(for prompt: BloggingPrompt, siteID: Int, siteTitle: String? = nil, at time: Time) -> String? {
let content = UNMutableNotificationContent()
content.title = Constants.notificationTitle
content.subtitle = blog.title ?? String()
content.subtitle = siteTitle ?? String()
content.body = prompt.text
content.categoryIdentifier = InteractiveNotificationsManager.NoteCategoryDefinition.bloggingPrompt.rawValue
content.userInfo = notificationPayload(for: blog, prompt: prompt)
content.userInfo = notificationPayload(for: siteID, prompt: prompt)

guard let reminderDateComponents = reminderDateComponents(for: prompt, at: time) else {
return nil
Expand Down Expand Up @@ -282,12 +291,11 @@ private extension PromptRemindersScheduler {
func addStaticNotifications(after afterDate: Date,
with schedule: Schedule,
time: Time,
blog: Blog,
siteID: Int,
maxDays: Int = Constants.staticNotificationMaxDays) -> [String]? {
guard case .weekdays(let weekdays) = schedule,
maxDays > 0,
let maxDate = Calendar.current.date(byAdding: .day, value: maxDays, to: afterDate),
blog.dotComID != nil else {
let maxDate = Calendar.current.date(byAdding: .day, value: maxDays, to: afterDate) else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR. But I didn't realise that Calendar.current—the calendar that user picked in the device's System Settings—is used a lot in the app. In most cases, we want the Gregorian calendar, not the calendar the user likes their phone to display.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah, I think I didn't really think about it and just followed how it's done across the codebase. I'll consider fixing this during the refactor later. Thanks!

return nil
}

Expand All @@ -297,7 +305,7 @@ private extension PromptRemindersScheduler {
content.title = Constants.notificationTitle
content.body = Constants.staticNotificationContent
content.categoryIdentifier = InteractiveNotificationsManager.NoteCategoryDefinition.bloggingPrompt.rawValue
content.userInfo = notificationPayload(for: blog)
content.userInfo = notificationPayload(for: siteID)

var date = afterDate
var identifiers = [String]()
Expand Down Expand Up @@ -358,11 +366,7 @@ private extension PromptRemindersScheduler {
return identifier
}

func notificationPayload(for blog: Blog, prompt: BloggingPrompt? = nil) -> [AnyHashable: Any] {
guard let siteID = blog.dotComID?.intValue else {
return [:]
}

func notificationPayload(for siteID: Int, prompt: BloggingPrompt? = nil) -> [AnyHashable: Any] {
var userInfo: [AnyHashable: Any] = [BloggingPrompt.NotificationKeys.siteID: siteID]

if let prompt = prompt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ private extension BloggingPromptCoordinator {
guard let service = promptsServiceFactory.makeService(for: blog),
let settings = service.localSettings,
let reminderDays = settings.reminderDays,
let context = settings.managedObjectContext,
settings.promptRemindersEnabled else {
completion()
return
Expand All @@ -125,10 +126,12 @@ private extension BloggingPromptCoordinator {
return
}

// Reschedule the prompt reminders.
let schedule = BloggingRemindersScheduler.Schedule.weekdays(reminderDays.getActiveWeekdays())
self.scheduler.schedule(schedule, for: blog, time: settings.reminderTimeDate()) { result in
completion()
context.performAndWait {
// Reschedule the prompt reminders.
let schedule = BloggingRemindersScheduler.Schedule.weekdays(reminderDays.getActiveWeekdays())
self.scheduler.schedule(schedule, for: blog, time: settings.reminderTimeDate()) { result in
completion()
}
}
}
}
Expand Down