From c14d816b879d05b876bc486ff0bfd86f3f39b5a1 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Tue, 23 Oct 2018 22:18:45 +0530 Subject: [PATCH] Tunnel detail: Start off with the tunnel detail view Signed-off-by: Roopesh Chander --- WireGuard/WireGuard.xcodeproj/project.pbxproj | 4 + .../iOS/TunnelDetailTableViewController.swift | 222 ++++++++++++++++++ .../iOS/TunnelsListTableViewController.swift | 12 + 3 files changed, 238 insertions(+) create mode 100644 WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 99690b2..17f8d5b 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; }; 6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3E217F3413003482A3 /* DNSServer.swift */; }; + 6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */; }; 6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; }; 6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; }; 6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; }; @@ -25,6 +26,7 @@ /* Begin PBXFileReference section */ 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewModel.swift; sourceTree = ""; }; 6F628C3E217F3413003482A3 /* DNSServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSServer.swift; sourceTree = ""; }; + 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = ""; }; 6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; }; 6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -70,6 +72,7 @@ 6F7774DF217181B1006A79B3 /* MainViewController.swift */, 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */, 6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */, + 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */, ); path = iOS; sourceTree = ""; @@ -216,6 +219,7 @@ 6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */, 6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */, 6F7774E82172020C006A79B3 /* Configuration.swift in Sources */, + 6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */, 6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */, 6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */, ); diff --git a/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift new file mode 100644 index 0000000..3ed1a86 --- /dev/null +++ b/WireGuard/WireGuard/UI/iOS/TunnelDetailTableViewController.swift @@ -0,0 +1,222 @@ +// +// TunnelDetailTableViewController.swift +// WireGuard +// +// Created by Roopesh Chander on 17/10/18. +// Copyright © 2018 Roopesh Chander. All rights reserved. +// + +import UIKit + +// MARK: TunnelDetailTableViewController + +class TunnelDetailTableViewController: UITableViewController { + + let interfaceFieldsBySection: [[TunnelViewModel.InterfaceField]] = [ + [.name], + [.publicKey, .copyPublicKey], + [.addresses, .listenPort, .mtu, .dns] + ] + + let peerFieldsBySection: [[TunnelViewModel.PeerField]] = [ + [.publicKey, .preSharedKey, .endpoint, + .allowedIPs, .persistentKeepAlive] + ] + + let tunnelsManager: TunnelsManager + let tunnelViewModel: TunnelViewModel + + init(tunnelsManager tm: TunnelsManager, tunnelConfiguration: TunnelConfiguration) { + tunnelsManager = tm + tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration) + super.init(style: .grouped) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + self.title = tunnelViewModel.interfaceData[.name] + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editTapped)) + + self.tableView.rowHeight = 44 + self.tableView.register(TunnelDetailTableViewKeyValueCell.self, forCellReuseIdentifier: TunnelDetailTableViewKeyValueCell.id) + self.tableView.register(TunnelDetailTableViewButtonCell.self, forCellReuseIdentifier: TunnelDetailTableViewButtonCell.id) + } + + @objc func editTapped() { + print("Edit") + } +} + +// MARK: UITableViewDataSource + +extension TunnelDetailTableViewController { + override func numberOfSections(in tableView: UITableView) -> Int { + let numberOfInterfaceSections = interfaceFieldsBySection.count + let numberOfPeerSections = peerFieldsBySection.count + let numberOfPeers = tunnelViewModel.peersData.count + + return numberOfInterfaceSections + (numberOfPeers * numberOfPeerSections) + 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + let numberOfInterfaceSections = interfaceFieldsBySection.count + let numberOfPeerSections = peerFieldsBySection.count + let numberOfPeers = tunnelViewModel.peersData.count + + if (section < numberOfInterfaceSections) { + // Interface + return interfaceFieldsBySection[section].count + } else if ((numberOfPeers > 0) && (section < (numberOfInterfaceSections + numberOfPeers * numberOfPeerSections))) { + // Peer + let fieldIndex = (section - numberOfInterfaceSections) % numberOfPeerSections + return peerFieldsBySection[fieldIndex].count + } else { + // Add peer + return 1 + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + let numberOfInterfaceSections = interfaceFieldsBySection.count + let numberOfPeerSections = peerFieldsBySection.count + let numberOfPeers = tunnelViewModel.peersData.count + + if (section < numberOfInterfaceSections) { + // Interface + return (section == 0) ? "Interface" : nil + } else if ((numberOfPeers > 0) && (section < (numberOfInterfaceSections + numberOfPeers * numberOfPeerSections))) { + // Peer + let fieldIndex = (section - numberOfInterfaceSections) % numberOfPeerSections + return (fieldIndex == 0) ? "Peer" : nil + } else { + // Add peer + return nil + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let numberOfInterfaceSections = interfaceFieldsBySection.count + let numberOfPeerSections = peerFieldsBySection.count + let numberOfPeers = tunnelViewModel.peersData.count + + let section = indexPath.section + let row = indexPath.row + + if (section < numberOfInterfaceSections) { + // Interface + let interfaceData = tunnelViewModel.interfaceData + let field = interfaceFieldsBySection[section][row] + if (field == .copyPublicKey) { + let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewButtonCell.id, for: indexPath) as! TunnelDetailTableViewButtonCell + cell.buttonText = field.rawValue + cell.onTapped = { + print("Copying public key is unimplemented") // TODO + } + return cell + } else { + let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewKeyValueCell.id, for: indexPath) as! TunnelDetailTableViewKeyValueCell + // Set key and value + cell.key = field.rawValue + cell.value = interfaceData[field] + if (field != .publicKey) { + cell.detailTextLabel?.allowsDefaultTighteningForTruncation = true + cell.detailTextLabel?.adjustsFontSizeToFitWidth = true + cell.detailTextLabel?.minimumScaleFactor = 0.85 + } + return cell + } + } else if ((numberOfPeers > 0) && (section < (numberOfInterfaceSections + numberOfPeers * numberOfPeerSections))) { + // Peer + let peerIndex = Int((section - numberOfInterfaceSections) / numberOfPeerSections) + let peerSectionIndex = (section - numberOfInterfaceSections) % numberOfPeerSections + let peerData = tunnelViewModel.peersData[peerIndex] + let field = peerFieldsBySection[peerSectionIndex][row] + let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewKeyValueCell.id, for: indexPath) as! TunnelDetailTableViewKeyValueCell + // Set key and value + cell.key = field.rawValue + cell.value = peerData[field] + if (field != .publicKey && field != .preSharedKey) { + cell.detailTextLabel?.allowsDefaultTighteningForTruncation = true + cell.detailTextLabel?.adjustsFontSizeToFitWidth = true + cell.detailTextLabel?.minimumScaleFactor = 0.85 + } + + return cell + } else { + assert(section == (numberOfInterfaceSections + numberOfPeers * numberOfPeerSections)) + // Delete configuration + let cell = tableView.dequeueReusableCell(withIdentifier: TunnelDetailTableViewButtonCell.id, for: indexPath) as! TunnelDetailTableViewButtonCell + cell.buttonText = "Delete tunnel" + cell.onTapped = { + print("Delete peer unimplemented") + } + return cell + } + } +} + +class TunnelDetailTableViewKeyValueCell: UITableViewCell { + static let id: String = "TunnelDetailTableViewKeyValueCell" + var key: String { + get { return textLabel?.text ?? "" } + set(value) { textLabel?.text = value } + } + var value: String { + get { return detailTextLabel?.text ?? "" } + set(value) { detailTextLabel?.text = value } + } + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: .value1, reuseIdentifier: TunnelDetailTableViewKeyValueCell.id) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func prepareForReuse() { + super.prepareForReuse() + key = "" + value = "" + } +} + +class TunnelDetailTableViewButtonCell: UITableViewCell { + static let id: String = "TunnelsEditTableViewButtonCell" + var buttonText: String { + get { return button.title(for: .normal) ?? "" } + set(value) { button.setTitle(value, for: .normal) } + } + var onTapped: (() -> Void)? = nil + + let button: UIButton + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + button = UIButton(type: .system) + super.init(style: style, reuseIdentifier: reuseIdentifier) + contentView.addSubview(button) + button.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + 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() { + buttonText = "" + onTapped = nil + } +} diff --git a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift index 2a57393..35e90b0 100644 --- a/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/TunnelsListTableViewController.swift @@ -81,6 +81,18 @@ extension TunnelsListTableViewController { } } +// MARK: UITableViewDelegate + +extension TunnelsListTableViewController { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let tunnelsManager = tunnelsManager else { return } + let tunnelConfiguration = tunnelsManager.tunnel(at: indexPath.row).tunnelProvider.tunnelConfiguration + let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, + tunnelConfiguration: tunnelConfiguration) + showDetailViewController(tunnelDetailVC, sender: self) // Shall get propagated up to the split-vc + } +} + // MARK: TunnelsManagerDelegate extension TunnelsListTableViewController: TunnelsManagerDelegate {