Pick profile/location for connection intent

- Host: ConnectVPN intent
- Provider: requires Pool selection
This commit is contained in:
Davide De Rosa 2019-03-18 22:00:55 +01:00
parent 1ea85ff32e
commit 15602f7dc9
9 changed files with 253 additions and 9 deletions

View File

@ -21,6 +21,8 @@ internal enum StoryboardScene {
internal static let configurationIdentifier = SceneType<ConfigurationViewController>(storyboard: Main.self, identifier: "ConfigurationIdentifier")
internal static let providerPoolViewController = SceneType<ProviderPoolViewController>(storyboard: Main.self, identifier: "ProviderPoolViewController")
internal static let serviceIdentifier = SceneType<UIKit.UINavigationController>(storyboard: Main.self, identifier: "ServiceIdentifier")
}
internal enum Organizer: StoryboardType {
@ -32,6 +34,11 @@ internal enum StoryboardScene {
internal static let wizardHostIdentifier = SceneType<UIKit.UINavigationController>(storyboard: Organizer.self, identifier: "WizardHostIdentifier")
}
internal enum Shortcuts: StoryboardType {
internal static let storyboardName = "Shortcuts"
internal static let initialScene = InitialSceneType<UIKit.UINavigationController>(storyboard: Shortcuts.self)
}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name

View File

@ -29,6 +29,10 @@ internal enum StoryboardSegue {
case siriShortcutsSegueIdentifier = "SiriShortcutsSegueIdentifier"
case versionSegueIdentifier = "VersionSegueIdentifier"
}
internal enum Shortcuts: String, SegueType {
case connectToSegueIdentifier = "ConnectToSegueIdentifier"
case pickLocationSegueIdentifier = "PickLocationSegueIdentifier"
}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name

View File

@ -0,0 +1,205 @@
//
// ShortcutsConnectToViewController.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 3/18/19.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
import Intents
import IntentsUI
import Passepartout_Core
class ShortcutsConnectToViewController: UITableViewController, TableModelHost {
private let service = TransientStore.shared.service
private var providers: [String] = []
private var hosts: [String] = []
private var selectedProfile: ConnectionProfile?
// MARK: TableModelHost
let model: TableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel()
model.setHeader(L10n.Organizer.Sections.Providers.header, for: .providers)
model.setHeader(L10n.Organizer.Sections.Hosts.header, for: .hosts)
return model
}()
func reloadModel() {
providers = service.ids(forContext: .provider).sorted()
hosts = service.ids(forContext: .host).sortedCaseInsensitive()
if !providers.isEmpty {
model.add(.providers)
model.set(.providerShortcut, count: providers.count, in: .providers)
}
if !hosts.isEmpty {
model.add(.hosts)
model.set(.hostShortcut, count: hosts.count, in: .hosts)
}
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
title = L10n.Shortcuts.Cells.Connect.caption
reloadModel()
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
guard identifier == StoryboardSegue.Shortcuts.pickLocationSegueIdentifier.rawValue else {
return false
}
guard let _ = selectedProfile as? ProviderConnectionProfile else {
return false
}
return true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let vc = segue.destination as? ProviderPoolViewController else {
return
}
guard let provider = selectedProfile as? ProviderConnectionProfile else {
return
}
vc.pools = provider.sortedPools()
vc.delegate = self
}
}
extension ShortcutsConnectToViewController {
enum SectionType {
case providers
case hosts
}
enum RowType {
case providerShortcut
case hostShortcut
}
override func numberOfSections(in tableView: UITableView) -> Int {
return model.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.apply(Theme.current)
switch model.row(at: indexPath) {
case .providerShortcut:
cell.leftText = providers[indexPath.row]
case .hostShortcut:
cell.leftText = hosts[indexPath.row]
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard #available(iOS 12, *) else {
return
}
switch model.row(at: indexPath) {
case .providerShortcut:
selectedProfile = service.profile(withContext: .provider, id: providers[indexPath.row])
pickProviderLocation()
case .hostShortcut:
selectedProfile = service.profile(withContext: .host, id: hosts[indexPath.row])
addConnect()
}
}
}
// MARK: Actions
@available(iOS 12, *)
extension ShortcutsConnectToViewController {
private func addConnect() {
guard let host = selectedProfile as? HostConnectionProfile else {
fatalError("Not a HostConnectionProfile")
}
let intent = ConnectVPNIntent()
intent.context = host.context.rawValue
intent.profileId = host.id
addShortcut(with: intent)
}
private func addMoveToLocation(pool: Pool) {
guard let provider = selectedProfile as? ProviderConnectionProfile else {
fatalError("Not a ProviderConnectionProfile")
}
let intent = MoveToLocationIntent()
intent.providerId = provider.id
intent.poolId = pool.id
intent.poolName = pool.name
addShortcut(with: intent)
}
private func addShortcut(with intent: INIntent) {
guard let shortcut = INShortcut(intent: intent) else {
return
}
let vc = INUIAddVoiceShortcutViewController(shortcut: shortcut)
vc.delegate = self
present(vc, animated: true, completion: nil)
}
private func pickProviderLocation() {
perform(segue: StoryboardSegue.Shortcuts.pickLocationSegueIdentifier)
}
}
extension ShortcutsConnectToViewController: ProviderPoolViewControllerDelegate {
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool) {
guard #available(iOS 12, *) else {
return
}
addMoveToLocation(pool: pool)
}
}
@available(iOS 12, *)
extension ShortcutsConnectToViewController: INUIAddVoiceShortcutViewControllerDelegate {
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {
dismiss(animated: true, completion: nil)
}
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
dismiss(animated: true, completion: nil)
}
}

View File

@ -152,14 +152,20 @@ extension ShortcutsViewController {
@available(iOS 12, *)
extension ShortcutsViewController {
private func addConnect() {
// FIXME: show hosts and providers, host delegates selection, provider requires location
let intent = ConnectVPNIntent()
guard let profileKey = TransientStore.shared.service.activeProfileKey else {
guard TransientStore.shared.service.hasProfiles() else {
let alert = Macros.alert(
L10n.Shortcuts.Cells.Connect.caption,
L10n.Shortcuts.Alerts.NoProfiles.message
)
alert.addAction(L10n.Global.ok) {
if let ip = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: ip, animated: true)
}
}
present(alert, animated: true, completion: nil)
return
}
intent.context = profileKey.context.rawValue
intent.profileId = profileKey.id
addShortcut(with: intent)
perform(segue: StoryboardSegue.Shortcuts.connectToSegueIdentifier)
}
private func addEnable() {

View File

@ -56,7 +56,7 @@
</barButtonItem>
</navigationItem>
<connections>
<segue destination="zpj-mS-isI" kind="show" id="Qer-qI-Oyn"/>
<segue destination="zpj-mS-isI" kind="show" identifier="ConnectToSegueIdentifier" id="Qer-qI-Oyn"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="0dW-yk-veP" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -72,7 +72,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="4LM-xw-0Rx" style="IBUITableViewCellStyleDefault" id="aw4-ca-yyE">
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="SettingTableViewCell" textLabel="4LM-xw-0Rx" detailTextLabel="4RM-B9-uCO" style="IBUITableViewCellStyleValue1" id="aw4-ca-yyE" customClass="SettingTableViewCell" customModule="Passepartout" customModuleProvider="target">
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="aw4-ca-yyE" id="HBB-Gp-iw2">
@ -80,7 +80,14 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4LM-xw-0Rx">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4RM-B9-uCO">
<rect key="frame" x="315" y="12" width="44" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>

View File

@ -62,6 +62,7 @@
0E3152DF223FA1DD00F61841 /* ConnectionService.json in Resources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F121822B4D00106008 /* ConnectionService.json */; };
0E36D24D2240234B006AF062 /* ShortcutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D24C2240234B006AF062 /* ShortcutsViewController.swift */; };
0E36D25822403469006AF062 /* Shortcuts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E36D25A22403469006AF062 /* Shortcuts.storyboard */; };
0E36D25C224034AD006AF062 /* ShortcutsConnectToViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */; };
0E3DA371215CB5BF00B40FC9 /* VersionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */; };
0E4C9CBB20DCF0D600A0C59C /* DestructiveTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4C9CBA20DCF0D600A0C59C /* DestructiveTableViewCell.swift */; };
0E4FD7F120D58618002221FF /* Macros.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7F020D58618002221FF /* Macros.swift */; };
@ -176,6 +177,7 @@
0E3152AC223F9EF500F61841 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0E36D24C2240234B006AF062 /* ShortcutsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsViewController.swift; sourceTree = "<group>"; };
0E36D25922403469006AF062 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Shortcuts.storyboard; sourceTree = "<group>"; };
0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConnectToViewController.swift; sourceTree = "<group>"; };
0E39BCEF214B9EF10035E9DE /* WebServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebServices.swift; sourceTree = "<group>"; };
0E39BCF2214DA9310035E9DE /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionViewController.swift; sourceTree = "<group>"; };
@ -411,6 +413,7 @@
0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */,
0EFD943F215BED8E00529B64 /* LabelViewController.swift */,
0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */,
0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */,
0E36D24C2240234B006AF062 /* ShortcutsViewController.swift */,
0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */,
0ED38AD8213F33150004D387 /* WizardHostViewController.swift */,
@ -989,6 +992,7 @@
0EF5CF252141CE58004FF1BD /* HUD.swift in Sources */,
0E05C5D720D1645F006EE732 /* ToggleTableViewCell.swift in Sources */,
0E05C5D420D1645F006EE732 /* FieldTableViewCell.swift in Sources */,
0E36D25C224034AD006AF062 /* ShortcutsConnectToViewController.swift in Sources */,
0E05C61D20D27C82006EE732 /* Theme.swift in Sources */,
0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */,
0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */,

View File

@ -201,6 +201,7 @@
"shortcuts.cells.untrust_wifi.caption" = "Untrust current Wi-Fi";
"shortcuts.cells.trust_cellular.caption" = "Trust cellular network";
"shortcuts.cells.untrust_cellular.caption" = "Untrust cellular network";
"shortcuts.alerts.no_profiles.message" = "There is no profile to connect to.";
"about.title" = "About";
"about.sections.web.header" = "Web";

View File

@ -291,6 +291,10 @@ public class ConnectionService: Codable {
// MARK: Profiles
public func hasProfiles() -> Bool {
return !cache.isEmpty
}
public func addProfile(_ profile: ConnectionProfile, credentials: Credentials?) -> Bool {
guard cache.index(forKey: ProfileKey(profile)) == nil else {
return false

View File

@ -651,6 +651,12 @@ public enum L10n {
}
public enum Shortcuts {
public enum Alerts {
public enum NoProfiles {
/// There is no profile to connect to.
public static let message = L10n.tr("Localizable", "shortcuts.alerts.no_profiles.message")
}
}
public enum Cells {
public enum Connect {
/// Connect to