mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-21 20:03:08 +09:00
331 lines
15 KiB
Swift
331 lines
15 KiB
Swift
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import UIKit
|
|
import SDWebImage
|
|
import Shared
|
|
|
|
protocol SearchEnginePickerDelegate: class {
|
|
func searchEnginePicker(_ searchEnginePicker: SearchEnginePicker?, didSelectSearchEngine engine: OpenSearchEngine?)
|
|
}
|
|
|
|
class SearchSettingsTableViewController: UITableViewController {
|
|
fileprivate let SectionDefault = 0
|
|
fileprivate let ItemDefaultEngine = 0
|
|
fileprivate let ItemDefaultSuggestions = 1
|
|
fileprivate let ItemAddCustomSearch = 2
|
|
fileprivate let NumberOfItemsInSectionDefault = 2
|
|
fileprivate let SectionOrder = 1
|
|
fileprivate let NumberOfSections = 2
|
|
fileprivate let IconSize = CGSize(width: OpenSearchEngine.PreferredIconSize, height: OpenSearchEngine.PreferredIconSize)
|
|
fileprivate let SectionHeaderIdentifier = "SectionHeaderIdentifier"
|
|
|
|
fileprivate var showDeletion = false
|
|
|
|
var profile: Profile?
|
|
var tabManager: TabManager?
|
|
|
|
fileprivate var isEditable: Bool {
|
|
// If the default engine is a custom one, make sure we have more than one since we can't edit the default.
|
|
// Otherwise, enable editing if we have at least one custom engine.
|
|
let customEngineCount = model.orderedEngines.filter({$0.isCustomEngine}).count
|
|
return model.defaultEngine.isCustomEngine ? customEngineCount > 1 : customEngineCount > 0
|
|
}
|
|
|
|
var model: SearchEngines!
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
navigationItem.title = NSLocalizedString("Search", comment: "Navigation title for search settings.")
|
|
|
|
// To allow re-ordering the list of search engines at all times.
|
|
tableView.isEditing = true
|
|
// So that we push the default search engine controller on selection.
|
|
tableView.allowsSelectionDuringEditing = true
|
|
|
|
tableView.register(SettingsTableSectionHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: SectionHeaderIdentifier)
|
|
|
|
// Insert Done button if being presented outside of the Settings Nav stack
|
|
if !(self.navigationController is SettingsNavigationController) {
|
|
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: Strings.SettingsSearchDoneButton, style: .done, target: self, action: #selector(self.dismissAnimated))
|
|
}
|
|
|
|
let footer = SettingsTableSectionHeaderFooterView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 44))
|
|
footer.showBottomBorder = false
|
|
tableView.tableFooterView = footer
|
|
|
|
tableView.separatorColor = UIConstants.TableViewSeparatorColor
|
|
tableView.backgroundColor = UIConstants.TableViewHeaderBackgroundColor
|
|
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(title: Strings.SettingsSearchEditButton, style: .plain, target: self,
|
|
action: #selector(SearchSettingsTableViewController.beginEditing))
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
// Only show the Edit button if custom search engines are in the list.
|
|
// Otherwise, there is nothing to delete.
|
|
navigationItem.rightBarButtonItem?.isEnabled = isEditable
|
|
tableView.reloadData()
|
|
}
|
|
|
|
override func viewDidDisappear(_ animated: Bool) {
|
|
super.viewDidDisappear(animated)
|
|
setEditing(false, animated: false)
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
var cell: UITableViewCell!
|
|
var engine: OpenSearchEngine!
|
|
|
|
if indexPath.section == SectionDefault {
|
|
switch indexPath.item {
|
|
case ItemDefaultEngine:
|
|
engine = model.defaultEngine
|
|
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: nil)
|
|
cell.editingAccessoryType = UITableViewCellAccessoryType.disclosureIndicator
|
|
cell.accessibilityLabel = NSLocalizedString("Default Search Engine", comment: "Accessibility label for default search engine setting.")
|
|
cell.accessibilityValue = engine.shortName
|
|
cell.textLabel?.text = engine.shortName
|
|
cell.imageView?.image = engine.image.createScaled(IconSize)
|
|
cell.imageView?.layer.cornerRadius = 4
|
|
cell.imageView?.layer.masksToBounds = true
|
|
case ItemDefaultSuggestions:
|
|
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: nil)
|
|
cell.textLabel?.text = NSLocalizedString("Show Search Suggestions", comment: "Label for show search suggestions setting.")
|
|
let toggle = UISwitch()
|
|
toggle.onTintColor = UIConstants.ControlTintColor
|
|
toggle.addTarget(self, action: #selector(SearchSettingsTableViewController.didToggleSearchSuggestions(_:)), for: UIControlEvents.valueChanged)
|
|
toggle.isOn = model.shouldShowSearchSuggestions
|
|
cell.editingAccessoryView = toggle
|
|
cell.selectionStyle = .none
|
|
default:
|
|
// Should not happen.
|
|
break
|
|
}
|
|
} else {
|
|
// The default engine is not a quick search engine.
|
|
let index = indexPath.item + 1
|
|
if index < model.orderedEngines.count {
|
|
engine = model.orderedEngines[index]
|
|
|
|
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: nil)
|
|
cell.showsReorderControl = true
|
|
|
|
let toggle = UISwitch()
|
|
toggle.onTintColor = UIConstants.ControlTintColor
|
|
// This is an easy way to get from the toggle control to the corresponding index.
|
|
toggle.tag = index
|
|
toggle.addTarget(self, action: #selector(SearchSettingsTableViewController.didToggleEngine(_:)), for: UIControlEvents.valueChanged)
|
|
toggle.isOn = model.isEngineEnabled(engine)
|
|
|
|
cell.editingAccessoryView = toggle
|
|
cell.textLabel?.text = engine.shortName
|
|
cell.textLabel?.adjustsFontSizeToFitWidth = true
|
|
cell.textLabel?.minimumScaleFactor = 0.5
|
|
cell.imageView?.image = engine.image.createScaled(IconSize)
|
|
cell.imageView?.layer.cornerRadius = 4
|
|
cell.imageView?.layer.masksToBounds = true
|
|
cell.selectionStyle = .none
|
|
} else {
|
|
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: nil)
|
|
cell.editingAccessoryType = UITableViewCellAccessoryType.disclosureIndicator
|
|
cell.accessibilityLabel = Strings.SettingsAddCustomEngineTitle
|
|
cell.accessibilityIdentifier = "customEngineViewButton"
|
|
cell.textLabel?.text = Strings.SettingsAddCustomEngine
|
|
}
|
|
}
|
|
|
|
// So that the seperator line goes all the way to the left edge.
|
|
cell.separatorInset = UIEdgeInsets.zero
|
|
|
|
return cell
|
|
}
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
return NumberOfSections
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
if section == SectionDefault {
|
|
return NumberOfItemsInSectionDefault
|
|
} else {
|
|
// The first engine -- the default engine -- is not shown in the quick search engine list.
|
|
// But the option to add Custom Engine is.
|
|
return AppConstants.MOZ_CUSTOM_SEARCH_ENGINE ? model.orderedEngines.count : model.orderedEngines.count - 1
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
|
if indexPath.section == SectionDefault && indexPath.item == ItemDefaultEngine {
|
|
let searchEnginePicker = SearchEnginePicker()
|
|
// Order alphabetically, so that picker is always consistently ordered.
|
|
// Every engine is a valid choice for the default engine, even the current default engine.
|
|
searchEnginePicker.engines = model.orderedEngines.sorted { e, f in e.shortName < f.shortName }
|
|
searchEnginePicker.delegate = self
|
|
searchEnginePicker.selectedSearchEngineName = model.defaultEngine.shortName
|
|
navigationController?.pushViewController(searchEnginePicker, animated: true)
|
|
} else if indexPath.item + 1 == model.orderedEngines.count {
|
|
let customSearchEngineForm = CustomSearchViewController()
|
|
customSearchEngineForm.profile = self.profile
|
|
customSearchEngineForm.successCallback = {
|
|
guard let window = self.view.window else { return }
|
|
SimpleToast().showAlertWithText(Strings.ThirdPartySearchEngineAdded, bottomContainer: window)
|
|
}
|
|
navigationController?.pushViewController(customSearchEngineForm, animated: true)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Don't show delete button on the left.
|
|
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
|
|
if indexPath.section == SectionDefault || indexPath.item + 1 == model.orderedEngines.count {
|
|
return UITableViewCellEditingStyle.none
|
|
}
|
|
|
|
let index = indexPath.item + 1
|
|
let engine = model.orderedEngines[index]
|
|
return (self.showDeletion && engine.isCustomEngine) ? .delete : .none
|
|
}
|
|
|
|
// Don't reserve space for the delete button on the left.
|
|
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
|
|
return false
|
|
}
|
|
|
|
// Hide a thin vertical line that iOS renders between the accessoryView and the reordering control.
|
|
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
|
if cell.isEditing {
|
|
for v in cell.subviews where v.frame.width == 1.0 {
|
|
v.backgroundColor = UIColor.clear
|
|
}
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
|
return 44
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
|
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SectionHeaderIdentifier) as! SettingsTableSectionHeaderFooterView
|
|
var sectionTitle: String
|
|
if section == SectionDefault {
|
|
sectionTitle = NSLocalizedString("Default Search Engine", comment: "Title for default search engine settings section.")
|
|
} else {
|
|
sectionTitle = NSLocalizedString("Quick-Search Engines", comment: "Title for quick-search engines settings section.")
|
|
}
|
|
headerView.titleLabel.text = sectionTitle
|
|
|
|
return headerView
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
|
|
if indexPath.section == SectionDefault || indexPath.item + 1 == model.orderedEngines.count {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, moveRowAt indexPath: IndexPath, to newIndexPath: IndexPath) {
|
|
// The first engine (default engine) is not shown in the list, so the indices are off-by-1.
|
|
let index = indexPath.item + 1
|
|
let newIndex = newIndexPath.item + 1
|
|
let engine = model.orderedEngines.remove(at: index)
|
|
model.orderedEngines.insert(engine, at: newIndex)
|
|
tableView.reloadData()
|
|
}
|
|
|
|
// Snap to first or last row of the list of engines.
|
|
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
|
|
// You can't drag or drop on the default engine.
|
|
if sourceIndexPath.section == SectionDefault || proposedDestinationIndexPath.section == SectionDefault {
|
|
return sourceIndexPath
|
|
}
|
|
|
|
//Can't drag/drop over "Add Custom Engine button"
|
|
if sourceIndexPath.item + 1 == model.orderedEngines.count || proposedDestinationIndexPath.item + 1 == model.orderedEngines.count {
|
|
return sourceIndexPath
|
|
}
|
|
|
|
if sourceIndexPath.section != proposedDestinationIndexPath.section {
|
|
var row = 0
|
|
if sourceIndexPath.section < proposedDestinationIndexPath.section {
|
|
row = tableView.numberOfRows(inSection: sourceIndexPath.section) - 1
|
|
}
|
|
return IndexPath(row: row, section: sourceIndexPath.section)
|
|
}
|
|
return proposedDestinationIndexPath
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
|
|
if editingStyle == .delete {
|
|
let index = indexPath.item + 1
|
|
let engine = model.orderedEngines[index]
|
|
model.deleteCustomEngine(engine)
|
|
tableView.deleteRows(at: [indexPath], with: .right)
|
|
|
|
// End editing if we are no longer edit since we've deleted all editable cells.
|
|
if !isEditable {
|
|
finishEditing()
|
|
}
|
|
}
|
|
}
|
|
|
|
override func setEditing(_ editing: Bool, animated: Bool) {
|
|
showDeletion = editing
|
|
UIView.performWithoutAnimation {
|
|
self.navigationItem.rightBarButtonItem?.title = editing ? Strings.SettingsSearchDoneButton : Strings.SettingsSearchEditButton
|
|
}
|
|
navigationItem.rightBarButtonItem?.isEnabled = isEditable
|
|
navigationItem.rightBarButtonItem?.action = editing ?
|
|
#selector(SearchSettingsTableViewController.finishEditing) : #selector(SearchSettingsTableViewController.beginEditing)
|
|
tableView.reloadData()
|
|
}
|
|
}
|
|
|
|
// MARK: - Selectors
|
|
extension SearchSettingsTableViewController {
|
|
func didToggleEngine(_ toggle: UISwitch) {
|
|
let engine = model.orderedEngines[toggle.tag] // The tag is 1-based.
|
|
if toggle.isOn {
|
|
model.enableEngine(engine)
|
|
} else {
|
|
model.disableEngine(engine)
|
|
}
|
|
}
|
|
|
|
func didToggleSearchSuggestions(_ toggle: UISwitch) {
|
|
// Setting the value in settings dismisses any opt-in.
|
|
model.shouldShowSearchSuggestions = toggle.isOn
|
|
}
|
|
|
|
func cancel() {
|
|
_ = navigationController?.popViewController(animated: true)
|
|
}
|
|
|
|
func dismissAnimated() {
|
|
self.dismiss(animated: true, completion: nil)
|
|
}
|
|
|
|
func beginEditing() {
|
|
setEditing(true, animated: false)
|
|
}
|
|
|
|
func finishEditing() {
|
|
setEditing(false, animated: false)
|
|
}
|
|
}
|
|
|
|
extension SearchSettingsTableViewController: SearchEnginePickerDelegate {
|
|
func searchEnginePicker(_ searchEnginePicker: SearchEnginePicker?, didSelectSearchEngine searchEngine: OpenSearchEngine?) {
|
|
if let engine = searchEngine {
|
|
model.defaultEngine = engine
|
|
self.tableView.reloadData()
|
|
UnifiedTelemetry.recordEvent(category: .action, method: .change, object: .setting, value: "defaultSearchEngine", extras: ["to": engine.engineID ?? "custom"])
|
|
}
|
|
_ = navigationController?.popViewController(animated: true)
|
|
}
|
|
}
|