Make shortcuts contextual in ProfileView
- Add toolbar item - Target current profile - Only list relevant shortcuts to profile
This commit is contained in:
parent
b1882dcf80
commit
480738d126
|
@ -31,6 +31,7 @@
|
|||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; };
|
||||
0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */; };
|
||||
0E3CD47F280DA14B007075C0 /* OrganizerView+AddMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CD47E280DA14B007075C0 /* OrganizerView+AddMenu.swift */; };
|
||||
0E3CD483280DAE92007075C0 /* ProfileView+MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CD482280DAE92007075C0 /* ProfileView+MenuBar.swift */; };
|
||||
0E44689627B051C300A14CE4 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689527B051C300A14CE4 /* ProfileView.swift */; };
|
||||
0E44689C27B11B5300A14CE4 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44689B27B11B5300A14CE4 /* AboutView.swift */; };
|
||||
0E49F6BB27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */; };
|
||||
|
@ -105,7 +106,6 @@
|
|||
0ED89C1727DE0E05008B36D6 /* IntentEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */; };
|
||||
0ED89C1C27DE3ABC008B36D6 /* ShortcutsView+Add.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */; };
|
||||
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */; };
|
||||
0ED89C2027DE423B008B36D6 /* ShortcutsView+ConnectTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */; };
|
||||
0ED89C2527DE45A3008B36D6 /* ProfileHeaderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */; };
|
||||
0EDE02C227F61C79000FBE3C /* EditableTextList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */; };
|
||||
0EE11CD2280D8317003BE431 /* OrganizerView+SettingsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE11CD1280D8317003BE431 /* OrganizerView+SettingsMenu.swift */; };
|
||||
|
@ -211,6 +211,7 @@
|
|||
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
|
||||
0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Provider.swift"; sourceTree = "<group>"; };
|
||||
0E3CD47E280DA14B007075C0 /* OrganizerView+AddMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+AddMenu.swift"; sourceTree = "<group>"; };
|
||||
0E3CD482280DAE92007075C0 /* ProfileView+MenuBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+MenuBar.swift"; sourceTree = "<group>"; };
|
||||
0E44689527B051C300A14CE4 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
0E44689B27B11B5300A14CE4 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
0E49F6BA27D7638300385834 /* EndpointAdvancedView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointAdvancedView+OpenVPN.swift"; sourceTree = "<group>"; };
|
||||
|
@ -315,7 +316,6 @@
|
|||
0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentEditView.swift; sourceTree = "<group>"; };
|
||||
0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShortcutsView+Add.swift"; sourceTree = "<group>"; };
|
||||
0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentAddView.swift; sourceTree = "<group>"; };
|
||||
0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShortcutsView+ConnectTo.swift"; sourceTree = "<group>"; };
|
||||
0ED89C2427DE45A3008B36D6 /* ProfileHeaderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderRow.swift; sourceTree = "<group>"; };
|
||||
0EDE02C127F61C79000FBE3C /* EditableTextList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextList.swift; sourceTree = "<group>"; };
|
||||
0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutOpenVPNTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -398,6 +398,7 @@
|
|||
0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */,
|
||||
0E92D7F327F104B80033CB7B /* ProfileView+Diagnostics.swift */,
|
||||
0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */,
|
||||
0E3CD482280DAE92007075C0 /* ProfileView+MenuBar.swift */,
|
||||
0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */,
|
||||
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */,
|
||||
0E0AD48F27BD53CB00FBB520 /* ProfileView+Welcome.swift */,
|
||||
|
@ -406,7 +407,6 @@
|
|||
0EBC075A27EC4FFF00208AD9 /* ReportIssueView.swift */,
|
||||
0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */,
|
||||
0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */,
|
||||
0ED89C1F27DE423B008B36D6 /* ShortcutsView+ConnectTo.swift */,
|
||||
0E71ACFA27C12E5300F85C4B /* VersionView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
|
@ -988,7 +988,6 @@
|
|||
0ED89C2527DE45A3008B36D6 /* ProfileHeaderRow.swift in Sources */,
|
||||
0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */,
|
||||
0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */,
|
||||
0ED89C2027DE423B008B36D6 /* ShortcutsView+ConnectTo.swift in Sources */,
|
||||
0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */,
|
||||
0E6059CF27FCC618003F4063 /* SwiftGen+Assets.swift in Sources */,
|
||||
0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */,
|
||||
|
@ -1001,6 +1000,7 @@
|
|||
0E71ACF927C12E4800F85C4B /* CreditsView.swift in Sources */,
|
||||
0ED89C1527DE0A0C008B36D6 /* Shortcut.swift in Sources */,
|
||||
0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */,
|
||||
0E3CD483280DAE92007075C0 /* ProfileView+MenuBar.swift in Sources */,
|
||||
0EB17EAE27D226CF00D473B5 /* LocalProduct.swift in Sources */,
|
||||
0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */,
|
||||
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */,
|
||||
|
|
|
@ -34,10 +34,6 @@ extension OrganizerView {
|
|||
|
||||
@Binding var alertType: AlertType?
|
||||
|
||||
private var isEligibleForSiri: Bool {
|
||||
productManager.isEligible(forFeature: .siriShortcuts)
|
||||
}
|
||||
|
||||
private let redditURL = Constants.URLs.subreddit
|
||||
|
||||
private let alternativeToURL = Constants.URLs.alternativeTo
|
||||
|
@ -62,7 +58,6 @@ extension OrganizerView {
|
|||
shareMenu
|
||||
}
|
||||
Divider()
|
||||
shortcutsButton
|
||||
aboutButton
|
||||
// RemoveVPNSection()
|
||||
// betaSection
|
||||
|
@ -71,14 +66,6 @@ extension OrganizerView {
|
|||
}
|
||||
}
|
||||
|
||||
private var shortcutsButton: some View {
|
||||
Button {
|
||||
presentShortcutsOrPaywall()
|
||||
} label: {
|
||||
Label(L10n.Organizer.Items.SiriShortcuts.caption, systemImage: themeShortcutsImage)
|
||||
}
|
||||
}
|
||||
|
||||
private var supportMenu: some View {
|
||||
Group {
|
||||
Button {
|
||||
|
@ -112,16 +99,6 @@ extension OrganizerView {
|
|||
}
|
||||
}
|
||||
|
||||
private func presentShortcutsOrPaywall() {
|
||||
|
||||
// eligibility: enter Siri shortcuts or present paywall
|
||||
if isEligibleForSiri {
|
||||
modalType = .shortcuts
|
||||
} else {
|
||||
modalType = .presentPaywallShortcuts
|
||||
}
|
||||
}
|
||||
|
||||
private func shareOnTwitter() {
|
||||
let url = Unlocalized.Social.twitterIntent(withMessage: shareMessage)
|
||||
URL.openURL(url)
|
||||
|
|
|
@ -32,16 +32,12 @@ struct OrganizerView: View {
|
|||
|
||||
case addHost(URL, Bool)
|
||||
|
||||
case shortcuts
|
||||
|
||||
case donate
|
||||
|
||||
case share([Any])
|
||||
|
||||
case about
|
||||
|
||||
case presentPaywallShortcuts
|
||||
|
||||
// XXX: alert ids
|
||||
var id: Int {
|
||||
switch self {
|
||||
|
@ -49,15 +45,11 @@ struct OrganizerView: View {
|
|||
|
||||
case .addHost: return 2
|
||||
|
||||
case .shortcuts: return 3
|
||||
|
||||
case .donate: return 4
|
||||
|
||||
case .share: return 5
|
||||
|
||||
case .about: return 6
|
||||
|
||||
case .presentPaywallShortcuts: return 7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,11 +152,6 @@ extension OrganizerView {
|
|||
)
|
||||
}.themeGlobal()
|
||||
|
||||
case .shortcuts:
|
||||
NavigationView {
|
||||
ShortcutsView()
|
||||
}.themeGlobal()
|
||||
|
||||
case .donate:
|
||||
NavigationView {
|
||||
DonateView()
|
||||
|
@ -177,14 +164,6 @@ extension OrganizerView {
|
|||
NavigationView {
|
||||
AboutView()
|
||||
}.themeGlobal()
|
||||
|
||||
case .presentPaywallShortcuts:
|
||||
NavigationView {
|
||||
PaywallView(
|
||||
modalType: $modalType,
|
||||
feature: .siriShortcuts
|
||||
)
|
||||
}.themeGlobal()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// ProfileView+MenuBar.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 2/6/22.
|
||||
// Copyright (c) 2022 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 SwiftUI
|
||||
import PassepartoutCore
|
||||
|
||||
extension ProfileView {
|
||||
struct MenuBar: View {
|
||||
@ObservedObject private var productManager: ProductManager
|
||||
|
||||
@ObservedObject private var currentProfile: ObservableProfile
|
||||
|
||||
@Binding private var modalType: ModalType?
|
||||
|
||||
private var isEligibleForSiri: Bool {
|
||||
productManager.isEligible(forFeature: .siriShortcuts)
|
||||
}
|
||||
|
||||
init(currentProfile: ObservableProfile, modalType: Binding<ModalType?>) {
|
||||
productManager = .shared
|
||||
self.currentProfile = currentProfile
|
||||
_modalType = modalType
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
presentShortcutsOrPaywall()
|
||||
} label: {
|
||||
themeShortcutsImage.asSystemImage
|
||||
}
|
||||
Button {
|
||||
modalType = .rename
|
||||
} label: {
|
||||
themeRenameProfileImage.asSystemImage
|
||||
}
|
||||
}
|
||||
|
||||
private func presentShortcutsOrPaywall() {
|
||||
|
||||
// eligibility: enter Siri shortcuts or present paywall
|
||||
if isEligibleForSiri {
|
||||
modalType = .shortcuts
|
||||
} else {
|
||||
modalType = .paywallShortcuts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,13 +24,16 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import PassepartoutCore
|
||||
|
||||
struct ProfileView: View {
|
||||
enum ModalType: Int, Identifiable {
|
||||
case shortcuts
|
||||
|
||||
case rename
|
||||
|
||||
case paywallShortcuts
|
||||
|
||||
case paywallNetworkSettings
|
||||
|
||||
case paywallTrustedNetworks
|
||||
|
@ -106,11 +109,10 @@ struct ProfileView: View {
|
|||
private func toolbar() -> some ToolbarContent {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
if !isDeleted {
|
||||
Button {
|
||||
modalType = .rename
|
||||
} label: {
|
||||
Image(systemName: themeRenameProfileImage)
|
||||
}
|
||||
MenuBar(
|
||||
currentProfile: profileManager.currentProfile,
|
||||
modalType: $modalType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,11 +120,24 @@ struct ProfileView: View {
|
|||
@ViewBuilder
|
||||
private func presentedModal(_ modalType: ModalType) -> some View {
|
||||
switch modalType {
|
||||
case .shortcuts:
|
||||
NavigationView {
|
||||
ShortcutsView(target: profileManager.currentProfile.value)
|
||||
}.themeGlobal()
|
||||
|
||||
case .rename:
|
||||
NavigationView {
|
||||
RenameView(currentProfile: profileManager.currentProfile)
|
||||
}.themeGlobal()
|
||||
|
||||
case .paywallShortcuts:
|
||||
NavigationView {
|
||||
PaywallView(
|
||||
modalType: $modalType,
|
||||
feature: .siriShortcuts
|
||||
)
|
||||
}.themeGlobal()
|
||||
|
||||
case .paywallNetworkSettings:
|
||||
NavigationView {
|
||||
PaywallView(
|
||||
|
|
|
@ -29,76 +29,131 @@ import PassepartoutCore
|
|||
|
||||
extension ShortcutsView {
|
||||
struct AddView: View {
|
||||
@ObservedObject private var profileManager: ProfileManager
|
||||
@ObservedObject private var providerManager: ProviderManager
|
||||
|
||||
@StateObject private var pendingProfile = ObservableProfile()
|
||||
|
||||
private let target: Profile
|
||||
|
||||
@Binding private var pendingShortcut: INShortcut?
|
||||
|
||||
init(pendingShortcut: Binding<INShortcut?>) {
|
||||
profileManager = .shared
|
||||
|
||||
@State private var isPresentingProviderLocation = false
|
||||
|
||||
init(target: Profile, pendingShortcut: Binding<INShortcut?>) {
|
||||
providerManager = .shared
|
||||
self.target = target
|
||||
_pendingShortcut = pendingShortcut
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section(
|
||||
header: Text(Unlocalized.VPN.vpn)
|
||||
) {
|
||||
NavigationLink(L10n.Shortcuts.Add.Items.Connect.caption) {
|
||||
ConnectToView(
|
||||
pendingProfile: pendingProfile,
|
||||
pendingShortcut: $pendingShortcut
|
||||
)
|
||||
}.disabled(profileManager.headers.isEmpty)
|
||||
ZStack {
|
||||
List {
|
||||
Section(
|
||||
header: Text(Unlocalized.VPN.vpn)
|
||||
) {
|
||||
addConnectView
|
||||
Button(L10n.Shortcuts.Add.Items.EnableVpn.caption, action: addEnableVPN)
|
||||
Button(L10n.Shortcuts.Add.Items.DisableVpn.caption, action: addDisableVPN)
|
||||
}
|
||||
Section(
|
||||
header: Text(L10n.Shortcuts.Add.Sections.Wifi.header)
|
||||
) {
|
||||
Button(L10n.Shortcuts.Add.Items.TrustCurrentWifi.caption, action: addTrustWiFi)
|
||||
Button(L10n.Shortcuts.Add.Items.UntrustCurrentWifi.caption, action: addUntrustWiFi)
|
||||
}
|
||||
Section(
|
||||
header: Text(L10n.Shortcuts.Add.Sections.Cellular.header)
|
||||
) {
|
||||
Button(L10n.Shortcuts.Add.Items.TrustCellular.caption, action: addTrustCellular)
|
||||
Button(L10n.Shortcuts.Add.Items.UntrustCellular.caption, action: addUntrustCellular)
|
||||
}
|
||||
}
|
||||
|
||||
Button(L10n.Shortcuts.Add.Items.EnableVpn.caption, action: addEnableVPN)
|
||||
Button(L10n.Shortcuts.Add.Items.DisableVpn.caption, action: addDisableVPN)
|
||||
}
|
||||
Section(
|
||||
header: Text(L10n.Shortcuts.Add.Sections.Wifi.header)
|
||||
) {
|
||||
Button(L10n.Shortcuts.Add.Items.TrustCurrentWifi.caption, action: addTrustWiFi)
|
||||
Button(L10n.Shortcuts.Add.Items.UntrustCurrentWifi.caption, action: addUntrustWiFi)
|
||||
}
|
||||
Section(
|
||||
header: Text(L10n.Shortcuts.Add.Sections.Cellular.header)
|
||||
) {
|
||||
Button(L10n.Shortcuts.Add.Items.TrustCellular.caption, action: addTrustCellular)
|
||||
Button(L10n.Shortcuts.Add.Items.UntrustCellular.caption, action: addUntrustCellular)
|
||||
}
|
||||
providerLocationLink
|
||||
}.navigationTitle(L10n.Shortcuts.Add.title)
|
||||
}
|
||||
|
||||
private func addEnableVPN() {
|
||||
addShortcut(with: IntentDispatcher.intentEnable())
|
||||
}
|
||||
|
||||
private func addDisableVPN() {
|
||||
addShortcut(with: IntentDispatcher.intentDisable())
|
||||
}
|
||||
|
||||
private func addTrustWiFi() {
|
||||
addShortcut(with: IntentDispatcher.intentTrustWiFi())
|
||||
}
|
||||
|
||||
private func addUntrustWiFi() {
|
||||
addShortcut(with: IntentDispatcher.intentUntrustWiFi())
|
||||
}
|
||||
|
||||
private func addTrustCellular() {
|
||||
addShortcut(with: IntentDispatcher.intentTrustCellular())
|
||||
}
|
||||
|
||||
private func addUntrustCellular() {
|
||||
addShortcut(with: IntentDispatcher.intentUntrustCellular())
|
||||
}
|
||||
|
||||
private func addShortcut(with intent: INIntent) {
|
||||
guard let shortcut = INShortcut(intent: intent) else {
|
||||
fatalError("Unable to create INShortcut, intent '\(intent.description)' not exposed by app?")
|
||||
private var addConnectView: some View {
|
||||
Button(L10n.Shortcuts.Add.Items.Connect.caption) {
|
||||
if target.isProvider {
|
||||
pendingProfile.value = target
|
||||
isPresentingProviderLocation = true
|
||||
} else {
|
||||
addConnect(target.header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var providerLocationLink: some View {
|
||||
NavigationLink("", isActive: $isPresentingProviderLocation) {
|
||||
ProviderLocationView(
|
||||
currentProfile: pendingProfile,
|
||||
isEditable: false,
|
||||
isPresented: isProviderLocationPresented
|
||||
)
|
||||
}
|
||||
pendingShortcut = shortcut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ShortcutsView.AddView {
|
||||
private var isProviderLocationPresented: Binding<Bool> {
|
||||
.init {
|
||||
isPresentingProviderLocation
|
||||
} set: {
|
||||
if !$0 {
|
||||
isPresentingProviderLocation = false
|
||||
addMoveToPendingProfile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addConnect(_ header: Profile.Header) {
|
||||
pendingShortcut = INShortcut(intent: IntentDispatcher.intentConnect(
|
||||
header: header
|
||||
))
|
||||
}
|
||||
|
||||
private func addMoveToPendingProfile() {
|
||||
let header = pendingProfile.value.header
|
||||
guard let server = pendingProfile.value.providerServer(providerManager) else {
|
||||
return
|
||||
}
|
||||
pendingShortcut = INShortcut(intent: IntentDispatcher.intentMoveTo(
|
||||
header: header,
|
||||
providerFullName: server.providerMetadata.fullName,
|
||||
server: server
|
||||
))
|
||||
}
|
||||
|
||||
private func addEnableVPN() {
|
||||
addShortcut(with: IntentDispatcher.intentEnable())
|
||||
}
|
||||
|
||||
private func addDisableVPN() {
|
||||
addShortcut(with: IntentDispatcher.intentDisable())
|
||||
}
|
||||
|
||||
private func addTrustWiFi() {
|
||||
addShortcut(with: IntentDispatcher.intentTrustWiFi())
|
||||
}
|
||||
|
||||
private func addUntrustWiFi() {
|
||||
addShortcut(with: IntentDispatcher.intentUntrustWiFi())
|
||||
}
|
||||
|
||||
private func addTrustCellular() {
|
||||
addShortcut(with: IntentDispatcher.intentTrustCellular())
|
||||
}
|
||||
|
||||
private func addUntrustCellular() {
|
||||
addShortcut(with: IntentDispatcher.intentUntrustCellular())
|
||||
}
|
||||
|
||||
private func addShortcut(with intent: INIntent) {
|
||||
guard let shortcut = INShortcut(intent: intent) else {
|
||||
fatalError("Unable to create INShortcut, intent '\(intent.description)' not exposed by app?")
|
||||
}
|
||||
pendingShortcut = shortcut
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
//
|
||||
// ShortcutsView+ConnectTo.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 3/13/22.
|
||||
// Copyright (c) 2022 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 SwiftUI
|
||||
import Intents
|
||||
import PassepartoutCore
|
||||
|
||||
extension ShortcutsView {
|
||||
struct ConnectToView: View {
|
||||
@ObservedObject private var profileManager: ProfileManager
|
||||
|
||||
@ObservedObject private var providerManager: ProviderManager
|
||||
|
||||
@ObservedObject private var pendingProfile: ObservableProfile
|
||||
|
||||
@Binding private var pendingShortcut: INShortcut?
|
||||
|
||||
@State private var profileIdMakingReady: UUID?
|
||||
|
||||
@State private var presentedHeader: Profile.Header?
|
||||
|
||||
private var isLocationPresented: Binding<Bool> {
|
||||
.init {
|
||||
presentedHeader != nil
|
||||
} set: {
|
||||
if !$0 {
|
||||
presentedHeader = nil
|
||||
addMoveToPendingProfile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(pendingProfile: ObservableProfile, pendingShortcut: Binding<INShortcut?>) {
|
||||
profileManager = .shared
|
||||
providerManager = .shared
|
||||
self.pendingProfile = pendingProfile
|
||||
_pendingShortcut = pendingShortcut
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
debugChanges()
|
||||
return ZStack {
|
||||
let headers = profileManager.headers
|
||||
List {
|
||||
Section {
|
||||
ForEach(headers.sorted(), content: profileRow)
|
||||
.disabled(profileIdMakingReady != nil)
|
||||
}
|
||||
}
|
||||
ForEach(Array(headers.filter {
|
||||
$0.providerName != nil
|
||||
}), content: providerLocationLink)
|
||||
}.navigationTitle(L10n.Shortcuts.Add.Items.Connect.caption)
|
||||
}
|
||||
|
||||
private func profileRow(_ header: Profile.Header) -> some View {
|
||||
Button {
|
||||
if let _ = header.providerName {
|
||||
Task {
|
||||
profileIdMakingReady = header.id
|
||||
await loadAndSelectProfile(withHeader: header)
|
||||
profileIdMakingReady = nil
|
||||
}
|
||||
} else {
|
||||
addConnect(header)
|
||||
}
|
||||
} label: {
|
||||
ProfileHeaderRow(header: header)
|
||||
}.withTrailingProgress(when: profileIdMakingReady == header.id)
|
||||
}
|
||||
|
||||
private func providerLocationLink(_ header: Profile.Header) -> some View {
|
||||
NavigationLink("", tag: header, selection: $presentedHeader) {
|
||||
ProviderLocationView(
|
||||
currentProfile: pendingProfile,
|
||||
isEditable: false,
|
||||
isPresented: isLocationPresented
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAndSelectProfile(withHeader header: Profile.Header) async {
|
||||
do {
|
||||
let result = try profileManager.loadProfile(withId: header.id)
|
||||
if !result.isReady {
|
||||
try await profileManager.makeProfileReady(result.profile)
|
||||
}
|
||||
pendingProfile.value = result.profile
|
||||
presentedHeader = header
|
||||
} catch {
|
||||
pp_log.error("Unable to select profile: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func addConnect(_ header: Profile.Header) {
|
||||
pendingShortcut = INShortcut(intent: IntentDispatcher.intentConnect(
|
||||
header: header
|
||||
))
|
||||
}
|
||||
|
||||
private func addMoveToPendingProfile() {
|
||||
let header = pendingProfile.value.header
|
||||
guard let server = pendingProfile.value.providerServer(providerManager) else {
|
||||
return
|
||||
}
|
||||
pendingShortcut = INShortcut(intent: IntentDispatcher.intentMoveTo(
|
||||
header: header,
|
||||
providerFullName: server.providerMetadata.fullName,
|
||||
server: server
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,6 +44,8 @@ struct ShortcutsView: View {
|
|||
}
|
||||
|
||||
@ObservedObject private var intentsManager: IntentsManager
|
||||
|
||||
private let target: Profile
|
||||
|
||||
@State private var modalType: ModalType?
|
||||
|
||||
|
@ -51,8 +53,9 @@ struct ShortcutsView: View {
|
|||
|
||||
@State private var pendingShortcut: INShortcut?
|
||||
|
||||
init() {
|
||||
init(target: Profile) {
|
||||
intentsManager = .shared
|
||||
self.target = target
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
@ -75,10 +78,16 @@ struct ShortcutsView: View {
|
|||
Section(
|
||||
header: Text(L10n.Shortcuts.Edit.Sections.All.header)
|
||||
) {
|
||||
ForEach(intentsManager.shortcuts.values.sorted(), content: rowView)
|
||||
ForEach(relevantShortcuts, content: rowView)
|
||||
}
|
||||
}
|
||||
|
||||
private var relevantShortcuts: [Shortcut] {
|
||||
intentsManager.shortcuts.values.filter {
|
||||
$0.isRelevant(to: target)
|
||||
}.sorted()
|
||||
}
|
||||
|
||||
private var addSection: some View {
|
||||
Section(
|
||||
// FIXME: l10n, string id
|
||||
|
@ -86,6 +95,7 @@ struct ShortcutsView: View {
|
|||
) {
|
||||
NavigationLink(isActive: $isNavigationPresented) {
|
||||
AddView(
|
||||
target: target,
|
||||
pendingShortcut: delegatingPendingShortcut
|
||||
)
|
||||
} label: {
|
||||
|
@ -141,3 +151,18 @@ struct ShortcutsView: View {
|
|||
modalType = .add(shortcut: shortcut)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Shortcut {
|
||||
func isRelevant(to profile: Profile) -> Bool {
|
||||
guard let intent = native.shortcut.intent else {
|
||||
return true
|
||||
}
|
||||
if let connectIntent = intent as? ConnectVPNIntent {
|
||||
return connectIntent.profileId == profile.id.uuidString
|
||||
}
|
||||
if let moveToIntent = intent as? MoveToLocationIntent {
|
||||
return moveToIntent.profileId == profile.id.uuidString
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue