Adds history browser view

This commit is contained in:
James Magahern
2023-01-20 17:28:15 -08:00
parent f374f3ebe8
commit 53efb5389e
9 changed files with 177 additions and 2 deletions

View File

@@ -37,10 +37,19 @@ class BrowserHistory
}
}
public func allHistory() -> [HistoryItem] {
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
}
let entities: [HistoryItemEntity] = (try? dataContext.fetch(fetchRequest)) ?? []
return entities.map { (entity) -> HistoryItem in

View File

@@ -7,15 +7,25 @@
import Foundation
struct HistoryItem: Hashable
struct HistoryItem: Hashable, Identifiable
{
var url: URL
var title: String
var lastVisited: Date
var id: ObjectIdentifier
init(entity: HistoryItemEntity) {
self.url = entity.url ?? URL(string: "about:blank")!
self.lastVisited = entity.lastVisited ?? Date()
self.title = entity.title ?? ""
self.id = entity.id
}
// For testing/previews
public init(url: URL, title: String, lastVisited: Date) {
self.url = url
self.title = title
self.lastVisited = lastVisited
self.id = ObjectIdentifier(NSUUID())
}
}

View File

@@ -156,6 +156,10 @@ extension BrowserViewController: ShortcutResponder
showSettingsWindow()
}
func showHistory(_ sender: Any?) {
showHistoryWindow()
}
func toggleDarkMode(_ sender: Any?) {
self.darkModeEnabled = !self.darkModeEnabled
}
@@ -163,4 +167,14 @@ extension BrowserViewController: ShortcutResponder
func openInReaderMode(_ sender: Any?) {
showReaderWindow()
}
func handleOpenURL(_ sender: Any?, url: URL?) {
guard let url else { return }
if tab.url == nil {
tab.beginLoadingURL(url)
} else {
createNewTab(withURL: url)
}
}
}

View File

@@ -333,6 +333,12 @@ class BrowserViewController: UIViewController
showShareSheetForCurrentURL(fromViewController: documentControls)
}, for: .touchUpInside)
// History
documentControls.historyView.addAction(UIAction { [unowned self] action in
documentControls.dismiss(animated: false, completion: nil)
showHistory(action)
}, for: .touchUpInside)
present(documentControls, animated: true, completion: nil)
}), for: .touchUpInside)
@@ -463,6 +469,17 @@ class BrowserViewController: UIViewController
}
}
internal func showHistoryWindow() {
let historyViewController = HistoryBrowserViewController()
historyViewController.title = "History"
historyViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction { _ in
historyViewController.dismiss(animated: true)
})
let navigationController = UINavigationController(rootViewController: historyViewController)
present(navigationController, animated: true)
}
internal func updateLoadProgress(forWebView webView: WKWebView) {
if let loadError = tab.loadError {
toolbarController.urlBar.loadProgress = .error(error: loadError)

View File

@@ -20,6 +20,7 @@ class DocumentControlViewController: UIViewController
let archiveView = DocumentControlItemView().title("Archive.today") .symbol("shippingbox")
let emailView = DocumentControlItemView().title("Email") .symbol("envelope")
let sharingView = DocumentControlItemView().title("Share") .symbol("square.and.arrow.up")
let historyView = DocumentControlItemView().title("History") .symbol("clock.arrow.circlepath")
let darkModeView = DocumentControlItemView().title("Dark Mode")
var observations: [NSKeyValueObservation] = []
@@ -46,6 +47,7 @@ class DocumentControlViewController: UIViewController
documentControlsView.stackView.addArrangedSubview(darkModeView)
documentControlsView.stackView.addArrangedSubview(readabilityView)
documentControlsView.stackView.addArrangedSubview(archiveView)
documentControlsView.stackView.addArrangedSubview(historyView)
documentControlsView.stackView.addArrangedSubview(settingsView)

View File

@@ -0,0 +1,20 @@
//
// HistoryBrowserViewController.swift
// App
//
// Created by James Magahern on 1/20/23.
//
import SwiftUI
import UIKit
@MainActor
class HistoryBrowserViewController: UIHostingController<HistoryView> {
public init() {
super.init(rootView: HistoryView(historyItems: BrowserHistory.shared.allHistory(limit: 500)))
}
required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@@ -0,0 +1,72 @@
//
// HistoryView.swift
// App
//
// Created by James Magahern on 1/20/23.
//
import SwiftUI
import UniformTypeIdentifiers
struct HistoryView: View {
var historyItems: [HistoryItem]
private let dateFormatter: DateFormatter
@State public var selectedItems = Set<HistoryItem.ID>()
@Environment(\.dismiss) private var dismissAction
init(historyItems: [HistoryItem]) {
self.historyItems = historyItems
let formatter = DateFormatter()
formatter.locale = Locale.current
formatter.dateStyle = .medium
formatter.timeStyle = .short
self.dateFormatter = formatter
}
var body: some View {
Table(historyItems, selection: $selectedItems) {
TableColumn("Title", value: \.title)
TableColumn("URL") { item in
Text(item.url.absoluteString)
}
TableColumn("Last Visited") { item in
Text(dateFormatter.string(from: item.lastVisited))
}
}
.contextMenu(forSelectionType: HistoryItem.ID.self, menu: { items in
if let firstItem: HistoryItem.ID = items.first,
let historyItem = historyItems.first { $0.id == firstItem }
{
Button("Copy") {
UIPasteboard.general.addItems([
[ UTType.url.identifier : historyItem.url ]
])
}
// TODO: Delete?
}
}, primaryAction: { items in
if let firstItem: HistoryItem.ID = items.first,
let historyItem = historyItems.first(where: { $0.id == firstItem })
{
UIApplication.shared.open(historyItem.url)
dismissAction()
}
})
}
}
struct HistoryViewPreviewProvider: PreviewProvider {
static var previews: some View {
HistoryView(historyItems: [
HistoryItem(url: URL(string: "https://apple.com")!, title: "Apple", lastVisited: Date.now),
HistoryItem(url: URL(string: "https://google.com")!, title: "Google", lastVisited: Date.now)
])
.previewLayout(.fixed(width: 480.0, height: 800.0))
}
}

View File

@@ -50,6 +50,12 @@ protocol ShortcutResponder: AnyObject {
@objc
optional func openInReaderMode(_ sender: Any?)
@objc
optional func showHistory(_ sender: Any?)
@objc
optional func handleOpenURL(_ sender: Any?, url: URL?)
}
fileprivate extension Array {
@@ -166,6 +172,13 @@ public class KeyboardShortcuts {
title: "Go Forward",
action: #selector(ShortcutResponder.goForward)
),
UIKeyCommand(
modifiers: [.command, .shift],
input: "h",
title: "Show History…",
action: #selector(ShortcutResponder.showHistory)
),
]),
// Tab Navigation

View File

@@ -40,6 +40,8 @@
1ADFF4CD24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CC24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift */; };
CD01D5A5254A10BB00189CDC /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD01D5A4254A10BB00189CDC /* TabBarView.swift */; };
CD01D5AB254A206D00189CDC /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD01D5AA254A206D00189CDC /* TabBarViewController.swift */; };
CD15332F297B6798009A7F3A /* HistoryBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD15332E297B6798009A7F3A /* HistoryBrowserViewController.swift */; };
CD153331297B6806009A7F3A /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD153330297B6806009A7F3A /* HistoryView.swift */; };
CD16844D269E709400B8F8A5 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD16844C269E709400B8F8A5 /* Box.swift */; };
CD19576D268BE95900E8089B /* GenericContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD19576C268BE95900E8089B /* GenericContentView.swift */; };
CD361CF6271A3718006E9CA5 /* SBRScriptPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */; };
@@ -150,6 +152,8 @@
1ADFF4CC24CBB0C8006DC7AE /* ScriptOriginPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptOriginPolicyViewController.swift; sourceTree = "<group>"; };
CD01D5A4254A10BB00189CDC /* TabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = "<group>"; };
CD01D5AA254A206D00189CDC /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = "<group>"; };
CD15332E297B6798009A7F3A /* HistoryBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryBrowserViewController.swift; sourceTree = "<group>"; };
CD153330297B6806009A7F3A /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
CD16844C269E709400B8F8A5 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
CD19576C268BE95900E8089B /* GenericContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericContentView.swift; sourceTree = "<group>"; };
CD361CF4271A3718006E9CA5 /* SBRScriptPolicy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SBRScriptPolicy.h; sourceTree = "<group>"; };
@@ -302,6 +306,7 @@
CDCE2662251AA7FC007FE92A /* Document Controls UI */,
1AD3104125254FA300A4A952 /* Find on Page */,
CD7F2133265DACEE0001D042 /* Hacks */,
CD15332D297B6784009A7F3A /* History UI */,
CDC5DA3C25DB7A5500BA8D99 /* Reader View */,
CDB6807B28B4456B007D787E /* Scene Delegates */,
1ADFF4CE24CBBCBD006DC7AE /* Script Policy UI */,
@@ -406,6 +411,15 @@
path = "Script Policy UI";
sourceTree = "<group>";
};
CD15332D297B6784009A7F3A /* History UI */ = {
isa = PBXGroup;
children = (
CD15332E297B6798009A7F3A /* HistoryBrowserViewController.swift */,
CD153330297B6806009A7F3A /* HistoryView.swift */,
);
path = "History UI";
sourceTree = "<group>";
};
CD7A7E9B2686A99600E20BA3 /* Amber */ = {
isa = PBXGroup;
children = (
@@ -634,6 +648,7 @@
CDB6807D28B446FC007D787E /* ReaderSceneDelegate.swift in Sources */,
CDD0522425F8055700DD1771 /* SearchProvider.swift in Sources */,
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */,
CD15332F297B6798009A7F3A /* HistoryBrowserViewController.swift in Sources */,
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */,
CD7313E4270534B800053347 /* ScriptPolicyViewControllerDelegate.swift in Sources */,
@@ -663,6 +678,7 @@
CD01D5AB254A206D00189CDC /* TabBarViewController.swift in Sources */,
1ADFF47924C7DFF8006DC7AE /* BrowserView.swift in Sources */,
CDCE2664251AA80F007FE92A /* DocumentControlViewController.swift in Sources */,
CD153331297B6806009A7F3A /* HistoryView.swift in Sources */,
CDF3468E276C105900FB3141 /* SettingsSceneDelegate.swift in Sources */,
1AB88EFF24D3BBA50006F850 /* TabPickerViewController.swift in Sources */,
CD19576D268BE95900E8089B /* GenericContentView.swift in Sources */,
@@ -834,6 +850,7 @@
CURRENT_PROJECT_VERSION = 4;
DEAD_CODE_STRIPPING = YES;
INFOPLIST_FILE = "App/Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -861,6 +878,7 @@
CURRENT_PROJECT_VERSION = 4;
DEAD_CODE_STRIPPING = YES;
INFOPLIST_FILE = "App/Supporting Files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",