Redirect Rules: implements redirect rules UI

This commit is contained in:
James Magahern
2021-12-14 18:50:33 -08:00
parent 2ac814cd4e
commit 3cedd3f387
7 changed files with 198 additions and 39 deletions

View File

@@ -1,24 +0,0 @@
//
// PersonalRedirectRules.swift
// App
//
// Created by James Magahern on 9/30/20.
//
import Foundation
class PersonalRedirectRules
{
func redirectedURL(for url: URL) -> URL? {
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
// Rule for nitter.net
if url.host == "twitter.com" {
components.host = "nitter.net"
return components.url
}
}
return nil
}
}

View File

@@ -97,7 +97,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
preferences.allowsContentJavaScript = allowJavaScript
if let url = navigationAction.request.url,
let redirectedURL = redirectRules.redirectedURL(for: url)
let redirectedURL = Settings.shared.redirectRule(for: url)
{
tab.beginLoadingURL(redirectedURL)
decisionHandler(.cancel, preferences)

View File

@@ -22,8 +22,6 @@ class BrowserViewController: UIViewController
internal let findOnPageController = FindOnPageViewController()
internal let autocompleteViewController = AutocompleteViewController()
internal let redirectRules = PersonalRedirectRules()
internal var policyManager: ResourcePolicyManager { tabController.policyManager }
override var canBecomeFirstResponder: Bool { true }

View File

@@ -29,17 +29,22 @@ class GenericContentView<View, Configuration> : UIView, UIContentView
self.applicator = applicator
super.init(frame: .zero)
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
let guide = layoutMarginsGuide
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: guide.leadingAnchor),
view.trailingAnchor.constraint(equalTo: guide.trailingAnchor),
view.topAnchor.constraint(equalTo: guide.topAnchor),
view.bottomAnchor.constraint(equalTo: guide.bottomAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
view.frame = bounds.inset(by: insets)
}
internal func apply(_ configuration: Configuration) {
applicator(configuration, view)
}

View File

@@ -7,10 +7,52 @@
import UIKit
class RedirectRulesSettingsViewController: UIViewController
class RedirectRulesSettingsViewController: UICollectionViewController
{
private enum Section {
case rules
case addButton
}
private enum Items: String {
case addButton
}
private lazy var dataSource: UICollectionViewDiffableDataSource<Section, String> = {
let registry = UICollectionView.CellRegistration<UICollectionViewListCell, String> { [unowned self] cell, indexPath, item in
var config = cell.defaultContentConfiguration()
if item == Items.addButton.rawValue {
config.text = "Add Redirect Rule"
config.textProperties.font = .preferredFont(forTextStyle: .subheadline)
config.textProperties.alignment = .center
config.image = .init(systemName: "plus.circle")
cell.accessories = []
} else {
if let rule = Settings.shared.redirectRules[item] {
config.text = "\(item)\(rule)"
}
let deleteOptions = UICellAccessory.DeleteOptions(isHidden: false, reservedLayoutWidth: nil, tintColor: .red, backgroundColor: nil)
cell.accessories = [
.delete(displayed: .always, options: deleteOptions, actionHandler: { [unowned self] in
deleteRedirectRule(fromHost: item)
})
]
}
cell.contentConfiguration = config
}
return UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) { (collectionView, indexPath, itemIdentifier) in
collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: itemIdentifier)
}
}()
init() {
super.init(nibName: nil, bundle: nil)
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
let layout = UICollectionViewCompositionalLayout.list(using: config)
super.init(collectionViewLayout: layout)
tabBarItem.title = "Redirect Rules"
tabBarItem.image = UIImage(systemName: "arrowshape.zigzag.forward")
@@ -21,7 +63,122 @@ class RedirectRulesSettingsViewController: UIViewController
}
override func viewDidLoad() {
view.backgroundColor = .red
var snapshot = dataSource.snapshot()
snapshot.appendSections([ .rules, .addButton ])
snapshot.appendItems(Settings.shared.redirectRules.keys.sorted(), toSection: .rules)
snapshot.appendItems([ Items.addButton.rawValue ], toSection: .addButton)
dataSource.applySnapshotUsingReloadData(snapshot)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = dataSource.itemIdentifier(for: indexPath)
if item == Items.addButton.rawValue {
let createRuleViewController = CreateRedirectRuleViewController()
createRuleViewController.finishedCreatingRule = { [unowned self] (fromHost, toHost) in
if fromHost.count > 0 && toHost.count > 0 {
addRedirectRule(fromHost: fromHost, toHost: toHost)
}
}
let navController = UINavigationController(rootViewController: createRuleViewController)
present(navController, animated: true, completion: nil)
}
}
private func addRedirectRule(fromHost: String, toHost: String) {
Settings.shared.redirectRules[fromHost] = toHost
var snapshot = dataSource.snapshot()
snapshot.appendItems([ fromHost ], toSection: .rules)
dataSource.apply(snapshot, animatingDifferences: true)
}
private func deleteRedirectRule(fromHost: String) {
Settings.shared.redirectRules.removeValue(forKey: fromHost)
var snapshot = dataSource.snapshot()
snapshot.deleteItems([ fromHost ])
dataSource.apply(snapshot, animatingDifferences: true)
}
}
class CreateRedirectRuleViewController: UICollectionViewController
{
enum Section {
case hosts
}
enum Item: String {
case fromHost
case toHost
}
struct TextFieldCellConfiguration: UIContentConfiguration {
var textField: UITextField
func makeContentView() -> UIView & UIContentView {
return GenericContentView<UITextField, TextFieldCellConfiguration>(configuration: self, view: textField) { _,_ in }
}
func updated(for state: UIConfigurationState) -> TextFieldCellConfiguration {
self
}
}
public var finishedCreatingRule: ((_ fromHost: String, _ toHost: String) -> Void)?
private let fromHostField = UITextField(frame: .zero)
private let toHostField = UITextField(frame: .zero)
private let doneButton = UIBarButtonItem(systemItem: .done, primaryAction: nil, menu: nil)
private lazy var dataSource: UICollectionViewDiffableDataSource<Section, Item> = {
let registry = UICollectionView.CellRegistration<UICollectionViewListCell, Item> { [unowned self] cell, indexPath, item in
if item == Item.fromHost {
cell.contentConfiguration = TextFieldCellConfiguration(textField: fromHostField)
} else if item == Item.toHost {
cell.contentConfiguration = TextFieldCellConfiguration(textField: toHostField)
}
}
return UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { (collectionView, indexPath, itemIdentifier) in
collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: itemIdentifier)
}
}()
init() {
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
let layout = UICollectionViewCompositionalLayout.list(using: config)
super.init(collectionViewLayout: layout)
preferredContentSize = CGSize(width: -1, height: 320.0)
self.title = "Add Redirect Rule"
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction { [unowned self] _ in
dismiss(animated: true, completion: nil)
}, menu: nil)
doneButton.primaryAction = UIAction { [unowned self] _ in
finishedCreatingRule?(fromHostField.text!, toHostField.text!)
dismiss(animated: true, completion: nil)
}
navigationItem.rightBarButtonItem = doneButton
fromHostField.placeholder = "From Host"
toHostField.placeholder = "To Host"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
var snapshot = dataSource.snapshot()
snapshot.appendSections([ Section.hosts ])
snapshot.appendItems([ Item.fromHost, Item.toHost ], toSection: .hosts)
dataSource.applySnapshotUsingReloadData(snapshot)
}
}

View File

@@ -28,6 +28,19 @@ public struct SettingProperty<T: RawRepresentable>
}
}
extension Dictionary: RawRepresentable where Key == String, Value == String {
public typealias RawValue = [String: String]
public init?(rawValue: [String : String]) {
self.init()
self.merge(rawValue, uniquingKeysWith: { $1 })
}
public var rawValue: [String : String] {
return self
}
}
class Settings
{
static let shared = Settings()
@@ -50,4 +63,18 @@ class Settings
@SettingProperty(key: "searchProvider")
public var searchProvider: SearchProviderSetting = .searxnor
@SettingProperty(key: "redirectRules")
public var redirectRules: [String: String] = [:]
func redirectRule(for url: URL) -> URL? {
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false), let host = url.host {
if let alternateHost = redirectRules[host] {
components.host = alternateHost
return components.url
}
}
return nil
}
}