Skip to content

Commit

Permalink
Use swift concurrency to improve behavior (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
shp7724 committed Mar 24, 2024
1 parent 844be7b commit 02cd6ad
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 83 deletions.
2 changes: 2 additions & 0 deletions reminders-menubar/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct RemindersMenuBar: App {
}
}

@MainActor
class AppDelegate: NSObject, NSApplicationDelegate {
static private(set) var shared: AppDelegate!

Expand All @@ -27,6 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {

let popover = NSPopover()
lazy var statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

var contentViewController: NSViewController {
let contentView = ContentView()
let remindersData = RemindersData()
Expand Down
131 changes: 73 additions & 58 deletions reminders-menubar/Models/RemindersData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,121 +2,136 @@ import SwiftUI
import Combine
import EventKit

@MainActor
class RemindersData: ObservableObject {
private let userPreferences = UserPreferences.shared

private var cancellationTokens: [AnyCancellable] = []

init() {
addObservers()
update()
Task {
await update()
}
}

private func addObservers() {
NotificationCenter.default.addObserver(self,
selector: #selector(update),
name: .EKEventStoreChanged,
object: nil)

NotificationCenter.default.addObserver(self,
selector: #selector(update),
name: .NSCalendarDayChanged,
object: nil)

cancellationTokens.append(
userPreferences.$menuBarCounterType.dropFirst().sink { [weak self] menuBarCounterType in
let count = self?.getMenuBarCount(menuBarCounterType) ?? -1
self?.updateMenuBarCount(with: count)
NotificationCenter.default.publisher(for: .EKEventStoreChanged)
.sink { [weak self] _ in
Task {
await self?.update()
}
}
)

cancellationTokens.append(
userPreferences.$upcomingRemindersInterval.dropFirst().sink { [weak self] upcomingRemindersInterval in
self?.upcomingReminders = RemindersService.shared.getUpcomingReminders(upcomingRemindersInterval)
.store(in: &cancellationTokens)

NotificationCenter.default.publisher(for: .NSCalendarDayChanged)
.sink { [weak self] _ in
Task {
await self?.update()
}
}
)

cancellationTokens.append(
$calendarIdentifiersFilter.dropFirst().sink { [weak self] calendarIdentifiersFilter in
self?.filteredReminderLists = RemindersService.shared.getReminders(of: calendarIdentifiersFilter)
.store(in: &cancellationTokens)

userPreferences.$menuBarCounterType
.dropFirst()
.sink { [weak self] menuBarCounterType in
Task {
guard let self else { return }
let count = await self.getMenuBarCount(menuBarCounterType)
self.updateMenuBarCount(with: count)
}
}
)
.store(in: &cancellationTokens)

userPreferences.$upcomingRemindersInterval
.dropFirst()
.sink { [weak self] upcomingRemindersInterval in
Task {
self?.upcomingReminders = await RemindersService.shared.getUpcomingReminders(upcomingRemindersInterval)
}
}.store(in: &cancellationTokens)

$calendarIdentifiersFilter
.dropFirst()
.sink { [weak self] calendarIdentifiersFilter in
Task {
self?.filteredReminderLists = await RemindersService.shared.getReminders(of: calendarIdentifiersFilter)
}

}
.store(in: &cancellationTokens)
}

@Published var calendars: [EKCalendar] = []

@Published var upcomingReminders: [ReminderItem] = []

@Published var filteredReminderLists: [ReminderList] = []

@Published var calendarIdentifiersFilter: [String] = {
guard let identifiers = UserPreferences.shared.preferredCalendarIdentifiersFilter else {
// NOTE: On first use it will load all reminder lists.
let calendars = RemindersService.shared.getCalendars()
let allIdentifiers = calendars.map({ $0.calendarIdentifier })
return allIdentifiers
}

return identifiers
}() {
didSet {
UserPreferences.shared.preferredCalendarIdentifiersFilter = calendarIdentifiersFilter
}
}

@Published var calendarForSaving: EKCalendar? = {
guard RemindersService.shared.authorizationStatus() == .authorized else {
return nil
}

guard let identifier = UserPreferences.shared.preferredCalendarIdentifierForSaving,
let calendar = RemindersService.shared.getCalendar(withIdentifier: identifier) else {
return RemindersService.shared.getDefaultCalendar()
}

return calendar
}() {
didSet {
let identifier = calendarForSaving?.calendarIdentifier
UserPreferences.shared.preferredCalendarIdentifierForSaving = identifier
}
}
@objc func update() {

func update() async {
let calendars = RemindersService.shared.getCalendars()

let calendarsSet = Set(calendars.map({ $0.calendarIdentifier }))
let calendarIdentifiersFilter = self.calendarIdentifiersFilter.filter({
// NOTE: Checking if calendar in filter still exist
calendarsSet.contains($0)
})

let upcomingRemindersInterval = self.userPreferences.upcomingRemindersInterval
let upcomingReminders = RemindersService.shared.getUpcomingReminders(upcomingRemindersInterval)

let menuBarCount = getMenuBarCount(self.userPreferences.menuBarCounterType)

// TODO: Prefer receive(on:options:) over explicit use of dispatch queues when performing work in subscribers.
// https://developer.apple.com/documentation/combine/fail/receive(on:options:)
DispatchQueue.main.async {
self.calendars = calendars
self.calendarIdentifiersFilter = calendarIdentifiersFilter
self.upcomingReminders = upcomingReminders
self.updateMenuBarCount(with: menuBarCount)
}
let upcomingReminders = await RemindersService.shared.getUpcomingReminders(upcomingRemindersInterval)

let menuBarCount = await getMenuBarCount(self.userPreferences.menuBarCounterType)

self.calendars = calendars
self.calendarIdentifiersFilter = calendarIdentifiersFilter
self.upcomingReminders = upcomingReminders
self.updateMenuBarCount(with: menuBarCount)
}
private func getMenuBarCount(_ menuBarCounterType: RmbMenuBarCounterType) -> Int {

private func getMenuBarCount(_ menuBarCounterType: RmbMenuBarCounterType) async -> Int {
switch menuBarCounterType {
case .today:
return RemindersService.shared.getUpcomingReminders(.today).count
return await RemindersService.shared.getUpcomingReminders(.today).count
case .allReminders:
return RemindersService.shared.getAllRemindersCount()
return await RemindersService.shared.getAllRemindersCount()
case .disabled:
return -1
}
}

private func updateMenuBarCount(with count: Int) {
AppDelegate.shared.updateMenuBarTodayCount(to: count)
}
Expand Down
40 changes: 16 additions & 24 deletions reminders-menubar/Services/RemindersService.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import EventKit

@MainActor
class RemindersService {
static let shared = RemindersService()

Expand Down Expand Up @@ -37,27 +38,18 @@ class RemindersService {
return eventStore.defaultCalendarForNewReminders() ?? eventStore.calendars(for: .reminder).first!
}

private func fetchRemindersSynchronously(matching predicate: NSPredicate) -> [EKReminder] {
var reminders: [EKReminder] = []
// TODO: Remove use of DispatchGroup
let group = DispatchGroup()
group.enter()
eventStore.fetchReminders(matching: predicate) { allReminders in
guard let allReminders else {
print("Reminders was nil during 'fetchReminders'")
group.leave()
return
private func fetchReminders(matching predicate: NSPredicate) async -> [EKReminder] {
await withCheckedContinuation { continuation in
eventStore.fetchReminders(matching: predicate) { allReminders in
guard let allReminders else {
continuation.resume(returning: [])
return
}
continuation.resume(returning: allReminders)
}

reminders = allReminders
group.leave()
}

_ = group.wait(timeout: .distantFuture)

return reminders
}

private func createReminderItems(for calendarReminders: [EKReminder]) -> [ReminderItem] {
var reminderListItems: [ReminderItem] = []

Expand All @@ -73,10 +65,10 @@ class RemindersService {
return reminderListItems
}

func getReminders(of calendarIdentifiers: [String]) -> [ReminderList] {
func getReminders(of calendarIdentifiers: [String]) async -> [ReminderList] {
let calendars = getCalendars().filter({ calendarIdentifiers.contains($0.calendarIdentifier) })
let predicate = eventStore.predicateForReminders(in: calendars)
let remindersByCalendar = Dictionary(grouping: fetchRemindersSynchronously(matching: predicate),
let remindersByCalendar = Dictionary(grouping: await fetchReminders(matching: predicate),
by: { $0.calendar.calendarIdentifier })

var reminderLists: [ReminderList] = []
Expand All @@ -89,20 +81,20 @@ class RemindersService {
return reminderLists
}

func getUpcomingReminders(_ interval: ReminderInterval) -> [ReminderItem] {
func getUpcomingReminders(_ interval: ReminderInterval) async -> [ReminderItem] {
let calendars = getCalendars()
let predicate = eventStore.predicateForIncompleteReminders(withDueDateStarting: nil,
ending: interval.endingDate,
calendars: calendars)
let reminders = fetchRemindersSynchronously(matching: predicate).map({ ReminderItem(for: $0) })
let reminders = await fetchReminders(matching: predicate).map({ ReminderItem(for: $0) })
return reminders.sortedReminders
}

func getAllRemindersCount() -> Int {
func getAllRemindersCount() async -> Int {
let predicate = eventStore.predicateForIncompleteReminders(withDueDateStarting: nil,
ending: nil,
calendars: nil)
let reminders = fetchRemindersSynchronously(matching: predicate)
let reminders = await fetchReminders(matching: predicate)
return reminders.count
}

Expand Down
2 changes: 2 additions & 0 deletions reminders-menubar/Views/ReminderItemView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import SwiftUI
import EventKit

@MainActor
struct ReminderItemView: View {
@EnvironmentObject var remindersData: RemindersData

Expand Down Expand Up @@ -158,6 +159,7 @@ struct ReminderItemView: View {
}
}

@MainActor
struct ChangePriorityOptionMenu: View {
var reminder: EKReminder

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ struct SettingsBarGearMenu: View {
Divider()

Button(action: {
remindersData.update()
Task {
await remindersData.update()
}
}) {
Text(rmbLocalized(.reloadRemindersDataButton))
}
Expand Down

0 comments on commit 02cd6ad

Please sign in to comment.