Merge pull request #2293 from stuartbreckenridge/feature/sidebar-context-menus
Feature/sidebar context menus
This commit is contained in:
commit
48c721a468
@ -7,15 +7,20 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import RSCore
|
||||||
|
import Account
|
||||||
|
|
||||||
struct SidebarContextMenu: View {
|
struct SidebarContextMenu: View {
|
||||||
|
|
||||||
|
@Environment(\.undoManager) var undoManager
|
||||||
|
@Environment(\.openURL) var openURL
|
||||||
|
@EnvironmentObject private var sidebarModel: SidebarModel
|
||||||
@Binding var showInspector: Bool
|
@Binding var showInspector: Bool
|
||||||
var sidebarItem: SidebarItem
|
var sidebarItem: SidebarItem
|
||||||
|
|
||||||
|
|
||||||
@ViewBuilder var body: some View {
|
@ViewBuilder var body: some View {
|
||||||
|
// MARK: Account Context Menu
|
||||||
if sidebarItem.representedType == .account {
|
if sidebarItem.representedType == .account {
|
||||||
Button {
|
Button {
|
||||||
showInspector = true
|
showInspector = true
|
||||||
@ -26,6 +31,7 @@ struct SidebarContextMenu: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
|
sidebarModel.markAllAsRead(account: sidebarItem.represented as! Account)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Mark All As Read")
|
Text("Mark All As Read")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -34,8 +40,13 @@ struct SidebarContextMenu: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Pseudofeed Context Menu
|
||||||
if sidebarItem.representedType == .pseudoFeed {
|
if sidebarItem.representedType == .pseudoFeed {
|
||||||
Button {
|
Button {
|
||||||
|
guard let feed = sidebarItem.feed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sidebarModel.markAllAsRead(feed: feed)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Mark All As Read")
|
Text("Mark All As Read")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -44,6 +55,7 @@ struct SidebarContextMenu: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Webfeed Context Menu
|
||||||
if sidebarItem.representedType == .webFeed {
|
if sidebarItem.representedType == .webFeed {
|
||||||
Button {
|
Button {
|
||||||
showInspector = true
|
showInspector = true
|
||||||
@ -54,6 +66,10 @@ struct SidebarContextMenu: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
|
guard let feed = sidebarItem.feed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sidebarModel.markAllAsRead(feed: feed)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Mark All As Read")
|
Text("Mark All As Read")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -62,6 +78,11 @@ struct SidebarContextMenu: View {
|
|||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
Button {
|
Button {
|
||||||
|
guard let homepage = (sidebarItem.feed as? WebFeed)?.homePageURL,
|
||||||
|
let url = URL(string: homepage) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
openURL(url)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Open Home Page")
|
Text("Open Home Page")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -70,6 +91,15 @@ struct SidebarContextMenu: View {
|
|||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
Button {
|
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: {
|
} label: {
|
||||||
Text("Copy Feed URL")
|
Text("Copy Feed URL")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -77,6 +107,14 @@ struct SidebarContextMenu: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
Button {
|
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: {
|
} label: {
|
||||||
Text("Copy Home Page URL")
|
Text("Copy Home Page URL")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -85,6 +123,7 @@ struct SidebarContextMenu: View {
|
|||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
Button {
|
Button {
|
||||||
|
sidebarModel.deleteItems(item: sidebarItem)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Delete")
|
Text("Delete")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -93,6 +132,7 @@ struct SidebarContextMenu: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Folder Context Menu
|
||||||
if sidebarItem.representedType == .folder {
|
if sidebarItem.representedType == .folder {
|
||||||
Button {
|
Button {
|
||||||
showInspector = true
|
showInspector = true
|
||||||
@ -103,6 +143,10 @@ struct SidebarContextMenu: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
|
guard let feed = sidebarItem.feed else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sidebarModel.markAllAsRead(feed: feed)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Mark All As Read")
|
Text("Mark All As Read")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -111,6 +155,7 @@ struct SidebarContextMenu: View {
|
|||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
Button {
|
Button {
|
||||||
|
sidebarModel.deleteItems(item: sidebarItem)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Delete")
|
Text("Delete")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import SwiftUI
|
||||||
import RSCore
|
import RSCore
|
||||||
import Account
|
import Account
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import Account
|
|||||||
struct SidebarItemView: View {
|
struct SidebarItemView: View {
|
||||||
|
|
||||||
@StateObject var feedIconImageLoader = FeedIconImageLoader()
|
@StateObject var feedIconImageLoader = FeedIconImageLoader()
|
||||||
|
@EnvironmentObject private var sidebarModel: SidebarModel
|
||||||
@State private var showInspector: Bool = false
|
@State private var showInspector: Bool = false
|
||||||
var sidebarItem: SidebarItem
|
var sidebarItem: SidebarItem
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ struct SidebarItemView: View {
|
|||||||
}
|
}
|
||||||
}.contextMenu {
|
}.contextMenu {
|
||||||
SidebarContextMenu(showInspector: $showInspector, sidebarItem: sidebarItem)
|
SidebarContextMenu(showInspector: $showInspector, sidebarItem: sidebarItem)
|
||||||
|
.environmentObject(sidebarModel)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showInspector, onDismiss: { showInspector = false}) {
|
.sheet(isPresented: $showInspector, onDismiss: { showInspector = false}) {
|
||||||
InspectorView(sidebarItem: sidebarItem)
|
InspectorView(sidebarItem: sidebarItem)
|
||||||
|
@ -10,6 +10,7 @@ import Foundation
|
|||||||
import Combine
|
import Combine
|
||||||
import RSCore
|
import RSCore
|
||||||
import Account
|
import Account
|
||||||
|
import Articles
|
||||||
|
|
||||||
protocol SidebarModelDelegate: class {
|
protocol SidebarModelDelegate: class {
|
||||||
func unreadCount(for: Feed) -> Int
|
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<Article>()
|
||||||
|
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<Article>()
|
||||||
|
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
|
// MARK: Private
|
||||||
|
|
||||||
private extension SidebarModel {
|
private extension SidebarModel {
|
||||||
|
@ -13,6 +13,7 @@ struct SidebarView: View {
|
|||||||
|
|
||||||
// I had to comment out SceneStorage because it blows up if used on macOS
|
// I had to comment out SceneStorage because it blows up if used on macOS
|
||||||
// @SceneStorage("expandedContainers") private var expandedContainerData = Data()
|
// @SceneStorage("expandedContainers") private var expandedContainerData = Data()
|
||||||
|
@Environment(\.undoManager) var undoManager
|
||||||
@StateObject private var expandedContainers = SidebarExpandedContainers()
|
@StateObject private var expandedContainers = SidebarExpandedContainers()
|
||||||
@EnvironmentObject private var refreshProgress: RefreshProgressModel
|
@EnvironmentObject private var refreshProgress: RefreshProgressModel
|
||||||
@EnvironmentObject private var sceneModel: SceneModel
|
@EnvironmentObject private var sceneModel: SceneModel
|
||||||
@ -60,6 +61,9 @@ struct SidebarView: View {
|
|||||||
.transition(.move(edge: .bottom))
|
.transition(.move(edge: .bottom))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
sidebarModel.undoManager = undoManager
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
List {
|
List {
|
||||||
@ -74,7 +78,11 @@ struct SidebarView: View {
|
|||||||
ProgressView().offset(y: -40)
|
ProgressView().offset(y: -40)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
sidebarModel.undoManager = undoManager
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// .onAppear {
|
// .onAppear {
|
||||||
// expandedContainers.data = expandedContainerData
|
// expandedContainers.data = expandedContainerData
|
||||||
// }
|
// }
|
||||||
@ -158,15 +166,19 @@ struct SidebarView: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
SidebarItemView(sidebarItem: sidebarItem).padding(.leading, 4)
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
|
.padding(.leading, 4)
|
||||||
|
.environmentObject(sidebarModel)
|
||||||
#else
|
#else
|
||||||
if sidebarItem.representedType == .smartFeedController {
|
if sidebarItem.representedType == .smartFeedController {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
SidebarItemView(sidebarItem: sidebarItem)
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
.preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .movingView, bounds: proxy.frame(in: .global))])
|
.preference(key: RefreshKeyTypes.PrefKey.self, value: [RefreshKeyTypes.PrefData(vType: .movingView, bounds: proxy.frame(in: .global))])
|
||||||
|
.environmentObject(sidebarModel)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SidebarItemView(sidebarItem: sidebarItem)
|
SidebarItemView(sidebarItem: sidebarItem)
|
||||||
|
.environmentObject(sidebarModel)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user