kiwix-apple/ViewModel/NavigationViewModel.swift
Emmanuel Engelhart d6c23073aa Add GPL headers
2024-04-05 18:20:18 +02:00

155 lines
5.3 KiB
Swift

/*
* This file is part of Kiwix for iOS & macOS.
*
* Kiwix is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* Kiwix is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kiwix; If not, see https://www.gnu.org/licenses/.
*/
//
// NavigationViewModel.swift
// Kiwix
//
// Created by Chris Li on 7/29/23.
// Copyright © 2023 Chris Li. All rights reserved.
//
import CoreData
import WebKit
@MainActor
final class NavigationViewModel: ObservableObject {
// remained optional due to focusedSceneValue conformance
@Published var currentItem: NavigationItem? = .loading
// MARK: - Tab Management
private func makeTab(context: NSManagedObjectContext) -> Tab {
let tab = Tab(context: context)
tab.created = Date()
tab.lastOpened = Date()
try? context.obtainPermanentIDs(for: [tab])
try? context.save()
return tab
}
func navigateToMostRecentTab() {
let context = Database.viewContext
let fetchRequest = Tab.fetchRequest(sortDescriptors: [NSSortDescriptor(key: "lastOpened", ascending: false)])
fetchRequest.fetchLimit = 1
let tab = (try? context.fetch(fetchRequest).first) ?? self.makeTab(context: context)
Task {
await MainActor.run {
currentItem = NavigationItem.tab(objectID: tab.objectID)
}
}
}
@discardableResult
func createTab() -> NSManagedObjectID {
let context = Database.viewContext
let tab = self.makeTab(context: context)
#if !os(macOS)
currentItem = NavigationItem.tab(objectID: tab.objectID)
#endif
return tab.objectID
}
@MainActor
func tabIDFor(url: URL?) -> NSManagedObjectID {
guard let url,
let coordinator = Database.viewContext.persistentStoreCoordinator,
let tabID = coordinator.managedObjectID(forURIRepresentation: url) else {
return createTab()
}
return tabID
}
/// Delete a single tab, and select another tab
/// - Parameter tabID: ID of the tab to delete
func deleteTab(tabID: NSManagedObjectID) {
Database.performBackgroundTask { context in
let sortByCreation = [NSSortDescriptor(key: "created", ascending: false)]
guard let tabs: [Tab] = try? context.fetch(Tab.fetchRequest(predicate: nil,
sortDescriptors: sortByCreation)),
let tab: Tab = tabs.first(where: { $0.objectID == tabID }) else {
return
}
let newlySelectedTab: Tab?
// select a closeBy tab if the currently selected tab is to be deleted
if case let .tab(selectedTabID) = self.currentItem, selectedTabID == tabID {
newlySelectedTab = tabs.closeBy(toWhere: { $0.objectID == tabID }) ?? self.makeTab(context: context)
} else {
newlySelectedTab = nil // the current selection should remain
}
// delete tab
context.delete(tab)
try? context.save()
// update selection if needed
if let newlySelectedTab {
Task {
await MainActor.run {
self.currentItem = NavigationItem.tab(objectID: newlySelectedTab.objectID)
}
}
}
}
}
/// Delete all tabs, and open a new tab
func deleteAllTabs() {
Database.performBackgroundTask { context in
// delete all existing tabs
let tabs = try? context.fetch(Tab.fetchRequest())
tabs?.forEach { context.delete($0) }
// create new tab
let newTab = self.makeTab(context: context)
Task {
await MainActor.run {
self.currentItem = NavigationItem.tab(objectID: newTab.objectID)
}
}
}
}
}
extension Array {
/// Return an element close to the one defined in the where callback,
/// either the one before or if this is the first, the one after
/// - Parameter toWhere: similar role as in find(where: ) closure, this element is never returned
/// - Returns: the element before or the one after, never the one that matches by toWhere:
func closeBy(toWhere whereCallback: @escaping (Element) -> Bool) -> Element? {
var previous: Element?
var returnNext: Bool = false
for element in self {
if returnNext {
return element
}
if whereCallback(element) {
if let previous {
return previous
} else {
returnNext = true
}
} else {
previous = element
}
}
return previous
}
}