Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ format:

lint:
swift format lint --recursive Sources Tests
swiftlint
swiftlint lint --no-cache

test:
scripts/generate-version.sh
Expand Down
19 changes: 19 additions & 0 deletions Sources/RemindCore/EventKitStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ public actor RemindersStore {
if let dueDate = draft.dueDate {
reminder.dueDateComponents = calendarComponents(from: dueDate)
}
if let recurrence = draft.recurrence {
reminder.recurrenceRules = [RecurrenceAdapter.rule(from: recurrence)]
}
try eventStore.save(reminder, commit: true)
return ReminderItem(
id: reminder.calendarItemIdentifier,
Expand All @@ -112,6 +115,7 @@ public actor RemindersStore {
completionDate: reminder.completionDate,
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
recurrence: reminder.recurrenceRules?.compactMap(RecurrenceAdapter.recurrence(from:)).first,
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
)
Expand All @@ -136,6 +140,13 @@ public actor RemindersStore {
if let priority = update.priority {
reminder.priority = priority.eventKitValue
}
if let recurrenceUpdate = update.recurrence {
if let recurrence = recurrenceUpdate {
reminder.recurrenceRules = [RecurrenceAdapter.rule(from: recurrence)]
} else {
reminder.recurrenceRules = nil
}
}
if let listName = update.listName {
reminder.calendar = try calendar(named: listName)
}
Expand All @@ -153,6 +164,7 @@ public actor RemindersStore {
completionDate: reminder.completionDate,
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
recurrence: reminder.recurrenceRules?.compactMap(RecurrenceAdapter.recurrence(from:)).first,
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
)
Expand All @@ -173,6 +185,7 @@ public actor RemindersStore {
completionDate: reminder.completionDate,
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
recurrence: reminder.recurrenceRules?.compactMap(RecurrenceAdapter.recurrence(from:)).first,
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
)
Expand All @@ -190,7 +203,9 @@ public actor RemindersStore {
}
return deleted
}
}

extension RemindersStore {
private func requestFullAccess() async throws -> Bool {
try await withCheckedThrowingContinuation { continuation in
eventStore.requestFullAccessToReminders { granted, error in
Expand All @@ -212,6 +227,7 @@ public actor RemindersStore {
let completionDate: Date?
let priority: Int
let dueDateComponents: DateComponents?
let recurrence: ReminderRecurrence?
let listID: String
let listName: String
}
Expand All @@ -228,6 +244,7 @@ public actor RemindersStore {
completionDate: reminder.completionDate,
priority: Int(reminder.priority),
dueDateComponents: reminder.dueDateComponents,
recurrence: reminder.recurrenceRules?.compactMap(RecurrenceAdapter.recurrence(from:)).first,
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
)
Expand All @@ -245,6 +262,7 @@ public actor RemindersStore {
completionDate: data.completionDate,
priority: ReminderPriority(eventKitValue: data.priority),
dueDate: date(from: data.dueDateComponents),
recurrence: data.recurrence,
listID: data.listID,
listName: data.listName
)
Expand Down Expand Up @@ -284,6 +302,7 @@ public actor RemindersStore {
completionDate: reminder.completionDate,
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
recurrence: reminder.recurrenceRules?.compactMap(RecurrenceAdapter.recurrence(from:)).first,
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
)
Expand Down
74 changes: 73 additions & 1 deletion Sources/RemindCore/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,64 @@ public enum ReminderPriority: String, Codable, CaseIterable, Sendable {
}
}

public enum ReminderRecurrenceFrequency: String, Codable, CaseIterable, Sendable {
case daily
case weekly
}

public enum ReminderWeekday: String, Codable, CaseIterable, Sendable {
case monday = "mon"
case tuesday = "tue"
case wednesday = "wed"
case thursday = "thu"
case friday = "fri"
case saturday = "sat"
case sunday = "sun"

public var displayOrder: Int {
switch self {
case .monday:
return 1
case .tuesday:
return 2
case .wednesday:
return 3
case .thursday:
return 4
case .friday:
return 5
case .saturday:
return 6
case .sunday:
return 7
}
}
}

public enum ReminderRecurrenceEnd: Codable, Sendable, Equatable {
case count(Int)
case until(Date)
}

public struct ReminderRecurrence: Codable, Sendable, Equatable {
public let frequency: ReminderRecurrenceFrequency
public let interval: Int
public let daysOfWeek: [ReminderWeekday]?
public let end: ReminderRecurrenceEnd?

public init(
frequency: ReminderRecurrenceFrequency,
interval: Int = 1,
daysOfWeek: [ReminderWeekday]? = nil,
end: ReminderRecurrenceEnd? = nil
) {
self.frequency = frequency
self.interval = interval
self.daysOfWeek = daysOfWeek
self.end = end
}
}

public struct ReminderList: Identifiable, Codable, Sendable, Equatable {
public let id: String
public let title: String
Expand All @@ -51,6 +109,7 @@ public struct ReminderItem: Identifiable, Codable, Sendable, Equatable {
public let completionDate: Date?
public let priority: ReminderPriority
public let dueDate: Date?
public let recurrence: ReminderRecurrence?
public let listID: String
public let listName: String

Expand All @@ -62,6 +121,7 @@ public struct ReminderItem: Identifiable, Codable, Sendable, Equatable {
completionDate: Date?,
priority: ReminderPriority,
dueDate: Date?,
recurrence: ReminderRecurrence? = nil,
listID: String,
listName: String
) {
Expand All @@ -72,6 +132,7 @@ public struct ReminderItem: Identifiable, Codable, Sendable, Equatable {
self.completionDate = completionDate
self.priority = priority
self.dueDate = dueDate
self.recurrence = recurrence
self.listID = listID
self.listName = listName
}
Expand All @@ -82,12 +143,20 @@ public struct ReminderDraft: Sendable {
public let notes: String?
public let dueDate: Date?
public let priority: ReminderPriority
public let recurrence: ReminderRecurrence?

public init(title: String, notes: String?, dueDate: Date?, priority: ReminderPriority) {
public init(
title: String,
notes: String?,
dueDate: Date?,
priority: ReminderPriority,
recurrence: ReminderRecurrence? = nil
) {
self.title = title
self.notes = notes
self.dueDate = dueDate
self.priority = priority
self.recurrence = recurrence
}
}

Expand All @@ -96,6 +165,7 @@ public struct ReminderUpdate: Sendable {
public let notes: String?
public let dueDate: Date??
public let priority: ReminderPriority?
public let recurrence: ReminderRecurrence??
public let listName: String?
public let isCompleted: Bool?

Expand All @@ -104,13 +174,15 @@ public struct ReminderUpdate: Sendable {
notes: String? = nil,
dueDate: Date?? = nil,
priority: ReminderPriority? = nil,
recurrence: ReminderRecurrence?? = nil,
listName: String? = nil,
isCompleted: Bool? = nil
) {
self.title = title
self.notes = notes
self.dueDate = dueDate
self.priority = priority
self.recurrence = recurrence
self.listName = listName
self.isCompleted = isCompleted
}
Expand Down
128 changes: 128 additions & 0 deletions Sources/RemindCore/RecurrenceAdapter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import EventKit
import Foundation

enum RecurrenceAdapter {
static func rule(from recurrence: ReminderRecurrence) -> EKRecurrenceRule {
let frequency = eventKitFrequency(from: recurrence.frequency)
let interval = max(recurrence.interval, 1)
let end = recurrence.end.map(recurrenceEnd(from:))
let daysOfWeek = recurrence.daysOfWeek?
.sorted { $0.displayOrder < $1.displayOrder }
.compactMap(eventKitDayOfWeek(from:))
return EKRecurrenceRule(
recurrenceWith: frequency,
interval: interval,
daysOfTheWeek: daysOfWeek,
daysOfTheMonth: nil,
monthsOfTheYear: nil,
weeksOfTheYear: nil,
daysOfTheYear: nil,
setPositions: nil,
end: end
)
}

static func recurrence(from rule: EKRecurrenceRule) -> ReminderRecurrence? {
guard let frequency = reminderFrequency(from: rule.frequency) else {
return nil
}
let interval = max(rule.interval, 1)
let end = rule.recurrenceEnd.flatMap(reminderEnd(from:))
let daysOfWeek = rule.daysOfTheWeek?
.compactMap(reminderDayOfWeek(from:))
.sorted { $0.displayOrder < $1.displayOrder }
return ReminderRecurrence(
frequency: frequency,
interval: interval,
daysOfWeek: daysOfWeek,
end: end
)
}

private static func eventKitFrequency(from frequency: ReminderRecurrenceFrequency) -> EKRecurrenceFrequency {
switch frequency {
case .daily:
return .daily
case .weekly:
return .weekly
}
}

private static func reminderFrequency(from frequency: EKRecurrenceFrequency) -> ReminderRecurrenceFrequency? {
switch frequency {
case .daily:
return .daily
case .weekly:
return .weekly
default:
return nil
}
}

private static func eventKitDayOfWeek(from day: ReminderWeekday) -> EKRecurrenceDayOfWeek {
EKRecurrenceDayOfWeek(dayOfTheWeek: eventKitWeekday(from: day), weekNumber: 0)
}

private static func reminderDayOfWeek(from day: EKRecurrenceDayOfWeek) -> ReminderWeekday? {
reminderWeekday(from: day.dayOfTheWeek)
}

private static func eventKitWeekday(from day: ReminderWeekday) -> EKWeekday {
switch day {
case .sunday:
return .sunday
case .monday:
return .monday
case .tuesday:
return .tuesday
case .wednesday:
return .wednesday
case .thursday:
return .thursday
case .friday:
return .friday
case .saturday:
return .saturday
}
}

private static func reminderWeekday(from day: EKWeekday) -> ReminderWeekday? {
switch day {
case .sunday:
return .sunday
case .monday:
return .monday
case .tuesday:
return .tuesday
case .wednesday:
return .wednesday
case .thursday:
return .thursday
case .friday:
return .friday
case .saturday:
return .saturday
@unknown default:
return nil
}
}

private static func recurrenceEnd(from end: ReminderRecurrenceEnd) -> EKRecurrenceEnd {
switch end {
case .count(let count):
return EKRecurrenceEnd(occurrenceCount: max(count, 1))
case .until(let date):
return EKRecurrenceEnd(end: date)
}
}

private static func reminderEnd(from end: EKRecurrenceEnd) -> ReminderRecurrenceEnd? {
if end.occurrenceCount > 0 {
return .count(end.occurrenceCount)
}
if let endDate = end.endDate {
return .until(endDate)
}
return nil
}
}
Loading