NetNewsWire/Multiplatform/Shared/Timeline/TimelineModel.swift

124 lines
3.0 KiB
Swift
Raw Normal View History

2020-06-30 18:03:33 +02:00
//
// TimelineModel.swift
// NetNewsWire
//
// Created by Maurice Parker on 6/30/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
import RSCore
import Account
2020-07-01 19:30:55 +02:00
import Articles
2020-06-30 18:03:33 +02:00
protocol TimelineModelDelegate: class {
func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed)
}
class TimelineModel: ObservableObject {
weak var delegate: TimelineModelDelegate?
@Published var timelineItems = [TimelineItem]()
2020-07-01 18:13:11 +02:00
private var feeds = [Feed]()
2020-07-01 19:30:55 +02:00
private var fetchSerialNumber = 0
private let fetchRequestQueue = FetchRequestQueue()
private var exceptionArticleFetcher: ArticleFetcher?
private var isReadFiltered = false
private var articles = [Article]()
private var sortDirection = AppDefaults.timelineSortDirection {
didSet {
if sortDirection != oldValue {
sortParametersDidChange()
}
}
}
private var groupByFeed = AppDefaults.timelineGroupByFeed {
didSet {
if groupByFeed != oldValue {
sortParametersDidChange()
}
}
}
2020-07-01 18:13:11 +02:00
2020-06-30 18:03:33 +02:00
init() {
}
// MARK: API
2020-07-01 18:13:11 +02:00
func rebuildTimelineItems(_ feed: Feed) {
feeds = [feed]
2020-07-01 19:30:55 +02:00
fetchAndReplaceArticlesAsync()
2020-06-30 18:03:33 +02:00
}
}
// MARK: Private
2020-07-01 19:30:55 +02:00
2020-06-30 18:03:33 +02:00
private extension TimelineModel {
2020-07-01 19:30:55 +02:00
func sortParametersDidChange() {
performBlockAndRestoreSelection {
let unsortedArticles = Set(articles)
replaceArticles(with: unsortedArticles)
}
}
func performBlockAndRestoreSelection(_ block: (() -> Void)) {
// let savedSelection = selectedArticleIDs()
block()
// restoreSelection(savedSelection)
}
// MARK: Article Fetching
func fetchAndReplaceArticlesAsync() {
cancelPendingAsyncFetches()
var fetchers = feeds as [ArticleFetcher]
if let fetcher = exceptionArticleFetcher {
fetchers.append(fetcher)
exceptionArticleFetcher = nil
}
fetchUnsortedArticlesAsync(for: fetchers) { [weak self] (articles) in
self?.replaceArticles(with: articles)
}
}
func cancelPendingAsyncFetches() {
fetchSerialNumber += 1
fetchRequestQueue.cancelAllRequests()
}
func fetchUnsortedArticlesAsync(for representedObjects: [Any], completion: @escaping ArticleSetBlock) {
// The callback will *not* be called if the fetch is no longer relevant that is,
// if its been superseded by a newer fetch, or the timeline was emptied, etc., it wont get called.
precondition(Thread.isMainThread)
cancelPendingAsyncFetches()
let fetchOperation = FetchRequestOperation(id: fetchSerialNumber, readFilter: isReadFiltered ?? true, representedObjects: representedObjects) { [weak self] (articles, operation) in
precondition(Thread.isMainThread)
guard !operation.isCanceled, let strongSelf = self, operation.id == strongSelf.fetchSerialNumber else {
return
}
completion(articles)
}
fetchRequestQueue.add(fetchOperation)
}
2020-06-30 18:03:33 +02:00
2020-07-01 19:30:55 +02:00
func replaceArticles(with unsortedArticles: Set<Article>) {
articles = Array(unsortedArticles).sortedByDate(sortDirection, groupByFeed: groupByFeed)
timelineItems = articles.map { TimelineItem(article: $0) }
// TODO: Update unread counts and other item done in didSet on AppKit
}
// MARK: - Notifications
2020-06-30 18:03:33 +02:00
}