From 06826a23bb3162b5dbd87a5198b72bf33c755607 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 15 Aug 2020 09:37:18 +0800 Subject: [PATCH 1/3] Partially fixes #566 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • 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. --- Multiplatform/Shared/AppDefaults.swift | 13 ++++- .../Shared/Sidebar/SidebarContextMenu.swift | 20 ++++++- .../Shared/Sidebar/SidebarModel.swift | 54 ++++++++++++++++++- .../Shared/Sidebar/SidebarView.swift | 34 ++++++++++++ Multiplatform/iOS/Settings/SettingsView.swift | 7 +++ .../General/GeneralPreferencesView.swift | 1 + 6 files changed, 124 insertions(+), 5 deletions(-) diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift index 408154b33..76c682176 100644 --- a/Multiplatform/Shared/AppDefaults.swift +++ b/Multiplatform/Shared/AppDefaults.swift @@ -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 diff --git a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift index 148571f06..21971884a 100644 --- a/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift +++ b/Multiplatform/Shared/Sidebar/SidebarContextMenu.swift @@ -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 } + */ } } diff --git a/Multiplatform/Shared/Sidebar/SidebarModel.swift b/Multiplatform/Shared/Sidebar/SidebarModel.swift index 2891ac5cd..2ffd3d8ea 100644 --- a/Multiplatform/Shared/Sidebar/SidebarModel.swift +++ b/Multiplatform/Shared/Sidebar/SidebarModel.swift @@ -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() var deleteFromAccount = PassthroughSubject() + var sidebarItemToDelete: Feed? + private var cancellables = Set() 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 } + + } diff --git a/Multiplatform/Shared/Sidebar/SidebarView.swift b/Multiplatform/Shared/Sidebar/SidebarView.swift index 41e3396f5..97cfe801f 100644 --- a/Multiplatform/Shared/Sidebar/SidebarView.swift +++ b/Multiplatform/Shared/Sidebar/SidebarView.swift @@ -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 { diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index e021262eb..414a5fe43 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -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) diff --git a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift index 3d88312c1..71634bae9 100644 --- a/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift +++ b/Multiplatform/macOS/Preferences/Preference Panes/General/GeneralPreferencesView.swift @@ -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) From e3a221befd964fb009a78c8b4efab25ff609706b Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 15 Aug 2020 14:31:56 +0800 Subject: [PATCH 2/3] Moves feed deletion to Feeds section --- Multiplatform/iOS/Settings/SettingsView.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index 414a5fe43..28276f71a 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -26,7 +26,6 @@ struct SettingsView: View { systemSettings accounts importExport - sidebarFeedManagement timeline articles appearance @@ -101,6 +100,7 @@ struct SettingsView: View { .foregroundColor(.primary) } } + Toggle("Confirm When Deleting", isOn: $settings.sidebarConfirmDelete) }) .alert(isPresented: $feedsSettingsModel.showError) { Alert( @@ -138,17 +138,14 @@ struct SettingsView: View { Text(viewModel.accounts[i].nameForDisplay) } }) + }) } .listStyle(InsetGroupedListStyle()) .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: { From 3610f71ac2aa271290272520ea8822a4854098ed Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 15 Aug 2020 14:34:20 +0800 Subject: [PATCH 3/3] Adds accentColor to toggle --- Multiplatform/iOS/Settings/SettingsView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Multiplatform/iOS/Settings/SettingsView.swift b/Multiplatform/iOS/Settings/SettingsView.swift index 28276f71a..927842b51 100644 --- a/Multiplatform/iOS/Settings/SettingsView.swift +++ b/Multiplatform/iOS/Settings/SettingsView.swift @@ -101,6 +101,7 @@ struct SettingsView: View { } } Toggle("Confirm When Deleting", isOn: $settings.sidebarConfirmDelete) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) }) .alert(isPresented: $feedsSettingsModel.showError) { Alert(