From 3e61c7044bd21d0930a17fe85e6e72093dfd4be4 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 9 Jul 2020 16:34:47 -0500 Subject: [PATCH] Stub out mark as read and star functionality --- ...onState.swift => ArticleButtonState.swift} | 10 +++++ .../Shared/Article/ArticleModel.swift | 22 ++++++++++ Multiplatform/Shared/SceneModel.swift | 41 ++++++++++++++++++- .../Shared/SceneNavigationView.swift | 2 + .../iOS/Article/ArticleViewController.swift | 4 +- Multiplatform/macOS/Article/ArticleView.swift | 2 +- .../macOS/Article/WebViewController.swift | 16 ++++---- NetNewsWire.xcodeproj/project.pbxproj | 16 +++++--- 8 files changed, 95 insertions(+), 18 deletions(-) rename Multiplatform/Shared/Article/{ArticleExtractorButtonState.swift => ArticleButtonState.swift} (72%) diff --git a/Multiplatform/Shared/Article/ArticleExtractorButtonState.swift b/Multiplatform/Shared/Article/ArticleButtonState.swift similarity index 72% rename from Multiplatform/Shared/Article/ArticleExtractorButtonState.swift rename to Multiplatform/Shared/Article/ArticleButtonState.swift index 6c9f6d04a..1126253df 100644 --- a/Multiplatform/Shared/Article/ArticleExtractorButtonState.swift +++ b/Multiplatform/Shared/Article/ArticleButtonState.swift @@ -8,6 +8,16 @@ import Foundation +enum ArticleReadButtonState { + case on + case off +} + +enum ArticleStarButtonState { + case on + case off +} + enum ArticleExtractorButtonState { case error case animated diff --git a/Multiplatform/Shared/Article/ArticleModel.swift b/Multiplatform/Shared/Article/ArticleModel.swift index f87c7897e..befd31676 100644 --- a/Multiplatform/Shared/Article/ArticleModel.swift +++ b/Multiplatform/Shared/Article/ArticleModel.swift @@ -20,14 +20,23 @@ protocol ArticleModelDelegate: class { func selectArticle(_: ArticleModel, article: Article) } +protocol ArticleManager: class { + var currentArticle: Article? { get } +} + class ArticleModel: ObservableObject { + weak var articleManager: ArticleManager? weak var delegate: ArticleModelDelegate? var webViewProvider: WebViewProvider? { return delegate?.articleModelWebViewProvider } + var currentArticle: Article? { + return articleManager?.currentArticle + } + // MARK: API func findPrevArticle(_ article: Article) -> Article? { @@ -41,6 +50,19 @@ class ArticleModel: ObservableObject { func selectArticle(_ article: Article) { delegate?.selectArticle(self, article: article) } + + func toggleReadForCurrentArticle() { + if let article = currentArticle { + markArticles([article], statusKey: .starred, flag: !article.status.starred) + } + } + + func toggleStarForCurrentArticle() { + if let article = currentArticle { + markArticles([article], statusKey: .starred, flag: !article.status.starred) + } + } + } diff --git a/Multiplatform/Shared/SceneModel.swift b/Multiplatform/Shared/SceneModel.swift index 0a5ac5bd6..9063c5bc0 100644 --- a/Multiplatform/Shared/SceneModel.swift +++ b/Multiplatform/Shared/SceneModel.swift @@ -9,11 +9,15 @@ import Foundation import Account import Articles +import RSCore final class SceneModel: ObservableObject { @Published var refreshProgressState = RefreshProgressModel.State.none + var undoManager: UndoManager? + var undoableCommands = [UndoableCommand]() + var sidebarModel: SidebarModel? var timelineModel: TimelineModel? var articleModel: ArticleModel? @@ -22,7 +26,7 @@ final class SceneModel: ObservableObject { private var articleIconSchemeHandler: ArticleIconSchemeHandler? = nil private var webViewProvider: WebViewProvider? = nil - // MARK: API + // MARK: Initialization API func startup() { self.refreshProgressModel = RefreshProgressModel() @@ -31,7 +35,28 @@ final class SceneModel: ObservableObject { self.articleIconSchemeHandler = ArticleIconSchemeHandler(sceneModel: self) self.webViewProvider = WebViewProvider(articleIconSchemeHandler: self.articleIconSchemeHandler!) } + + // MARK: Article Status Change API + func toggleReadForCurrentArticle() { + articleModel?.toggleReadForCurrentArticle() + } + + func toggleRead(_ article: Article) { + guard !article.status.read || article.isAvailableToMarkUnread else { return } + markArticles([article], statusKey: .read, flag: !article.status.read) + } + + func toggleStarForCurrentArticle() { + articleModel?.toggleStarForCurrentArticle() + } + + func toggleStar(_ article: Article) { + markArticles([article], statusKey: .starred, flag: !article.status.starred) + } + + // MARK: Resource lookup API + func articleFor(_ articleID: String) -> Article? { return timelineModel?.articleFor(articleID) } @@ -80,8 +105,22 @@ extension SceneModel: ArticleModelDelegate { } +// MARK: UndoableCommandRunner + +extension SceneModel: UndoableCommandRunner { + + 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: Private private extension SceneModel { + } diff --git a/Multiplatform/Shared/SceneNavigationView.swift b/Multiplatform/Shared/SceneNavigationView.swift index 96cab8c76..c39964e5e 100644 --- a/Multiplatform/Shared/SceneNavigationView.swift +++ b/Multiplatform/Shared/SceneNavigationView.swift @@ -10,6 +10,7 @@ import SwiftUI struct SceneNavigationView: View { + @Environment(\.undoManager) var undoManager @StateObject private var sceneModel = SceneModel() @State private var showSheet: Bool = false @State private var sheetToShow: ToolbarSheets = .none @@ -48,6 +49,7 @@ struct SceneNavigationView: View { } .environmentObject(sceneModel) .onAppear { + sceneModel.undoManager = undoManager sceneModel.startup() } .sheet(isPresented: $showSheet, onDismiss: { sheetToShow = .none }) { diff --git a/Multiplatform/iOS/Article/ArticleViewController.swift b/Multiplatform/iOS/Article/ArticleViewController.swift index 6a5aa157c..ae0a8d175 100644 --- a/Multiplatform/iOS/Article/ArticleViewController.swift +++ b/Multiplatform/iOS/Article/ArticleViewController.swift @@ -12,7 +12,7 @@ import Account import Articles import SafariServices -class ArticleViewController: UIViewController { +class ArticleViewController: UIViewController, ArticleManager { weak var articleModel: ArticleModel? @@ -22,7 +22,7 @@ class ArticleViewController: UIViewController { return pageViewController?.viewControllers?.first as? WebViewController } - var article: Article? { + var currentArticle: Article? { didSet { if let controller = currentWebViewController, controller.article != article { controller.setArticle(article) diff --git a/Multiplatform/macOS/Article/ArticleView.swift b/Multiplatform/macOS/Article/ArticleView.swift index 758d5a81b..abd4a17b8 100644 --- a/Multiplatform/macOS/Article/ArticleView.swift +++ b/Multiplatform/macOS/Article/ArticleView.swift @@ -26,7 +26,7 @@ struct ArticleView: NSViewControllerRepresentable { func makeNSViewController(context: Context) -> WebViewController { let controller = WebViewController() controller.articleModel = articleModel - controller.article = article + controller.currentArticle = article return controller } diff --git a/Multiplatform/macOS/Article/WebViewController.swift b/Multiplatform/macOS/Article/WebViewController.swift index a967062db..3bf93c802 100644 --- a/Multiplatform/macOS/Article/WebViewController.swift +++ b/Multiplatform/macOS/Article/WebViewController.swift @@ -22,7 +22,7 @@ protocol WebViewControllerDelegate: class { func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState) } -class WebViewController: NSViewController { +class WebViewController: NSViewController, ArticleManager { private struct MessageName { static let imageWasClicked = "imageWasClicked" @@ -49,7 +49,7 @@ class WebViewController: NSViewController { var articleModel: ArticleModel? weak var delegate: WebViewControllerDelegate? - var article: Article? + var currentArticle: Article? override func loadView() { view = NSView() @@ -117,7 +117,7 @@ class WebViewController: NSViewController { func toggleArticleExtractor() { - guard let article = article else { + guard let article = currentArticle else { return } @@ -247,15 +247,15 @@ private extension WebViewController { if let articleExtractor = articleExtractor, articleExtractor.state == .processing { rendering = ArticleRenderer.loadingHTML(style: style) - } else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = article { + } else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = currentArticle { rendering = ArticleRenderer.articleHTML(article: article, style: style) - } else if let article = article, let extractedArticle = extractedArticle { + } else if let article = currentArticle, let extractedArticle = extractedArticle { if isShowingExtractedArticle { rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) } else { rendering = ArticleRenderer.articleHTML(article: article, style: style) } - } else if let article = article { + } else if let article = currentArticle { rendering = ArticleRenderer.articleHTML(article: article, style: style) } else { rendering = ArticleRenderer.noSelectionHTML(style: style) @@ -297,7 +297,7 @@ private extension WebViewController { } func startArticleExtractor() { - if let link = article?.preferredLink, let extractor = ArticleExtractor(link) { + if let link = currentArticle?.preferredLink, let extractor = ArticleExtractor(link) { extractor.delegate = self extractor.process() articleExtractor = extractor @@ -313,7 +313,7 @@ private extension WebViewController { } func reloadArticleImage() { - guard let article = article else { return } + guard let article = currentArticle else { return } var components = URLComponents() components.scheme = ArticleRenderer.imageIconScheme diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 50492f3b1..9a5aa1a5e 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -209,6 +209,8 @@ 516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; }; + 5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; + 5171B4F624B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630232336657E00E15FFF /* WebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* WebViewProvider.swift */; }; @@ -228,7 +230,7 @@ 5177471C24B387AC00EB0F74 /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471B24B387AC00EB0F74 /* ImageScrollView.swift */; }; 5177471E24B387E100EB0F74 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471D24B387E100EB0F74 /* ImageTransition.swift */; }; 5177472024B3882600EB0F74 /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471F24B3882600EB0F74 /* ImageViewController.swift */; }; - 5177472224B38CAE00EB0F74 /* ArticleExtractorButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */; }; + 5177472224B38CAE00EB0F74 /* ArticleButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177472124B38CAE00EB0F74 /* ArticleButtonState.swift */; }; 5177475C24B39AD500EB0F74 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5177475824B39AD400EB0F74 /* Credits.rtf */; }; 5177475D24B39AD500EB0F74 /* Dedication.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5177475924B39AD400EB0F74 /* Dedication.rtf */; }; 5177475E24B39AD500EB0F74 /* Thanks.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5177475A24B39AD500EB0F74 /* Thanks.rtf */; }; @@ -330,7 +332,7 @@ 51B54A6624B549CB0014348B /* PreloadedWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471324B37D4000EB0F74 /* PreloadedWebView.swift */; }; 51B54A6724B549FE0014348B /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177471524B37D9700EB0F74 /* ArticleIconSchemeHandler.swift */; }; 51B54A6924B54A490014348B /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54A6824B54A490014348B /* IconView.swift */; }; - 51B54AB324B5AC830014348B /* ArticleExtractorButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */; }; + 51B54AB324B5AC830014348B /* ArticleButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5177472124B38CAE00EB0F74 /* ArticleButtonState.swift */; }; 51B54AB624B5B33C0014348B /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54AB524B5B33C0014348B /* WebViewController.swift */; }; 51B54ABC24B5BEF20014348B /* ArticleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54ABB24B5BEF20014348B /* ArticleView.swift */; }; 51B54B6724B6A7960014348B /* WebStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B54B6624B6A7960014348B /* WebStatusBarView.swift */; }; @@ -1919,7 +1921,7 @@ 5177471B24B387AC00EB0F74 /* ImageScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = ""; }; 5177471D24B387E100EB0F74 /* ImageTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransition.swift; sourceTree = ""; }; 5177471F24B3882600EB0F74 /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; - 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButtonState.swift; sourceTree = ""; }; + 5177472124B38CAE00EB0F74 /* ArticleButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleButtonState.swift; sourceTree = ""; }; 5177475824B39AD400EB0F74 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 5177475924B39AD400EB0F74 /* Dedication.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Dedication.rtf; sourceTree = ""; }; 5177475A24B39AD500EB0F74 /* Thanks.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Thanks.rtf; sourceTree = ""; }; @@ -2858,8 +2860,8 @@ 517B2EDF24B3E8FE001AC46C /* blank.html */, 517B2EDE24B3E8FE001AC46C /* page.html */, 517B2EE124B3E8FE001AC46C /* main_multiplatform.js */, + 5177472124B38CAE00EB0F74 /* ArticleButtonState.swift */, 51A5769524AE617200078888 /* ArticleContainerView.swift */, - 5177472124B38CAE00EB0F74 /* ArticleExtractorButtonState.swift */, 5177471524B37D9700EB0F74 /* ArticleIconSchemeHandler.swift */, 51A576BA24AE621800078888 /* ArticleModel.swift */, 5177470524B2910300EB0F74 /* ArticleToolbarModifier.swift */, @@ -5072,6 +5074,7 @@ 51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, 51E499D824A912C200B667CB /* SceneModel.swift in Sources */, 5177470E24B2FF6F00EB0F74 /* ArticleView.swift in Sources */, + 5171B4F624B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */, 65422D1724B75CD1008A2FA2 /* SettingsAddAccountModel.swift in Sources */, 5177471424B37D4000EB0F74 /* PreloadedWebView.swift in Sources */, 517B2EBC24B3E62A001AC46C /* WrapperScriptMessageHandler.swift in Sources */, @@ -5087,7 +5090,7 @@ 17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */, 51E4995124A8734D00B667CB /* ExtensionPointManager.swift in Sources */, 51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */, - 5177472224B38CAE00EB0F74 /* ArticleExtractorButtonState.swift in Sources */, + 5177472224B38CAE00EB0F74 /* ArticleButtonState.swift in Sources */, 5177471A24B3863000EB0F74 /* WebViewProvider.swift in Sources */, 51E4992124A8095000B667CB /* RSImage-Extensions.swift in Sources */, 51E4990324A808BB00B667CB /* FaviconDownloader.swift in Sources */, @@ -5133,13 +5136,14 @@ 51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */, 51919FEF24AB85E400541E64 /* TimelineContainerView.swift in Sources */, 51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */, + 5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */, 5177470724B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */, FA80C11824B0728000974098 /* AddFolderView.swift in Sources */, 51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */, 51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */, 51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */, 51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */, - 51B54AB324B5AC830014348B /* ArticleExtractorButtonState.swift in Sources */, + 51B54AB324B5AC830014348B /* ArticleButtonState.swift in Sources */, 17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */, 514E6C0724AD2B5F00AC6F6E /* Image-Extensions.swift in Sources */, 51E4994D24A8734C00B667CB /* ExtensionPointIdentifer.swift in Sources */,