From abe5d3848d91238ea34b65795ff76a27d788df3e Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Tue, 30 Apr 2019 18:28:06 +0530 Subject: [PATCH] iOS: Show Home screen quick actions for recent tunnels --- WireGuard/WireGuard.xcodeproj/project.pbxproj | 4 +++ .../WireGuard/Tunnel/TunnelsManager.swift | 4 +++ WireGuard/WireGuard/UI/iOS/AppDelegate.swift | 26 +++++++++++++++++-- .../WireGuard/UI/iOS/QuickActionItem.swift | 25 ++++++++++++++++++ .../ViewController/MainViewController.swift | 13 +++++++++- 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 WireGuard/WireGuard/UI/iOS/QuickActionItem.swift diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index a9b5c3b..090d4b0 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */; }; 6F2449E8226587B90047B9E9 /* MacAppStoreUpdateDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */; }; 6F29A9432278518D00DC6A6B /* RecentTunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */; }; + 6F29A94722787B1600DC6A6B /* QuickActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F29A94622787B1600DC6A6B /* QuickActionItem.swift */; }; 6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; }; 6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; }; 6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; }; @@ -302,6 +303,7 @@ 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlertPresenter.swift; sourceTree = ""; }; 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAppStoreUpdateDetector.swift; sourceTree = ""; }; 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentTunnelsTracker.swift; sourceTree = ""; }; + 6F29A94622787B1600DC6A6B /* QuickActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionItem.swift; sourceTree = ""; }; 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = ""; }; 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = ""; }; 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = ""; }; @@ -567,6 +569,7 @@ 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */, 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */, 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */, + 6F29A94622787B1600DC6A6B /* QuickActionItem.swift */, 6FF4AC23211EC472002C96EB /* Info.plist */, 6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */, ); @@ -1350,6 +1353,7 @@ buildActionMask = 2147483647; files = ( 6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */, + 6F29A94722787B1600DC6A6B /* QuickActionItem.swift in Sources */, 6FF3527221C2616C0008484E /* ringlogger.c in Sources */, 6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */, 6FF3527321C2616C0008484E /* Logger.swift in Sources */, diff --git a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift index 460a9f6..2781131 100644 --- a/WireGuard/WireGuard/Tunnel/TunnelsManager.swift +++ b/WireGuard/WireGuard/Tunnel/TunnelsManager.swift @@ -311,6 +311,10 @@ class TunnelsManager { return tunnels[index] } + func mapTunnels(transform: (TunnelContainer) throws -> T) rethrows -> [T] { + return try tunnels.map(transform) + } + func index(of tunnel: TunnelContainer) -> Int? { return tunnels.firstIndex(of: tunnel) } diff --git a/WireGuard/WireGuard/UI/iOS/AppDelegate.swift b/WireGuard/WireGuard/UI/iOS/AppDelegate.swift index d274280..8884d44 100644 --- a/WireGuard/WireGuard/UI/iOS/AppDelegate.swift +++ b/WireGuard/WireGuard/UI/iOS/AppDelegate.swift @@ -9,10 +9,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var mainVC: MainViewController? + var isLaunchedForSpecificAction = false func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path) + if let launchOptions = launchOptions { + if launchOptions[.url] != nil || launchOptions[.shortcutItem] != nil { + isLaunchedForSpecificAction = true + } + } + let window = UIWindow(frame: UIScreen.main.bounds) window.backgroundColor = .white self.window = window @@ -37,6 +44,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationDidBecomeActive(_ application: UIApplication) { mainVC?.refreshTunnelConnectionStatuses() } + + func applicationWillResignActive(_ application: UIApplication) { + guard let allTunnelNames = mainVC?.allTunnelNames() else { return } + application.shortcutItems = QuickActionItem.createItems(allTunnelNames: allTunnelNames) + } + + func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { + guard shortcutItem.type == QuickActionItem.type else { + completionHandler(false) + return + } + let tunnelName = shortcutItem.localizedTitle + mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: true) + completionHandler(true) + } } extension AppDelegate { @@ -45,7 +67,7 @@ extension AppDelegate { } func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool { - return true + return !self.isLaunchedForSpecificAction } func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? { @@ -58,7 +80,7 @@ extension AppDelegate { } } else { // Show it when tunnelsManager is available - mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false) + mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: false) } } return nil diff --git a/WireGuard/WireGuard/UI/iOS/QuickActionItem.swift b/WireGuard/WireGuard/UI/iOS/QuickActionItem.swift new file mode 100644 index 0000000..4367fa9 --- /dev/null +++ b/WireGuard/WireGuard/UI/iOS/QuickActionItem.swift @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import UIKit + +class QuickActionItem: UIApplicationShortcutItem { + static let type = "WireGuardTunnelActivateAndShow" + + init(tunnelName: String) { + super.init(type: QuickActionItem.type, localizedTitle: tunnelName, localizedSubtitle: nil, icon: nil, userInfo: nil) + } + + static func createItems(allTunnelNames: [String]) -> [QuickActionItem] { + let numberOfItems = 10 + // Currently, only 4 items shown by iOS, but that can increase in the future. + // iOS will discard additional items we give it. + var tunnelNames = RecentTunnelsTracker.recentlyActivatedTunnelNames(limit: numberOfItems) + let numberOfSlotsRemaining = numberOfItems - tunnelNames.count + if numberOfSlotsRemaining > 0 { + let moreTunnels = allTunnelNames.filter { !tunnelNames.contains($0) }.prefix(numberOfSlotsRemaining) + tunnelNames.append(contentsOf: moreTunnels) + } + return tunnelNames.map { QuickActionItem(tunnelName: $0) } + } +} diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/MainViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/MainViewController.swift index 380299c..1929c79 100644 --- a/WireGuard/WireGuard/UI/iOS/ViewController/MainViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/ViewController/MainViewController.swift @@ -57,6 +57,10 @@ class MainViewController: UISplitViewController { } } + func allTunnelNames() -> [String]? { + guard let tunnelsManager = self.tunnelsManager else { return nil } + return tunnelsManager.mapTunnels { $0.name } + } } extension MainViewController: TunnelsManagerActivationDelegate { @@ -84,7 +88,7 @@ extension MainViewController { } } - func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool) { + func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool, shouldToggleStatus: Bool) { let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in if let tunnel = tunnelsManager.tunnel(named: tunnelName) { let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel) @@ -99,6 +103,13 @@ extension MainViewController { } } } + if shouldToggleStatus { + if tunnel.status == .inactive { + tunnelsManager.startActivation(of: tunnel) + } else if tunnel.status == .active { + tunnelsManager.startDeactivation(of: tunnel) + } + } } } if let tunnelsManager = tunnelsManager {