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)
|
||||
|
||||
// Start requesting favicon
|
||||
if let url = webView.url {
|
||||
// Start requesting favicon
|
||||
tab.updateFaviconForURL(url)
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
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)
|
||||
@@ -305,6 +311,15 @@ class BrowserViewController: UIViewController, WKNavigationDelegate,
|
||||
|
||||
// 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 {
|
||||
if let text = textField.text?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) {
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CA24CB8278006DC7AE /* ScriptControllerIconView.swift */; };
|
||||
1ADFF4CD24CBB0C8006DC7AE /* ScriptPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADFF4CC24CBB0C8006DC7AE /* ScriptPolicyViewController.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 */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -101,6 +104,9 @@
|
||||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -224,6 +230,7 @@
|
||||
1ADFF47A24C7E176006DC7AE /* Backend */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CD853BD224E77BEF00D2BDCC /* History */,
|
||||
1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */,
|
||||
);
|
||||
path = Backend;
|
||||
@@ -278,6 +285,16 @@
|
||||
path = "Script Policy UI";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CD853BD224E77BEF00D2BDCC /* History */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */,
|
||||
CD853BCD24E7763900D2BDCC /* BrowserHistory.swift */,
|
||||
CD853BD324E77BF900D2BDCC /* HistoryItem.swift */,
|
||||
);
|
||||
path = History;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -384,14 +401,17 @@
|
||||
1A03811024E71CF000826501 /* ReliefButton.swift in Sources */,
|
||||
1A03811224E71EAA00826501 /* GradientView.swift in Sources */,
|
||||
1ADFF4C024CA6964006DC7AE /* URLBar.swift in Sources */,
|
||||
CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */,
|
||||
1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */,
|
||||
1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */,
|
||||
1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */,
|
||||
1ADFF4D024CBBCD1006DC7AE /* ScriptPolicyControl.swift in Sources */,
|
||||
1A03810D24E71CA700826501 /* ToolbarView.swift in Sources */,
|
||||
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */,
|
||||
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
|
||||
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */,
|
||||
1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */,
|
||||
CD853BCE24E7763900D2BDCC /* BrowserHistory.swift in Sources */,
|
||||
1A03810B24E71C5600826501 /* ToolbarButtonContainerView.swift in Sources */,
|
||||
1ADFF4CB24CB8278006DC7AE /* ScriptControllerIconView.swift in Sources */,
|
||||
1AB88EFD24D3BA560006F850 /* TabController.swift in Sources */,
|
||||
@@ -676,6 +696,19 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* 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 */;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user