HistoryView: Adds ability to delete history items

This commit is contained in:
2023-01-25 15:04:58 -08:00
parent 53efb5389e
commit 34ca35ea5a
5 changed files with 138 additions and 46 deletions

View File

@@ -7,9 +7,58 @@
import Foundation
import CoreData
import Combine
class BrowserHistory
{
class ViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
@Published var searchQuery: String = ""
@Published private(set) var historyItems: [HistoryItem] = []
fileprivate let historyController: BrowserHistory
fileprivate var searchQueryObserver: AnyCancellable? = nil
fileprivate var fetchedResultsController: NSFetchedResultsController<HistoryItemEntity> {
didSet { fetchedResultsController.delegate = self; performInitialFetch() }
}
fileprivate init(fetchedResultsController: NSFetchedResultsController<HistoryItemEntity>, historyController: BrowserHistory) {
self.fetchedResultsController = fetchedResultsController
self.historyController = historyController
super.init()
searchQueryObserver = $searchQuery.sink { [unowned self] newValue in
self.fetchedResultsController = historyController.fetchRequestController(forQuery: newValue)
}
}
public func item(forIdentifier identifier: HistoryItem.ID) -> HistoryItem? {
if let object = fetchedResultsController.managedObjectContext.object(with: identifier) as? HistoryItemEntity {
return HistoryItem(entity: object)
}
return nil
}
public func deleteItems(_ items: Set<HistoryItem.ID>) {
items.forEach { identifier in
historyController.deleteItem(withIdentifier: identifier)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
if let objects = controller.fetchedObjects as? [HistoryItemEntity] {
self.historyItems = objects.map { HistoryItem(entity: $0) }
}
}
fileprivate func performInitialFetch() {
try? fetchedResultsController.performFetch()
if let objects = fetchedResultsController.fetchedObjects {
self.historyItems = objects.map { HistoryItem(entity: $0) }
}
}
}
static public let shared = BrowserHistory()
lazy fileprivate var persistentContainer: NSPersistentContainer = {
@@ -37,19 +86,30 @@ class BrowserHistory
}
}
public func allHistory(limit: Int? = nil) -> [HistoryItem] {
let dataContext = persistentContainer.viewContext
let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest()
fetchRequest.sortDescriptors = [
// Sort by date
NSSortDescriptor(keyPath: \HistoryItemEntity.lastVisited, ascending: false)
]
if let limit {
fetchRequest.fetchLimit = limit
public func viewModel(forFilterString filterString: String? = nil, limit: Int? = nil) -> ViewModel {
let resultsController = fetchRequestController(forQuery: filterString, limit: limit)
return ViewModel(fetchedResultsController: resultsController, historyController: self)
}
public func historyItem(forIdentifier: HistoryItem.ID) -> HistoryItem? {
if let entity = try? persistentContainer.viewContext.existingObject(with: forIdentifier) as? HistoryItemEntity {
return HistoryItem(entity: entity)
}
return nil
}
public func deleteItem(withIdentifier identifier: HistoryItem.ID) {
let dataContext = persistentContainer.viewContext
if let object = try? dataContext.existingObject(with: identifier) {
dataContext.delete(object)
try? dataContext.save()
}
}
public func allHistory(filteredBy filterString: String? = nil, limit: Int? = nil) -> [HistoryItem] {
let dataContext = persistentContainer.viewContext
let fetchRequest = fetchRequest(forStringContaining: filterString, limit: limit)
let entities: [HistoryItemEntity] = (try? dataContext.fetch(fetchRequest)) ?? []
return entities.map { (entity) -> HistoryItem in
@@ -98,6 +158,38 @@ class BrowserHistory
}
}
extension BrowserHistory
{
fileprivate func fetchRequestController(forQuery query: String? = nil, limit: Int? = nil) -> NSFetchedResultsController<HistoryItemEntity> {
let fetchRequest = fetchRequest(forStringContaining: query, limit: limit)
return NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: persistentContainer.viewContext,
sectionNameKeyPath: nil, cacheName: nil)
}
fileprivate func fetchRequest(forStringContaining filterString: String? = nil, limit: Int? = nil) -> NSFetchRequest<HistoryItemEntity> {
let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest()
fetchRequest.sortDescriptors = [
// Sort by date
NSSortDescriptor(keyPath: \HistoryItemEntity.lastVisited, ascending: false)
]
if let limit {
fetchRequest.fetchLimit = limit
}
if let filterString, filterString.count > 0 {
fetchRequest.predicate = NSPredicate(format: """
host CONTAINS[cd] %@
OR title CONTAINS[cd] %@
OR url CONTAINS[cd] %@
""", filterString, filterString, filterString)
}
return fetchRequest
}
}
extension URL
{
public func topLevelURL() -> URL {

View File

@@ -12,13 +12,13 @@ struct HistoryItem: Hashable, Identifiable
var url: URL
var title: String
var lastVisited: Date
var id: ObjectIdentifier
var id: NSManagedObjectID
init(entity: HistoryItemEntity) {
self.url = entity.url ?? URL(string: "about:blank")!
self.lastVisited = entity.lastVisited ?? Date()
self.title = entity.title ?? ""
self.id = entity.id
self.id = entity.objectID
}
// For testing/previews
@@ -26,6 +26,6 @@ struct HistoryItem: Hashable, Identifiable
self.url = url
self.title = title
self.lastVisited = lastVisited
self.id = ObjectIdentifier(NSUUID())
self.id = NSManagedObjectID()
}
}