Move all source files to Sources/ and rename WireGuardKit targets
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class BorderedTextButton: UIView {
|
||||
let button: UIButton = {
|
||||
let button = UIButton(type: .system)
|
||||
button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
button.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||
return button
|
||||
}()
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
let buttonSize = button.intrinsicContentSize
|
||||
return CGSize(width: buttonSize.width + 32, height: buttonSize.height + 16)
|
||||
}
|
||||
|
||||
var title: String {
|
||||
get { return button.title(for: .normal) ?? "" }
|
||||
set(value) { button.setTitle(value, for: .normal) }
|
||||
}
|
||||
|
||||
var onTapped: (() -> Void)?
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
layer.borderWidth = 1
|
||||
layer.cornerRadius = 5
|
||||
layer.borderColor = button.tintColor.cgColor
|
||||
|
||||
addSubview(button)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
button.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
button.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
])
|
||||
|
||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func buttonTapped() {
|
||||
onTapped?()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class ButtonCell: UITableViewCell {
|
||||
var buttonText: String {
|
||||
get { return button.title(for: .normal) ?? "" }
|
||||
set(value) { button.setTitle(value, for: .normal) }
|
||||
}
|
||||
var hasDestructiveAction: Bool {
|
||||
get { return button.tintColor == .systemRed }
|
||||
set(value) { button.tintColor = value ? .systemRed : buttonStandardTintColor }
|
||||
}
|
||||
var onTapped: (() -> Void)?
|
||||
|
||||
let button: UIButton = {
|
||||
let button = UIButton(type: .system)
|
||||
button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
button.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||
return button
|
||||
}()
|
||||
|
||||
var buttonStandardTintColor: UIColor
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
buttonStandardTintColor = button.tintColor
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(button)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
button.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
|
||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
|
||||
button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
|
||||
])
|
||||
|
||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func buttonTapped() {
|
||||
onTapped?()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
buttonText = ""
|
||||
onTapped = nil
|
||||
hasDestructiveAction = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class CheckmarkCell: UITableViewCell {
|
||||
var message: String {
|
||||
get { return textLabel?.text ?? "" }
|
||||
set(value) { textLabel!.text = value }
|
||||
}
|
||||
var isChecked: Bool {
|
||||
didSet {
|
||||
accessoryType = isChecked ? .checkmark : .none
|
||||
}
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
isChecked = false
|
||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
message = ""
|
||||
isChecked = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 }
|
||||
}
|
||||
|
||||
var detailMessage: String {
|
||||
get { return detailTextLabel?.text ?? "" }
|
||||
set(value) { detailTextLabel?.text = value }
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: .value1, reuseIdentifier: reuseIdentifier)
|
||||
accessoryType = .disclosureIndicator
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
message = ""
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class KeyValueCell: UITableViewCell {
|
||||
|
||||
let keyLabel: UILabel = {
|
||||
let keyLabel = UILabel()
|
||||
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
keyLabel.adjustsFontForContentSizeCategory = true
|
||||
if #available(iOS 13.0, *) {
|
||||
keyLabel.textColor = .label
|
||||
} else {
|
||||
keyLabel.textColor = .black
|
||||
}
|
||||
keyLabel.textAlignment = .left
|
||||
return keyLabel
|
||||
}()
|
||||
|
||||
let valueLabelScrollView: UIScrollView = {
|
||||
let scrollView = UIScrollView(frame: .zero)
|
||||
scrollView.isDirectionalLockEnabled = true
|
||||
scrollView.showsHorizontalScrollIndicator = false
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
return scrollView
|
||||
}()
|
||||
|
||||
let valueTextField: UITextField = {
|
||||
let valueTextField = UITextField()
|
||||
valueTextField.textAlignment = .right
|
||||
valueTextField.isEnabled = false
|
||||
valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
valueTextField.adjustsFontForContentSizeCategory = true
|
||||
valueTextField.autocapitalizationType = .none
|
||||
valueTextField.autocorrectionType = .no
|
||||
valueTextField.spellCheckingType = .no
|
||||
if #available(iOS 13.0, *) {
|
||||
valueTextField.textColor = .secondaryLabel
|
||||
} else {
|
||||
valueTextField.textColor = .gray
|
||||
}
|
||||
return valueTextField
|
||||
}()
|
||||
|
||||
var copyableGesture = true
|
||||
|
||||
var key: String {
|
||||
get { return keyLabel.text ?? "" }
|
||||
set(value) { keyLabel.text = value }
|
||||
}
|
||||
var value: String {
|
||||
get { return valueTextField.text ?? "" }
|
||||
set(value) { valueTextField.text = value }
|
||||
}
|
||||
var placeholderText: String {
|
||||
get { return valueTextField.placeholder ?? "" }
|
||||
set(value) { valueTextField.placeholder = value }
|
||||
}
|
||||
var keyboardType: UIKeyboardType {
|
||||
get { return valueTextField.keyboardType }
|
||||
set(value) { valueTextField.keyboardType = value }
|
||||
}
|
||||
|
||||
var isValueValid = true {
|
||||
didSet {
|
||||
if #available(iOS 13.0, *) {
|
||||
if isValueValid {
|
||||
keyLabel.textColor = .label
|
||||
} else {
|
||||
keyLabel.textColor = .systemRed
|
||||
}
|
||||
} else {
|
||||
if isValueValid {
|
||||
keyLabel.textColor = .black
|
||||
} else {
|
||||
keyLabel.textColor = .red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isStackedHorizontally = false
|
||||
var isStackedVertically = false
|
||||
var contentSizeBasedConstraints = [NSLayoutConstraint]()
|
||||
|
||||
var onValueChanged: ((String, String) -> Void)?
|
||||
var onValueBeingEdited: ((String) -> Void)?
|
||||
|
||||
var observationToken: AnyObject?
|
||||
|
||||
private var textFieldValueOnBeginEditing: String = ""
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(keyLabel)
|
||||
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
keyLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||
keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
|
||||
])
|
||||
|
||||
valueTextField.delegate = self
|
||||
valueLabelScrollView.addSubview(valueTextField)
|
||||
valueTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
valueTextField.leadingAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.leadingAnchor),
|
||||
valueTextField.topAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.topAnchor),
|
||||
valueTextField.bottomAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.bottomAnchor),
|
||||
valueTextField.trailingAnchor.constraint(equalTo: valueLabelScrollView.contentLayoutGuide.trailingAnchor),
|
||||
valueTextField.heightAnchor.constraint(equalTo: valueLabelScrollView.heightAnchor)
|
||||
])
|
||||
let expandToFitValueLabelConstraint = NSLayoutConstraint(item: valueTextField, attribute: .width, relatedBy: .equal, toItem: valueLabelScrollView, attribute: .width, multiplier: 1, constant: 0)
|
||||
expandToFitValueLabelConstraint.priority = .defaultLow + 1
|
||||
expandToFitValueLabelConstraint.isActive = true
|
||||
|
||||
contentView.addSubview(valueLabelScrollView)
|
||||
|
||||
contentView.addSubview(valueLabelScrollView)
|
||||
valueLabelScrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
valueLabelScrollView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueLabelScrollView.bottomAnchor, multiplier: 0.5)
|
||||
])
|
||||
|
||||
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal)
|
||||
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
valueLabelScrollView.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
|
||||
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
|
||||
addGestureRecognizer(gestureRecognizer)
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
configureForContentSize()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configureForContentSize() {
|
||||
var constraints = [NSLayoutConstraint]()
|
||||
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
// Stack vertically
|
||||
if !isStackedVertically {
|
||||
constraints = [
|
||||
valueLabelScrollView.topAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
|
||||
valueLabelScrollView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||
keyLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor)
|
||||
]
|
||||
isStackedVertically = true
|
||||
isStackedHorizontally = false
|
||||
}
|
||||
} else {
|
||||
// Stack horizontally
|
||||
if !isStackedHorizontally {
|
||||
constraints = [
|
||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: keyLabel.bottomAnchor, multiplier: 0.5),
|
||||
valueLabelScrollView.leadingAnchor.constraint(equalToSystemSpacingAfter: keyLabel.trailingAnchor, multiplier: 1),
|
||||
valueLabelScrollView.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
|
||||
]
|
||||
isStackedHorizontally = true
|
||||
isStackedVertically = false
|
||||
}
|
||||
}
|
||||
if !constraints.isEmpty {
|
||||
NSLayoutConstraint.deactivate(contentSizeBasedConstraints)
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
contentSizeBasedConstraints = constraints
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
|
||||
if !copyableGesture {
|
||||
return
|
||||
}
|
||||
guard recognizer.state == .recognized else { return }
|
||||
|
||||
if let recognizerView = recognizer.view,
|
||||
let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
|
||||
let menuController = UIMenuController.shared
|
||||
menuController.setTargetRect(detailTextLabel?.frame ?? recognizerView.frame, in: detailTextLabel?.superview ?? recognizerSuperView)
|
||||
menuController.setMenuVisible(true, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
return (action == #selector(UIResponderStandardEditActions.copy(_:)))
|
||||
}
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
UIPasteboard.general.string = valueTextField.text
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
copyableGesture = true
|
||||
placeholderText = ""
|
||||
isValueValid = true
|
||||
keyboardType = .default
|
||||
onValueChanged = nil
|
||||
onValueBeingEdited = nil
|
||||
observationToken = nil
|
||||
key = ""
|
||||
value = ""
|
||||
configureForContentSize()
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyValueCell: UITextFieldDelegate {
|
||||
|
||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
textFieldValueOnBeginEditing = textField.text ?? ""
|
||||
isValueValid = true
|
||||
}
|
||||
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
|
||||
guard isModified else { return }
|
||||
onValueChanged?(textFieldValueOnBeginEditing, textField.text ?? "")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SwitchCell: UITableViewCell {
|
||||
var message: String {
|
||||
get { return textLabel?.text ?? "" }
|
||||
set(value) { textLabel?.text = value }
|
||||
}
|
||||
var isOn: Bool {
|
||||
get { return switchView.isOn }
|
||||
set(value) { switchView.isOn = value }
|
||||
}
|
||||
var isEnabled: Bool {
|
||||
get { return switchView.isEnabled }
|
||||
set(value) {
|
||||
switchView.isEnabled = value
|
||||
if #available(iOS 13.0, *) {
|
||||
textLabel?.textColor = value ? .label : .secondaryLabel
|
||||
} else {
|
||||
textLabel?.textColor = value ? .black : .gray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var onSwitchToggled: ((Bool) -> Void)?
|
||||
|
||||
var observationToken: AnyObject?
|
||||
|
||||
let switchView = UISwitch()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
accessoryView = switchView
|
||||
switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func switchToggled() {
|
||||
onSwitchToggled?(switchView.isOn)
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
onSwitchToggled = nil
|
||||
isEnabled = true
|
||||
message = ""
|
||||
isOn = false
|
||||
observationToken = nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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")
|
||||
}
|
||||
|
||||
func setTextColor(_ color: UIColor) {
|
||||
textLabel?.textColor = color
|
||||
}
|
||||
|
||||
func setTextAlignment(_ alignment: NSTextAlignment) {
|
||||
textLabel?.textAlignment = alignment
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
message = ""
|
||||
if #available(iOS 13.0, *) {
|
||||
setTextColor(.label)
|
||||
} else {
|
||||
setTextColor(.black)
|
||||
}
|
||||
setTextAlignment(.left)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class TunnelEditKeyValueCell: KeyValueCell {
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
keyLabel.textAlignment = .right
|
||||
valueTextField.textAlignment = .left
|
||||
|
||||
let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.4, constant: 0)
|
||||
// In case the key doesn't fit into 0.4 * width,
|
||||
// set a CR priority > the 0.4-constraint's priority.
|
||||
widthRatioConstraint.priority = .defaultHigh + 1
|
||||
widthRatioConstraint.isActive = true
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TunnelEditEditableKeyValueCell: TunnelEditKeyValueCell {
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
copyableGesture = false
|
||||
if #available(iOS 13.0, *) {
|
||||
valueTextField.textColor = .label
|
||||
} else {
|
||||
valueTextField.textColor = .black
|
||||
}
|
||||
valueTextField.isEnabled = true
|
||||
valueLabelScrollView.isScrollEnabled = false
|
||||
valueTextField.widthAnchor.constraint(equalTo: valueLabelScrollView.widthAnchor).isActive = true
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
copyableGesture = false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
class TunnelListCell: UITableViewCell {
|
||||
var tunnel: TunnelContainer? {
|
||||
didSet(value) {
|
||||
// Bind to the tunnel's name
|
||||
nameLabel.text = tunnel?.name ?? ""
|
||||
nameObservationToken = tunnel?.observe(\.name) { [weak self] tunnel, _ in
|
||||
self?.nameLabel.text = tunnel.name
|
||||
}
|
||||
// Bind to the tunnel's status
|
||||
update(from: tunnel?.status)
|
||||
statusObservationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in
|
||||
self?.update(from: tunnel.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
var onSwitchToggled: ((Bool) -> Void)?
|
||||
|
||||
let nameLabel: UILabel = {
|
||||
let nameLabel = UILabel()
|
||||
nameLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
nameLabel.adjustsFontForContentSizeCategory = true
|
||||
nameLabel.numberOfLines = 0
|
||||
return nameLabel
|
||||
}()
|
||||
|
||||
let busyIndicator: UIActivityIndicatorView = {
|
||||
if #available(iOS 13.0, *) {
|
||||
let busyIndicator = UIActivityIndicatorView(style: .medium)
|
||||
busyIndicator.hidesWhenStopped = true
|
||||
return busyIndicator
|
||||
} else {
|
||||
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||
busyIndicator.hidesWhenStopped = true
|
||||
return busyIndicator
|
||||
}
|
||||
}()
|
||||
|
||||
let statusSwitch = UISwitch()
|
||||
|
||||
private var statusObservationToken: AnyObject?
|
||||
private var nameObservationToken: AnyObject?
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(statusSwitch)
|
||||
statusSwitch.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
contentView.trailingAnchor.constraint(equalTo: statusSwitch.trailingAnchor)
|
||||
])
|
||||
|
||||
contentView.addSubview(busyIndicator)
|
||||
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
statusSwitch.leadingAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.trailingAnchor, multiplier: 1)
|
||||
])
|
||||
|
||||
contentView.addSubview(nameLabel)
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: nameLabel.bottomAnchor, multiplier: 1)
|
||||
bottomAnchorConstraint.priority = .defaultLow
|
||||
NSLayoutConstraint.activate([
|
||||
nameLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
|
||||
nameLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leadingAnchor, multiplier: 1),
|
||||
busyIndicator.leadingAnchor.constraint(equalToSystemSpacingAfter: nameLabel.trailingAnchor, multiplier: 1),
|
||||
bottomAnchorConstraint
|
||||
])
|
||||
|
||||
accessoryType = .disclosureIndicator
|
||||
|
||||
statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
||||
}
|
||||
|
||||
@objc func switchToggled() {
|
||||
onSwitchToggled?(statusSwitch.isOn)
|
||||
}
|
||||
|
||||
private func update(from status: TunnelStatus?) {
|
||||
guard let status = status else {
|
||||
reset()
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak statusSwitch, weak busyIndicator] in
|
||||
guard let statusSwitch = statusSwitch, let busyIndicator = busyIndicator else { return }
|
||||
statusSwitch.isOn = !(status == .deactivating || status == .inactive)
|
||||
statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
|
||||
if status == .inactive || status == .active {
|
||||
busyIndicator.stopAnimating()
|
||||
} else {
|
||||
busyIndicator.startAnimating()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setEditing(_ editing: Bool, animated: Bool) {
|
||||
super.setEditing(editing, animated: animated)
|
||||
statusSwitch.isEnabled = !editing
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
statusSwitch.isOn = false
|
||||
statusSwitch.isUserInteractionEnabled = false
|
||||
busyIndicator.stopAnimating()
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
reset()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user