Merge pull request #640 from omnivore-app/feature/deeplink-web-reader

Use FetchedResultsController for HomeFeed
This commit is contained in:
Satindar Dhillon
2022-05-17 11:19:23 -07:00
committed by GitHub
2 changed files with 81 additions and 37 deletions

View File

@ -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<LinkedItem>?
@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,77 @@ 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<Models.LinkedItem> = 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
} else {
updateFetchController(dataService: dataService)
}
isLoading = false
showLoadingBar = false
}
private var fetchRequest: NSFetchRequest<Models.LinkedItem> {
let fetchRequest: NSFetchRequest<Models.LinkedItem> = 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()
items = fetchedResultsController.fetchedObjects ?? []
}
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 +195,9 @@ import Views
return query
}
}
extension HomeFeedViewModel: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
items = controller.fetchedObjects as? [LinkedItem] ?? []
}
}

View File

@ -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() {