Skip to content

Commit e7b090a

Browse files
committed
Reload the data so the user does not get the popup that suggests a folder is non-empty when we deleted that folder's contents via bookmarks search.
1 parent 0cf6cf2 commit e7b090a

3 files changed

Lines changed: 68 additions & 11 deletions

File tree

firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksViewController.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,12 +326,17 @@ final class BookmarksViewController: SiteTableViewController,
326326
/// table view data source immediately for responsiveness.
327327
private func deleteBookmarkNode(_ indexPath: IndexPath, bookmarkNode: FxBookmarkNode) {
328328
tableView.beginUpdates()
329-
viewModel.remove(bookmark: bookmarkNode)
329+
// Remove the bookmark from local data arrays for optimistic UI update, then re-queries and reloads the table
330+
// because deleting a node while searching can alter the bookmarks tree at subfolder depths.
331+
viewModel.remove(bookmark: bookmarkNode, afterAsyncRemoval: { [weak self] in
332+
self?.tableView.reloadData()
333+
})
330334
tableView.deleteRows(at: [indexPath], with: .left)
331335
tableView.endUpdates()
332336

333-
// If the last bookmark in this folder was deleted and the user is searching, exit search
337+
// If the last bookmark in this folder was deleted and the user is searching, exit search.
334338
if viewModel.isCurrentFolderEmpty && state == .bookmarks(state: .search) {
339+
// Exits search and updates the empty state as needed
335340
exitSearchState()
336341
} else {
337342
updateEmptyState(animated: false)

firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protocol BookmarksPanelViewModelProtocol: Sendable, CanRemoveQuickActionBookmark
2626
func resetSearch()
2727
func searchBookmarks(query: String, completion: @escaping @MainActor @Sendable () -> Void)
2828
func moveBookmarkToFolder(bookmark: FxBookmarkNode, withGUID parentFolderGUID: String)
29-
func remove(bookmark: FxBookmarkNode)
29+
func remove(bookmark: FxBookmarkNode, afterAsyncRemoval completion: @escaping @MainActor () -> Void)
3030
func getSiteDetails(for bookmark: FxBookmarkNode, completion: @escaping @MainActor (Site?) -> Void)
3131
func moveRow(at sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
3232
func createPinUnpinAction(
@@ -50,6 +50,10 @@ final class BookmarksPanelViewModel: BookmarksPanelViewModelProtocol {
5050
case bookmarks
5151
}
5252

53+
private var isBookmarksSearchEnabled: Bool {
54+
LegacyFeatureFlagsManager.shared.isFeatureEnabled(.bookmarksSearchFeature, checking: .buildOnly)
55+
}
56+
5357
var isRootNode: Bool {
5458
return bookmarkFolderGUID == BookmarkRoots.MobileFolderGUID
5559
}
@@ -62,6 +66,7 @@ final class BookmarksPanelViewModel: BookmarksPanelViewModelProtocol {
6266
let bookmarkFolderGUID: GUID
6367
var bookmarkFolder: FxBookmarkNode?
6468
var isShowingSearchResults = false
69+
private var currentSearchQuery: String?
6570

6671
/// Backing data array of all bookmarks
6772
private var allBookmarkNodes = [FxBookmarkNode]()
@@ -99,19 +104,38 @@ final class BookmarksPanelViewModel: BookmarksPanelViewModelProtocol {
99104
return true
100105
}
101106

107+
/// Reloads all the bookmarks data (and search results data, if the user is currently searching bookmarks).
102108
func reloadData(completion: @escaping @MainActor () -> Void) {
103109
// Can be called while app backgrounded and the db closed, don't try to reload the data source in this case
104110
if profile.isShutdown {
105111
completion()
106112
return
107113
}
108114

115+
// After reloading bookmarks, we should also reload our active search results as well, if applicable
116+
// That is because our bookmarks results are fetched recursively and backed by a separate data array
117+
let completionAfterSetup: @MainActor () -> Void = { [weak self] in
118+
guard let self else {
119+
completion()
120+
return
121+
}
122+
123+
// If search results are present, we need to refresh those, too
124+
if self.isShowingSearchResults, let currentSearchQuery = self.currentSearchQuery {
125+
self.searchBookmarks(query: currentSearchQuery) {
126+
completion()
127+
}
128+
} else {
129+
completion()
130+
}
131+
}
132+
109133
if bookmarkFolderGUID == BookmarkRoots.MobileFolderGUID {
110-
setupMobileFolderData(completion: completion)
134+
setupMobileFolderData(completion: completionAfterSetup)
111135
} else if bookmarkFolderGUID == LocalDesktopFolder.localDesktopFolderGuid {
112-
setupLocalDesktopFolderData(completion: completion)
136+
setupLocalDesktopFolderData(completion: completionAfterSetup)
113137
} else {
114-
setupSubfolderData(completion: completion)
138+
setupSubfolderData(completion: completionAfterSetup)
115139
}
116140
}
117141

@@ -197,6 +221,8 @@ final class BookmarksPanelViewModel: BookmarksPanelViewModelProtocol {
197221
return
198222
}
199223

224+
currentSearchQuery = query
225+
200226
bookmarksHandler
201227
.getBookmarksTree(rootGUID: bookmarkFolderGUID, recursive: true)
202228
.uponQueue(.main) { [weak self] result in
@@ -398,25 +424,49 @@ final class BookmarksPanelViewModel: BookmarksPanelViewModelProtocol {
398424

399425
/// Deletes the bookmark. Deletion of the bookmark in places happens asynchronously, but tableView source arrays are
400426
/// updated immediately for UI responsiveness.
401-
func remove(bookmark: FxBookmarkNode) {
427+
/// - Parameters:
428+
/// - bookmark: The bookmark to delete.
429+
/// - completion: The completion handler to call after the bookmark has been asynchronously removed from the backing
430+
/// store.
431+
func remove(bookmark: FxBookmarkNode, afterAsyncRemoval completion: @escaping @MainActor () -> Void) {
432+
let removalCompletion: @MainActor () -> Void = { [weak self] in
433+
if let self, self.isBookmarksSearchEnabled, self.isShowingSearchResults {
434+
// Reload the bookmarks tree for the current folder. If a bookmark was deleted via search, there is a chance
435+
// a subfolder becomes empty that was previously non-empty. We need to know whether folders in the current
436+
// folder contain bookmarks or not because we show an alert when deleting folders with non-empty contents.
437+
//
438+
// Note: A race condition exists where the user might try to delete a folder before this refresh happens,
439+
// since we optimistically update the UI but can't synchronously update the bookmarks tree local copy (at
440+
// least not easily).
441+
self.reloadData {
442+
completion()
443+
}
444+
}
445+
}
402446
// Remove the bookmark from places (async background work)
403447
profile.places.deleteBookmarkNode(guid: bookmark.guid).uponQueue(.main) { _ in
404448
// FXIOS-13228 It should be safe to assumeIsolated here because of `.main` queue above
405449
MainActor.assumeIsolated {
450+
// Remove this bookmark from quick actions
451+
self.removeBookmarkShortcut()
452+
406453
// Remove this bookmark out of recent places
407454
if let recentBookmarkFolderGuid = self.profile.prefs.stringForKey(PrefsKeys.RecentBookmarkFolder) {
408455
self.profile.places.getBookmark(guid: recentBookmarkFolderGuid).uponQueue(.main) { node in
409456
// FXIOS-13228 It should be safe to assumeIsolated here because of `.main` queue above
410457
MainActor.assumeIsolated {
411-
guard let nodeValue = node.successValue, nodeValue == nil else { return }
458+
guard let nodeValue = node.successValue, nodeValue == nil else {
459+
removalCompletion()
460+
return
461+
}
412462

413463
self.profile.prefs.removeObjectForKey(PrefsKeys.RecentBookmarkFolder)
464+
removalCompletion()
414465
}
415466
}
467+
} else {
468+
removalCompletion()
416469
}
417-
418-
// Remove this bookmark from quick actions
419-
self.removeBookmarkShortcut()
420470
}
421471
}
422472

firefox-ios/Storage/Rust/RustPlaces.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ public protocol BookmarksHandler {
5555
completion: @Sendable @escaping (Result<Void, any Error>) -> Void
5656
)
5757

58+
func deleteBookmarkNode(guid: GUID) -> Success
59+
5860
func isBookmarked(url: String, completion: @escaping @Sendable (Result<Bool, Error>) -> Void)
5961
}
6062

0 commit comments

Comments
 (0)