diff --git a/WWDC/AppCoordinator.swift b/WWDC/AppCoordinator.swift index b89f8bbf..13ba1c75 100644 --- a/WWDC/AppCoordinator.swift +++ b/WWDC/AppCoordinator.swift @@ -80,12 +80,14 @@ final class AppCoordinator: Logging { exploreItem.label = "Explore" tabController.addTabViewItem(exploreItem) + _playerOwnerSessionIdentifier = .init(initialValue: nil) // Schedule scheduleController = ScheduleContainerViewController( windowController: windowController, rowProvider: ScheduleSessionRowProvider( scheduleSections: storage.scheduleShallowObservable, - filterPredicate: searchCoordinator.$scheduleFilterPredicate + filterPredicate: searchCoordinator.$scheduleFilterPredicate, + playingSessionIdentifier: _playerOwnerSessionIdentifier.projectedValue ), searchController: scheduleSearchController ) @@ -102,7 +104,8 @@ final class AppCoordinator: Logging { windowController: windowController, rowProvider: VideosSessionRowProvider( tracks: storage.tracks, - filterPredicate: searchCoordinator.$videosFilterPredicate + filterPredicate: searchCoordinator.$videosFilterPredicate, + playingSessionIdentifier: _playerOwnerSessionIdentifier.projectedValue ), searchController: videosSearchController ) diff --git a/WWDC/SessionRowProvider.swift b/WWDC/SessionRowProvider.swift index ed6adacd..e29af213 100644 --- a/WWDC/SessionRowProvider.swift +++ b/WWDC/SessionRowProvider.swift @@ -133,7 +133,11 @@ final class VideosSessionRowProvider: SessionRowProvider, Logging { @Published var rows: SessionRows? var rowsPublisher: AnyPublisher { $rows.dropFirst().compacted().eraseToAnyPublisher() } - init(tracks: Results, filterPredicate: P) where P.Output == FilterPredicate, P.Failure == Never { + init( + tracks: Results, + filterPredicate: P, + playingSessionIdentifier: PlayingSession + ) where P.Output == FilterPredicate, P.Failure == Never, PlayingSession.Output == String?, PlayingSession.Failure == Never { let tracksAndSessions = tracks.collectionChangedPublisher .replaceErrorWithEmpty() .map { @@ -170,18 +174,27 @@ final class VideosSessionRowProvider: SessionRowProvider, Logging { .do { Self.log.debug("Filter predicate updated") } - Publishers.CombineLatest( + Publishers.CombineLatest3( tracksAndSessions.replaceErrorWithEmpty(), - filterPredicate + filterPredicate, + playingSessionIdentifier ) - .map { (allViewModels, predicate) in + .map { (allViewModels, predicate, playingSessionIdentifier) in + let effectivePredicate: NSPredicate + if let playingSessionIdentifier { + effectivePredicate = NSCompoundPredicate( + orPredicateWithSubpredicates: [predicate.predicate ?? NSPredicate(value: true), NSPredicate(format: "identifier == %@", playingSessionIdentifier)] + ) + } else { + effectivePredicate = predicate.predicate ?? NSPredicate(value: true) + } // Observe the filtered sessions // These observers emit elements in the case of a session having some property changed that is // affected by the query. e.g. Filtering on favorites then unfavorite a session - allViewModels.map { (key: SessionRow, value: (Results, OrderedDictionary)) in + return allViewModels.map { (key: SessionRow, value: (Results, OrderedDictionary)) in let (allTrackSessions, sessionRows) = value return allTrackSessions - .filter(predicate.predicate ?? NSPredicate(value: true)) + .filter(effectivePredicate) .collectionChangedPublisher .replaceErrorWithEmpty() .map { @@ -194,11 +207,8 @@ final class VideosSessionRowProvider: SessionRowProvider, Logging { } } .switchToLatest() - .map { thing in - Self.log.debug("Received new update") - - return Self.sessionRows(thing) - } + .map(Self.sessionRows) + .do { Self.log.debug("Received new update") } .assign(to: &$rows) } @@ -253,10 +263,11 @@ final class ScheduleSessionRowProvider: SessionRowProvider, Logging { @Published var rows: SessionRows? var rowsPublisher: AnyPublisher { $rows.dropFirst().compacted().eraseToAnyPublisher() } - init( + init( scheduleSections: S, - filterPredicate: P - ) where P.Output == FilterPredicate, P.Failure == Never, S.Output == Results { + filterPredicate: P, + playingSessionIdentifier: PlayingSession + ) where P.Output == FilterPredicate, P.Failure == Never, S.Output == Results, PlayingSession.Output == String?, PlayingSession.Failure == Never { let sectionsAndSessions = scheduleSections .replaceErrorWithEmpty() @@ -289,18 +300,27 @@ final class ScheduleSessionRowProvider: SessionRowProvider, Logging { }) .do { Self.log.debug("Filter predicate updated") } - Publishers.CombineLatest( + Publishers.CombineLatest3( sectionsAndSessions.replaceErrorWithEmpty(), - filterPredicate + filterPredicate, + playingSessionIdentifier ) - .map { (allViewModels, predicate) in + .map { (allViewModels, predicate, playingSessionIdentifier) in + let effectivePredicate: NSPredicate + if let playingSessionIdentifier { + effectivePredicate = NSCompoundPredicate( + orPredicateWithSubpredicates: [predicate.predicate ?? NSPredicate(value: true), NSPredicate(format: "identifier == %@", playingSessionIdentifier)] + ) + } else { + effectivePredicate = predicate.predicate ?? NSPredicate(value: true) + } // Observe the filtered sessions // These observers emit elements in the case of a session having some property changed that is // affected by the query. e.g. Filtering on favorites then unfavorite a session - allViewModels.map { (key: SessionRow, value: (Results, OrderedDictionary)) in + return allViewModels.map { (key: SessionRow, value: (Results, OrderedDictionary)) in let (allSectionInstances, sessionRows) = value return allSectionInstances - .filter(predicate.predicate ?? NSPredicate(value: true)) + .filter(effectivePredicate) .collectionChangedPublisher .replaceErrorWithEmpty() .map { diff --git a/WWDC/SessionsTableViewController+SupportingTypesAndExtensions.swift b/WWDC/SessionsTableViewController+SupportingTypesAndExtensions.swift index aaa4fc60..922cbdf4 100644 --- a/WWDC/SessionsTableViewController+SupportingTypesAndExtensions.swift +++ b/WWDC/SessionsTableViewController+SupportingTypesAndExtensions.swift @@ -82,93 +82,3 @@ extension Array where Element == SessionRow { } } } - -final class FilterResults: Logging { - static let log = makeLogger() - - static var empty: FilterResults { - return FilterResults(storage: nil, query: nil) - } - - private let query: NSPredicate? - - private let storage: Storage? - - private(set) var latestSearchResults: Results? - - private lazy var cancellables: Set = [] - private var nowPlayingBag: Set = [] - - private var observerClosure: ((Results?) -> Void)? - private var observerToken: NotificationToken? - - init(storage: Storage?, query: NSPredicate?) { - self.storage = storage - self.query = query - - if let coordinator = (NSApplication.shared.delegate as? AppDelegate)?.coordinator { - - coordinator - .$playerOwnerSessionIdentifier - .sink(receiveValue: { [weak self] _ in - self?.bindResults() - }) - .store(in: &nowPlayingBag) - } - } - - func observe(with closure: @escaping (Results?) -> Void) { - assert(observerClosure == nil) - - guard query != nil, storage != nil else { - closure(nil) - return - } - - observerClosure = closure - - bindResults() - } - - private func bindResults() { - guard let observerClosure = observerClosure else { return } - guard let storage = storage, let query = query?.orCurrentlyPlayingSession() else { return } - - cancellables = [] - - do { - let realm = try Realm(configuration: storage.realmConfig) - - let objects = realm.objects(Session.self).filter(query) - - // Immediately provide the first value - self.latestSearchResults = objects - observerClosure(objects) - - objects - .collectionChangedPublisher - .dropFirst(1) // first value is provided synchronously to help with timing issues - .replaceErrorWithEmpty() - .sink { [weak self] in - self?.latestSearchResults = $0 - observerClosure($0) - } - .store(in: &cancellables) - } catch { - observerClosure(nil) - log.error("Failed to initialize Realm for searching: \(String(describing: error), privacy: .public)") - } - } -} - -fileprivate extension NSPredicate { - - func orCurrentlyPlayingSession() -> NSPredicate { - - guard let playingSession = (NSApplication.shared.delegate as? AppDelegate)?.coordinator?.playerOwnerSessionIdentifier else { - return self - } - - return NSCompoundPredicate(orPredicateWithSubpredicates: [self, NSPredicate(format: "identifier == %@", playingSession)]) - } -}