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.
|
|
|
|
//
|
|
|
|
|
2020-07-18 03:08:49 +02:00
|
|
|
import Foundation
|
2020-07-12 02:52:28 +02:00
|
|
|
import Combine
|
2020-06-29 00:43:20 +02:00
|
|
|
import Account
|
2020-07-07 04:14:05 +02:00
|
|
|
import Articles
|
2020-07-09 23:34:47 +02:00
|
|
|
import RSCore
|
2020-06-28 21:21:43 +02:00
|
|
|
|
|
|
|
final class SceneModel: ObservableObject {
|
|
|
|
|
2020-07-14 00:24:40 +02:00
|
|
|
@Published var markAllAsReadButtonState: Bool?
|
|
|
|
@Published var nextUnreadButtonState: Bool?
|
2020-07-13 23:26:53 +02:00
|
|
|
@Published var readButtonState: Bool?
|
|
|
|
@Published var starButtonState: Bool?
|
2020-07-14 00:24:40 +02:00
|
|
|
@Published var extractorButtonState: ArticleExtractorButtonState?
|
|
|
|
@Published var openInBrowserButtonState: Bool?
|
|
|
|
@Published var shareButtonState: Bool?
|
2020-07-25 10:40:04 +02:00
|
|
|
@Published var accountSyncErrors: [AccountSyncError] = []
|
2020-07-16 03:06:29 +02:00
|
|
|
|
2020-07-14 03:18:39 +02:00
|
|
|
var selectedArticles: [Article] {
|
2020-07-25 23:28:23 +02:00
|
|
|
timelineModel.selectedArticles
|
2020-07-14 03:18:39 +02:00
|
|
|
}
|
|
|
|
|
2020-07-10 01:44:51 +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
|
2020-07-25 04:05:30 +02:00
|
|
|
private(set) lazy var sidebarModel = SidebarModel(delegate: self)
|
|
|
|
private(set) lazy var timelineModel = TimelineModel(delegate: self)
|
2020-07-10 01:44:51 +02:00
|
|
|
|
2020-07-19 04:33:24 +02:00
|
|
|
private var cancellables = Set<AnyCancellable>()
|
2020-07-12 02:52:28 +02:00
|
|
|
|
2020-07-09 23:34:47 +02:00
|
|
|
// MARK: Initialization API
|
2020-07-07 04:14:05 +02:00
|
|
|
|
2020-07-10 01:44:51 +02:00
|
|
|
/// Prepares the SceneModel to be used in the views
|
2020-07-06 02:49:07 +02:00
|
|
|
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
|
|
|
|
2020-07-25 10:40:04 +02:00
|
|
|
subscribeToAccountSyncErrors()
|
2020-07-19 18:15:02 +02:00
|
|
|
subscribeToToolbarChangeEvents()
|
2020-07-07 04:14:05 +02:00
|
|
|
}
|
2020-07-09 23:34:47 +02:00
|
|
|
|
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() {
|
2020-07-27 00:17:02 +02:00
|
|
|
timelineModel.selectNextUnreadSubject.send(true)
|
2020-07-25 01:30:54 +02:00
|
|
|
sidebarModel.selectNextUnread.send()
|
2020-07-19 22:24:59 +02:00
|
|
|
}
|
2020-07-19 20:28:22 +02:00
|
|
|
}
|
|
|
|
|
2020-07-10 01:44:51 +02:00
|
|
|
// MARK: Article Management API
|
2020-07-09 23:34:47 +02:00
|
|
|
|
2020-07-18 11:58:46 +02:00
|
|
|
/// Marks all the articles in the Timeline as read
|
|
|
|
func markAllAsRead() {
|
2020-07-27 22:43:58 +02:00
|
|
|
timelineModel.markAllAsRead()
|
2020-07-18 11:58:46 +02:00
|
|
|
}
|
|
|
|
|
2020-07-18 03:08:49 +02:00
|
|
|
/// Toggles the read status for the selected articles
|
2020-07-12 02:52:28 +02:00
|
|
|
func toggleReadStatusForSelectedArticles() {
|
2020-07-27 22:43:58 +02:00
|
|
|
timelineModel.toggleReadStatusForSelectedArticles()
|
2020-07-12 02:52:28 +02:00
|
|
|
}
|
|
|
|
|
2020-07-18 03:08:49 +02:00
|
|
|
/// Toggles the star status for the selected articles
|
2020-07-12 02:52:28 +02:00
|
|
|
func toggleStarredStatusForSelectedArticles() {
|
2020-07-27 22:43:58 +02:00
|
|
|
timelineModel.toggleStarredStatusForSelectedArticles()
|
2020-07-12 02:52:28 +02:00
|
|
|
}
|
2020-07-18 03:08:49 +02:00
|
|
|
|
|
|
|
/// Opens the selected article in an external browser
|
|
|
|
func openSelectedArticleInBrowser() {
|
2020-07-27 22:43:58 +02:00
|
|
|
timelineModel.openSelectedArticleInBrowser()
|
2020-07-18 03:08:49 +02:00
|
|
|
}
|
2020-07-12 02:52:28 +02:00
|
|
|
|
2020-07-10 01:44:51 +02:00
|
|
|
/// 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)
|
2020-07-10 01:44:51 +02:00
|
|
|
}
|
2020-07-09 23:34:47 +02:00
|
|
|
|
2020-07-10 01:44:51 +02:00
|
|
|
/// 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)
|
2020-07-10 01:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the article with the given articleID
|
2020-07-07 04:14:05 +02:00
|
|
|
func articleFor(_ articleID: String) -> Article? {
|
2020-07-12 01:22:47 +02:00
|
|
|
return timelineModel.articleFor(articleID)
|
2020-07-04 17:20:50 +02:00
|
|
|
}
|
2020-07-07 04:14:05 +02:00
|
|
|
|
2020-06-28 21:21:43 +02:00
|
|
|
}
|
2020-06-29 00:43:20 +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-29 00:43:20 +02:00
|
|
|
}
|
2020-06-30 18:03:33 +02:00
|
|
|
|
|
|
|
// MARK: TimelineModelDelegate
|
|
|
|
|
|
|
|
extension SceneModel: TimelineModelDelegate {
|
|
|
|
|
2020-07-24 18:40:17 +02:00
|
|
|
var selectedFeedsPublisher: AnyPublisher<[Feed], Never>? {
|
|
|
|
return sidebarModel.selectedFeedsPublisher
|
2020-07-18 11:35:44 +02:00
|
|
|
}
|
|
|
|
|
2020-06-30 18:03:33 +02:00
|
|
|
func timelineRequestedWebFeedSelection(_: TimelineModel, webFeed: WebFeed) {
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-07-02 22:30:50 +02:00
|
|
|
|
2020-07-07 04:14:05 +02:00
|
|
|
// MARK: Private
|
2020-07-02 22:30:50 +02:00
|
|
|
|
2020-07-07 04:14:05 +02:00
|
|
|
private extension SceneModel {
|
2020-07-02 22:30:50 +02:00
|
|
|
|
2020-07-19 18:15:02 +02:00
|
|
|
// MARK: Subscriptions
|
|
|
|
func subscribeToToolbarChangeEvents() {
|
2020-07-26 19:00:57 +02:00
|
|
|
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
|
2020-07-26 19:00:57 +02:00
|
|
|
.combineLatest(timelineModel.articlesSubject)
|
2020-07-25 19:46:33 +02:00
|
|
|
.sink { [weak self] _, articles in
|
|
|
|
self?.updateMarkAllAsReadButtonsState(articles: articles)
|
|
|
|
}
|
|
|
|
.store(in: &cancellables)
|
2020-07-10 01:44:51 +02:00
|
|
|
}
|
|
|
|
|
2020-07-25 10:40:04 +02:00
|
|
|
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 {
|
2020-07-25 10:40:04 +02:00
|
|
|
return
|
|
|
|
}
|
2020-07-26 01:36:24 +02:00
|
|
|
self?.accountSyncErrors = syncErrors
|
2020-07-25 10:40:04 +02:00
|
|
|
}.store(in: &cancellables)
|
|
|
|
}
|
|
|
|
|
2020-07-12 01:22:47 +02:00
|
|
|
// MARK: Button State Updates
|
2020-07-10 01:44:51 +02:00
|
|
|
|
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 {
|
2020-07-10 01:44:51 +02:00
|
|
|
readButtonState = nil
|
|
|
|
starButtonState = nil
|
2020-07-17 20:55:54 +02:00
|
|
|
openInBrowserButtonState = nil
|
|
|
|
shareButtonState = nil
|
2020-07-10 01:44:51 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-18 11:58:46 +02:00
|
|
|
if selectedArticles.anyArticleIsUnread() {
|
2020-07-13 23:26:53 +02:00
|
|
|
readButtonState = true
|
2020-07-18 11:58:46 +02:00
|
|
|
} else if selectedArticles.anyArticleIsReadAndCanMarkUnread() {
|
2020-07-13 23:26:53 +02:00
|
|
|
readButtonState = false
|
2020-07-10 01:44:51 +02:00
|
|
|
} else {
|
|
|
|
readButtonState = nil
|
|
|
|
}
|
|
|
|
|
2020-07-18 11:58:46 +02:00
|
|
|
if selectedArticles.anyArticleIsUnstarred() {
|
2020-07-13 23:26:53 +02:00
|
|
|
starButtonState = false
|
2020-07-12 02:52:28 +02:00
|
|
|
} else {
|
2020-07-13 23:26:53 +02:00
|
|
|
starButtonState = true
|
2020-07-12 01:22:47 +02:00
|
|
|
}
|
2020-07-14 00:24:40 +02:00
|
|
|
|
2020-07-18 11:58:46 +02:00
|
|
|
if selectedArticles.count == 1, selectedArticles.first?.preferredLink != nil {
|
2020-07-14 00:24:40 +02:00
|
|
|
openInBrowserButtonState = true
|
|
|
|
shareButtonState = true
|
|
|
|
} else {
|
|
|
|
openInBrowserButtonState = nil
|
|
|
|
shareButtonState = nil
|
|
|
|
}
|
2020-07-10 01:44:51 +02:00
|
|
|
}
|
2020-07-09 23:34:47 +02:00
|
|
|
|
2020-07-02 22:30:50 +02:00
|
|
|
}
|