From 15602f7dc95e8a104f3be9706aceffb3ca75fd54 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Mon, 18 Mar 2019 22:00:55 +0100 Subject: [PATCH] Pick profile/location for connection intent - Host: ConnectVPN intent - Provider: requires Pool selection --- Passepartout-iOS/Global/SwiftGen+Scenes.swift | 7 + Passepartout-iOS/Global/SwiftGen+Segues.swift | 4 + .../ShortcutsConnectToViewController.swift | 205 ++++++++++++++++++ .../Organizer/ShortcutsViewController.swift | 18 +- .../en.lproj/Shortcuts.storyboard | 13 +- Passepartout.xcodeproj/project.pbxproj | 4 + .../Resources/en.lproj/Localizable.strings | 1 + .../Sources/Model/ConnectionService.swift | 4 + Passepartout/Sources/SwiftGen+Strings.swift | 6 + 9 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 Passepartout-iOS/Scenes/Organizer/ShortcutsConnectToViewController.swift diff --git a/Passepartout-iOS/Global/SwiftGen+Scenes.swift b/Passepartout-iOS/Global/SwiftGen+Scenes.swift index 368d8f0e..4fb8f6f3 100644 --- a/Passepartout-iOS/Global/SwiftGen+Scenes.swift +++ b/Passepartout-iOS/Global/SwiftGen+Scenes.swift @@ -21,6 +21,8 @@ internal enum StoryboardScene { internal static let configurationIdentifier = SceneType(storyboard: Main.self, identifier: "ConfigurationIdentifier") + internal static let providerPoolViewController = SceneType(storyboard: Main.self, identifier: "ProviderPoolViewController") + internal static let serviceIdentifier = SceneType(storyboard: Main.self, identifier: "ServiceIdentifier") } internal enum Organizer: StoryboardType { @@ -32,6 +34,11 @@ internal enum StoryboardScene { internal static let wizardHostIdentifier = SceneType(storyboard: Organizer.self, identifier: "WizardHostIdentifier") } + internal enum Shortcuts: StoryboardType { + internal static let storyboardName = "Shortcuts" + + internal static let initialScene = InitialSceneType(storyboard: Shortcuts.self) + } } // swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name diff --git a/Passepartout-iOS/Global/SwiftGen+Segues.swift b/Passepartout-iOS/Global/SwiftGen+Segues.swift index aee35534..206b7e71 100644 --- a/Passepartout-iOS/Global/SwiftGen+Segues.swift +++ b/Passepartout-iOS/Global/SwiftGen+Segues.swift @@ -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 diff --git a/Passepartout-iOS/Scenes/Organizer/ShortcutsConnectToViewController.swift b/Passepartout-iOS/Scenes/Organizer/ShortcutsConnectToViewController.swift new file mode 100644 index 00000000..f698e86f --- /dev/null +++ b/Passepartout-iOS/Scenes/Organizer/ShortcutsConnectToViewController.swift @@ -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 . +// + +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 = { + let model: TableModel = 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) + } +} diff --git a/Passepartout-iOS/Scenes/Organizer/ShortcutsViewController.swift b/Passepartout-iOS/Scenes/Organizer/ShortcutsViewController.swift index 41f675a8..e1117628 100644 --- a/Passepartout-iOS/Scenes/Organizer/ShortcutsViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/ShortcutsViewController.swift @@ -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() { diff --git a/Passepartout-iOS/en.lproj/Shortcuts.storyboard b/Passepartout-iOS/en.lproj/Shortcuts.storyboard index 08138a5b..4e3dae3a 100644 --- a/Passepartout-iOS/en.lproj/Shortcuts.storyboard +++ b/Passepartout-iOS/en.lproj/Shortcuts.storyboard @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -80,7 +80,14 @@ +