diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2021-05-20 13:11:52 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2021-05-27 14:36:59 +0200 |
| commit | f66690efdd956f9f8be76720386a28136f011052 (patch) | |
| tree | 2ff05c43f8f5d63535fc85e92efe356b739be218 | |
| parent | 488649a3bd05e6391054d61a44f5ac76d0a16791 (diff) | |
| download | mullvadvpn-f66690efdd956f9f8be76720386a28136f011052.tar.xz mullvadvpn-f66690efdd956f9f8be76720386a28136f011052.zip | |
Add preferences
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 16 | ||||
| -rw-r--r-- | ios/MullvadVPN/CustomSwitch.swift | 72 | ||||
| -rw-r--r-- | ios/MullvadVPN/CustomSwitchContainer.swift | 66 | ||||
| -rw-r--r-- | ios/MullvadVPN/PreferencesViewController.swift | 126 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsNavigationController.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsSwitchCell.swift | 36 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsViewController.swift | 12 | ||||
| -rw-r--r-- | ios/MullvadVPN/UIColor+Palette.swift | 6 |
8 files changed, 337 insertions, 1 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index ac4e13422f..9d9d32f83e 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -153,6 +153,10 @@ 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */; }; 58A8BE81239FBE62006B74AC /* IPEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58561C98239A5D1500BD6B5E /* IPEndpoint.swift */; }; 58A99ED3240014A0006599E9 /* ConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A99ED2240014A0006599E9 /* ConsentViewController.swift */; }; + 58ACF6492655365700ACE4B7 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */; }; + 58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */; }; + 58ACF64D26567A5000ACE4B7 /* CustomSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */; }; + 58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */; }; 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF642344A36000C9BBD5 /* KeychainError.swift */; }; 58AEEF662344A37400C9BBD5 /* KeychainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF642344A36000C9BBD5 /* KeychainError.swift */; }; 58AEEF6B2344A46200C9BBD5 /* TunnelSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF6A2344A46200C9BBD5 /* TunnelSettingsManager.swift */; }; @@ -369,6 +373,10 @@ 58A1AA8623F43901009F7EA6 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; }; 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPanelView.swift; sourceTree = "<group>"; }; 58A99ED2240014A0006599E9 /* ConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentViewController.swift; sourceTree = "<group>"; }; + 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; }; + 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSwitchCell.swift; sourceTree = "<group>"; }; + 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSwitch.swift; sourceTree = "<group>"; }; + 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSwitchContainer.swift; sourceTree = "<group>"; }; 58AEEF642344A36000C9BBD5 /* KeychainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainError.swift; sourceTree = "<group>"; }; 58AEEF6A2344A46200C9BBD5 /* TunnelSettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManager.swift; sourceTree = "<group>"; }; 58B0A2A0238EE67E00BC001D /* MullvadVPNTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadVPNTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -678,6 +686,10 @@ 58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */, 5868BD32261DCD2600E6027F /* CustomSplitViewController.swift */, 584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */, + 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */, + 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */, + 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */, + 58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */, ); path = MullvadVPN; sourceTree = "<group>"; @@ -1007,8 +1019,10 @@ 58BFA5C622A7C97F00A6173D /* RelayCache.swift in Sources */, 582BB1B1229569620055B6EF /* CustomNavigationBar.swift in Sources */, 584789E026529D72000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */, + 58ACF6492655365700ACE4B7 /* PreferencesViewController.swift in Sources */, 588D2FE3248AC27F00E313F7 /* AsyncOperation.swift in Sources */, 5877153023981F7B001F8237 /* WireguardKeysViewController.swift in Sources */, + 58ACF64D26567A5000ACE4B7 /* CustomSwitch.swift in Sources */, 5850367F25A481D800A43E93 /* IPAddressRange+Codable.swift in Sources */, 5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */, 58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */, @@ -1070,6 +1084,7 @@ 5815039D24D6ECE600C9C50E /* TextFileOutputStream.swift in Sources */, 581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, + 58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */, 5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */, 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */, 58C3B06924EAA25000C0348E /* StringStreamIterator.swift in Sources */, @@ -1105,6 +1120,7 @@ 58C4CB0124EBE5A700A22D49 /* LogEntryParser.swift in Sources */, 58F840B22464491D0044E708 /* ChainedError.swift in Sources */, 58FAEDFF24533A7000CB0F5B /* KeychainReturn.swift in Sources */, + 58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */, 580EE20C24B3225F00F9D8A1 /* DelayOperation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/MullvadVPN/CustomSwitch.swift b/ios/MullvadVPN/CustomSwitch.swift new file mode 100644 index 0000000000..b804dae7ae --- /dev/null +++ b/ios/MullvadVPN/CustomSwitch.swift @@ -0,0 +1,72 @@ +// +// CustomSwitch.swift +// MullvadVPN +// +// Created by pronebird on 20/05/2021. +// Copyright © 2021 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +class CustomSwitch: UISwitch { + + /// Returns the private `UISwitch` background view + private var backgroundView: UIView? { + // Go two levels deep only + let subviewsToExamine = subviews.flatMap { (view) -> [UIView] in + return [view] + view.subviews + } + + // Find the first subview that has background color set. + let backgroundView = subviewsToExamine.first { (subview) in + return subview.backgroundColor != nil + } + + return backgroundView + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.tintColor = .clear + self.onTintColor = .clear + + updateThumbColor(isOn: self.isOn, animated: false) + + addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setOn(_ on: Bool, animated: Bool) { + super.setOn(on, animated: animated) + + updateThumbColor(isOn: on, animated: animated) + } + + private func updateThumbColor(isOn: Bool, animated: Bool) { + let actions = { + self.thumbTintColor = isOn ? UIColor.Switch.onThumbColor : UIColor.Switch.offThumbColor + self.backgroundView?.backgroundColor = .clear + } + + if animated { + UIView.animate(withDuration: 0.25, animations: actions) + } else { + actions() + } + } + + @objc private func valueChanged(_ sender: Any) { + if #available(iOS 13, *) { + self.updateThumbColor(isOn: self.isOn, animated: true) + } else { + // Wait for animations to finish before changing the thumb color to prevent the jumpy behaviour. + CATransaction.setCompletionBlock { + self.updateThumbColor(isOn: self.isOn, animated: false) + } + } + } +} diff --git a/ios/MullvadVPN/CustomSwitchContainer.swift b/ios/MullvadVPN/CustomSwitchContainer.swift new file mode 100644 index 0000000000..ebdcc33b13 --- /dev/null +++ b/ios/MullvadVPN/CustomSwitchContainer.swift @@ -0,0 +1,66 @@ +// +// CustomSwitchContainer.swift +// MullvadVPN +// +// Created by pronebird on 20/05/2021. +// Copyright © 2021 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +class CustomSwitchContainer: UIView { + static let borderEdgeInsets = UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3) + + private let borderShape: CAShapeLayer = { + let shapeLayer = CAShapeLayer() + shapeLayer.borderColor = UIColor.Switch.borderColor.cgColor + shapeLayer.borderWidth = 2 + if #available(iOS 13.0, *) { + shapeLayer.cornerCurve = .continuous + } + return shapeLayer + }() + + let control = CustomSwitch() + + override var intrinsicContentSize: CGSize { + return controlSize() + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return controlSize() + } + + override init(frame: CGRect) { + super.init(frame: frame) + + addSubview(control) + layer.addSublayer(borderShape) + + control.sizeToFit() + sizeToFit() + + borderShape.cornerRadius = self.bounds.height * 0.5 + borderShape.frame = self.bounds + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + control.frame.origin = CGPoint(x: Self.borderEdgeInsets.left, y: Self.borderEdgeInsets.top) + } + + // MARK: - Private + + private func controlSize() -> CGSize { + var size = control.bounds.size + size.width += Self.borderEdgeInsets.left + Self.borderEdgeInsets.right + size.height += Self.borderEdgeInsets.top + Self.borderEdgeInsets.bottom + return size + } + +} diff --git a/ios/MullvadVPN/PreferencesViewController.swift b/ios/MullvadVPN/PreferencesViewController.swift new file mode 100644 index 0000000000..4220d273e5 --- /dev/null +++ b/ios/MullvadVPN/PreferencesViewController.swift @@ -0,0 +1,126 @@ +// +// PreferencesViewController.swift +// MullvadVPN +// +// Created by pronebird on 19/05/2021. +// Copyright © 2021 Mullvad VPN AB. All rights reserved. +// + +import UIKit +import Logging + +class PreferencesViewController: UITableViewController, TunnelObserver { + + private let logger = Logger(label: "PreferencesViewController") + private var dnsSettings: DNSSettings? + + private enum CellIdentifier: String { + case switchCell + } + + private let staticDataSource = PreferencesTableViewDataSource() + + init() { + super.init(style: .grouped) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.backgroundColor = .secondaryColor + tableView.separatorColor = .secondaryColor + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 60 + tableView.sectionHeaderHeight = 18 + tableView.sectionFooterHeight = 18 + + tableView.dataSource = staticDataSource + tableView.delegate = staticDataSource + + tableView.register(SettingsSwitchCell.self, forCellReuseIdentifier: CellIdentifier.switchCell.rawValue) + + navigationItem.title = NSLocalizedString("Preferences", comment: "Navigation title") + navigationItem.largeTitleDisplayMode = .always + + TunnelManager.shared.addObserver(self) + self.dnsSettings = TunnelManager.shared.tunnelSettings?.interface.dnsSettings + + setupDataSource() + } + + // MARK: - TunnelObserver + + func tunnelStateDidChange(tunnelState: TunnelState) { + // no-op + } + + func tunnelSettingsDidChange(tunnelSettings: TunnelSettings?) { + DispatchQueue.main.async { + if tunnelSettings?.interface.dnsSettings != self.dnsSettings { + self.dnsSettings = tunnelSettings?.interface.dnsSettings + self.tableView.reloadData() + } + } + } + + // MARK: - Private + + private func setupDataSource() { + let blockAdvertisingRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.switchCell.rawValue) { (indexPath, cell) in + let cell = cell as! SettingsSwitchCell + + cell.titleLabel.text = NSLocalizedString("Block ads", comment: "") + cell.switchControl.setOn(self.dnsSettings?.blockAdvertising ?? false, animated: false) + cell.action = { [weak self] (isOn) in + self?.dnsSettings?.blockAdvertising = isOn + self?.saveDNSSettings() + } + } + blockAdvertisingRow.isSelectable = false + + let blockTrackingRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.switchCell.rawValue) { (indexPath, cell) in + let cell = cell as! SettingsSwitchCell + + cell.titleLabel.text = NSLocalizedString("Block trackers", comment: "") + cell.switchControl.setOn(self.dnsSettings?.blockTracking ?? false, animated: false) + cell.action = { [weak self] (isOn) in + self?.dnsSettings?.blockTracking = isOn + self?.saveDNSSettings() + } + } + blockTrackingRow.isSelectable = false + + let section = StaticTableViewSection() + section.addRows([blockAdvertisingRow, blockTrackingRow]) + staticDataSource.addSections([section]) + } + + private func saveDNSSettings() { + guard let dnsSettings = dnsSettings else { return } + + TunnelManager.shared.setDNSSettings(dnsSettings) { [weak self] (result) in + if case .failure(let error) = result { + self?.logger.error(chainedError: error, message: "Failed to save DNS settings") + } + } + } + +} + +class PreferencesTableViewDataSource: StaticTableViewDataSource { + + // MARK: - UITableViewDelegate + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 24 + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return 0.01 + } + +} diff --git a/ios/MullvadVPN/SettingsNavigationController.swift b/ios/MullvadVPN/SettingsNavigationController.swift index 59ff50d499..0bb586f8b6 100644 --- a/ios/MullvadVPN/SettingsNavigationController.swift +++ b/ios/MullvadVPN/SettingsNavigationController.swift @@ -11,6 +11,7 @@ import UIKit enum SettingsNavigationRoute { case account + case preferences case wireguardKeys case problemReport } @@ -79,6 +80,9 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont controller.delegate = self pushViewController(controller, animated: animated) + case .preferences: + pushViewController(PreferencesViewController(), animated: animated) + case .wireguardKeys: pushViewController(WireguardKeysViewController(), animated: animated) diff --git a/ios/MullvadVPN/SettingsSwitchCell.swift b/ios/MullvadVPN/SettingsSwitchCell.swift new file mode 100644 index 0000000000..d644c5cf77 --- /dev/null +++ b/ios/MullvadVPN/SettingsSwitchCell.swift @@ -0,0 +1,36 @@ +// +// SettingsSwitchCell.swift +// MullvadVPN +// +// Created by pronebird on 19/05/2021. +// Copyright © 2021 Mullvad VPN AB. All rights reserved. +// + +import UIKit + +class SettingsSwitchCell: SettingsCell { + + let switchContainer = CustomSwitchContainer() + var switchControl: CustomSwitch { + return switchContainer.control + } + + var action: ((Bool) -> Void)? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + self.accessoryView = switchContainer + + switchControl.addTarget(self, action: #selector(switchValueDidChange), for: .valueChanged) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func switchValueDidChange() { + self.action?(self.switchControl.isOn) + } + +} diff --git a/ios/MullvadVPN/SettingsViewController.swift b/ios/MullvadVPN/SettingsViewController.swift index 31eef92835..67a2d78d99 100644 --- a/ios/MullvadVPN/SettingsViewController.swift +++ b/ios/MullvadVPN/SettingsViewController.swift @@ -103,6 +103,16 @@ class SettingsViewController: UITableViewController, AccountObserver { self?.settingsNavigationController?.navigate(to: .account, animated: true) } + let preferencesRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.basicCell.rawValue) { (_, cell) in + let cell = cell as! SettingsCell + cell.titleLabel.text = NSLocalizedString("Preferences", comment: "") + cell.accessoryType = .disclosureIndicator + } + + preferencesRow.actionBlock = { [weak self] (indexPath) in + self?.settingsNavigationController?.navigate(to: .preferences, animated: true) + } + let wireguardKeyRow = StaticTableViewRow(reuseIdentifier: CellIdentifier.basicCell.rawValue) { (_, cell) in let cell = cell as! SettingsCell @@ -117,7 +127,7 @@ class SettingsViewController: UITableViewController, AccountObserver { self.accountRow = accountRow - topSection.addRows([accountRow, wireguardKeyRow]) + topSection.addRows([accountRow, preferencesRow, wireguardKeyRow]) staticDataSource.addSections([topSection]) } diff --git a/ios/MullvadVPN/UIColor+Palette.swift b/ios/MullvadVPN/UIColor+Palette.swift index b883cbf2b7..b50b31fe70 100644 --- a/ios/MullvadVPN/UIColor+Palette.swift +++ b/ios/MullvadVPN/UIColor+Palette.swift @@ -41,6 +41,12 @@ extension UIColor { static let disabledTitleColor = UIColor.lightGray } + enum Switch { + static let borderColor = UIColor(white: 1.0, alpha: 0.8) + static let onThumbColor = successColor + static let offThumbColor = dangerColor + } + // Relay availability indicator view enum RelayStatusIndicator { static let activeColor = successColor.withAlphaComponent(0.9) |
