diff --git a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift index fc013bafb..d9a27f295 100644 --- a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift +++ b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift @@ -7,15 +7,20 @@ // 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 @ViewBuilder var body: some View { - + // MARK: Account Context Menu if sidebarItem.representedType == .account { Button { showInspector = true @@ -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) @@ -34,8 +40,13 @@ struct SidebarContextMenu: View { } } + // MARK: Pseudofeed Context Menu 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) @@ -44,6 +55,7 @@ struct SidebarContextMenu: View { } } + // MARK: Webfeed Context Menu if sidebarItem.representedType == .webFeed { Button { showInspector = true @@ -54,6 +66,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 +78,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 +91,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 +107,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 +123,7 @@ struct SidebarContextMenu: View { } Divider() Button { + sidebarModel.deleteItems(item: sidebarItem) } label: { Text("Delete") #if os(iOS) @@ -93,6 +132,7 @@ struct SidebarContextMenu: View { } } + // MARK: Folder Context Menu if sidebarItem.representedType == .folder { Button { showInspector = true @@ -103,6 +143,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 +155,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 82f94c536..b14e18a62 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 @@ -53,6 +54,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 084edc580..f588ebcca 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 @@ -49,6 +50,93 @@ 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 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) + } + 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) + } + }) + } + #endif + } +} + + + // 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 }