From 23173e6f0b58be0467a5e49516f2895cc9c75c15 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 16 May 2022 20:31:14 -0700 Subject: [PATCH 1/4] update function that fetches username for deep links --- .../App/Views/RootView/RootViewModel.swift | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift index 0f26b9a92..51cdf52e5 100644 --- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift @@ -61,18 +61,21 @@ public final class RootViewModel: ObservableObject { @MainActor func onOpenURL(url: URL) async { guard let linkRequestID = DeepLink.make(from: url)?.linkRequestID else { return } - if let username = services.dataService.currentViewer?.username { - let path = linkRequestPath(username: username, requestID: linkRequestID) - webLinkPath = SafariWebLinkPath(id: UUID(), path: path) - return - } - - if let viewerObjectID = try? await services.dataService.fetchViewer() { - if let viewer = services.dataService.viewContext.object(with: viewerObjectID) as? Viewer { - let path = linkRequestPath(username: viewer.unwrappedUsername, requestID: linkRequestID) - webLinkPath = SafariWebLinkPath(id: UUID(), path: path) + let username: String? = await { + if let cachedUsername = services.dataService.currentViewer?.username { + return cachedUsername } - } + + if let viewerObjectID = try? await services.dataService.fetchViewer() { + let viewer = services.dataService.viewContext.object(with: viewerObjectID) as? Viewer + return viewer?.unwrappedUsername + } + + return nil + }() + + guard let username = username else { return } + webLinkPath = SafariWebLinkPath(id: UUID(), path: linkRequestPath(username: username, requestID: linkRequestID)) } func triggerPushNotificationRequestIfNeeded() { From 36083c1f7b8314a1d21315acce0fa45a847beaa7 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 17 May 2022 10:54:34 -0700 Subject: [PATCH 2/4] use fetched results controller to manage linked item list --- .../App/Views/Home/HomeFeedViewModel.swift | 89 +++++++++++++------ 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index d16de27b1..1d3bee7a4 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -5,9 +5,11 @@ import SwiftUI import Utils import Views -@MainActor final class HomeFeedViewModel: ObservableObject { +@MainActor final class HomeFeedViewModel: NSObject, ObservableObject { var currentDetailViewModel: LinkItemDetailViewModel? + private var fetchedResultsController: NSFetchedResultsController? + @Published var items = [LinkedItem]() @Published var isLoading = false @Published var showPushNotificationPrimer = false @@ -29,8 +31,6 @@ import Views var searchIdx = 0 var receivedIdx = 0 - init() {} - func itemAppeared(item: LinkedItem, dataService: DataService) async { if isLoading { return } let itemIndex = items.firstIndex(where: { $0.id == item.id }) @@ -81,42 +81,73 @@ import Views } return itemObjects }() - items = isRefresh ? newItems : items + newItems + + if searchTerm.replacingOccurrences(of: " ", with: "").isEmpty { + updateFetchController(dataService: dataService) + } else { + // Don't use FRC for searching. Use server results directly. + if fetchedResultsController != nil { + fetchedResultsController = nil + items = [] + } + items = isRefresh ? newItems : items + newItems + } + isLoading = false receivedIdx = thisSearchIdx cursor = queryResult.cursor await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID)) - showLoadingBar = false - } else if searchTerm.replacingOccurrences(of: " ", with: "").isEmpty { - await dataService.viewContext.perform { - let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() - fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)] - if let predicate = LinkedItemFilter(rawValue: self.appliedFilter)?.predicate { - fetchRequest.predicate = predicate - } -// // TODO: Filter on label - - if let fetchedItems = try? dataService.viewContext.fetch(fetchRequest) { - self.items = fetchedItems - self.cursor = nil - self.isLoading = false - } - } - showLoadingBar = false } + + showLoadingBar = false + } + + private var fetchRequest: NSFetchRequest { + let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \LinkedItem.savedAt, ascending: false)] + + var subPredicates = [NSPredicate]() + + subPredicates.append((LinkedItemFilter(rawValue: appliedFilter) ?? .inbox).predicate) + + if !selectedLabels.isEmpty { + var labelSubPredicates = [NSPredicate]() + + for label in selectedLabels { + labelSubPredicates.append( + NSPredicate(format: "SUBQUERY(labels, $label, $label.id == \"\(label.unwrappedID)\").@count > 0") + ) + } + + subPredicates.append(NSCompoundPredicate(orPredicateWithSubpredicates: labelSubPredicates)) + } + + fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: subPredicates) + return fetchRequest + } + + private func updateFetchController(dataService: DataService) { + fetchedResultsController = NSFetchedResultsController( + fetchRequest: fetchRequest, + managedObjectContext: dataService.viewContext, + sectionNameKeyPath: nil, + cacheName: nil + ) + + guard let fetchedResultsController = fetchedResultsController else { + return + } + + fetchedResultsController.delegate = self + try? fetchedResultsController.performFetch() } func setLinkArchived(dataService: DataService, objectID: NSManagedObjectID, archived: Bool) { - // TODO: remove this by making list always fetch from Coredata - guard let itemIndex = items.firstIndex(where: { $0.objectID == objectID }) else { return } - items.remove(at: itemIndex) dataService.archiveLink(objectID: objectID, archived: archived) Snackbar.show(message: archived ? "Link archived" : "Link moved to Inbox") } func removeLink(dataService: DataService, objectID: NSManagedObjectID) { - guard let itemIndex = items.firstIndex(where: { $0.objectID == objectID }) else { return } - items.remove(at: itemIndex) Snackbar.show(message: "Link removed") dataService.removeLink(objectID: objectID) } @@ -160,3 +191,9 @@ import Views return query } } + +extension HomeFeedViewModel: NSFetchedResultsControllerDelegate { + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + items = controller.fetchedObjects as? [LinkedItem] ?? [] + } +} From b9d0fef3c05812931b705ba5ac73703bf182fc69 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 17 May 2022 11:04:34 -0700 Subject: [PATCH 3/4] set items after initial fetch request --- apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 1d3bee7a4..dbd4943f3 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -140,6 +140,7 @@ import Views fetchedResultsController.delegate = self try? fetchedResultsController.performFetch() + items = fetchedResultsController.fetchedObjects ?? [] } func setLinkArchived(dataService: DataService, objectID: NSManagedObjectID, archived: Bool) { From 76d3478ed4b388ccb2fdcba67bbe91d6c9e3849c Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 17 May 2022 11:13:30 -0700 Subject: [PATCH 4/4] update fetch results when network request fails --- .../OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index dbd4943f3..797f3973d 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -97,8 +97,11 @@ import Views receivedIdx = thisSearchIdx cursor = queryResult.cursor await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID)) + } else { + updateFetchController(dataService: dataService) } + isLoading = false showLoadingBar = false }