diff --git a/CHANGELOG.md b/CHANGELOG.md index bf8a72d0..4bc27399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- 3D Touch items. [#267](https://github.com/passepartoutvpn/passepartout-apple/pull/267) - Prompt for password interactively. [#3](https://github.com/passepartoutvpn/passepartout-apple/issues/3) - Ukranian translations (Dmitry Chirkin). [#243](https://github.com/passepartoutvpn/passepartout-apple/pull/243) - Restore DNS "Domain" setting. [#260](https://github.com/passepartoutvpn/passepartout-apple/pull/260) diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index f66bcfdc..7b23bae6 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 0E0BD27627B2EB2200583AC5 /* DonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27527B2EB2200583AC5 /* DonateView.swift */; }; 0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */; }; 0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E0C072B236087A100155AAC /* InfoPlist.strings */; }; + 0E0F4C5A29C761850022E884 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5929C761850022E884 /* SceneDelegate.swift */; }; + 0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */; }; 0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; }; 0E1B5F5C29C506AD00FE7D18 /* ProfileView+Diagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */; }; 0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; }; @@ -304,6 +306,8 @@ 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsView.swift; sourceTree = ""; }; 0E0C072A236087A100155AAC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 0E0C072C236087C800155AAC /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; + 0E0F4C5929C761850022E884 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SceneDelegate+Shortcuts.swift"; sourceTree = ""; }; 0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = ""; }; 0E1B5F5B29C506AC00FE7D18 /* ProfileView+Diagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Diagnostics.swift"; sourceTree = ""; }; 0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -875,6 +879,8 @@ 0E0C072B236087A100155AAC /* InfoPlist.strings */, 0E3FC6852867A3F9009B851C /* AppDelegate.swift */, 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */, + 0E0F4C5929C761850022E884 /* SceneDelegate.swift */, + 0E0F4C5B29C76B790022E884 /* SceneDelegate+Shortcuts.swift */, ); path = App; sourceTree = ""; @@ -1420,6 +1426,7 @@ 0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */, 0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */, 0E0392772818732D00827C10 /* BuildProducts.swift in Sources */, + 0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */, 0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */, 0E3FC6862867A3F9009B851C /* AppDelegate.swift in Sources */, 0E71ACFD27C1321A00F85C4B /* ActivityView.swift in Sources */, @@ -1456,6 +1463,7 @@ 0ED7D62F2867328A009F2F8F /* Constants+Library.swift in Sources */, 0E5467FA2867AA0A00F74D1C /* MacBundleDelegate.swift in Sources */, 0E0BD27327B2EA2C00583AC5 /* MainView.swift in Sources */, + 0E0F4C5A29C761850022E884 /* SceneDelegate.swift in Sources */, 0EB17EBA27D2560300D473B5 /* PassepartoutProviders+Extensions.swift in Sources */, 0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */, 0E5468062867AEC500F74D1C /* MacMenu.swift in Sources */, diff --git a/Passepartout/App/AppDelegate.swift b/Passepartout/App/AppDelegate.swift index f0e46acf..e1dcb4d4 100644 --- a/Passepartout/App/AppDelegate.swift +++ b/Passepartout/App/AppDelegate.swift @@ -25,6 +25,7 @@ import Foundation import UIKit +import PassepartoutLibrary class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { private let mac = MacBundle.shared @@ -39,4 +40,10 @@ class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { #endif return true } + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + let sceneConfiguration = UISceneConfiguration(name: "SceneDelegate", sessionRole: connectingSceneSession.role) + sceneConfiguration.delegateClass = SceneDelegate.self + return sceneConfiguration + } } diff --git a/Passepartout/App/Constants/Theme.swift b/Passepartout/App/Constants/Theme.swift index a8fcc54f..46c4c921 100644 --- a/Passepartout/App/Constants/Theme.swift +++ b/Passepartout/App/Constants/Theme.swift @@ -380,6 +380,23 @@ extension View { } } +// MARK: Shortcuts + +extension ShortcutType { + var themeImageName: String { + switch self { + case .enableVPN: + return "power" + + case .disableVPN: + return "xmark" + + case .reconnectVPN: + return "arrow.clockwise" + } + } +} + // MARK: Animations extension View { diff --git a/Passepartout/App/SceneDelegate+Shortcuts.swift b/Passepartout/App/SceneDelegate+Shortcuts.swift new file mode 100644 index 00000000..eb45011f --- /dev/null +++ b/Passepartout/App/SceneDelegate+Shortcuts.swift @@ -0,0 +1,87 @@ +// +// SceneDelegate+Shortcuts.swift +// Passepartout +// +// Created by Davide De Rosa on 3/19/23. +// Copyright (c) 2023 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 SwiftUI +import PassepartoutLibrary + +enum ShortcutType: String { + case enableVPN + + case disableVPN + + case reconnectVPN +} + +extension SceneDelegate { + func rebuildShortcutItems() { + let items: [UIApplicationShortcutItem] + if VPNManager.shared.currentState.isEnabled { + items = [ + ShortcutType.disableVPN.shortcutItem(withTitle: L10n.Shortcuts.Add.Items.DisableVpn.caption), + ShortcutType.reconnectVPN.shortcutItem(withTitle: L10n.Global.Strings.reconnect) + ] + } else if ProfileManager.shared.hasActiveProfile { + items = [ + ShortcutType.enableVPN.shortcutItem(withTitle: L10n.Shortcuts.Add.Items.EnableVpn.caption) + ] + } else { + items = [] + } + UIApplication.shared.shortcutItems = items + } + + func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem) { + switch shortcutItem.type { + case ShortcutType.enableVPN.rawValue: + Task { + try await VPNManager.shared.connectWithActiveProfile(toServer: nil) + } + + case ShortcutType.disableVPN.rawValue: + Task { + await VPNManager.shared.disable() + } + + case ShortcutType.reconnectVPN.rawValue: + Task { + await VPNManager.shared.reconnect() + } + + default: + break + } + } +} + +private extension ShortcutType { + func shortcutItem(withTitle title: String) -> UIApplicationShortcutItem { + UIApplicationShortcutItem( + type: rawValue, + localizedTitle: title, + localizedSubtitle: nil, + icon: .init(systemImageName: themeImageName) + ) + } +} diff --git a/Passepartout/App/SceneDelegate.swift b/Passepartout/App/SceneDelegate.swift new file mode 100644 index 00000000..2e0d1e74 --- /dev/null +++ b/Passepartout/App/SceneDelegate.swift @@ -0,0 +1,41 @@ +// +// SceneDelegate.swift +// Passepartout +// +// Created by Davide De Rosa on 3/19/23. +// Copyright (c) 2023 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 SwiftUI +import PassepartoutLibrary + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + func sceneDidEnterBackground(_ scene: UIScene) { + ProfileManager.shared.persist() + #if targetEnvironment(macCatalyst) + MacBundle.shared.utils.sendAppToBackground() + #endif + rebuildShortcutItems() + } + + func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { + handleShortcutItem(shortcutItem) + } +} diff --git a/Passepartout/App/Views/OrganizerView+Scene.swift b/Passepartout/App/Views/OrganizerView+Scene.swift index c67b4925..19f64504 100644 --- a/Passepartout/App/Views/OrganizerView+Scene.swift +++ b/Passepartout/App/Views/OrganizerView+Scene.swift @@ -53,7 +53,6 @@ extension OrganizerView { Text("Scene") .hidden() .onAppear(perform: onAppear) - .onChange(of: scenePhase, perform: onScenePhase) } private func onAppear() { @@ -79,22 +78,5 @@ extension OrganizerView { profileManager.currentProfileId = activeProfileId } } - - private func onScenePhase(_ phase: ScenePhase) { - switch phase { - case .background: - persist() - #if targetEnvironment(macCatalyst) - MacBundle.shared.utils.sendAppToBackground() - #endif - - default: - break - } - } - - private func persist() { - profileManager.persist() - } } }