Files
Morgan Pretty 73d59be6e0 Removed the old libSessionObservation, fixed a couple of bugs
• Added a mechanism to trigger an immediate event (it forces the debouncer to flush)
• Updated the ThreadNotificationSettingsViewModel to use the ObservationBuilder
• Updated the AppearanceViewModel to use the ObservationBuilder
• Updated the ConversationSettingsViewModel to use the ObservationBuilder
• Updated the NotificationContentViewModel to use the ObservationBuilder
• Updated the NotificationSettingsViewModel to use the ObservationBuilder
• Updated the PrivacySettingsViewModel to use the ObservationBuilder
• Updated the SettingsViewModel to use the ObservationBuilder
• Cleaned up the MessageRequestsViewModel and HomeViewModel to be consistent with the other refactored view models
• Removed the `libSessionObservation` logic (replaced with new ObservationBuilder)
• Fixed a bug where the notification sound setting might not work correctly
• Fixed a few incorrect events being sent
2025-07-14 16:44:03 +10:00

112 lines
3.1 KiB
Swift

// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.
import Foundation
public actor DebounceTaskManager<Event: Sendable> {
private let debounceInterval: DispatchTimeInterval
private var debounceTask: Task<Void, Never>? = nil
private var pendingEvents: [Event] = []
private var pendingEventSet: Set<AnyHashable> = []
private var action: (@Sendable ([Event]) async -> Void)?
public init(debounceInterval: DispatchTimeInterval) {
self.debounceInterval = debounceInterval
}
public func setAction(_ newAction: @Sendable @escaping ([Event]) async -> Void) {
self.action = newAction
}
public func reset() {
debounceTask?.cancel()
debounceTask = nil
pendingEvents.removeAll()
pendingEventSet.removeAll()
action = nil
}
// MARK: - Internal Functions
fileprivate func scheduleSignal() {
debounceTask?.cancel()
debounceTask = Task { [weak self] in
guard let self = self else { return }
do {
/// Only debounce if we want to
if debounceInterval != .never {
try await Task.sleep(for: self.debounceInterval)
}
guard !Task.isCancelled else { return }
let eventsToProcess: [Event] = await self.clearPendingEvents()
await self.action?(eventsToProcess)
} catch {
// Task was cancelled so no need to do anything
}
}
}
fileprivate func flushPendingEvents() {
debounceTask?.cancel()
debounceTask = Task { [weak self] in
guard let self = self else { return }
let eventsToProcess: [Event] = await self.clearPendingEvents()
await self.action?(eventsToProcess)
}
}
private func clearPendingEvents() -> [Event] {
let events: [Event] = pendingEvents
pendingEvents.removeAll()
pendingEventSet.removeAll()
return events
}
}
public extension DebounceTaskManager where Event == Void {
func signal() {
pendingEvents.append(())
scheduleSignal()
}
func flush() {
pendingEvents.append(())
flushPendingEvents()
}
}
public extension DebounceTaskManager {
func signal(event: Event) {
pendingEvents.append(event)
scheduleSignal()
}
func flush(event: Event) {
pendingEvents.append(event)
flushPendingEvents()
}
}
public extension DebounceTaskManager where Event: Hashable {
func signal(event: Event) {
/// Ignore duplicate events
guard !pendingEventSet.contains(event) else { return }
pendingEvents.append(event)
pendingEventSet.insert(event)
scheduleSignal()
}
func flush(event: Event) {
/// Ignore duplicate events
guard !pendingEventSet.contains(event) else { return }
pendingEvents.append(event)
pendingEventSet.insert(event)
flushPendingEvents()
}
}