diff --git a/Multiplatform/Shared/Timeline/TimelineContextMenu.swift b/Multiplatform/Shared/Timeline/TimelineContextMenu.swift index 6b98e519f..839800b97 100644 --- a/Multiplatform/Shared/Timeline/TimelineContextMenu.swift +++ b/Multiplatform/Shared/Timeline/TimelineContextMenu.swift @@ -15,6 +15,50 @@ struct TimelineContextMenu: View { @ViewBuilder var body: some View { + if timelineModel.canMarkIndicatedArticlesAsRead(timelineItem.article) { + Button { + timelineModel.markIndicatedArticlesAsRead(timelineItem.article) + } label: { + Text("Mark as Read") + #if os(iOS) + AppAssets.readOpenImage + #endif + } + } + + if timelineModel.canMarkIndicatedArticlesAsUnread(timelineItem.article) { + Button { + timelineModel.markIndicatedArticlesAsUnread(timelineItem.article) + } label: { + Text("Mark as Unread") + #if os(iOS) + AppAssets.readClosedImage + #endif + } + } + + if timelineModel.canMarkIndicatedArticlesAsStarred(timelineItem.article) { + Button { + timelineModel.markIndicatedArticlesAsStarred(timelineItem.article) + } label: { + Text("Mark as Starred") + #if os(iOS) + AppAssets.starClosedImage + #endif + } + } + + if timelineModel.canMarkIndicatedArticlesAsUnstarred(timelineItem.article) { + Button { + timelineModel.markIndicatedArticlesAsUnstarred(timelineItem.article) + } label: { + Text("Mark as Unstarred") + #if os(iOS) + AppAssets.starOpenImage + #endif + } + } + if timelineModel.canMarkAboveAsRead(timelineItem.article) { Button { timelineModel.markAboveAsRead(timelineItem.article) diff --git a/Multiplatform/Shared/Timeline/TimelineModel.swift b/Multiplatform/Shared/Timeline/TimelineModel.swift index fb9edf275..3bbe17553 100644 --- a/Multiplatform/Shared/Timeline/TimelineModel.swift +++ b/Multiplatform/Shared/Timeline/TimelineModel.swift @@ -133,18 +133,56 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { } } + func canMarkIndicatedArticlesAsRead(_ article: Article) -> Bool { + let articles = indicatedArticles(article) + return articles.anyArticleIsUnread() + } + + func markIndicatedArticlesAsRead(_ article: Article) { + let articles = indicatedArticles(article) + markArticlesWithUndo(articles, statusKey: .read, flag: true) + } + func markSelectedArticlesAsRead() { - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else { - return - } - runCommand(markReadCommand) + markArticlesWithUndo(selectedArticles, statusKey: .read, flag: true) + } + + func canMarkIndicatedArticlesAsUnread(_ article: Article) -> Bool { + let articles = indicatedArticles(article) + return articles.anyArticleIsReadAndCanMarkUnread() + } + + func markIndicatedArticlesAsUnread(_ article: Article) { + let articles = indicatedArticles(article) + markArticlesWithUndo(articles, statusKey: .read, flag: false) } func markSelectedArticlesAsUnread() { - guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else { - return - } - runCommand(markUnreadCommand) + markArticlesWithUndo(selectedArticles, statusKey: .read, flag: false) + } + + func canMarkAboveAsRead(_ article: Article) -> Bool { + let article = indicatedAboveArticle(article) + return articles.articlesAbove(article: article).canMarkAllAsRead() + } + + func markAboveAsRead(_ article: Article) { + let article = indicatedAboveArticle(article) + let articlesToMark = articles.articlesAbove(article: article) + guard !articlesToMark.isEmpty else { return } + markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true) + } + + func canMarkBelowAsRead(_ article: Article) -> Bool { + let article = indicatedBelowArticle(article) + return articles.articlesBelow(article: article).canMarkAllAsRead() + } + + func markBelowAsRead(_ article: Article) { + let article = indicatedBelowArticle(article) + let articlesToMark = articles.articlesBelow(article: article) + guard !articlesToMark.isEmpty else { return } + markArticlesWithUndo(articlesToMark, statusKey: .read, flag: true) } func toggleStarredStatusForSelectedArticles() { @@ -158,50 +196,34 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { } } + func canMarkIndicatedArticlesAsStarred(_ article: Article) -> Bool { + let articles = indicatedArticles(article) + return articles.anyArticleIsUnstarred() + } + + func markIndicatedArticlesAsStarred(_ article: Article) { + let articles = indicatedArticles(article) + markArticlesWithUndo(articles, statusKey: .starred, flag: true) + } + func markSelectedArticlesAsStarred() { - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: true, undoManager: undoManager) else { - return - } - runCommand(markReadCommand) + markArticlesWithUndo(selectedArticles, statusKey: .starred, flag: true) } + func canMarkIndicatedArticlesAsUnstarred(_ article: Article) -> Bool { + let articles = indicatedArticles(article) + return articles.anyArticleIsStarred() + } + + func markIndicatedArticlesAsUnstarred(_ article: Article) { + let articles = indicatedArticles(article) + markArticlesWithUndo(articles, statusKey: .starred, flag: false) + } + func markSelectedArticlesAsUnstarred() { - guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: false, undoManager: undoManager) else { - return - } - runCommand(markUnreadCommand) + markArticlesWithUndo(selectedArticles, statusKey: .starred, flag: false) } - func canMarkAboveAsRead(_ article: Article) -> Bool { - let article = correctedAboveArticle(article) - return articles.articlesAbove(article: article).canMarkAllAsRead() - } - - func canMarkBelowAsRead(_ article: Article) -> Bool { - let article = correctedBelowArticle(article) - return articles.articlesBelow(article: article).canMarkAllAsRead() - } - - func markAboveAsRead(_ article: Article) { - let article = correctedAboveArticle(article) - let articlesToMark = articles.articlesAbove(article: article) - guard !articlesToMark.isEmpty else { return } - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { - return - } - runCommand(markReadCommand) - } - - func markBelowAsRead(_ article: Article) { - let article = correctedBelowArticle(article) - let articlesToMark = articles.articlesBelow(article: article) - guard !articlesToMark.isEmpty else { return } - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { - return - } - runCommand(markReadCommand) - } - func articleFor(_ articleID: String) -> Article? { return idToArticleDictionary[articleID] } @@ -230,7 +252,15 @@ class TimelineModel: ObservableObject, UndoableCommandRunner { private extension TimelineModel { - func correctedAboveArticle(_ article: Article) -> Article { + func indicatedArticles(_ article: Article) -> [Article] { + if selectedArticles.contains(article) { + return selectedArticles + } else { + return [article] + } + } + + func indicatedAboveArticle(_ article: Article) -> Article { if selectedArticles.contains(article) { return selectedArticles.first! } else { @@ -238,7 +268,7 @@ private extension TimelineModel { } } - func correctedBelowArticle(_ article: Article) -> Article { + func indicatedBelowArticle(_ article: Article) -> Article { if selectedArticles.contains(article) { return selectedArticles.last! } else { @@ -246,6 +276,13 @@ private extension TimelineModel { } } + func markArticlesWithUndo(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) { + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) else { + return + } + runCommand(markReadCommand) + } + // MARK: Notifications @objc func statusesDidChange(_ note: Notification) {