2019-02-27 08:00:57 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-12-04 11:15:29 +00:00
|
|
|
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
2019-02-27 08:00:57 +00:00
|
|
|
|
|
|
|
import UIKit
|
2019-03-05 13:07:53 +00:00
|
|
|
import SystemConfiguration.CaptiveNetwork
|
2021-03-08 23:57:35 +00:00
|
|
|
import NetworkExtension
|
2019-02-27 08:00:57 +00:00
|
|
|
|
|
|
|
protocol SSIDOptionEditTableViewControllerDelegate: class {
|
|
|
|
func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String])
|
|
|
|
}
|
|
|
|
|
|
|
|
class SSIDOptionEditTableViewController: UITableViewController {
|
|
|
|
private enum Section {
|
|
|
|
case ssidOption
|
|
|
|
case selectedSSIDs
|
|
|
|
case addSSIDs
|
|
|
|
}
|
|
|
|
|
2019-03-05 13:07:53 +00:00
|
|
|
private enum AddSSIDRow {
|
|
|
|
case addConnectedSSID(connectedSSID: String)
|
|
|
|
case addNewSSID
|
|
|
|
}
|
|
|
|
|
2019-02-27 08:00:57 +00:00
|
|
|
weak var delegate: SSIDOptionEditTableViewControllerDelegate?
|
|
|
|
|
|
|
|
private var sections = [Section]()
|
2019-03-05 13:07:53 +00:00
|
|
|
private var addSSIDRows = [AddSSIDRow]()
|
2019-02-27 08:00:57 +00:00
|
|
|
|
|
|
|
let ssidOptionFields: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [
|
|
|
|
.anySSID,
|
|
|
|
.onlySpecificSSIDs,
|
|
|
|
.exceptSpecificSSIDs
|
|
|
|
]
|
|
|
|
|
|
|
|
var selectedOption: ActivateOnDemandViewModel.OnDemandSSIDOption
|
|
|
|
var selectedSSIDs: [String]
|
2019-03-05 13:07:53 +00:00
|
|
|
var connectedSSID: String?
|
2019-02-27 08:00:57 +00:00
|
|
|
|
|
|
|
init(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String]) {
|
|
|
|
selectedOption = option
|
|
|
|
selectedSSIDs = ssids
|
|
|
|
super.init(style: .grouped)
|
|
|
|
loadSections()
|
2021-03-08 23:57:35 +00:00
|
|
|
addSSIDRows.removeAll()
|
|
|
|
addSSIDRows.append(.addNewSSID)
|
|
|
|
|
|
|
|
getConnectedSSID { [weak self] ssid in
|
|
|
|
guard let self = self else { return }
|
|
|
|
self.connectedSSID = ssid
|
|
|
|
self.updateCurrentSSIDEntry()
|
|
|
|
self.updateTableViewAddSSIDRows()
|
|
|
|
}
|
2019-02-27 08:00:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
super.viewDidLoad()
|
2019-03-09 04:17:35 +00:00
|
|
|
title = tr("tunnelOnDemandSSIDViewTitle")
|
2019-02-27 08:00:57 +00:00
|
|
|
|
|
|
|
tableView.estimatedRowHeight = 44
|
|
|
|
tableView.rowHeight = UITableView.automaticDimension
|
|
|
|
|
|
|
|
tableView.register(CheckmarkCell.self)
|
|
|
|
tableView.register(EditableTextCell.self)
|
|
|
|
tableView.register(TextCell.self)
|
|
|
|
tableView.isEditing = true
|
|
|
|
tableView.allowsSelectionDuringEditing = true
|
2021-03-08 23:02:03 +00:00
|
|
|
tableView.keyboardDismissMode = .onDrag
|
2019-02-27 08:00:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func loadSections() {
|
|
|
|
sections.removeAll()
|
|
|
|
sections.append(.ssidOption)
|
|
|
|
if selectedOption != .anySSID {
|
2019-03-09 04:17:35 +00:00
|
|
|
sections.append(.selectedSSIDs)
|
2019-02-27 08:00:57 +00:00
|
|
|
sections.append(.addSSIDs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-08 23:57:35 +00:00
|
|
|
func updateCurrentSSIDEntry() {
|
|
|
|
if let connectedSSID = connectedSSID, !selectedSSIDs.contains(connectedSSID) {
|
|
|
|
if let first = addSSIDRows.first, case .addNewSSID = first {
|
|
|
|
addSSIDRows.insert(.addConnectedSSID(connectedSSID: connectedSSID), at: 0)
|
2019-03-05 13:07:53 +00:00
|
|
|
}
|
2021-03-08 23:57:35 +00:00
|
|
|
} else if let first = addSSIDRows.first, case .addConnectedSSID = first {
|
|
|
|
addSSIDRows.removeFirst()
|
2019-03-05 13:07:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateTableViewAddSSIDRows() {
|
|
|
|
guard let addSSIDSection = sections.firstIndex(of: .addSSIDs) else { return }
|
|
|
|
let numberOfAddSSIDRows = addSSIDRows.count
|
|
|
|
let numberOfAddSSIDRowsInTableView = tableView.numberOfRows(inSection: addSSIDSection)
|
|
|
|
switch (numberOfAddSSIDRowsInTableView, numberOfAddSSIDRows) {
|
|
|
|
case (1, 2):
|
|
|
|
tableView.insertRows(at: [IndexPath(row: 0, section: addSSIDSection)], with: .automatic)
|
|
|
|
case (2, 1):
|
|
|
|
tableView.deleteRows(at: [IndexPath(row: 0, section: addSSIDSection)], with: .automatic)
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-27 08:00:57 +00:00
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
|
|
delegate?.ssidOptionSaved(option: selectedOption, ssids: selectedSSIDs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension SSIDOptionEditTableViewController {
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
|
|
return sections.count
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
|
|
switch sections[section] {
|
|
|
|
case .ssidOption:
|
|
|
|
return ssidOptionFields.count
|
|
|
|
case .selectedSSIDs:
|
2019-03-09 04:17:35 +00:00
|
|
|
return selectedSSIDs.isEmpty ? 1 : selectedSSIDs.count
|
2019-02-27 08:00:57 +00:00
|
|
|
case .addSSIDs:
|
2019-03-05 13:07:53 +00:00
|
|
|
return addSSIDRows.count
|
2019-02-27 08:00:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
|
|
switch sections[indexPath.section] {
|
|
|
|
case .ssidOption:
|
|
|
|
return ssidOptionCell(for: tableView, at: indexPath)
|
|
|
|
case .selectedSSIDs:
|
2019-03-09 04:17:35 +00:00
|
|
|
if !selectedSSIDs.isEmpty {
|
|
|
|
return selectedSSIDCell(for: tableView, at: indexPath)
|
|
|
|
} else {
|
|
|
|
return noSSIDsCell(for: tableView, at: indexPath)
|
|
|
|
}
|
2019-02-27 08:00:57 +00:00
|
|
|
case .addSSIDs:
|
|
|
|
return addSSIDCell(for: tableView, at: indexPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
|
|
|
switch sections[indexPath.section] {
|
|
|
|
case .ssidOption:
|
|
|
|
return false
|
2019-03-09 04:17:35 +00:00
|
|
|
case .selectedSSIDs:
|
|
|
|
return !selectedSSIDs.isEmpty
|
|
|
|
case .addSSIDs:
|
2019-02-27 08:00:57 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
|
|
|
|
switch sections[indexPath.section] {
|
|
|
|
case .ssidOption:
|
|
|
|
return .none
|
|
|
|
case .selectedSSIDs:
|
|
|
|
return .delete
|
|
|
|
case .addSSIDs:
|
|
|
|
return .insert
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
|
|
|
switch sections[section] {
|
|
|
|
case .ssidOption:
|
|
|
|
return nil
|
|
|
|
case .selectedSSIDs:
|
|
|
|
return tr("tunnelOnDemandSectionTitleSelectedSSIDs")
|
|
|
|
case .addSSIDs:
|
|
|
|
return tr("tunnelOnDemandSectionTitleAddSSIDs")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func ssidOptionCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
|
|
|
let field = ssidOptionFields[indexPath.row]
|
|
|
|
let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath)
|
|
|
|
cell.message = field.localizedUIString
|
|
|
|
cell.isChecked = selectedOption == field
|
|
|
|
cell.isEditing = false
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
|
2019-03-09 04:17:35 +00:00
|
|
|
private func noSSIDsCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
|
|
|
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
|
|
|
|
cell.message = tr("tunnelOnDemandNoSSIDs")
|
2019-10-14 21:43:56 +00:00
|
|
|
if #available(iOS 13.0, *) {
|
|
|
|
cell.setTextColor(.secondaryLabel)
|
|
|
|
} else {
|
|
|
|
cell.setTextColor(.gray)
|
|
|
|
}
|
2019-03-09 04:17:35 +00:00
|
|
|
cell.setTextAlignment(.center)
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
|
2019-02-27 08:00:57 +00:00
|
|
|
private func selectedSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
|
|
|
let cell: EditableTextCell = tableView.dequeueReusableCell(for: indexPath)
|
|
|
|
cell.message = selectedSSIDs[indexPath.row]
|
2021-03-08 23:02:03 +00:00
|
|
|
cell.placeholder = tr("tunnelOnDemandSSIDTextFieldPlaceholder")
|
2019-02-27 08:00:57 +00:00
|
|
|
cell.isEditing = true
|
|
|
|
cell.onValueBeingEdited = { [weak self, weak cell] text in
|
|
|
|
guard let self = self, let cell = cell else { return }
|
|
|
|
if let row = self.tableView.indexPath(for: cell)?.row {
|
|
|
|
self.selectedSSIDs[row] = text
|
2021-03-08 23:57:35 +00:00
|
|
|
self.updateCurrentSSIDEntry()
|
2019-03-05 13:07:53 +00:00
|
|
|
self.updateTableViewAddSSIDRows()
|
2019-02-27 08:00:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
|
|
|
|
private func addSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
|
|
|
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
|
2019-03-05 13:07:53 +00:00
|
|
|
switch addSSIDRows[indexPath.row] {
|
|
|
|
case .addConnectedSSID:
|
|
|
|
cell.message = tr(format: "tunnelOnDemandAddMessageAddConnectedSSID (%@)", connectedSSID!)
|
|
|
|
case .addNewSSID:
|
|
|
|
cell.message = tr("tunnelOnDemandAddMessageAddNewSSID")
|
|
|
|
}
|
2019-02-27 08:00:57 +00:00
|
|
|
cell.isEditing = true
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
|
|
|
switch sections[indexPath.section] {
|
|
|
|
case .ssidOption:
|
|
|
|
assertionFailure()
|
|
|
|
case .selectedSSIDs:
|
|
|
|
assert(editingStyle == .delete)
|
|
|
|
selectedSSIDs.remove(at: indexPath.row)
|
2019-03-09 04:17:35 +00:00
|
|
|
if !selectedSSIDs.isEmpty {
|
2019-02-27 08:00:57 +00:00
|
|
|
tableView.deleteRows(at: [indexPath], with: .automatic)
|
|
|
|
} else {
|
2019-03-09 04:17:35 +00:00
|
|
|
tableView.reloadRows(at: [indexPath], with: .automatic)
|
2019-02-27 08:00:57 +00:00
|
|
|
}
|
2021-03-08 23:57:35 +00:00
|
|
|
updateCurrentSSIDEntry()
|
2019-03-05 13:07:53 +00:00
|
|
|
updateTableViewAddSSIDRows()
|
2019-02-27 08:00:57 +00:00
|
|
|
case .addSSIDs:
|
|
|
|
assert(editingStyle == .insert)
|
2019-03-05 13:07:53 +00:00
|
|
|
let newSSID: String
|
|
|
|
switch addSSIDRows[indexPath.row] {
|
|
|
|
case .addConnectedSSID(let connectedSSID):
|
|
|
|
newSSID = connectedSSID
|
|
|
|
case .addNewSSID:
|
|
|
|
newSSID = ""
|
|
|
|
}
|
|
|
|
selectedSSIDs.append(newSSID)
|
2019-02-27 08:00:57 +00:00
|
|
|
loadSections()
|
|
|
|
let selectedSSIDsSection = sections.firstIndex(of: .selectedSSIDs)!
|
|
|
|
let indexPath = IndexPath(row: selectedSSIDs.count - 1, section: selectedSSIDsSection)
|
2019-03-09 04:17:35 +00:00
|
|
|
if selectedSSIDs.count == 1 {
|
|
|
|
tableView.reloadRows(at: [indexPath], with: .automatic)
|
2019-02-27 08:00:57 +00:00
|
|
|
} else {
|
|
|
|
tableView.insertRows(at: [indexPath], with: .automatic)
|
|
|
|
}
|
2021-03-08 23:57:35 +00:00
|
|
|
updateCurrentSSIDEntry()
|
2019-03-05 13:07:53 +00:00
|
|
|
updateTableViewAddSSIDRows()
|
|
|
|
if newSSID.isEmpty {
|
|
|
|
if let selectedSSIDCell = tableView.cellForRow(at: indexPath) as? EditableTextCell {
|
|
|
|
selectedSSIDCell.beginEditing()
|
|
|
|
}
|
2019-02-27 08:00:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-08 23:57:35 +00:00
|
|
|
|
|
|
|
private func getConnectedSSID(completionHandler: @escaping (String?) -> Void) {
|
|
|
|
#if targetEnvironment(simulator)
|
|
|
|
completionHandler("Simulator Wi-Fi")
|
|
|
|
#else
|
|
|
|
if #available(iOS 14, *) {
|
|
|
|
NEHotspotNetwork.fetchCurrent { hotspotNetwork in
|
|
|
|
completionHandler(hotspotNetwork?.ssid)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if let supportedInterfaces = CNCopySupportedInterfaces() as? [CFString] {
|
|
|
|
for interface in supportedInterfaces {
|
|
|
|
if let networkInfo = CNCopyCurrentNetworkInfo(interface) {
|
|
|
|
if let ssid = (networkInfo as NSDictionary)[kCNNetworkInfoKeySSID as String] as? String {
|
|
|
|
completionHandler(!ssid.isEmpty ? ssid : nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
completionHandler(nil)
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
2019-02-27 08:00:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
extension SSIDOptionEditTableViewController {
|
|
|
|
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
|
|
|
switch sections[indexPath.section] {
|
|
|
|
case .ssidOption:
|
|
|
|
return indexPath
|
|
|
|
case .selectedSSIDs, .addSSIDs:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
|
|
switch sections[indexPath.section] {
|
|
|
|
case .ssidOption:
|
|
|
|
let previousOption = selectedOption
|
|
|
|
selectedOption = ssidOptionFields[indexPath.row]
|
2019-06-09 18:17:05 +00:00
|
|
|
guard previousOption != selectedOption else {
|
|
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
return
|
|
|
|
}
|
2019-02-27 08:00:57 +00:00
|
|
|
loadSections()
|
|
|
|
if previousOption == .anySSID {
|
2019-03-09 04:17:35 +00:00
|
|
|
let indexSet = IndexSet(1 ... 2)
|
2019-02-27 08:00:57 +00:00
|
|
|
tableView.insertSections(indexSet, with: .fade)
|
|
|
|
}
|
|
|
|
if selectedOption == .anySSID {
|
2019-03-09 04:17:35 +00:00
|
|
|
let indexSet = IndexSet(1 ... 2)
|
2019-02-27 08:00:57 +00:00
|
|
|
tableView.deleteSections(indexSet, with: .fade)
|
|
|
|
}
|
|
|
|
tableView.reloadSections(IndexSet(integer: indexPath.section), with: .none)
|
|
|
|
case .selectedSSIDs, .addSSIDs:
|
|
|
|
assertionFailure()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-05 13:07:53 +00:00
|
|
|
|