// The MIT License (MIT) // // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean). import CoreData import Pulse import Combine import SwiftUI final class ConsoleViewModel: NSObject, NSFetchedResultsControllerDelegate, ObservableObject { #if os(iOS) || os(macOS) let table: ConsoleTableViewModel #endif @Published private(set) var entities: [LoggerMessageEntity] = [] let details: ConsoleDetailsRouterViewModel // Search criteria let searchCriteria: ConsoleSearchCriteriaViewModel @Published var isOnlyErrors: Bool = false @Published var isOnlyNetwork: Bool = false @Published var filterTerm: String = "" var onDismiss: (() -> Void)? private(set) var store: LoggerStore private let controller: NSFetchedResultsController private var isActive = false private var latestSessionId: UUID? private var cancellables: [AnyCancellable] = [] init(store: LoggerStore) { self.store = store self.details = ConsoleDetailsRouterViewModel() let request = NSFetchRequest(entityName: "\(LoggerMessageEntity.self)") request.fetchBatchSize = 100 request.relationshipKeyPathsForPrefetching = ["request"] request.sortDescriptors = [NSSortDescriptor(keyPath: \LoggerMessageEntity.createdAt, ascending: false)] self.controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: store.viewContext, sectionNameKeyPath: nil, cacheName: nil) self.searchCriteria = ConsoleSearchCriteriaViewModel(store: store) #if os(iOS) || os(macOS) self.table = ConsoleTableViewModel(searchCriteriaViewModel: searchCriteria) #endif super.init() controller.delegate = self $filterTerm.throttle(for: 0.25, scheduler: RunLoop.main, latest: true).dropFirst().sink { [weak self] filterTerm in self?.refresh(filterTerm: filterTerm) }.store(in: &cancellables) searchCriteria.dataNeedsReload.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true).sink { [weak self] in self?.refreshNow() }.store(in: &cancellables) $isOnlyErrors.receive(on: DispatchQueue.main).dropFirst().sink { [weak self] _ in self?.refreshNow() }.store(in: &cancellables) refreshNow() } // MARK: Appearance func onAppear() { isActive = true reloadMessages() } func onDisappear() { isActive = false } // MARK: Refresh private func refreshNow() { refresh(filterTerm: filterTerm) } private func refresh(filterTerm: String) { // Get sessionId if latestSessionId == nil { latestSessionId = entities.first?.session } let sessionId = store === LoggerStore.shared ? LoggerStore.Session.current.id : latestSessionId // Search messages ConsoleSearchCriteria.update(request: controller.fetchRequest, filterTerm: filterTerm, criteria: searchCriteria.criteria, filters: searchCriteria.filters, sessionId: sessionId, isOnlyErrors: isOnlyErrors, isOnlyNetwork: isOnlyNetwork) try? controller.performFetch() reloadMessages() } // MARK: - NSFetchedResultsControllerDelegate func controller(_ controller: NSFetchedResultsController, didChangeContentWith diff: CollectionDifference) { guard isActive else { return } #if os(iOS) || os(macOS) self.table.diff = diff #endif withAnimation { reloadMessages() } } private func reloadMessages() { entities = controller.fetchedObjects ?? [] #if os(iOS) || os(macOS) table.entities = entities #endif } }