TabPickerViewController: Refactor to not reference tab controller directly.
This commit is contained in:
@@ -137,9 +137,15 @@ class BrowserViewController: UIViewController
|
||||
|
||||
// Tabs button
|
||||
toolbarController.windowButton.addAction(UIAction(handler: { [unowned self] _ in
|
||||
let tabPickerController = TabPickerViewController(tabController: self.tabController)
|
||||
let tabPickerController = TabPickerViewController()
|
||||
tabPickerController.delegate = self
|
||||
tabPickerController.selectedTab = self.tab
|
||||
tabPickerController.selectedTabIdentifier = self.tab.identifier
|
||||
tabPickerController.tabIdentifiers = tabController.tabs.map { $0.identifier }
|
||||
tabPickerController.tabObserver = tabController.$tabs
|
||||
.receive(on: RunLoop.main)
|
||||
.sink(receiveValue: { (newTabs: [Tab]) in
|
||||
tabPickerController.tabIdentifiers = newTabs.map { $0.identifier }
|
||||
})
|
||||
|
||||
let navController = UINavigationController(rootViewController: tabPickerController)
|
||||
navController.modalPresentationStyle = .popover
|
||||
@@ -611,12 +617,28 @@ extension BrowserViewController: TabDelegate
|
||||
|
||||
extension BrowserViewController: TabPickerViewControllerDelegate
|
||||
{
|
||||
func tabPicker(_ picker: TabPickerViewController, didSelectTab tab: Tab) {
|
||||
func tabPicker(_ picker: TabPickerViewController, createNewTabWithURL url: URL?) {
|
||||
self.tab = tabController.createNewTab(url: url)
|
||||
picker.dismiss(animated: true)
|
||||
}
|
||||
|
||||
func tabPicker(_ picker: TabPickerViewController, tabInfoForIdentifier identifier: UUID) -> TabInfo {
|
||||
guard let tab = tabController.tab(forIdentifier: identifier) else { fatalError() }
|
||||
return tab.tabInfo
|
||||
}
|
||||
|
||||
func tabPicker(_ picker: TabPickerViewController, didSelectTabIdentifier tabIdentifier: UUID) {
|
||||
guard let tab = tabController.tab(forIdentifier: tabIdentifier) else { return }
|
||||
|
||||
self.tab = tab
|
||||
picker.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func tabPicker(_ picker: TabPickerViewController, willCloseTab tab: Tab) {
|
||||
func tabPicker(_ picker: TabPickerViewController, closeTabWithIdentifier tabIdentifier: UUID) {
|
||||
guard let tab = tabController.tab(forIdentifier: tabIdentifier) else { return }
|
||||
|
||||
tabController.closeTab(tab)
|
||||
|
||||
// Dismiss picker if current tab is closed using the picker
|
||||
if tab == self.tab {
|
||||
picker.dismiss(animated: true, completion: nil)
|
||||
|
||||
@@ -17,6 +17,17 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
|
||||
{
|
||||
public weak var delegate: TabDelegate?
|
||||
|
||||
public var tabInfo: TabInfo {
|
||||
get {
|
||||
TabInfo(
|
||||
title: loadedWebView?.title,
|
||||
url: loadedWebView?.url ?? self.homeURL,
|
||||
favicon: self.favicon,
|
||||
identifier: self.identifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public let homeURL: URL?
|
||||
public let bridge: ProcessBundleBridge
|
||||
public var webView: WKWebView {
|
||||
@@ -33,8 +44,8 @@ class Tab: NSObject, SBRProcessBundleBridgeDelegate
|
||||
public var policyManager: ResourcePolicyManager
|
||||
|
||||
private var loadedWebView: WKWebView? = nil
|
||||
public var title: String? { loadedWebView?.title }
|
||||
public var url: URL? { loadedWebView?.url ?? self.homeURL }
|
||||
public var title: String? { get { tabInfo.title } }
|
||||
public var url: URL? { get { tabInfo.url } }
|
||||
|
||||
public var javaScriptEnabled: Bool = false {
|
||||
didSet { bridge.allowAllScripts = javaScriptEnabled }
|
||||
|
||||
@@ -174,7 +174,7 @@ class TabBarView: UIView
|
||||
} else {
|
||||
let newTabView = makeTabView(withIdentifier: identifier)
|
||||
if animated { newTabView.collapsed = true }
|
||||
if i < tabViews.count {
|
||||
if i > 0 && i < tabViews.count {
|
||||
tabViews.insert(newTabView, at: i - 1)
|
||||
} else {
|
||||
tabViews.append(newTabView)
|
||||
|
||||
21
App/Tabs/TabInfo.swift
Normal file
21
App/Tabs/TabInfo.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// TabInfo.swift
|
||||
// App
|
||||
//
|
||||
// Created by James Magahern on 8/5/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
struct TabInfo
|
||||
{
|
||||
public var title: String?
|
||||
public var url: URL?
|
||||
public var favicon: UIImage?
|
||||
public var identifier = UUID()
|
||||
|
||||
public static func ==(lhs: TabInfo, rhs: TabInfo) -> Bool {
|
||||
return lhs.identifier == rhs.identifier
|
||||
}
|
||||
}
|
||||
@@ -6,83 +6,47 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
protocol TabPickerViewControllerDelegate: AnyObject
|
||||
{
|
||||
func tabPicker(_ picker: TabPickerViewController, didSelectTab tab: Tab)
|
||||
func tabPicker(_ picker: TabPickerViewController, willCloseTab tab: Tab)
|
||||
func tabPicker(_ picker: TabPickerViewController, createNewTabWithURL: URL?)
|
||||
func tabPicker(_ picker: TabPickerViewController, didSelectTabIdentifier tab: UUID)
|
||||
func tabPicker(_ picker: TabPickerViewController, closeTabWithIdentifier tab: UUID)
|
||||
func tabPicker(_ picker: TabPickerViewController, tabInfoForIdentifier: UUID) -> TabInfo
|
||||
}
|
||||
|
||||
class TabPickerViewController: UIViewController, UICollectionViewDelegate
|
||||
{
|
||||
let tabController: TabController!
|
||||
var selectedTab: Tab?
|
||||
|
||||
weak var delegate: TabPickerViewControllerDelegate?
|
||||
|
||||
typealias TabID = UUID
|
||||
|
||||
private var selectedTabsForEditing: Set<Tab> = []
|
||||
private var collectionView: UICollectionView?
|
||||
private var dataSource: UICollectionViewDiffableDataSource<Int, TabID>?
|
||||
|
||||
override var traitCollection: UITraitCollection {
|
||||
get { return super.traitCollection.alwaysPadLike() }
|
||||
}
|
||||
|
||||
private lazy var newTabButton: UIBarButtonItem = {
|
||||
UIBarButtonItem(systemItem: .add, primaryAction: UIAction(handler: { [unowned self] _ in
|
||||
let newTab = self.tabController.createNewTab(url: nil)
|
||||
self.delegate?.tabPicker(self, didSelectTab: newTab)
|
||||
}), menu: nil)
|
||||
}()
|
||||
|
||||
lazy var deleteTabButton: UIBarButtonItem = {
|
||||
UIBarButtonItem(systemItem: .trash, primaryAction: UIAction(handler: { [unowned self] _ in
|
||||
deleteSelectedTabs()
|
||||
}), menu: nil)
|
||||
}()
|
||||
|
||||
init(tabController: TabController) {
|
||||
self.tabController = tabController
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.title = "Tabs"
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
var listConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
|
||||
listConfig.trailingSwipeActionsConfigurationProvider = { [unowned self] indexPath in
|
||||
if self.dataSource?.snapshot().numberOfItems ?? 0 <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return UISwipeActionsConfiguration(actions: [ UIContextualAction(style: .destructive, title: "Close", handler: { [unowned self] (action, view, completionHandler) in
|
||||
if let item = self.dataSource?.itemIdentifier(for: indexPath), var snapshot = self.dataSource?.snapshot() {
|
||||
if let tab = self.tabController.tab(forIdentifier: item) {
|
||||
self.delegate?.tabPicker(self, willCloseTab: tab)
|
||||
|
||||
self.tabController.closeTab(tab)
|
||||
snapshot.deleteItems([ item ])
|
||||
self.dataSource?.apply(snapshot, animatingDifferences: true)
|
||||
public var selectedTabIdentifier: UUID?
|
||||
public var tabIdentifiers: [ UUID ] = [] {
|
||||
didSet {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteAllItems()
|
||||
snapshot.appendSections([ 0 ])
|
||||
snapshot.appendItems(tabIdentifiers)
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
}
|
||||
})])
|
||||
}
|
||||
|
||||
let listLayout = UICollectionViewCompositionalLayout.list(using: listConfig)
|
||||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout)
|
||||
weak var delegate: TabPickerViewControllerDelegate?
|
||||
public var tabObserver: AnyCancellable?
|
||||
private var selectedTabIdentifiersForEditing: Set<UUID> = []
|
||||
|
||||
private var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
|
||||
private lazy var listLayout = UICollectionViewCompositionalLayout.list(using: listConfiguration)
|
||||
private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout).conf { collectionView in
|
||||
collectionView.allowsMultipleSelectionDuringEditing = true
|
||||
collectionView.backgroundColor = .systemGroupedBackground
|
||||
collectionView.delegate = self
|
||||
}
|
||||
|
||||
let registry = UICollectionView.CellRegistration<UICollectionViewListCell, TabID> { [unowned self] (listCell, indexPath, item) in
|
||||
private lazy var cellRegistry = UICollectionView.CellRegistration<UICollectionViewListCell, TabID> { [unowned self] (listCell, indexPath, item) in
|
||||
var config = listCell.defaultContentConfiguration()
|
||||
|
||||
if let tab = self.tabController.tab(forIdentifier: item) {
|
||||
if let tab = delegate?.tabPicker(self, tabInfoForIdentifier: item) {
|
||||
if let title = tab.title, title.count > 0 {
|
||||
config.text = title
|
||||
config.secondaryText = tab.url?.absoluteString
|
||||
@@ -105,7 +69,7 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate
|
||||
config.imageProperties.maximumSize = CGSize(width: 21.0, height: 21.0)
|
||||
config.imageProperties.cornerRadius = 3.0
|
||||
|
||||
if self.selectedTab == tab {
|
||||
if let selectedTabIdentifier, selectedTabIdentifier == item {
|
||||
listCell.accessories = [ .checkmark() ]
|
||||
} else {
|
||||
listCell.accessories = []
|
||||
@@ -115,23 +79,52 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate
|
||||
listCell.contentConfiguration = config
|
||||
}
|
||||
|
||||
let dataSource = UICollectionViewDiffableDataSource<Int, TabID>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in
|
||||
return collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: item)
|
||||
private lazy var dataSource = UICollectionViewDiffableDataSource<Int, TabID>(collectionView: collectionView)
|
||||
{ [unowned self] (collectionView, indexPath, item) -> UICollectionViewCell? in
|
||||
return collectionView.dequeueConfiguredReusableCell(using: cellRegistry, for: indexPath, item: item)
|
||||
}
|
||||
|
||||
collectionView.dataSource = dataSource
|
||||
collectionView.delegate = self
|
||||
override var traitCollection: UITraitCollection {
|
||||
get { return super.traitCollection.alwaysPadLike() }
|
||||
}
|
||||
|
||||
private lazy var newTabButton: UIBarButtonItem = {
|
||||
UIBarButtonItem(systemItem: .add, primaryAction: UIAction(handler: { [unowned self] _ in
|
||||
self.delegate?.tabPicker(self, createNewTabWithURL: nil)
|
||||
}), menu: nil)
|
||||
}()
|
||||
|
||||
lazy var deleteTabButton: UIBarButtonItem = {
|
||||
UIBarButtonItem(systemItem: .trash, primaryAction: UIAction(handler: { [unowned self] _ in
|
||||
deleteSelectedTabs()
|
||||
}), menu: nil)
|
||||
}()
|
||||
|
||||
init() {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.title = "Tabs"
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
// Load this lazy var now.
|
||||
_ = cellRegistry.self
|
||||
|
||||
listConfiguration.trailingSwipeActionsConfigurationProvider = { [unowned self] indexPath in
|
||||
return UISwipeActionsConfiguration(actions: [ UIContextualAction(style: .destructive, title: "Close", handler: { [unowned self] (action, view, completionHandler) in
|
||||
if let item = dataSource.itemIdentifier(for: indexPath) {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.appendSections([ 0 ])
|
||||
tabController.tabs.forEach { tab in
|
||||
snapshot.appendItems([ tab.identifier ])
|
||||
delegate?.tabPicker(self, closeTabWithIdentifier: item)
|
||||
|
||||
snapshot.deleteItems([ item ])
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
})])
|
||||
}
|
||||
|
||||
dataSource.apply(snapshot)
|
||||
|
||||
self.dataSource = dataSource
|
||||
self.collectionView = collectionView
|
||||
self.view = self.collectionView
|
||||
|
||||
configureNavigationButtons(forEditing: isEditing)
|
||||
@@ -141,7 +134,7 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate
|
||||
if !forEditing {
|
||||
navigationItem.rightBarButtonItem = newTabButton
|
||||
} else {
|
||||
deleteTabButton.isEnabled = collectionView?.indexPathsForSelectedItems?.count ?? 0 > 0
|
||||
deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0
|
||||
navigationItem.rightBarButtonItem = deleteTabButton
|
||||
}
|
||||
|
||||
@@ -150,43 +143,37 @@ class TabPickerViewController: UIViewController, UICollectionViewDelegate
|
||||
|
||||
override func setEditing(_ editing: Bool, animated: Bool) {
|
||||
super.setEditing(editing, animated: animated)
|
||||
|
||||
if let collectionView = collectionView {
|
||||
collectionView.isEditing = editing
|
||||
}
|
||||
|
||||
configureNavigationButtons(forEditing: editing)
|
||||
}
|
||||
|
||||
private func deleteSelectedTabs() {
|
||||
guard let dataSource = dataSource else { return }
|
||||
|
||||
var snapshot = dataSource.snapshot()
|
||||
for tab in selectedTabsForEditing {
|
||||
snapshot.deleteItems([ tab.identifier ])
|
||||
self.delegate?.tabPicker(self, willCloseTab: tab)
|
||||
self.tabController.closeTab(tab)
|
||||
for tab in selectedTabIdentifiersForEditing {
|
||||
snapshot.deleteItems([ tab ])
|
||||
delegate?.tabPicker(self, closeTabWithIdentifier: tab)
|
||||
}
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let tab = tabController.tabs[indexPath.row]
|
||||
guard let tab = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
if !isEditing {
|
||||
delegate?.tabPicker(self, didSelectTab: tab)
|
||||
delegate?.tabPicker(self, didSelectTabIdentifier: tab)
|
||||
} else {
|
||||
deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0
|
||||
selectedTabsForEditing.update(with: tab)
|
||||
selectedTabIdentifiersForEditing.update(with: tab)
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
|
||||
if isEditing {
|
||||
let tab = tabController.tabs[indexPath.row]
|
||||
selectedTabsForEditing.remove(tab)
|
||||
guard let tabIdentifier = dataSource.itemIdentifier(for: indexPath) else { return }
|
||||
|
||||
if isEditing {
|
||||
selectedTabIdentifiersForEditing.remove(tabIdentifier)
|
||||
deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0
|
||||
}
|
||||
}
|
||||
|
||||
20
App/Utilities/UIView+Utils.swift
Normal file
20
App/Utilities/UIView+Utils.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// UIView+Utils.swift
|
||||
// App
|
||||
//
|
||||
// Created by James Magahern on 8/5/22.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol Conf { }
|
||||
|
||||
extension Conf {
|
||||
@discardableResult
|
||||
func conf(_ block: (Self) -> Void) -> Self {
|
||||
block(self)
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView: Conf {}
|
||||
@@ -57,6 +57,8 @@
|
||||
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 */; };
|
||||
CD936A3B289DB3380093A1AC /* TabInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3A289DB3380093A1AC /* TabInfo.swift */; };
|
||||
CD936A3D289DB88B0093A1AC /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */; };
|
||||
CD97CF9225D5BE6F00288FEE /* NavigationControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */; };
|
||||
CD9B88C2272201E900DAAB7E /* SBRScriptPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */; };
|
||||
CDAD9CE8263A2DF200FF7199 /* DocumentControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */; };
|
||||
@@ -162,6 +164,8 @@
|
||||
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>"; };
|
||||
CD936A3A289DB3380093A1AC /* TabInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabInfo.swift; sourceTree = "<group>"; };
|
||||
CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = "<group>"; };
|
||||
CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControlsView.swift; sourceTree = "<group>"; };
|
||||
CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentControlsView.swift; sourceTree = "<group>"; };
|
||||
CDAD9CE9263A318F00FF7199 /* ShareableURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareableURL.swift; sourceTree = "<group>"; };
|
||||
@@ -227,6 +231,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1A14FC2724D26749009B3F83 /* Tab.swift */,
|
||||
CD936A3A289DB3380093A1AC /* TabInfo.swift */,
|
||||
CD01D5A4254A10BB00189CDC /* TabBarView.swift */,
|
||||
CD01D5AA254A206D00189CDC /* TabBarViewController.swift */,
|
||||
1AB88EFC24D3BA560006F850 /* TabController.swift */,
|
||||
@@ -375,6 +380,7 @@
|
||||
1ADFF4C624CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift */,
|
||||
1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */,
|
||||
CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */,
|
||||
CD936A3C289DB88B0093A1AC /* UIView+Utils.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
@@ -585,6 +591,7 @@
|
||||
CD470C4225DE056600AFBE0E /* BrowserViewController+WebKitDelegate.swift in Sources */,
|
||||
CDEDD8AA25D62ADB00862605 /* UITraitCollection+MacLike.swift in Sources */,
|
||||
CD7A8919251989C90075991E /* UIKeyCommand+ConvInit.swift in Sources */,
|
||||
CD936A3B289DB3380093A1AC /* TabInfo.swift in Sources */,
|
||||
1ADFF4C724CA6DEB006DC7AE /* UIEdgeInsets+Layout.swift in Sources */,
|
||||
1ADFF4AE24C8ED32006DC7AE /* ResourcePolicyManager.swift in Sources */,
|
||||
1ADFF47424C7DE9C006DC7AE /* BrowserViewController.swift in Sources */,
|
||||
@@ -592,6 +599,7 @@
|
||||
CDCE2668251AAA9A007FE92A /* FontSizeAdjustView.swift in Sources */,
|
||||
CD01D5A5254A10BB00189CDC /* TabBarView.swift in Sources */,
|
||||
1A03810D24E71CA700826501 /* ToolbarView.swift in Sources */,
|
||||
CD936A3D289DB88B0093A1AC /* UIView+Utils.swift in Sources */,
|
||||
CD470C4425DE070400AFBE0E /* BrowserViewController+Keyboard.swift in Sources */,
|
||||
CDD0522425F8055700DD1771 /* SearchProvider.swift in Sources */,
|
||||
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user