Started working on history tracking
This commit is contained in:
88
App/Backend/History/BrowserHistory.swift
Normal file
88
App/Backend/History/BrowserHistory.swift
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// BrowserHistory.swift
|
||||||
|
// App
|
||||||
|
//
|
||||||
|
// Created by James Magahern on 8/14/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
class BrowserHistory
|
||||||
|
{
|
||||||
|
static public let shared = BrowserHistory()
|
||||||
|
|
||||||
|
lazy fileprivate var persistentContainer: NSPersistentContainer = {
|
||||||
|
let container = NSPersistentContainer(name: "History")
|
||||||
|
container.loadPersistentStores { description, error in
|
||||||
|
assert(error == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return container
|
||||||
|
}()
|
||||||
|
|
||||||
|
public func didNavigate(toURL url: URL, title: String) {
|
||||||
|
let dataContext = persistentContainer.viewContext
|
||||||
|
|
||||||
|
let entity = HistoryItemEntity(context: dataContext)
|
||||||
|
entity.url = url
|
||||||
|
entity.lastVisited = Date()
|
||||||
|
entity.title = title
|
||||||
|
entity.host = url.host
|
||||||
|
|
||||||
|
do { try dataContext.save() }
|
||||||
|
catch {
|
||||||
|
let nserror = error as NSError
|
||||||
|
fatalError("Failed saving persistent entity to store: \(nserror), \(nserror.userInfo)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func allHistory() -> [HistoryItem] {
|
||||||
|
let dataContext = persistentContainer.viewContext
|
||||||
|
|
||||||
|
let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest()
|
||||||
|
let entities: [HistoryItemEntity] = (try? dataContext.fetch(fetchRequest)) ?? []
|
||||||
|
|
||||||
|
return entities.map { (entity) -> HistoryItem in
|
||||||
|
HistoryItem(entity: entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visitedToplevelHistoryItems(matching: String) -> [HistoryItem] {
|
||||||
|
let dataContext = persistentContainer.viewContext
|
||||||
|
|
||||||
|
let fetchRequest: NSFetchRequest<HistoryItemEntity> = HistoryItemEntity.fetchRequest()
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "host CONTAINS %@ OR title contains %@", matching, matching)
|
||||||
|
|
||||||
|
let entities: [HistoryItemEntity] = (try? dataContext.fetch(fetchRequest)) ?? []
|
||||||
|
let allItems: [HistoryItem] = entities.map { HistoryItem(entity: $0) }
|
||||||
|
|
||||||
|
var topLevelItems: [URL: (HistoryItem, Int)] = [:]
|
||||||
|
allItems.forEach { item in
|
||||||
|
let topLevelURL = item.url.topLevelURL()
|
||||||
|
var topLevelItem = topLevelItems[topLevelURL] ?? (item, 0)
|
||||||
|
topLevelItem.0.url = topLevelURL
|
||||||
|
topLevelItems[topLevelURL] = topLevelItem
|
||||||
|
}
|
||||||
|
|
||||||
|
return topLevelItems.values.map { return $0.0 }.sorted { (item1, item2) -> Bool in
|
||||||
|
return topLevelItems[item1.url]!.1 < topLevelItems[item2.url]!.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URL
|
||||||
|
{
|
||||||
|
public func topLevelURL() -> URL {
|
||||||
|
if var components = URLComponents(url: self, resolvingAgainstBaseURL: false) {
|
||||||
|
components.path = ""
|
||||||
|
components.query = ""
|
||||||
|
components.queryItems = []
|
||||||
|
if let url = components.url {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17189" systemVersion="20A2348b" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
|
<entity name="HistoryItemEntity" representedClassName="HistoryItemEntity" syncable="YES" codeGenerationType="class">
|
||||||
|
<attribute name="host" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="lastVisited" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="title" optional="YES" attributeType="String"/>
|
||||||
|
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||||
|
<attribute name="visitCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
</entity>
|
||||||
|
<elements>
|
||||||
|
<element name="HistoryItemEntity" positionX="-63" positionY="-18" width="128" height="104"/>
|
||||||
|
</elements>
|
||||||
|
</model>
|
||||||
21
App/Backend/History/HistoryItem.swift
Normal file
21
App/Backend/History/HistoryItem.swift
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// HistoryItem.swift
|
||||||
|
// App
|
||||||
|
//
|
||||||
|
// Created by James Magahern on 8/14/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct HistoryItem: Hashable
|
||||||
|
{
|
||||||
|
var url: URL
|
||||||
|
var title: String
|
||||||
|
var lastVisited: Date
|
||||||
|
|
||||||
|
init(entity: HistoryItemEntity) {
|
||||||
|
self.url = entity.url ?? URL(string: "about:blank")!
|
||||||
|
self.lastVisited = entity.lastVisited ?? Date()
|
||||||
|
self.title = entity.title ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -273,14 +273,20 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
|||||||
|
|
||||||
updateTitleAndURL(forWebView: webView)
|
updateTitleAndURL(forWebView: webView)
|
||||||
|
|
||||||
// Start requesting favicon
|
|
||||||
if let url = webView.url {
|
if let url = webView.url {
|
||||||
|
// Start requesting favicon
|
||||||
tab.updateFaviconForURL(url)
|
tab.updateFaviconForURL(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||||
toolbarController.urlBar.loadProgress = .complete
|
toolbarController.urlBar.loadProgress = .complete
|
||||||
|
|
||||||
|
// Update history
|
||||||
|
if let url = webView.url {
|
||||||
|
let title = webView.title ?? ""
|
||||||
|
BrowserHistory.shared.didNavigate(toURL: url, title: title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void)
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void)
|
||||||
@@ -305,6 +311,15 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
|||||||
|
|
||||||
// MARK: UITextField Delegate
|
// MARK: UITextField Delegate
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
if let text = textField.text {
|
||||||
|
let matches = BrowserHistory.shared.visitedToplevelHistoryItems(matching: text)
|
||||||
|
print(matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
if let text = textField.text?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) {
|
if let text = textField.text?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) {
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */; };
|
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */; };
|
||||||
1ADFF4CD24CBB0C8006DC7AE /* ScriptPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.swift */; };
|
1ADFF4CD24CBB0C8006DC7AE /* ScriptPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.swift */; };
|
||||||
1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CF24CBBCD1006DC7AE /* ScriptPolicyControl.swift */; };
|
1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CF24CBBCD1006DC7AE /* ScriptPolicyControl.swift */; };
|
||||||
|
CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */; };
|
||||||
|
CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */; };
|
||||||
|
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BD324E77BF900D2BDCC /* HistoryItem.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -101,6 +104,9 @@
|
|||||||
1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptControllerIconView.swift; sourceTree = "<group>"; };
|
1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptControllerIconView.swift; sourceTree = "<group>"; };
|
||||||
1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyViewController.swift; sourceTree = "<group>"; };
|
1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyViewController.swift; sourceTree = "<group>"; };
|
||||||
1ADFF4CF24CBBCD1006DC7AE /* ScriptPolicyControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyControl.swift; sourceTree = "<group>"; };
|
1ADFF4CF24CBBCD1006DC7AE /* ScriptPolicyControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptPolicyControl.swift; sourceTree = "<group>"; };
|
||||||
|
CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserHistory.swift; sourceTree = "<group>"; };
|
||||||
|
CD853BD024E778B800D2BDCC /* History.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = History.xcdatamodel; sourceTree = "<group>"; };
|
||||||
|
CD853BD324E77BF900D2BDCC /* HistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItem.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -224,6 +230,7 @@
|
|||||||
1ADFF47A24C7E176006DC7AE /* Backend */ = {
|
1ADFF47A24C7E176006DC7AE /* Backend */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
CD853BD224E77BEF00D2BDCC /* History */,
|
||||||
1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */,
|
1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */,
|
||||||
);
|
);
|
||||||
path = Backend;
|
path = Backend;
|
||||||
@@ -278,6 +285,16 @@
|
|||||||
path = "Script Policy UI";
|
path = "Script Policy UI";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
CD853BD224E77BEF00D2BDCC /* History */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */,
|
||||||
|
CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */,
|
||||||
|
CD853BD324E77BF900D2BDCC /* HistoryItem.swift */,
|
||||||
|
);
|
||||||
|
path = History;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@@ -384,14 +401,17 @@
|
|||||||
1A03811024E71CF000826501 /* ReliefButton.swift in Sources */,
|
1A03811024E71CF000826501 /* ReliefButton.swift in Sources */,
|
||||||
1A03811224E71EAA00826501 /* GradientView.swift in Sources */,
|
1A03811224E71EAA00826501 /* GradientView.swift in Sources */,
|
||||||
1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */,
|
1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */,
|
||||||
|
CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */,
|
||||||
1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */,
|
1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */,
|
||||||
1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */,
|
1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */,
|
||||||
1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */,
|
1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */,
|
||||||
1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */,
|
1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */,
|
||||||
1A03810D24E71CA700826501 /* ToolbarView.swift in Sources */,
|
1A03810D24E71CA700826501 /* ToolbarView.swift in Sources */,
|
||||||
|
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */,
|
||||||
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
|
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
|
||||||
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */,
|
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */,
|
||||||
1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */,
|
1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */,
|
||||||
|
CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */,
|
||||||
1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */,
|
1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */,
|
||||||
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */,
|
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */,
|
||||||
1AB88EFD24D3BA560006F850 /* TabController.swift in Sources */,
|
1AB88EFD24D3BA560006F850 /* TabController.swift in Sources */,
|
||||||
@@ -676,6 +696,19 @@
|
|||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCVersionGroup section */
|
||||||
|
CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */ = {
|
||||||
|
isa = XCVersionGroup;
|
||||||
|
children = (
|
||||||
|
CD853BD024E778B800D2BDCC /* History.xcdatamodel */,
|
||||||
|
);
|
||||||
|
currentVersion = CD853BD024E778B800D2BDCC /* History.xcdatamodel */;
|
||||||
|
path = History.xcdatamodeld;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
versionGroupType = wrapper.xcdatamodel;
|
||||||
|
};
|
||||||
|
/* End XCVersionGroup section */
|
||||||
};
|
};
|
||||||
rootObject = 1ADFF45424C7DE53006DC7AE /* Project object */;
|
rootObject = 1ADFF45424C7DE53006DC7AE /* Project object */;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user