Merge pull request #640 from omnivore-app/feature/deeplink-web-reader
Use FetchedResultsController for HomeFeed
This commit is contained in:
@ -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] ?? []
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user