mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Something is working Finally
This commit is contained in:
parent
afda162c1f
commit
29b5111a86
10 changed files with 404 additions and 315 deletions
|
|
@ -10,6 +10,7 @@ import Defaults
|
|||
import EventKit
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
import os
|
||||
|
||||
@MainActor
|
||||
final class ReminderLiveActivityManager: ObservableObject {
|
||||
|
|
@ -33,6 +34,22 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
@Published private(set) var upcomingEntries: [ReminderEntry] = []
|
||||
@Published private(set) var activeWindowReminders: [ReminderEntry] = []
|
||||
|
||||
private let logger: os.Logger = os.Logger(subsystem: "com.ebullioscopic.Atoll", category: "ReminderLiveActivity")
|
||||
|
||||
private enum RefreshReason: String {
|
||||
case initialization
|
||||
case defaults
|
||||
case manual
|
||||
case eventStoreChange
|
||||
case calendarListChange
|
||||
case calendarEventsUpdate
|
||||
case fallback
|
||||
case evaluation
|
||||
case hover
|
||||
case ticker
|
||||
case other
|
||||
}
|
||||
|
||||
private var nextReminder: ReminderEntry?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var tickerTask: Task<Void, Never>? { didSet { oldValue?.cancel() } }
|
||||
|
|
@ -42,11 +59,28 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
private var pendingRefreshTask: Task<Void, Never>?
|
||||
private var pendingRefreshForce = false
|
||||
private var pendingRefreshToken = UUID()
|
||||
private var pendingRefreshReason: RefreshReason = .other
|
||||
private var lastKnownCalendarIDs = Set<String>()
|
||||
private var lastRefreshDate: Date?
|
||||
private var lastRefreshCompletionDate: Date?
|
||||
private let minimumRefreshInterval: TimeInterval = 60
|
||||
private let refreshDebounceInterval: TimeInterval = 0.3
|
||||
private var refreshTaskToken = UUID()
|
||||
private var hasShownCriticalSneakPeek = false
|
||||
private let eventStoreChangeNoiseInterval: TimeInterval = 4
|
||||
private let eventStoreForceThrottleInterval: TimeInterval = 20
|
||||
private let manualRefreshSuppressionInterval: TimeInterval = 20
|
||||
private var lastEventStoreForceRefreshDate: Date?
|
||||
private var lastManualRefreshDate: Date?
|
||||
private var pendingEventStoreChange: (date: Date, reason: RefreshReason)?
|
||||
private var reminderCompletionSuppressionDeadline: Date?
|
||||
private let reminderCompletionSuppressionInterval: TimeInterval = 15
|
||||
private var lastUpcomingSignature: [String] = []
|
||||
private var lastNoopEventStoreRefreshDate: Date?
|
||||
private let noopEventStoreSuppressionInterval: TimeInterval = 30
|
||||
private var lastCalendarEventSnapshot: [EventModel] = []
|
||||
private var lastCalendarSnapshotDate: Date?
|
||||
private var lastCalendarEventSnapshotSignature: [String] = []
|
||||
|
||||
private let calendarService: CalendarServiceProviding
|
||||
private let calendarManager = CalendarManager.shared
|
||||
|
|
@ -56,7 +90,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
private init(calendarService: CalendarServiceProviding = CalendarService()) {
|
||||
self.calendarService = calendarService
|
||||
setupObservers()
|
||||
scheduleRefresh(force: true)
|
||||
scheduleRefresh(force: true, reason: .initialization)
|
||||
}
|
||||
|
||||
private func setupObservers() {
|
||||
|
|
@ -64,7 +98,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
.sink { [weak self] change in
|
||||
guard let self else { return }
|
||||
if change.newValue {
|
||||
self.scheduleRefresh(force: true)
|
||||
self.scheduleRefresh(force: true, reason: .defaults)
|
||||
} else {
|
||||
self.deactivateReminder()
|
||||
}
|
||||
|
|
@ -74,7 +108,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
Defaults.publisher(.reminderLeadTime, options: [])
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.scheduleRefresh(force: true)
|
||||
self.scheduleRefresh(force: true, reason: .defaults)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
|
@ -89,23 +123,27 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
.store(in: &cancellables)
|
||||
|
||||
calendarManager.$allCalendars
|
||||
.sink { [weak self] _ in
|
||||
.sink { [weak self] calendars in
|
||||
guard let self else { return }
|
||||
self.scheduleRefresh(force: true)
|
||||
let identifiers = Set(calendars.map { $0.id })
|
||||
guard identifiers != self.lastKnownCalendarIDs else { return }
|
||||
self.lastKnownCalendarIDs = identifiers
|
||||
self.handleEventStoreDrivenChange(reason: .calendarListChange)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
calendarManager.$events
|
||||
.sink { [weak self] _ in
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] events in
|
||||
guard let self else { return }
|
||||
self.scheduleRefresh(force: false)
|
||||
self.handleCalendarEventsUpdate(events)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
NotificationCenter.default.publisher(for: .EKEventStoreChanged)
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.scheduleRefresh(force: true)
|
||||
self.handleEventStoreDrivenChange(reason: .eventStoreChange)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
|
@ -122,6 +160,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
refreshTask?.cancel()
|
||||
refreshTask = nil
|
||||
hasShownCriticalSneakPeek = false
|
||||
pendingEventStoreChange = nil
|
||||
}
|
||||
|
||||
private func deactivateReminder() {
|
||||
|
|
@ -130,6 +169,8 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
upcomingEntries = []
|
||||
activeWindowReminders = []
|
||||
cancelAllTimers()
|
||||
lastUpcomingSignature = []
|
||||
lastNoopEventStoreRefreshDate = nil
|
||||
}
|
||||
|
||||
private func selectedCalendarIDs() -> [String] {
|
||||
|
|
@ -138,6 +179,24 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
.map { $0.id }
|
||||
}
|
||||
|
||||
private func handleCalendarEventsUpdate(_ events: [EventModel]) {
|
||||
let now = Date()
|
||||
let signature = events.map { $0.id }
|
||||
if signature == lastCalendarEventSnapshotSignature,
|
||||
let snapshotDate = lastCalendarSnapshotDate,
|
||||
now.timeIntervalSince(snapshotDate) < 5 {
|
||||
return
|
||||
}
|
||||
|
||||
lastCalendarEventSnapshot = events
|
||||
lastCalendarSnapshotDate = now
|
||||
lastCalendarEventSnapshotSignature = signature
|
||||
|
||||
guard Defaults[.enableReminderLiveActivity] else { return }
|
||||
logger.debug("[Reminder] Applying calendar snapshot update (events=\(events.count, privacy: .public))")
|
||||
refreshFromEvents(events, referenceDate: now, reason: .calendarEventsUpdate)
|
||||
}
|
||||
|
||||
private func shouldHide(_ event: EventModel) -> Bool {
|
||||
if event.isAllDay && Defaults[.hideAllDayEvents] {
|
||||
return true
|
||||
|
|
@ -174,18 +233,28 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
private func scheduleFallbackRefresh() {
|
||||
fallbackRefreshTask?.cancel()
|
||||
let delay: TimeInterval = 15 * 60
|
||||
logger.debug("[Reminder] Scheduling fallback refresh in \(delay, format: .fixed(precision: 0))s")
|
||||
fallbackRefreshTask = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
|
||||
await self.scheduleRefresh(force: true)
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
|
||||
} catch {
|
||||
self.logger.debug("[Reminder] Cancelled pending fallback refresh before execution")
|
||||
return
|
||||
}
|
||||
await self.scheduleRefresh(force: true, reason: .fallback)
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleRefresh(force: Bool) {
|
||||
private func scheduleRefresh(force: Bool, reason: RefreshReason = .other) {
|
||||
let wasPending = pendingRefreshTask != nil
|
||||
pendingRefreshForce = pendingRefreshForce || force
|
||||
pendingRefreshToken = UUID()
|
||||
pendingRefreshReason = reason
|
||||
let token = pendingRefreshToken
|
||||
|
||||
logger.debug("[Reminder] scheduleRefresh reason=\(reason.rawValue, privacy: .public) force=\(force, privacy: .public) pending=\(wasPending, privacy: .public)")
|
||||
|
||||
refreshTask?.cancel()
|
||||
pendingRefreshTask?.cancel()
|
||||
pendingRefreshTask = Task { [weak self] in
|
||||
|
|
@ -193,6 +262,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
do {
|
||||
try await Task.sleep(nanoseconds: UInt64(self.refreshDebounceInterval * 1_000_000_000))
|
||||
} catch {
|
||||
self.logger.debug("[Reminder] Debounced refresh for reason=\(reason.rawValue, privacy: .public) was cancelled")
|
||||
return
|
||||
}
|
||||
await self.executeScheduledRefresh(token: token)
|
||||
|
|
@ -204,15 +274,18 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
pendingRefreshTask = nil
|
||||
|
||||
if Task.isCancelled {
|
||||
logger.debug("[Reminder] Cancelled refresh token=\(token.uuidString, privacy: .public)")
|
||||
return
|
||||
}
|
||||
|
||||
let reason = pendingRefreshReason
|
||||
let now = Date()
|
||||
let force = pendingRefreshForce
|
||||
if !force, let last = lastRefreshDate {
|
||||
let elapsed = now.timeIntervalSince(last)
|
||||
if elapsed < minimumRefreshInterval {
|
||||
let remaining = max(minimumRefreshInterval - elapsed, refreshDebounceInterval)
|
||||
logger.debug("[Reminder] Deferring refresh reason=\(reason.rawValue, privacy: .public); next attempt in \(remaining, format: .fixed(precision: 2))s")
|
||||
pendingRefreshToken = UUID()
|
||||
let nextToken = pendingRefreshToken
|
||||
pendingRefreshTask = Task { [weak self] in
|
||||
|
|
@ -220,6 +293,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
do {
|
||||
try await Task.sleep(nanoseconds: UInt64(remaining * 1_000_000_000))
|
||||
} catch {
|
||||
self.logger.debug("[Reminder] Deferred refresh for reason=\(reason.rawValue, privacy: .public) was cancelled")
|
||||
return
|
||||
}
|
||||
await self.executeScheduledRefresh(token: nextToken)
|
||||
|
|
@ -231,17 +305,24 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
pendingRefreshForce = false
|
||||
lastRefreshDate = now
|
||||
|
||||
let startTime = Date()
|
||||
logger.debug("[Reminder] Starting refresh reason=\(reason.rawValue, privacy: .public) force=\(force, privacy: .public)")
|
||||
|
||||
refreshTask?.cancel()
|
||||
let taskToken = UUID()
|
||||
refreshTaskToken = taskToken
|
||||
let task = Task { [weak self] in
|
||||
guard let self else { return }
|
||||
await self.refreshUpcomingReminder(force: force)
|
||||
await self.refreshUpcomingReminder(force: force, reason: reason)
|
||||
}
|
||||
refreshTask = task
|
||||
defer {
|
||||
if refreshTaskToken == taskToken {
|
||||
refreshTask = nil
|
||||
lastRefreshCompletionDate = Date()
|
||||
let duration = Date().timeIntervalSince(startTime)
|
||||
logger.debug("[Reminder] Finished refresh reason=\(reason.rawValue, privacy: .public) duration=\(duration, format: .fixed(precision: 2))s upcoming=\(self.upcomingEntries.count, privacy: .public)")
|
||||
processPendingEventStoreChangeIfNeeded()
|
||||
}
|
||||
}
|
||||
_ = try? await task.value
|
||||
|
|
@ -262,6 +343,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
}
|
||||
|
||||
private func stopTicker() {
|
||||
tickerTask?.cancel()
|
||||
tickerTask = nil
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +354,7 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
Task { await self.evaluateCurrentState(at: referenceDate) }
|
||||
}
|
||||
|
||||
private func refreshFromEvents(_ events: [EventModel], referenceDate: Date) {
|
||||
private func refreshFromEvents(_ events: [EventModel], referenceDate: Date, reason: RefreshReason) {
|
||||
let leadMinutes = Defaults[.reminderLeadTime]
|
||||
let upcoming = events
|
||||
.filter { !shouldHide($0) }
|
||||
|
|
@ -282,42 +364,92 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
upcomingEntries = upcoming
|
||||
updateActiveWindowReminders(for: referenceDate)
|
||||
|
||||
logger.debug("[Reminder] Reduced \(events.count, privacy: .public) events to \(upcoming.count, privacy: .public) upcoming reminders")
|
||||
|
||||
guard let first = upcoming.first else {
|
||||
deactivateReminder()
|
||||
logger.debug("[Reminder] No upcoming reminders found; scheduling fallback refresh")
|
||||
scheduleFallbackRefresh()
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug("[Reminder] Next reminder ‘\(first.event.title, privacy: .public)’ at \(first.triggerDate.timeIntervalSinceReferenceDate, privacy: .public)")
|
||||
|
||||
handleEntrySelection(first, referenceDate: referenceDate)
|
||||
|
||||
let signature = upcoming.map { $0.event.id }
|
||||
if lastUpcomingSignature == signature {
|
||||
if reason == .eventStoreChange {
|
||||
lastNoopEventStoreRefreshDate = Date()
|
||||
}
|
||||
} else {
|
||||
lastNoopEventStoreRefreshDate = nil
|
||||
}
|
||||
lastUpcomingSignature = signature
|
||||
}
|
||||
|
||||
func requestRefresh(force: Bool = false) {
|
||||
scheduleRefresh(force: force)
|
||||
let reason: RefreshReason = force ? .manual : .other
|
||||
if force {
|
||||
let now = Date()
|
||||
if let lastManualRefreshDate,
|
||||
now.timeIntervalSince(lastManualRefreshDate) < manualRefreshSuppressionInterval {
|
||||
let delta = now.timeIntervalSince(lastManualRefreshDate)
|
||||
logger.debug("[Reminder] Skipping manual refresh; last manual was \(delta, format: .fixed(precision: 2))s ago")
|
||||
return
|
||||
}
|
||||
lastManualRefreshDate = now
|
||||
}
|
||||
scheduleRefresh(force: force, reason: reason)
|
||||
}
|
||||
|
||||
func refreshUpcomingReminder(force: Bool = false) async {
|
||||
private func refreshUpcomingReminder(force: Bool = false, reason: RefreshReason = .other) async {
|
||||
guard Defaults[.enableReminderLiveActivity] else {
|
||||
deactivateReminder()
|
||||
logger.debug("[Reminder] Refresh aborted (feature disabled)")
|
||||
return
|
||||
}
|
||||
|
||||
guard calendarManager.hasReminderAccess else {
|
||||
deactivateReminder()
|
||||
logger.debug("[Reminder] Refresh aborted; reminder permissions missing")
|
||||
return
|
||||
}
|
||||
|
||||
let now = Date()
|
||||
|
||||
if !force, let entry = nextReminder, entry.event.start > now {
|
||||
logger.debug("[Reminder] Skipping fetch; cached entry \(entry.event.title, privacy: .public) still valid for reason=\(reason.rawValue, privacy: .public)")
|
||||
await evaluateCurrentState(at: now)
|
||||
return
|
||||
}
|
||||
|
||||
if let snapshotDate = lastCalendarSnapshotDate,
|
||||
now.timeIntervalSince(snapshotDate) < 5,
|
||||
!lastCalendarEventSnapshot.isEmpty {
|
||||
logger.debug("[Reminder] Using calendar snapshot (age=\(now.timeIntervalSince(snapshotDate), format: .fixed(precision: 2))s) for reason=\(reason.rawValue, privacy: .public)")
|
||||
refreshFromEvents(lastCalendarEventSnapshot, referenceDate: now, reason: reason)
|
||||
return
|
||||
}
|
||||
|
||||
let calendars = selectedCalendarIDs()
|
||||
guard !calendars.isEmpty else {
|
||||
deactivateReminder()
|
||||
logger.debug("[Reminder] Refresh aborted; no calendars selected")
|
||||
return
|
||||
}
|
||||
|
||||
let windowEnd = Calendar.current.date(byAdding: .hour, value: 24, to: now) ?? now.addingTimeInterval(24 * 60 * 60)
|
||||
logger.debug("[Reminder] Fetching reminders (reason=\(reason.rawValue, privacy: .public), force=\(force, privacy: .public), calendars=\(calendars.count, privacy: .public))")
|
||||
let fetchStart = Date()
|
||||
let events = await calendarService.events(from: now, to: windowEnd, calendars: calendars)
|
||||
let fetchDuration = Date().timeIntervalSince(fetchStart)
|
||||
logger.debug("[Reminder] EventKit fetch completed (events=\(events.count, privacy: .public)) in \(fetchDuration, format: .fixed(precision: 2))s")
|
||||
lastCalendarEventSnapshot = events
|
||||
lastCalendarSnapshotDate = now
|
||||
lastCalendarEventSnapshotSignature = events.map { $0.id }
|
||||
await MainActor.run {
|
||||
self.refreshFromEvents(events, referenceDate: now)
|
||||
self.refreshFromEvents(events, referenceDate: now, reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -331,8 +463,14 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
updateActiveWindowReminders(for: date)
|
||||
|
||||
guard var entry = nextReminder else {
|
||||
activeReminder = nil
|
||||
if activeReminder != nil {
|
||||
activeReminder = nil
|
||||
}
|
||||
stopTicker()
|
||||
hasShownCriticalSneakPeek = false
|
||||
if fallbackRefreshTask == nil {
|
||||
scheduleFallbackRefresh()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +479,9 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
nextReminder = nil
|
||||
stopTicker()
|
||||
hasShownCriticalSneakPeek = false
|
||||
scheduleRefresh(force: true)
|
||||
logger.debug("[Reminder] Reminder reached start time; requesting evaluation refresh")
|
||||
reminderCompletionSuppressionDeadline = Date().addingTimeInterval(reminderCompletionSuppressionInterval)
|
||||
scheduleRefresh(force: true, reason: .evaluation)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -408,10 +548,88 @@ final class ReminderLiveActivityManager: ObservableObject {
|
|||
entry.triggerDate <= date && entry.event.start >= date
|
||||
}
|
||||
if filtered != activeWindowReminders {
|
||||
logger.debug("[Reminder] Active window reminder count -> \(filtered.count, privacy: .public)")
|
||||
activeWindowReminders = filtered
|
||||
}
|
||||
}
|
||||
|
||||
private func handleEventStoreDrivenChange(reason: RefreshReason) {
|
||||
processEventStoreChange(at: Date(), reason: reason)
|
||||
}
|
||||
|
||||
private func processEventStoreChange(at date: Date, reason: RefreshReason) {
|
||||
if let manual = lastManualRefreshDate {
|
||||
let delta = date.timeIntervalSince(manual)
|
||||
if delta < manualRefreshSuppressionInterval {
|
||||
logger.debug("[Reminder] Ignoring \(reason.rawValue, privacy: .public) change; manual refresh cooldown \(delta, format: .fixed(precision: 2))s")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let lastRefreshStart = lastRefreshDate {
|
||||
let sinceStart = date.timeIntervalSince(lastRefreshStart)
|
||||
if sinceStart >= 0 && sinceStart < eventStoreChangeNoiseInterval {
|
||||
logger.debug("[Reminder] Ignoring \(reason.rawValue, privacy: .public); \(sinceStart, format: .fixed(precision: 2))s since start")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let lastCompletion = lastRefreshCompletionDate {
|
||||
let sinceCompletion = abs(date.timeIntervalSince(lastCompletion))
|
||||
if sinceCompletion < eventStoreChangeNoiseInterval {
|
||||
logger.debug("[Reminder] Ignoring \(reason.rawValue, privacy: .public); \(sinceCompletion, format: .fixed(precision: 2))s since completion")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if reason == .eventStoreChange,
|
||||
let suppressionDeadline = reminderCompletionSuppressionDeadline,
|
||||
date <= suppressionDeadline {
|
||||
let remaining = suppressionDeadline.timeIntervalSince(date)
|
||||
logger.debug("[Reminder] Ignoring \(reason.rawValue, privacy: .public); reminder completion suppression \(remaining, format: .fixed(precision: 2))s")
|
||||
return
|
||||
}
|
||||
|
||||
if reason == .eventStoreChange,
|
||||
let noopDate = lastNoopEventStoreRefreshDate,
|
||||
date.timeIntervalSince(noopDate) < noopEventStoreSuppressionInterval {
|
||||
let remaining = noopEventStoreSuppressionInterval - date.timeIntervalSince(noopDate)
|
||||
logger.debug("[Reminder] Ignoring \(reason.rawValue, privacy: .public); identical upcoming entries \(remaining, format: .fixed(precision: 2))s window")
|
||||
return
|
||||
}
|
||||
|
||||
if let lastForce = lastEventStoreForceRefreshDate {
|
||||
let sinceForce = date.timeIntervalSince(lastForce)
|
||||
if sinceForce < self.eventStoreForceThrottleInterval {
|
||||
logger.debug("[Reminder] Ignoring \(reason.rawValue, privacy: .public); throttle window remaining \(self.eventStoreForceThrottleInterval - sinceForce, format: .fixed(precision: 2))s")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if refreshTask != nil {
|
||||
pendingEventStoreChange = (date, reason)
|
||||
logger.debug("[Reminder] Queued \(reason.rawValue, privacy: .public) change while refresh is running")
|
||||
return
|
||||
}
|
||||
|
||||
triggerEventStoreForcedRefresh(at: date, reason: reason)
|
||||
}
|
||||
|
||||
private func processPendingEventStoreChangeIfNeeded() {
|
||||
guard let pendingChange = pendingEventStoreChange else { return }
|
||||
pendingEventStoreChange = nil
|
||||
processEventStoreChange(at: pendingChange.date, reason: pendingChange.reason)
|
||||
}
|
||||
|
||||
private func triggerEventStoreForcedRefresh(at date: Date = Date(), reason: RefreshReason) {
|
||||
lastEventStoreForceRefreshDate = date
|
||||
if reason == .eventStoreChange, reminderCompletionSuppressionDeadline != nil {
|
||||
reminderCompletionSuppressionDeadline = nil
|
||||
}
|
||||
logger.debug("[Reminder] Triggering event-store refresh for \(reason.rawValue, privacy: .public)")
|
||||
scheduleRefresh(force: true, reason: reason)
|
||||
}
|
||||
|
||||
static func additionalHeight(forRowCount rowCount: Int) -> CGFloat {
|
||||
guard rowCount > 0 else { return 0 }
|
||||
let rows = CGFloat(rowCount)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue