Enabled toolbar buttons for read and star

This commit is contained in:
Maurice Parker 2020-07-11 19:52:28 -05:00
parent 92ac91d9d5
commit 7998b5450b
4 changed files with 85 additions and 13 deletions

View File

@ -33,7 +33,7 @@ struct ArticleToolbarModifier: ViewModifier {
} }
ToolbarItem(placement: .bottomBar) { ToolbarItem(placement: .bottomBar) {
Button(action: { }, label: { Button(action: { sceneModel.toggleReadStatusForSelectedArticles() }, label: {
if sceneModel.readButtonState == .on { if sceneModel.readButtonState == .on {
AppAssets.readClosedImage AppAssets.readClosedImage
} else { } else {
@ -49,7 +49,7 @@ struct ArticleToolbarModifier: ViewModifier {
} }
ToolbarItem(placement: .bottomBar) { ToolbarItem(placement: .bottomBar) {
Button(action: { }, label: { Button(action: { sceneModel.toggleStarredStatusForSelectedArticles() }, label: {
if sceneModel.starButtonState == .on { if sceneModel.starButtonState == .on {
AppAssets.starClosedImage AppAssets.starClosedImage
} else { } else {

View File

@ -7,6 +7,7 @@
// //
import Foundation import Foundation
import Combine
import Account import Account
import Articles import Articles
import RSCore import RSCore
@ -25,6 +26,8 @@ final class SceneModel: ObservableObject {
private(set) var sidebarModel = SidebarModel() private(set) var sidebarModel = SidebarModel()
private(set) var timelineModel = TimelineModel() private(set) var timelineModel = TimelineModel()
private var selectedArticlesCancellable: AnyCancellable?
// MARK: Initialization API // MARK: Initialization API
/// Prepares the SceneModel to be used in the views /// Prepares the SceneModel to be used in the views
@ -39,10 +42,22 @@ final class SceneModel: ObservableObject {
self.webViewProvider = WebViewProvider(articleIconSchemeHandler: self.articleIconSchemeHandler!) self.webViewProvider = WebViewProvider(articleIconSchemeHandler: self.articleIconSchemeHandler!)
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
selectedArticlesCancellable = timelineModel.$selectedArticles.sink { [weak self] articles in
self?.updateArticleState(articles: articles)
}
} }
// MARK: Article Management API // MARK: Article Management API
func toggleReadStatusForSelectedArticles() {
timelineModel.toggleReadStatusForSelectedArticles()
}
func toggleStarredStatusForSelectedArticles() {
timelineModel.toggleStarredStatusForSelectedArticles()
}
/// Retrieves the article before the given article in the Timeline /// Retrieves the article before the given article in the Timeline
func findPrevArticle(_ article: Article) -> Article? { func findPrevArticle(_ article: Article) -> Article? {
return timelineModel.findPrevArticle(article) return timelineModel.findPrevArticle(article)
@ -92,15 +107,13 @@ private extension SceneModel {
} }
let selectedArticleIDs = timelineModel.selectedArticles.map { $0.articleID } let selectedArticleIDs = timelineModel.selectedArticles.map { $0.articleID }
if !articleIDs.intersection(selectedArticleIDs).isEmpty { if !articleIDs.intersection(selectedArticleIDs).isEmpty {
updateArticleState() updateArticleState(articles: timelineModel.selectedArticles)
} }
} }
// MARK: Button State Updates // MARK: Button State Updates
func updateArticleState() { func updateArticleState(articles: [Article]) {
let articles = timelineModel.selectedArticles
guard !articles.isEmpty else { guard !articles.isEmpty else {
readButtonState = nil readButtonState = nil
starButtonState = nil starButtonState = nil
@ -116,9 +129,9 @@ private extension SceneModel {
} }
if articles.anyArticleIsUnstarred() { if articles.anyArticleIsUnstarred() {
starButtonState = .on
} else {
starButtonState = .off starButtonState = .off
} else {
starButtonState = .on
} }
} }

View File

@ -98,7 +98,7 @@ struct SceneNavigationView: View {
}).help("Go to Next Unread").padding(.trailing, 40) }).help("Go to Next Unread").padding(.trailing, 40)
} }
ToolbarItem { ToolbarItem {
Button(action: { }, label: { Button(action: { sceneModel.toggleReadStatusForSelectedArticles() }, label: {
if sceneModel.readButtonState == .on { if sceneModel.readButtonState == .on {
AppAssets.readClosedImage AppAssets.readClosedImage
} else { } else {
@ -109,7 +109,7 @@ struct SceneNavigationView: View {
.help(sceneModel.readButtonState == .on ? "Mark as Unread" : "Mark as Read") .help(sceneModel.readButtonState == .on ? "Mark as Unread" : "Mark as Read")
} }
ToolbarItem { ToolbarItem {
Button(action: { }, label: { Button(action: { sceneModel.toggleStarredStatusForSelectedArticles() }, label: {
if sceneModel.starButtonState == .on { if sceneModel.starButtonState == .on {
AppAssets.starClosedImage AppAssets.starClosedImage
} else { } else {

View File

@ -31,6 +31,7 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
private var selectedArticleIDsCancellable: AnyCancellable? private var selectedArticleIDsCancellable: AnyCancellable?
private var selectedArticleIDCancellable: AnyCancellable? private var selectedArticleIDCancellable: AnyCancellable?
private var selectedArticlesCancellable: AnyCancellable?
private var fetchSerialNumber = 0 private var fetchSerialNumber = 0
private let fetchRequestQueue = FetchRequestQueue() private let fetchRequestQueue = FetchRequestQueue()
@ -84,6 +85,16 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
self.selectedArticles = [article] self.selectedArticles = [article]
} }
} }
// TODO: This should be rewritten to use Combine correctly
selectedArticlesCancellable = $selectedArticles.sink { articles in
if articles.count == 1 {
let article = articles.first!
if !article.status.read {
markArticles(Set([article]), statusKey: .read, flag: true)
}
}
}
} }
// MARK: API // MARK: API
@ -97,6 +108,56 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
fetchAndReplaceArticlesAsync(feeds: feeds) fetchAndReplaceArticlesAsync(feeds: feeds)
} }
func toggleReadStatusForSelectedArticles() {
guard !selectedArticles.isEmpty else {
return
}
if selectedArticles.anyArticleIsUnread() {
markSelectedArticlesAsRead()
} else {
markSelectedArticlesAsUnread()
}
}
func markSelectedArticlesAsRead() {
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else {
return
}
runCommand(markReadCommand)
}
func markSelectedArticlesAsUnread() {
guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else {
return
}
runCommand(markUnreadCommand)
}
func toggleStarredStatusForSelectedArticles() {
guard !selectedArticles.isEmpty else {
return
}
if selectedArticles.anyArticleIsUnstarred() {
markSelectedArticlesAsStarred()
} else {
markSelectedArticlesAsUnstarred()
}
}
func markSelectedArticlesAsStarred() {
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: true, undoManager: undoManager) else {
return
}
runCommand(markReadCommand)
}
func markSelectedArticlesAsUnstarred() {
guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: false, undoManager: undoManager) else {
return
}
runCommand(markUnreadCommand)
}
func articleFor(_ articleID: String) -> Article? { func articleFor(_ articleID: String) -> Article? {
return idToArticleDictionary[articleID] return idToArticleDictionary[articleID]
} }
@ -138,7 +199,7 @@ private extension TimelineModel {
} }
} }
// MARK: // MARK: Timeline Management
func sortParametersDidChange() { func sortParametersDidChange() {
performBlockAndRestoreSelection { performBlockAndRestoreSelection {
@ -202,6 +263,4 @@ private extension TimelineModel {
// TODO: Update unread counts and other item done in didSet on AppKit // TODO: Update unread counts and other item done in didSet on AppKit
} }
// MARK: - Notifications
} }