Partially fixes #566
• Adds a preference to show alerts to confirm deletion of feeds (default is true) and this is configurable in Settings / Preferences • Supports single (iOS/macOS) and multiple selection (macOS) for deletion • Until folders are selectable (future beta, hopefully), selecting them for deletion is disabled.
This commit is contained in:
parent
9218b4d95c
commit
06826a23bb
|
@ -61,6 +61,9 @@ final class AppDefaults: ObservableObject {
|
|||
static let timelineGroupByFeed = "timelineGroupByFeed"
|
||||
static let timelineIconDimensions = "timelineIconDimensions"
|
||||
static let timelineNumberOfLines = "timelineNumberOfLines"
|
||||
|
||||
// Sidebar Defaults
|
||||
static let sidebarConfirmDelete = "sidebarConfirmDelete"
|
||||
|
||||
// iOS Defaults
|
||||
static let refreshClearsReadArticles = "refreshClearsReadArticles"
|
||||
|
@ -198,7 +201,7 @@ final class AppDefaults: ObservableObject {
|
|||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@AppStorage(wrappedValue: 40.0, Key.timelineIconDimensions, store: store) var timelineIconDimensions: Double {
|
||||
didSet {
|
||||
objectWillChange.send()
|
||||
|
@ -212,6 +215,14 @@ final class AppDefaults: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Sidebar
|
||||
@AppStorage(wrappedValue: true, Key.sidebarConfirmDelete, store: store) var sidebarConfirmDelete: Bool {
|
||||
didSet {
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Refresh
|
||||
@AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool
|
||||
|
||||
|
|
|
@ -123,7 +123,12 @@ struct SidebarContextMenu: View {
|
|||
}
|
||||
Divider()
|
||||
Button {
|
||||
sidebarModel.deleteFromAccount.send(sidebarItem.feed!)
|
||||
if AppDefaults.shared.sidebarConfirmDelete == false {
|
||||
sidebarModel.deleteFromAccount.send(sidebarItem.feed!)
|
||||
} else {
|
||||
sidebarModel.sidebarItemToDelete = sidebarItem.feed!
|
||||
sidebarModel.showDeleteConfirmation = true
|
||||
}
|
||||
} label: {
|
||||
Text("Delete")
|
||||
#if os(iOS)
|
||||
|
@ -153,15 +158,26 @@ struct SidebarContextMenu: View {
|
|||
AppAssets.markAllAsReadImage
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
You cannot select folder level items in b4. Delete is disabled for the time being.
|
||||
*/
|
||||
/*
|
||||
Divider()
|
||||
Button {
|
||||
sidebarModel.deleteFromAccount.send(sidebarItem.feed!)
|
||||
if AppDefaults.shared.sidebarConfirmDelete == false {
|
||||
sidebarModel.deleteFromAccount.send(sidebarItem.feed!)
|
||||
} else {
|
||||
sidebarModel.sidebarContextMenuItem = sidebarItem.feed
|
||||
sidebarModel.showDeleteConfirmation = true
|
||||
}
|
||||
} label: {
|
||||
Text("Delete")
|
||||
#if os(iOS)
|
||||
AppAssets.deleteImage
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ class SidebarModel: ObservableObject, UndoableCommandRunner {
|
|||
@Published var selectedFeedIdentifier: FeedIdentifier? = .none
|
||||
@Published var isReadFiltered = false
|
||||
@Published var expandedContainers = SidebarExpandedContainers()
|
||||
@Published var showDeleteConfirmation: Bool = false
|
||||
|
||||
weak var delegate: SidebarModelDelegate?
|
||||
|
||||
|
@ -33,6 +34,8 @@ class SidebarModel: ObservableObject, UndoableCommandRunner {
|
|||
var markAllAsReadInAccount = PassthroughSubject<Account, Never>()
|
||||
var deleteFromAccount = PassthroughSubject<Feed, Never>()
|
||||
|
||||
var sidebarItemToDelete: Feed?
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
var undoManager: UndoManager?
|
||||
|
@ -50,6 +53,37 @@ class SidebarModel: ObservableObject, UndoableCommandRunner {
|
|||
|
||||
}
|
||||
|
||||
|
||||
extension SidebarModel {
|
||||
|
||||
func countOfFeedsToDelete() -> Int {
|
||||
var selectedFeeds = selectedFeedIdentifiers
|
||||
|
||||
if sidebarItemToDelete != nil {
|
||||
selectedFeeds.insert(sidebarItemToDelete!.feedID!)
|
||||
}
|
||||
|
||||
return selectedFeeds.count
|
||||
}
|
||||
|
||||
|
||||
func namesOfFeedsToDelete() -> String {
|
||||
var selectedFeeds = selectedFeedIdentifiers
|
||||
|
||||
if sidebarItemToDelete != nil {
|
||||
selectedFeeds.insert(sidebarItemToDelete!.feedID!)
|
||||
}
|
||||
|
||||
let feeds: [Feed] = selectedFeeds
|
||||
.compactMap({ AccountManager.shared.existingFeed(with: $0) })
|
||||
|
||||
return feeds
|
||||
.map({ $0.nameForDisplay })
|
||||
.joined(separator: ", ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private extension SidebarModel {
|
||||
|
@ -57,10 +91,12 @@ private extension SidebarModel {
|
|||
// MARK: Subscriptions
|
||||
|
||||
func subscribeToSelectedFeedChanges() {
|
||||
|
||||
let selectedFeedIdentifersPublisher = $selectedFeedIdentifiers
|
||||
.map { [weak self] feedIDs -> [Feed] in
|
||||
return feedIDs.compactMap { self?.findFeed($0) }
|
||||
}
|
||||
|
||||
|
||||
let selectedFeedIdentiferPublisher = $selectedFeedIdentifier
|
||||
.compactMap { [weak self] feedID -> [Feed]? in
|
||||
|
@ -168,7 +204,7 @@ private extension SidebarModel {
|
|||
|
||||
func subscribeToDeleteFromAccount() {
|
||||
guard let selectedFeedsPublisher = selectedFeedsPublisher else { return }
|
||||
|
||||
|
||||
deleteFromAccount
|
||||
.withLatestFrom(selectedFeedsPublisher.prepend([Feed]()), resultSelector: { givenFeed, selectedFeeds -> [Feed] in
|
||||
if selectedFeeds.contains(where: { $0.feedID == givenFeed.feedID }) {
|
||||
|
@ -180,7 +216,19 @@ private extension SidebarModel {
|
|||
.sink { feeds in
|
||||
for feed in feeds {
|
||||
if let webFeed = feed as? WebFeed {
|
||||
webFeed.account?.removeWebFeed(webFeed)
|
||||
guard let account = webFeed.account,
|
||||
let containerID = account.containerID,
|
||||
let container = AccountManager.shared.existingContainer(with: containerID) else {
|
||||
return
|
||||
}
|
||||
account.removeWebFeed(webFeed, from: container, completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let err):
|
||||
print(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
if let folder = feed as? Folder {
|
||||
folder.account?.removeFolder(folder) { _ in }
|
||||
|
@ -304,4 +352,6 @@ private extension SidebarModel {
|
|||
selectedFeedIdentifier = feedID
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -62,6 +62,23 @@ struct SidebarView: View {
|
|||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $sidebarModel.showDeleteConfirmation, content: {
|
||||
Alert(title: sidebarModel.countOfFeedsToDelete() > 1 ?
|
||||
(Text("Delete multiple items?")) :
|
||||
(Text("Delete \(sidebarModel.namesOfFeedsToDelete())?")),
|
||||
message: Text("Are you sure you wish to delete \(sidebarModel.namesOfFeedsToDelete())?"),
|
||||
primaryButton: .destructive(Text("Delete"),
|
||||
action: {
|
||||
sidebarModel.deleteFromAccount.send(sidebarModel.sidebarItemToDelete!)
|
||||
sidebarModel.sidebarItemToDelete = nil
|
||||
sidebarModel.selectedFeedIdentifiers.removeAll()
|
||||
sidebarModel.showDeleteConfirmation = false
|
||||
}),
|
||||
secondaryButton: .cancel(Text("Cancel"), action: {
|
||||
sidebarModel.sidebarItemToDelete = nil
|
||||
sidebarModel.showDeleteConfirmation = false
|
||||
}))
|
||||
})
|
||||
#else
|
||||
ZStack(alignment: .top) {
|
||||
List {
|
||||
|
@ -76,6 +93,23 @@ struct SidebarView: View {
|
|||
ProgressView().offset(y: -40)
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $sidebarModel.showDeleteConfirmation, content: {
|
||||
Alert(title: sidebarModel.countOfFeedsToDelete() > 1 ?
|
||||
(Text("Delete multiple items?")) :
|
||||
(Text("Delete \(sidebarModel.namesOfFeedsToDelete())?")),
|
||||
message: Text("Are you sure you wish to delete \(sidebarModel.namesOfFeedsToDelete())?"),
|
||||
primaryButton: .destructive(Text("Delete"),
|
||||
action: {
|
||||
sidebarModel.deleteFromAccount.send(sidebarModel.sidebarItemToDelete!)
|
||||
sidebarModel.sidebarItemToDelete = nil
|
||||
sidebarModel.selectedFeedIdentifiers.removeAll()
|
||||
sidebarModel.showDeleteConfirmation = false
|
||||
}),
|
||||
secondaryButton: .cancel(Text("Cancel"), action: {
|
||||
sidebarModel.sidebarItemToDelete = nil
|
||||
sidebarModel.showDeleteConfirmation = false
|
||||
}))
|
||||
})
|
||||
#endif
|
||||
|
||||
// .onAppear {
|
||||
|
|
|
@ -26,6 +26,7 @@ struct SettingsView: View {
|
|||
systemSettings
|
||||
accounts
|
||||
importExport
|
||||
sidebarFeedManagement
|
||||
timeline
|
||||
articles
|
||||
appearance
|
||||
|
@ -143,6 +144,12 @@ struct SettingsView: View {
|
|||
.navigationBarTitle("Export Subscriptions", displayMode: .inline)
|
||||
}
|
||||
|
||||
var sidebarFeedManagement: some View {
|
||||
Section(header: Text("Feed Management")) {
|
||||
Toggle("Confirm When Deleting Feeds and Folders", isOn: $settings.sidebarConfirmDelete)
|
||||
}.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
|
||||
var timeline: some View {
|
||||
Section(header: Text("Timeline"), content: {
|
||||
Toggle("Sort Oldest to Newest", isOn: $settings.timelineSortDirection)
|
||||
|
|
|
@ -35,6 +35,7 @@ struct GeneralPreferencesView: View {
|
|||
})
|
||||
})
|
||||
|
||||
Toggle("Confirm when deleting feeds and folders", isOn: $defaults.sidebarConfirmDelete)
|
||||
|
||||
Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground)
|
||||
|
||||
|
|
Loading…
Reference in New Issue