NetNewsWire/Multiplatform/Shared/SceneModel.swift

216 lines
5.9 KiB
Swift
Raw Normal View History

2020-06-28 21:21:43 +02:00
//
// SceneModel.swift
// NetNewsWire
//
// Created by Maurice Parker on 6/28/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
import Combine
import Account
import Articles
import RSCore
2020-06-28 21:21:43 +02:00
final class SceneModel: ObservableObject {
@Published var markAllAsReadButtonState: Bool?
@Published var nextUnreadButtonState: Bool?
@Published var readButtonState: Bool?
@Published var starButtonState: Bool?
@Published var extractorButtonState: ArticleExtractorButtonState?
@Published var openInBrowserButtonState: Bool?
@Published var shareButtonState: Bool?
@Published var accountSyncErrors: [AccountSyncError] = []
2020-07-14 03:18:39 +02:00
var selectedArticles: [Article] {
timelineModel.selectedArticles
2020-07-14 03:18:39 +02:00
}
private var refreshProgressModel: RefreshProgressModel? = nil
private var articleIconSchemeHandler: ArticleIconSchemeHandler? = nil
2020-07-12 01:22:47 +02:00
private(set) var webViewProvider: WebViewProvider? = nil
private(set) lazy var sidebarModel = SidebarModel(delegate: self)
private(set) lazy var timelineModel = TimelineModel(delegate: self)
private var cancellables = Set<AnyCancellable>()
// MARK: Initialization API
/// Prepares the SceneModel to be used in the views
func startup() {
2020-07-08 17:20:04 +02:00
self.articleIconSchemeHandler = ArticleIconSchemeHandler(sceneModel: self)
self.webViewProvider = WebViewProvider(articleIconSchemeHandler: self.articleIconSchemeHandler!)
2020-07-18 11:58:46 +02:00
subscribeToAccountSyncErrors()
subscribeToToolbarChangeEvents()
}
2020-07-19 20:28:22 +02:00
// MARK: Navigation API
/// Goes to the next unread item found in Sidebar and Timeline order, top to bottom
func goToNextUnread() {
2020-07-19 22:24:59 +02:00
if !timelineModel.goToNextUnread() {
timelineModel.selectNextUnreadSubject.send(true)
sidebarModel.selectNextUnread.send()
2020-07-19 22:24:59 +02:00
}
2020-07-19 20:28:22 +02:00
}
// MARK: Article Management API
2020-07-18 11:58:46 +02:00
/// Marks all the articles in the Timeline as read
func markAllAsRead() {
timelineModel.markAllAsRead()
2020-07-18 11:58:46 +02:00
}
/// Toggles the read status for the selected articles
func toggleReadStatusForSelectedArticles() {
timelineModel.toggleReadStatusForSelectedArticles()
}
/// Toggles the star status for the selected articles
func toggleStarredStatusForSelectedArticles() {
timelineModel.toggleStarredStatusForSelectedArticles()
}
/// Opens the selected article in an external browser
func openSelectedArticleInBrowser() {
timelineModel.openSelectedArticleInBrowser()
}
/// Retrieves the article before the given article in the Timeline
func findPrevArticle(_ article: Article) -> Article? {
2020-07-12 01:22:47 +02:00
return timelineModel.findPrevArticle(article)
}
/// Retrieves the article after the given article in the Timeline
func findNextArticle(_ article: Article) -> Article? {
2020-07-12 01:22:47 +02:00
return timelineModel.findNextArticle(article)
}
/// Returns the article with the given articleID
func articleFor(_ articleID: String) -> Article? {
2020-07-12 01:22:47 +02:00
return timelineModel.articleFor(articleID)
}
2020-06-28 21:21:43 +02:00
}
// MARK: SidebarModelDelegate
extension SceneModel: SidebarModelDelegate {
2020-06-29 13:16:48 +02:00
func unreadCount(for feed: Feed) -> Int {
// TODO: Get the count from the timeline if Feed is the current timeline
return feed.unreadCount
}
}
2020-06-30 18:03:33 +02:00
// MARK: TimelineModelDelegate
extension SceneModel: TimelineModelDelegate {
var selectedFeedsPublisher: AnyPublisher<[Feed], Never>? {
return sidebarModel.selectedFeedsPublisher
}
2020-06-30 18:03:33 +02:00
func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed) {
}
}
2020-07-02 22:30:50 +02:00
// MARK: Private
2020-07-02 22:30:50 +02:00
private extension SceneModel {
2020-07-02 22:30:50 +02:00
// MARK: Subscriptions
func subscribeToToolbarChangeEvents() {
guard let selectedArticlesPublisher = timelineModel.selectedArticlesPublisher else { return }
2020-07-25 19:46:33 +02:00
NotificationCenter.default.publisher(for: .UnreadCountDidChange)
.compactMap { $0.object as? AccountManager }
.sink { [weak self] accountManager in
self?.updateNextUnreadButtonState(accountManager: accountManager)
}.store(in: &cancellables)
let blankNotification = Notification(name: .StatusesDidChange)
let statusesDidChangePublisher = NotificationCenter.default.publisher(for: .StatusesDidChange).prepend(blankNotification)
statusesDidChangePublisher
.combineLatest(selectedArticlesPublisher)
.sink { [weak self] _, selectedArticles in
self?.updateArticleButtonsState(selectedArticles: selectedArticles)
}
.store(in: &cancellables)
statusesDidChangePublisher
.combineLatest(timelineModel.articlesSubject)
2020-07-25 19:46:33 +02:00
.sink { [weak self] _, articles in
self?.updateMarkAllAsReadButtonsState(articles: articles)
}
.store(in: &cancellables)
}
func subscribeToAccountSyncErrors() {
NotificationCenter.default.publisher(for: .AccountsDidFailToSyncWithErrors)
.sink { [weak self] notification in
2020-07-26 01:36:24 +02:00
guard let syncErrors = notification.userInfo?[Account.UserInfoKey.syncErrors] as? [AccountSyncError] else {
return
}
2020-07-26 01:36:24 +02:00
self?.accountSyncErrors = syncErrors
}.store(in: &cancellables)
}
2020-07-12 01:22:47 +02:00
// MARK: Button State Updates
2020-07-19 20:23:08 +02:00
func updateNextUnreadButtonState(accountManager: AccountManager) {
if accountManager.unreadCount > 0 {
self.nextUnreadButtonState = false
} else {
self.nextUnreadButtonState = nil
}
}
2020-07-18 11:58:46 +02:00
func updateMarkAllAsReadButtonsState(articles: [Article]) {
if articles.canMarkAllAsRead() {
markAllAsReadButtonState = false
} else {
markAllAsReadButtonState = nil
}
}
func updateArticleButtonsState(selectedArticles: [Article]) {
guard !selectedArticles.isEmpty else {
readButtonState = nil
starButtonState = nil
openInBrowserButtonState = nil
shareButtonState = nil
return
}
2020-07-18 11:58:46 +02:00
if selectedArticles.anyArticleIsUnread() {
readButtonState = true
2020-07-18 11:58:46 +02:00
} else if selectedArticles.anyArticleIsReadAndCanMarkUnread() {
readButtonState = false
} else {
readButtonState = nil
}
2020-07-18 11:58:46 +02:00
if selectedArticles.anyArticleIsUnstarred() {
starButtonState = false
} else {
starButtonState = true
2020-07-12 01:22:47 +02:00
}
2020-07-18 11:58:46 +02:00
if selectedArticles.count == 1, selectedArticles.first?.preferredLink != nil {
openInBrowserButtonState = true
shareButtonState = true
} else {
openInBrowserButtonState = nil
shareButtonState = nil
}
}
2020-07-02 22:30:50 +02:00
}