From 7639ea2780ad07518add88e1b120d3239110222a Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Tue, 21 Jul 2020 16:05:31 +0800 Subject: [PATCH 1/2] Initial work on #2287 --- .../Shared/Sidebar/SidebarContextMenu.swift | 42 +++++++++ .../Shared/Sidebar/SidebarItem.swift | 2 +- .../Shared/Sidebar/SidebarItemView.swift | 2 + .../Shared/Sidebar/SidebarModel.swift | 85 +++++++++++++++++++ .../Shared/Sidebar/SidebarView.swift | 16 +++- NetNewsWire.xcodeproj/project.pbxproj | 4 + 6 files changed, 148 insertions(+), 3 deletions(-) diff --git a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift index fc013bafb..4f06af01a 100644 --- a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift +++ b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift @@ -7,9 +7,14 @@ // import SwiftUI +import RSCore +import Account struct SidebarContextMenu: View { + @Environment(\.undoManager) var undoManager + @Environment(\.openURL) var openURL + @EnvironmentObject private var sidebarModel: SidebarModel @Binding var showInspector: Bool var sidebarItem: SidebarItem @@ -26,6 +31,7 @@ struct SidebarContextMenu: View { #endif } Button { + sidebarModel.markAllAsRead(account: sidebarItem.represented as! Account) } label: { Text("Mark All As Read") #if os(iOS) @@ -36,6 +42,10 @@ struct SidebarContextMenu: View { if sidebarItem.representedType == .pseudoFeed { Button { + guard let feed = sidebarItem.feed else { + return + } + sidebarModel.markAllAsRead(feed: feed) } label: { Text("Mark All As Read") #if os(iOS) @@ -54,6 +64,10 @@ struct SidebarContextMenu: View { #endif } Button { + guard let feed = sidebarItem.feed else { + return + } + sidebarModel.markAllAsRead(feed: feed) } label: { Text("Mark All As Read") #if os(iOS) @@ -62,6 +76,11 @@ struct SidebarContextMenu: View { } Divider() Button { + guard let homepage = (sidebarItem.feed as? WebFeed)?.homePageURL, + let url = URL(string: homepage) else { + return + } + openURL(url) } label: { Text("Open Home Page") #if os(iOS) @@ -70,6 +89,15 @@ struct SidebarContextMenu: View { } Divider() Button { + guard let feedUrl = (sidebarItem.feed as? WebFeed)?.url else { + return + } + #if os(macOS) + URLPasteboardWriter.write(urlString: feedUrl, to: NSPasteboard.general) + #else + UIPasteboard.general.string = feedUrl + #endif + } label: { Text("Copy Feed URL") #if os(iOS) @@ -77,6 +105,14 @@ struct SidebarContextMenu: View { #endif } Button { + guard let homepage = (sidebarItem.feed as? WebFeed)?.homePageURL else { + return + } + #if os(macOS) + URLPasteboardWriter.write(urlString: homepage, to: NSPasteboard.general) + #else + UIPasteboard.general.string = homepage + #endif } label: { Text("Copy Home Page URL") #if os(iOS) @@ -85,6 +121,7 @@ struct SidebarContextMenu: View { } Divider() Button { + sidebarModel.deleteItems(item: sidebarItem) } label: { Text("Delete") #if os(iOS) @@ -103,6 +140,10 @@ struct SidebarContextMenu: View { #endif } Button { + guard let feed = sidebarItem.feed else { + return + } + sidebarModel.markAllAsRead(feed: feed) } label: { Text("Mark All As Read") #if os(iOS) @@ -111,6 +152,7 @@ struct SidebarContextMenu: View { } Divider() Button { + sidebarModel.deleteItems(item: sidebarItem) } label: { Text("Delete") #if os(iOS) diff --git a/Multiplatform/Shared/Sidebar/SidebarItem.swift b/Multiplatform/Shared/Sidebar/SidebarItem.swift index 01ae100d2..b4cda8772 100644 --- a/Multiplatform/Shared/Sidebar/SidebarItem.swift +++ b/Multiplatform/Shared/Sidebar/SidebarItem.swift @@ -6,7 +6,7 @@ // Copyright © 2020 Ranchero Software. All rights reserved. // -import Foundation +import SwiftUI import RSCore import Account diff --git a/Multiplatform/Shared/Sidebar/SidebarItemView.swift b/Multiplatform/Shared/Sidebar/SidebarItemView.swift index 338d329c4..e0b0d0a7e 100644 --- a/Multiplatform/Shared/Sidebar/SidebarItemView.swift +++ b/Multiplatform/Shared/Sidebar/SidebarItemView.swift @@ -12,6 +12,7 @@ import Account struct SidebarItemView: View { @StateObject var feedIconImageLoader = FeedIconImageLoader() + @EnvironmentObject private var sidebarModel: SidebarModel @State private var showInspector: Bool = false var sidebarItem: SidebarItem @@ -39,6 +40,7 @@ struct SidebarItemView: View { } }.contextMenu { SidebarContextMenu(showInspector: $showInspector, sidebarItem: sidebarItem) + .environmentObject(sidebarModel) } .sheet(isPresented: $showInspector, onDismiss: { showInspector = false}) { InspectorView(sidebarItem: sidebarItem) diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index 649313fdb..bb2c802ba 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -10,6 +10,7 @@ import Foundation import Combine import RSCore import Account +import Articles protocol SidebarModelDelegate: class { func unreadCount(for: Feed) -> Int @@ -54,6 +55,90 @@ class SidebarModel: ObservableObject, UndoableCommandRunner { } +// MARK: Side Context Menu Actions +extension SidebarModel { + + func markAllAsRead(feed: Feed) { + + var articles = Set
() + let fetchedArticles = try! feed.fetchArticles() + for article in fetchedArticles { + articles.insert(article) + } + + for selectedFeed in selectedFeeds { + let fetchedArticles = try! selectedFeed.fetchArticles() + for article in fetchedArticles { + articles.insert(article) + } + } + + markAllAsRead(Array(articles)) + } + + func markAllAsRead(account: Account) { + var articles = Set
() + for feed in account.flattenedWebFeeds() { + let unreadArticles = try! feed.fetchUnreadArticles() + articles.formUnion(unreadArticles) + } + markAllAsRead(Array(articles)) + } + + + /// Marks provided artices as read. + /// - Parameter articles: An array of `Article`s. + /// - Warning: An `UndoManager` is created here as the `Environment`'s undo manager appears to be `nil`. + private func markAllAsRead(_ articles: [Article]) { + guard let undoManager = undoManager ?? UndoManager(), + let markAsReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { + + return + } + runCommand(markAsReadCommand) + } + + func deleteItems(item: SidebarItem) { + if selectedFeeds.count > 0 { + for feed in selectedFeeds { + if feed is WebFeed { + let account = (feed as! WebFeed).account + account?.removeWebFeed(feed as! WebFeed) + } + if feed is Folder { + let account = (feed as! Folder).account + account?.removeFolder(feed as! Folder, completion: { (result) in + switch result { + case .success( _): + print("Deleted folder") + case .failure(let err): + print(err.localizedDescription) + } + }) + } + } + } else { + if item.feed is WebFeed { + let account = (item.feed as! WebFeed).account + account?.removeWebFeed(item.feed as! WebFeed) + } + if item.feed is Folder { + let account = (item.feed as! Folder).account + account?.removeFolder(item.feed as! Folder, completion: { (result) in + switch result { + case .success( _): + print("Deleted folder") + case .failure(let err): + print(err.localizedDescription) + } + }) + } + } + } +} + + + // MARK: Private private extension SidebarModel { diff --git a/Multiplatform/Shared/Sidebar/SidebarView.swift b/Multiplatform/Shared/Sidebar/SidebarView.swift index b0f33bd65..40187db63 100644 --- a/Multiplatform/Shared/Sidebar/SidebarView.swift +++ b/Multiplatform/Shared/Sidebar/SidebarView.swift @@ -13,11 +13,12 @@ struct SidebarView: View { // I had to comment out SceneStorage because it blows up if used on macOS // @SceneStorage("expandedContainers") private var expandedContainerData = Data() + @Environment(\.undoManager) var undoManager @StateObject private var expandedContainers = SidebarExpandedContainers() @EnvironmentObject private var refreshProgress: RefreshProgressModel @EnvironmentObject private var sceneModel: SceneModel @EnvironmentObject private var sidebarModel: SidebarModel - + private let threshold: CGFloat = 80 @State private var previousScrollOffset: CGFloat = 0 @State private var scrollOffset: CGFloat = 0 @@ -60,6 +61,9 @@ struct SidebarView: View { .transition(.move(edge: .bottom)) } } + .onAppear { + sidebarModel.undoManager = undoManager + } #else ZStack(alignment: .top) { List { @@ -74,7 +78,11 @@ struct SidebarView: View { ProgressView().offset(y: -40) } } + .onAppear { + sidebarModel.undoManager = undoManager + } #endif + // .onAppear { // expandedContainers.data = expandedContainerData // } @@ -158,15 +166,19 @@ struct SidebarView: View { } } label: { #if os(macOS) - SidebarItemView(sidebarItem: sidebarItem).padding(.leading, 4) + SidebarItemView(sidebarItem: sidebarItem) + .padding(.leading, 4) + .environmentObject(sidebarModel) #else if sidebarItem.representedType == .smartFeedController { GeometryReader { proxy in SidebarItemView(sidebarItem: sidebarItem) .preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .movingView, bounds: proxy.frame(in: .global))]) + .environmentObject(sidebarModel) } } else { SidebarItemView(sidebarItem: sidebarItem) + .environmentObject(sidebarModel) } #endif } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index f8ad4b4e8..2cca6f94b 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */; }; 1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529624AA1CD000D65E66 /* MacPreferencesView.swift */; }; 1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; }; + 173A66B024C6C5C800DDA840 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; + 173A66D224C6C5C800DDA840 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */; }; @@ -5255,6 +5257,7 @@ 51E498F724A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */, 175942AA24AD533200585066 /* RefreshInterval.swift in Sources */, 51E4993524A867E800B667CB /* AppNotifications.swift in Sources */, + 173A66D224C6C5C800DDA840 /* DeleteCommand.swift in Sources */, 51C0515E24A77DF800194D5E /* MainApp.swift in Sources */, 51919FF724AB8B7700541E64 /* TimelineView.swift in Sources */, 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */, @@ -5400,6 +5403,7 @@ 1799E6CE24C320D600511E91 /* InspectorModel.swift in Sources */, 514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */, 1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */, + 173A66B024C6C5C800DDA840 /* DeleteCommand.swift in Sources */, 1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */, 1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */, 5177470424B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */, From 1bd0bfce371a92b715352f75aef87420f6a2412c Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Wed, 22 Jul 2020 21:47:40 +0800 Subject: [PATCH 2/2] Minor changes for #2287 --- .../Shared/Sidebar/SidebarContextMenu.swift | 5 ++- .../Shared/Sidebar/SidebarModel.swift | 35 ++++++++++--------- NetNewsWire.xcodeproj/project.pbxproj | 4 --- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift index 4f06af01a..d9a27f295 100644 --- a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift +++ b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift @@ -20,7 +20,7 @@ struct SidebarContextMenu: View { @ViewBuilder var body: some View { - + // MARK: Account Context Menu if sidebarItem.representedType == .account { Button { showInspector = true @@ -40,6 +40,7 @@ struct SidebarContextMenu: View { } } + // MARK: Pseudofeed Context Menu if sidebarItem.representedType == .pseudoFeed { Button { guard let feed = sidebarItem.feed else { @@ -54,6 +55,7 @@ struct SidebarContextMenu: View { } } + // MARK: Webfeed Context Menu if sidebarItem.representedType == .webFeed { Button { showInspector = true @@ -130,6 +132,7 @@ struct SidebarContextMenu: View { } } + // MARK: Folder Context Menu if sidebarItem.representedType == .folder { Button { showInspector = true diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index bb2c802ba..07e2f278a 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -99,9 +99,11 @@ extension SidebarModel { } func deleteItems(item: SidebarItem) { + #if os(macOS) if selectedFeeds.count > 0 { for feed in selectedFeeds { if feed is WebFeed { + print(feed.nameForDisplay) let account = (feed as! WebFeed).account account?.removeWebFeed(feed as! WebFeed) } @@ -117,23 +119,24 @@ extension SidebarModel { }) } } - } else { - if item.feed is WebFeed { - let account = (item.feed as! WebFeed).account - account?.removeWebFeed(item.feed as! WebFeed) - } - if item.feed is Folder { - let account = (item.feed as! Folder).account - account?.removeFolder(item.feed as! Folder, completion: { (result) in - switch result { - case .success( _): - print("Deleted folder") - case .failure(let err): - print(err.localizedDescription) - } - }) - } } + #else + if item.feed is WebFeed { + let account = (item.feed as! WebFeed).account + account?.removeWebFeed(item.feed as! WebFeed) + } + if item.feed is Folder { + let account = (item.feed as! Folder).account + account?.removeFolder(item.feed as! Folder, completion: { (result) in + switch result { + case .success( _): + print("Deleted folder") + case .failure(let err): + print(err.localizedDescription) + } + }) + } + #endif } } diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 2cca6f94b..f8ad4b4e8 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -17,8 +17,6 @@ 1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */; }; 1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529624AA1CD000D65E66 /* MacPreferencesView.swift */; }; 1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; }; - 173A66B024C6C5C800DDA840 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; - 173A66D224C6C5C800DDA840 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */; }; @@ -5257,7 +5255,6 @@ 51E498F724A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */, 175942AA24AD533200585066 /* RefreshInterval.swift in Sources */, 51E4993524A867E800B667CB /* AppNotifications.swift in Sources */, - 173A66D224C6C5C800DDA840 /* DeleteCommand.swift in Sources */, 51C0515E24A77DF800194D5E /* MainApp.swift in Sources */, 51919FF724AB8B7700541E64 /* TimelineView.swift in Sources */, 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */, @@ -5403,7 +5400,6 @@ 1799E6CE24C320D600511E91 /* InspectorModel.swift in Sources */, 514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */, 1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */, - 173A66B024C6C5C800DDA840 /* DeleteCommand.swift in Sources */, 1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */, 1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */, 5177470424B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */,