// // RedirectRulesSettingsViewController.swift // RedirectRulesSettingsViewController // // Created by James Magahern on 6/25/21. // import UIKit import MachO class RedirectRulesSettingsViewController: UICollectionViewController { private enum Section { case rules case addButton } private enum Items: String { case addButton } private lazy var dataSource: UICollectionViewDiffableDataSource = { let registry = UICollectionView.CellRegistration { [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(collectionView: collectionView) { (collectionView, indexPath, itemIdentifier) in collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: itemIdentifier) } }() init() { let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) let layout = UICollectionViewCompositionalLayout { section, layoutEnvironment in let listSection = NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) #if targetEnvironment(macCatalyst) listSection.contentInsets = NSDirectionalEdgeInsets(top: 24.0, leading: 164.0, bottom: 24.0, trailing: 164.0) #endif return listSection } super.init(collectionViewLayout: layout) tabBarItem.title = "Redirect Rules" tabBarItem.image = UIImage(systemName: "arrowshape.zigzag.forward") } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { 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) { collectionView.deselectItem(at: indexPath, animated: true) 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(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 = { let registry = UICollectionView.CellRegistration { [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(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) } override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { false } }