aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard/WireGuard/UI
diff options
context:
space:
mode:
authorRoopesh Chander <roop@roopc.net>2019-02-27 13:30:57 +0530
committerJason A. Donenfeld <Jason@zx2c4.com>2019-03-18 06:46:55 +0100
commit5941bf181cc37368cfff5e1d92398bce6c7b8a5e (patch)
tree8375b2e35e3544512c25accedf2adb63ceea7346 /WireGuard/WireGuard/UI
parenton-demand: Introducing ActivateOnDemandViewModel (diff)
downloadwireguard-apple-5941bf181cc37368cfff5e1d92398bce6c7b8a5e.tar.xz
wireguard-apple-5941bf181cc37368cfff5e1d92398bce6c7b8a5e.zip
on-demand: iOS: Support for SSIDs
Signed-off-by: Roopesh Chander <roop@roopc.net>
Diffstat (limited to '')
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/ChevronCell.swift25
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/EditableTextCell.swift64
-rw-r--r--WireGuard/WireGuard/UI/iOS/View/TextCell.swift24
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/SSIDOptionEditTableViewController.swift227
-rw-r--r--WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift81
5 files changed, 380 insertions, 41 deletions
diff --git a/WireGuard/WireGuard/UI/iOS/View/ChevronCell.swift b/WireGuard/WireGuard/UI/iOS/View/ChevronCell.swift
new file mode 100644
index 0000000..94e4e05
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/View/ChevronCell.swift
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class ChevronCell: UITableViewCell {
+ var message: String {
+ get { return textLabel?.text ?? "" }
+ set(value) { textLabel?.text = value }
+ }
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: .default, reuseIdentifier: reuseIdentifier)
+ accessoryType = .disclosureIndicator
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ message = ""
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/View/EditableTextCell.swift b/WireGuard/WireGuard/UI/iOS/View/EditableTextCell.swift
new file mode 100644
index 0000000..178b200
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/View/EditableTextCell.swift
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class EditableTextCell: UITableViewCell {
+ var message: String {
+ get { return valueTextField.text ?? "" }
+ set(value) { valueTextField.text = value }
+ }
+
+ let valueTextField: UITextField = {
+ let valueTextField = UITextField()
+ valueTextField.textAlignment = .left
+ valueTextField.isEnabled = true
+ valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
+ valueTextField.adjustsFontForContentSizeCategory = true
+ valueTextField.autocapitalizationType = .none
+ valueTextField.autocorrectionType = .no
+ valueTextField.spellCheckingType = .no
+ return valueTextField
+ }()
+
+ var onValueBeingEdited: ((String) -> Void)?
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+ valueTextField.delegate = self
+ contentView.addSubview(valueTextField)
+ valueTextField.translatesAutoresizingMaskIntoConstraints = false
+ let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 1)
+ bottomAnchorConstraint.priority = .defaultLow
+ NSLayoutConstraint.activate([
+ valueTextField.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leadingAnchor, multiplier: 1),
+ contentView.layoutMarginsGuide.trailingAnchor.constraint(equalToSystemSpacingAfter: valueTextField.trailingAnchor, multiplier: 1),
+ valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
+ bottomAnchorConstraint
+ ])
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func beginEditing() {
+ valueTextField.becomeFirstResponder()
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ message = ""
+ }
+}
+
+extension EditableTextCell: UITextFieldDelegate {
+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ if let onValueBeingEdited = onValueBeingEdited {
+ let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
+ onValueBeingEdited(modifiedText)
+ }
+ return true
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/View/TextCell.swift b/WireGuard/WireGuard/UI/iOS/View/TextCell.swift
new file mode 100644
index 0000000..303f9c7
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/View/TextCell.swift
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class TextCell: UITableViewCell {
+ var message: String {
+ get { return textLabel?.text ?? "" }
+ set(value) { textLabel!.text = value }
+ }
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: .default, reuseIdentifier: reuseIdentifier)
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ message = ""
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/SSIDOptionEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/SSIDOptionEditTableViewController.swift
new file mode 100644
index 0000000..7027d34
--- /dev/null
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/SSIDOptionEditTableViewController.swift
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+protocol SSIDOptionEditTableViewControllerDelegate: class {
+ func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String])
+}
+
+class SSIDOptionEditTableViewController: UITableViewController {
+ private enum Section {
+ case ssidOption
+ case selectedSSIDs
+ case addSSIDs
+ }
+
+ weak var delegate: SSIDOptionEditTableViewControllerDelegate?
+
+ private var sections = [Section]()
+
+ let ssidOptionFields: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [
+ .anySSID,
+ .onlySpecificSSIDs,
+ .exceptSpecificSSIDs
+ ]
+
+ var selectedOption: ActivateOnDemandViewModel.OnDemandSSIDOption
+ var selectedSSIDs: [String]
+
+ init(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String]) {
+ selectedOption = option
+ selectedSSIDs = ssids
+ super.init(style: .grouped)
+ loadSections()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ title = tr("tunnelOnDemandSelectionViewTitle")
+
+ 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
+ }
+
+ func loadSections() {
+ sections.removeAll()
+ sections.append(.ssidOption)
+ if selectedOption != .anySSID {
+ if !selectedSSIDs.isEmpty {
+ sections.append(.selectedSSIDs)
+ }
+ sections.append(.addSSIDs)
+ }
+ }
+
+ 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:
+ return selectedSSIDs.count
+ case .addSSIDs:
+ return 1
+ }
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ switch sections[indexPath.section] {
+ case .ssidOption:
+ return ssidOptionCell(for: tableView, at: indexPath)
+ case .selectedSSIDs:
+ return selectedSSIDCell(for: tableView, at: indexPath)
+ 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
+ case .selectedSSIDs, .addSSIDs:
+ 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
+ }
+
+ private func selectedSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
+ let cell: EditableTextCell = tableView.dequeueReusableCell(for: indexPath)
+ cell.message = selectedSSIDs[indexPath.row]
+ 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
+ }
+ }
+ return cell
+ }
+
+ private func addSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
+ let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
+ cell.message = tr("tunnelOnDemandAddMessageAddNew")
+ 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)
+ loadSections()
+ let hasSelectedSSIDsSection = sections.contains(.selectedSSIDs)
+ if hasSelectedSSIDsSection {
+ tableView.deleteRows(at: [indexPath], with: .automatic)
+ } else {
+ tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic)
+ }
+ case .addSSIDs:
+ assert(editingStyle == .insert)
+ let hasSelectedSSIDsSection = sections.contains(.selectedSSIDs)
+ selectedSSIDs.append("")
+ loadSections()
+ let selectedSSIDsSection = sections.firstIndex(of: .selectedSSIDs)!
+ let indexPath = IndexPath(row: selectedSSIDs.count - 1, section: selectedSSIDsSection)
+ if !hasSelectedSSIDsSection {
+ tableView.insertSections(IndexSet(integer: selectedSSIDsSection), with: .automatic)
+ } else {
+ tableView.insertRows(at: [indexPath], with: .automatic)
+ }
+ if let selectedSSIDCell = tableView.cellForRow(at: indexPath) as? EditableTextCell {
+ selectedSSIDCell.beginEditing()
+ }
+ }
+ }
+
+ func lastSelectedSSIDItemIndexPath() -> IndexPath? {
+ guard !selectedSSIDs.isEmpty else { return nil }
+ guard let section = sections.firstIndex(of: .selectedSSIDs) else { return nil }
+ return IndexPath(row: selectedSSIDs.count - 1, section: section)
+ }
+}
+
+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
+ let previousSectionCount = sections.count
+ selectedOption = ssidOptionFields[indexPath.row]
+ loadSections()
+ if previousOption == .anySSID {
+ let indexSet = selectedSSIDs.isEmpty ? IndexSet(integer: 1) : IndexSet(1 ... 2)
+ tableView.insertSections(indexSet, with: .fade)
+ }
+ if selectedOption == .anySSID {
+ let indexSet = previousSectionCount == 2 ? IndexSet(integer: 1) : IndexSet(1 ... 2)
+ tableView.deleteSections(indexSet, with: .fade)
+ }
+ tableView.reloadSections(IndexSet(integer: indexPath.section), with: .none)
+ case .selectedSSIDs, .addSSIDs:
+ assertionFailure()
+ }
+ }
+}
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
index 22c3ec4..ef7fc60 100644
--- a/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
+++ b/WireGuard/WireGuard/UI/iOS/ViewController/TunnelEditTableViewController.swift
@@ -43,16 +43,16 @@ class TunnelEditTableViewController: UITableViewController {
.deletePeer
]
- let activateOnDemandOptions: [ActivateOnDemandOption] = [
- .anyInterface(.anySSID),
- .wiFiInterfaceOnly(.anySSID),
- .nonWiFiInterfaceOnly
+ let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
+ .nonWiFiInterface,
+ .wiFiInterface,
+ .ssidEdit
]
let tunnelsManager: TunnelsManager
let tunnel: TunnelContainer?
let tunnelViewModel: TunnelViewModel
- var activateOnDemandSetting: ActivateOnDemandSetting
+ var onDemandViewModel: ActivateOnDemandViewModel
private var sections = [Section]()
// Use this initializer to edit an existing tunnel.
@@ -60,7 +60,8 @@ class TunnelEditTableViewController: UITableViewController {
self.tunnelsManager = tunnelsManager
self.tunnel = tunnel
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
- activateOnDemandSetting = tunnel.activateOnDemandSetting
+ let onDemandOption = tunnel.activateOnDemandSetting.isActivateOnDemandEnabled ? tunnel.activateOnDemandSetting.activateOnDemandOption : .none
+ onDemandViewModel = ActivateOnDemandViewModel(from: onDemandOption)
super.init(style: .grouped)
loadSections()
}
@@ -70,7 +71,7 @@ class TunnelEditTableViewController: UITableViewController {
self.tunnelsManager = tunnelsManager
tunnel = nil
tunnelViewModel = TunnelViewModel(tunnelConfiguration: nil)
- activateOnDemandSetting = ActivateOnDemandSetting.defaultSetting
+ onDemandViewModel = ActivateOnDemandViewModel()
super.init(style: .grouped)
loadSections()
}
@@ -92,7 +93,7 @@ class TunnelEditTableViewController: UITableViewController {
tableView.register(TunnelEditEditableKeyValueCell.self)
tableView.register(ButtonCell.self)
tableView.register(SwitchCell.self)
- tableView.register(CheckmarkCell.self)
+ tableView.register(ChevronCell.self)
}
private func loadSections() {
@@ -113,6 +114,7 @@ class TunnelEditTableViewController: UITableViewController {
ErrorPresenter.showErrorAlert(title: alertTitle, message: errorMessage, from: self)
tableView.reloadData() // Highlight erroring fields
case .saved(let tunnelConfiguration):
+ let activateOnDemandSetting = ActivateOnDemandSetting(with: onDemandViewModel.toOnDemandOption())
if let tunnel = tunnel {
// We're modifying an existing tunnel
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: activateOnDemandSetting) { [weak self] error in
@@ -161,10 +163,10 @@ extension TunnelEditTableViewController {
case .addPeer:
return 1
case .onDemand:
- if activateOnDemandSetting.isActivateOnDemandEnabled {
- return 4
+ if onDemandViewModel.isWiFiInterfaceEnabled {
+ return 3
} else {
- return 1
+ return 2
}
}
}
@@ -419,36 +421,28 @@ extension TunnelEditTableViewController {
}
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
- if indexPath.row == 0 {
+ let field = onDemandFields[indexPath.row]
+ if indexPath.row < 2 {
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
- cell.message = tr("tunnelOnDemandKey")
- cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
+ cell.message = field.localizedUIString
+ cell.isOn = onDemandViewModel.isEnabled(field: field)
cell.onSwitchToggled = { [weak self] isOn in
guard let self = self else { return }
- guard isOn != self.activateOnDemandSetting.isActivateOnDemandEnabled else { return }
-
- self.activateOnDemandSetting.isActivateOnDemandEnabled = isOn
- self.loadSections()
-
+ self.onDemandViewModel.setEnabled(field: field, isEnabled: isOn)
let section = self.sections.firstIndex { $0 == .onDemand }!
- let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: section) }
- if isOn {
- if self.activateOnDemandSetting.activateOnDemandOption == .none {
- self.activateOnDemandSetting.activateOnDemandOption = TunnelViewModel.defaultActivateOnDemandOption()
+ let indexPath = IndexPath(row: 2, section: section)
+ if field == .wiFiInterface {
+ if isOn {
+ tableView.insertRows(at: [indexPath], with: .fade)
+ } else {
+ tableView.deleteRows(at: [indexPath], with: .fade)
}
- self.tableView.insertRows(at: indexPaths, with: .fade)
- } else {
- self.tableView.deleteRows(at: indexPaths, with: .fade)
}
}
return cell
} else {
- let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath)
- let rowOption = activateOnDemandOptions[indexPath.row - 1]
- let selectedOption = activateOnDemandSetting.activateOnDemandOption
- assert(selectedOption != .none)
- cell.message = TunnelViewModel.activateOnDemandOptionText(for: rowOption)
- cell.isChecked = selectedOption == rowOption
+ let cell: ChevronCell = tableView.dequeueReusableCell(for: indexPath)
+ cell.message = field.localizedUIString
return cell
}
}
@@ -484,7 +478,7 @@ extension TunnelEditTableViewController {
extension TunnelEditTableViewController {
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
- if case .onDemand = sections[indexPath.section], indexPath.row > 0 {
+ if case .onDemand = sections[indexPath.section], indexPath.row == 2 {
return indexPath
} else {
return nil
@@ -494,16 +488,21 @@ extension TunnelEditTableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch sections[indexPath.section] {
case .onDemand:
- let option = activateOnDemandOptions[indexPath.row - 1]
- assert(option != .none)
- activateOnDemandSetting.activateOnDemandOption = option
-
- let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) }
- UIView.performWithoutAnimation {
- tableView.reloadRows(at: indexPaths, with: .none)
- }
+ assert(indexPath.row == 2)
+ tableView.deselectRow(at: indexPath, animated: true)
+ let ssidOptionVC = SSIDOptionEditTableViewController(option: onDemandViewModel.ssidOption, ssids: onDemandViewModel.selectedSSIDs)
+ ssidOptionVC.delegate = self
+ navigationController?.pushViewController(ssidOptionVC, animated: true)
default:
assertionFailure()
}
}
}
+
+extension TunnelEditTableViewController: SSIDOptionEditTableViewControllerDelegate {
+ func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String]) {
+ let validSSIDs = ssids.filter { !$0.isEmpty }
+ onDemandViewModel.selectedSSIDs = validSSIDs
+ onDemandViewModel.ssidOption = validSSIDs.isEmpty ? .anySSID : option
+ }
+}