diff --git a/Passepartout/Library/Sources/AppUI/Views/Extensions/EnvironmentValues+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Extensions/EnvironmentValues+Extensions.swift
new file mode 100644
index 00000000..3ccc3dbb
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Extensions/EnvironmentValues+Extensions.swift
@@ -0,0 +1,41 @@
+//
+// EnvironmentValues+Extensions.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 10/23/24.
+// Copyright (c) 2024 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
+
+extension EnvironmentValues {
+ var navigationPath: Binding {
+ get {
+ self[NavigationPathKey.self]
+ }
+ set {
+ self[NavigationPathKey.self] = newValue
+ }
+ }
+}
+
+private struct NavigationPathKey: EnvironmentKey {
+ static let defaultValue: Binding = .constant(NavigationPath())
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Extensions/ModuleDraftEditing.swift b/Passepartout/Library/Sources/AppUI/Views/Extensions/ModuleDraftEditing.swift
new file mode 100644
index 00000000..c50b5365
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Extensions/ModuleDraftEditing.swift
@@ -0,0 +1,43 @@
+//
+// ModuleDraftEditing.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 10/23/24.
+// Copyright (c) 2024 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 PassepartoutKit
+import SwiftUI
+
+protocol ModuleDraftEditing {
+ associatedtype Draft: ModuleBuilder
+
+ var editor: ProfileEditor { get }
+
+ var module: Draft { get }
+}
+
+extension ModuleDraftEditing {
+
+ @MainActor
+ var draft: Binding {
+ editor[module]
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift
index 7fcbfd82..561f88cb 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift
@@ -28,21 +28,15 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct DNSView: View {
+struct DNSView: View, ModuleDraftEditing {
@EnvironmentObject
private var theme: Theme
@ObservedObject
- private var editor: ProfileEditor
+ var editor: ProfileEditor
- @Binding
- private var draft: DNSModule.Builder
-
- init(editor: ProfileEditor, module: DNSModule.Builder) {
- self.editor = editor
- _draft = editor.binding(forModule: module)
- }
+ let module: DNSModule.Builder
var body: some View {
debugChanges()
@@ -56,7 +50,7 @@ struct DNSView: View {
.labelsHidden()
}
.themeManualInput()
- .moduleView(editor: editor, draft: draft)
+ .moduleView(editor: editor, draft: draft.wrappedValue)
}
}
@@ -69,21 +63,21 @@ private extension DNSView {
var protocolSection: some View {
Section {
- Picker(Strings.Global.protocol, selection: $draft.protocolType) {
+ Picker(Strings.Global.protocol, selection: draft.protocolType) {
ForEach(Self.allProtocols, id: \.self) {
Text($0.localizedDescription)
}
}
- switch draft.protocolType {
+ switch draft.wrappedValue.protocolType {
case .cleartext:
EmptyView()
case .https:
- ThemeTextField(Strings.Unlocalized.url, text: $draft.dohURL, placeholder: Strings.Unlocalized.Placeholders.dohURL)
+ ThemeTextField(Strings.Unlocalized.url, text: draft.dohURL, placeholder: Strings.Unlocalized.Placeholders.dohURL)
.labelsHidden()
case .tls:
- ThemeTextField(Strings.Global.hostname, text: $draft.dotHostname, placeholder: Strings.Unlocalized.Placeholders.dotHostname)
+ ThemeTextField(Strings.Global.hostname, text: draft.dotHostname, placeholder: Strings.Unlocalized.Placeholders.dotHostname)
.labelsHidden()
}
}
@@ -91,7 +85,7 @@ private extension DNSView {
var domainSection: some View {
Group {
- ThemeTextField(Strings.Global.domain, text: $draft.domainName ?? "", placeholder: Strings.Unlocalized.Placeholders.hostname)
+ ThemeTextField(Strings.Global.domain, text: draft.domainName ?? "", placeholder: Strings.Unlocalized.Placeholders.hostname)
}
.themeSection(header: Strings.Global.domain)
}
@@ -100,7 +94,7 @@ private extension DNSView {
theme.listSection(
Strings.Entities.Dns.servers,
addTitle: Strings.Modules.Dns.Servers.add,
- originalItems: $draft.servers,
+ originalItems: draft.servers,
itemLabel: {
if $0 {
Text($1.wrappedValue)
@@ -115,7 +109,7 @@ private extension DNSView {
theme.listSection(
Strings.Entities.Dns.searchDomains,
addTitle: Strings.Modules.Dns.SearchDomains.add,
- originalItems: $draft.searchDomains ?? [],
+ originalItems: draft.searchDomains ?? [],
itemLabel: {
if $0 {
Text($1.wrappedValue)
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OpenVPNModule+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OpenVPNModule+Extensions.swift
index a2de730d..80fa95f0 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OpenVPNModule+Extensions.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OpenVPNModule+Extensions.swift
@@ -34,7 +34,7 @@ extension OpenVPNModule.Builder: ModuleViewProviding {
extension OpenVPNModule.Builder: InteractiveViewProviding {
func interactiveView(with editor: ProfileEditor) -> some View {
- let draft = editor.binding(forModule: self)
+ let draft = editor[self]
return OpenVPNView.CredentialsView(
isInteractive: draft.isInteractive,
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift
index 63ac3e49..594af411 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift
@@ -27,21 +27,15 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct HTTPProxyView: View {
+struct HTTPProxyView: View, ModuleDraftEditing {
@EnvironmentObject
private var theme: Theme
@ObservedObject
- private var editor: ProfileEditor
+ var editor: ProfileEditor
- @Binding
- private var draft: HTTPProxyModule.Builder
-
- init(editor: ProfileEditor, module: HTTPProxyModule.Builder) {
- self.editor = editor
- _draft = editor.binding(forModule: module)
- }
+ let module: HTTPProxyModule.Builder
var body: some View {
Group {
@@ -52,30 +46,30 @@ struct HTTPProxyView: View {
}
.labelsHidden()
.themeManualInput()
- .moduleView(editor: editor, draft: draft)
+ .moduleView(editor: editor, draft: draft.wrappedValue)
}
}
private extension HTTPProxyView {
var httpSection: some View {
Group {
- ThemeTextField(Strings.Global.address, text: $draft.address, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
- ThemeTextField(Strings.Global.port, text: $draft.port.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
+ ThemeTextField(Strings.Global.address, text: draft.address, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
+ ThemeTextField(Strings.Global.port, text: draft.port.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
}
.themeSection(header: Strings.Unlocalized.http)
}
var httpsSection: some View {
Group {
- ThemeTextField(Strings.Global.address, text: $draft.secureAddress, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
- ThemeTextField(Strings.Global.port, text: $draft.securePort.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
+ ThemeTextField(Strings.Global.address, text: draft.secureAddress, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
+ ThemeTextField(Strings.Global.port, text: draft.securePort.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
}
.themeSection(header: Strings.Unlocalized.https)
}
var pacSection: some View {
Group {
- ThemeTextField(Strings.Unlocalized.url, text: $draft.pacURLString, placeholder: Strings.Unlocalized.Placeholders.pacURL)
+ ThemeTextField(Strings.Unlocalized.url, text: draft.pacURLString, placeholder: Strings.Unlocalized.Placeholders.pacURL)
}
.themeSection(header: Strings.Unlocalized.pac)
}
@@ -85,7 +79,7 @@ private extension HTTPProxyView {
theme.listSection(
Strings.Entities.HttpProxy.bypassDomains,
addTitle: Strings.Modules.HttpProxy.BypassDomains.add,
- originalItems: $draft.bypassDomains,
+ originalItems: draft.bypassDomains,
itemLabel: {
if $0 {
Text($1.wrappedValue)
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift
index 937e6910..4ffbcf6a 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift
@@ -27,29 +27,23 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct IPView: View {
+struct IPView: View, ModuleDraftEditing {
@ObservedObject
- private var editor: ProfileEditor
+ var editor: ProfileEditor
- @Binding
- private var draft: IPModule.Builder
+ let module: IPModule.Builder
@State
private var routePresentation: RoutePresentation?
- init(editor: ProfileEditor, module: IPModule.Builder) {
- self.editor = editor
- _draft = editor.binding(forModule: module)
- }
-
var body: some View {
Group {
ipSections(for: .v4)
ipSections(for: .v6)
interfaceSection
}
- .moduleView(editor: editor, draft: draft)
+ .moduleView(editor: editor, draft: draft.wrappedValue)
.themeModal(item: $routePresentation, content: routeModal)
}
}
@@ -137,9 +131,9 @@ private extension IPView {
ThemeTextField(
Strings.Unlocalized.mtu,
text: Binding {
- draft.mtu?.description ?? ""
+ draft.wrappedValue.mtu?.description ?? ""
} set: {
- draft.mtu = Int($0)
+ draft.wrappedValue.mtu = Int($0)
},
placeholder: Strings.Unlocalized.Placeholders.mtu
)
@@ -153,16 +147,16 @@ private extension IPView {
switch family {
case .v4:
return Binding {
- draft.ipv4 ?? IPSettings(subnet: nil)
+ draft.wrappedValue.ipv4 ?? IPSettings(subnet: nil)
} set: {
- draft.ipv4 = $0
+ draft.wrappedValue.ipv4 = $0
}
case .v6:
return Binding {
- draft.ipv6 ?? IPSettings(subnet: nil)
+ draft.wrappedValue.ipv6 ?? IPSettings(subnet: nil)
} set: {
- draft.ipv6 = $0
+ draft.wrappedValue.ipv6 = $0
}
}
}
@@ -180,31 +174,31 @@ private extension IPView {
case .included(let family):
switch family {
case .v4:
- if draft.ipv4 == nil {
- draft.ipv4 = IPSettings(subnet: nil)
+ if draft.wrappedValue.ipv4 == nil {
+ draft.wrappedValue.ipv4 = IPSettings(subnet: nil)
}
- draft.ipv4?.include(route)
+ draft.wrappedValue.ipv4?.include(route)
case .v6:
- if draft.ipv6 == nil {
- draft.ipv6 = IPSettings(subnet: nil)
+ if draft.wrappedValue.ipv6 == nil {
+ draft.wrappedValue.ipv6 = IPSettings(subnet: nil)
}
- draft.ipv6?.include(route)
+ draft.wrappedValue.ipv6?.include(route)
}
case .excluded(let family):
switch family {
case .v4:
- if draft.ipv4 == nil {
- draft.ipv4 = IPSettings(subnet: nil)
+ if draft.wrappedValue.ipv4 == nil {
+ draft.wrappedValue.ipv4 = IPSettings(subnet: nil)
}
- draft.ipv4?.exclude(route)
+ draft.wrappedValue.ipv4?.exclude(route)
case .v6:
- if draft.ipv6 == nil {
- draft.ipv6 = IPSettings(subnet: nil)
+ if draft.wrappedValue.ipv6 == nil {
+ draft.wrappedValue.ipv6 = IPSettings(subnet: nil)
}
- draft.ipv6?.exclude(route)
+ draft.wrappedValue.ipv6?.exclude(route)
}
}
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift
index 79a29f1b..6ecdbb6f 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift
@@ -27,7 +27,7 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct OnDemandView: View {
+struct OnDemandView: View, ModuleDraftEditing {
@EnvironmentObject
private var theme: Theme
@@ -36,13 +36,12 @@ struct OnDemandView: View {
private var iapManager: IAPManager
@ObservedObject
- private var editor: ProfileEditor
+ var editor: ProfileEditor
+
+ let module: OnDemandModule.Builder
private let wifi: Wifi
- @Binding
- private var draft: OnDemandModule.Builder
-
@State
private var paywallReason: PaywallReason?
@@ -52,8 +51,8 @@ struct OnDemandView: View {
observer: WifiObserver? = nil
) {
self.editor = editor
+ self.module = module
wifi = Wifi(observer: observer ?? CoreLocationWifiObserver())
- _draft = editor.binding(forModule: module)
}
var body: some View {
@@ -61,7 +60,7 @@ struct OnDemandView: View {
enabledSection
restrictedArea
}
- .moduleView(editor: editor, draft: draft)
+ .moduleView(editor: editor, draft: draft.wrappedValue)
.modifier(PaywallModifier(reason: $paywallReason))
}
}
@@ -75,7 +74,7 @@ private extension OnDemandView {
var enabledSection: some View {
Section {
- Toggle(Strings.Global.enabled, isOn: $draft.isEnabled)
+ Toggle(Strings.Global.enabled, isOn: draft.isEnabled)
}
}
@@ -91,9 +90,9 @@ private extension OnDemandView {
EmptyView()
default:
- if draft.isEnabled {
+ if draft.wrappedValue.isEnabled {
policySection
- if draft.policy != .any {
+ if draft.wrappedValue.policy != .any {
networkSection
wifiSection
}
@@ -102,7 +101,7 @@ private extension OnDemandView {
}
var policySection: some View {
- Picker(Strings.Modules.OnDemand.policy, selection: $draft.policy) {
+ Picker(Strings.Modules.OnDemand.policy, selection: draft.policy) {
ForEach(Self.allPolicies, id: \.self) {
Text($0.localizedDescription)
}
@@ -111,16 +110,16 @@ private extension OnDemandView {
}
var policyFooterDescription: String {
- guard draft.isEnabled else {
+ guard draft.wrappedValue.isEnabled else {
return "" // better animation than removing footer completely
}
let suffix: String
- switch draft.policy {
+ switch draft.wrappedValue.policy {
case .any:
suffix = Strings.Modules.OnDemand.Policy.Footer.any
case .including, .excluding:
- if draft.policy == .including {
+ if draft.wrappedValue.policy == .including {
suffix = Strings.Modules.OnDemand.Policy.Footer.including
} else {
suffix = Strings.Modules.OnDemand.Policy.Footer.excluding
@@ -132,9 +131,9 @@ private extension OnDemandView {
var networkSection: some View {
Group {
if Utils.hasCellularData() {
- Toggle(Strings.Modules.OnDemand.mobile, isOn: $draft.withMobileNetwork)
+ Toggle(Strings.Modules.OnDemand.mobile, isOn: draft.withMobileNetwork)
} else if Utils.hasEthernet() {
- Toggle(Strings.Modules.OnDemand.ethernet, isOn: $draft.withEthernetNetwork)
+ Toggle(Strings.Modules.OnDemand.ethernet, isOn: draft.withEthernetNetwork)
}
}
.themeSection(header: Strings.Global.networks)
@@ -173,53 +172,53 @@ private extension OnDemandView {
private extension OnDemandView {
var allSSIDs: Binding<[String]> {
.init {
- Array(draft.withSSIDs.keys)
+ Array(draft.wrappedValue.withSSIDs.keys)
} set: { newValue in
- draft.withSSIDs.forEach {
+ draft.wrappedValue.withSSIDs.forEach {
guard newValue.contains($0.key) else {
- draft.withSSIDs.removeValue(forKey: $0.key)
+ draft.wrappedValue.withSSIDs.removeValue(forKey: $0.key)
return
}
}
newValue.forEach {
- guard draft.withSSIDs[$0] == nil else {
+ guard draft.wrappedValue.withSSIDs[$0] == nil else {
return
}
- draft.withSSIDs[$0] = false
+ draft.wrappedValue.withSSIDs[$0] = false
}
}
}
var onSSIDs: Binding> {
.init {
- Set(draft.withSSIDs.filter {
+ Set(draft.wrappedValue.withSSIDs.filter {
$0.value
}.map(\.key))
} set: { newValue in
- draft.withSSIDs.forEach {
+ draft.wrappedValue.withSSIDs.forEach {
guard newValue.contains($0.key) else {
- if draft.withSSIDs[$0.key] != nil {
- draft.withSSIDs[$0.key] = false
+ if draft.wrappedValue.withSSIDs[$0.key] != nil {
+ draft.wrappedValue.withSSIDs[$0.key] = false
} else {
- draft.withSSIDs.removeValue(forKey: $0.key)
+ draft.wrappedValue.withSSIDs.removeValue(forKey: $0.key)
}
return
}
}
newValue.forEach {
- guard !(draft.withSSIDs[$0] ?? false) else {
+ guard !(draft.wrappedValue.withSSIDs[$0] ?? false) else {
return
}
- draft.withSSIDs[$0] = true
+ draft.wrappedValue.withSSIDs[$0] = true
}
}
}
func isSSIDOn(_ ssid: String) -> Binding {
.init {
- draft.withSSIDs[ssid] ?? false
+ draft.wrappedValue.withSSIDs[ssid] ?? false
} set: {
- draft.withSSIDs[ssid] = $0
+ draft.wrappedValue.withSSIDs[ssid] = $0
}
}
}
@@ -228,7 +227,7 @@ private extension OnDemandView {
func requestSSID(_ text: Binding) {
Task { @MainActor in
let ssid = try await wifi.currentSSID()
- if !draft.withSSIDs.keys.contains(ssid) {
+ if !draft.wrappedValue.withSSIDs.keys.contains(ssid) {
text.wrappedValue = ssid
}
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift
index 5048e11c..13bf1b0f 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift
@@ -26,35 +26,36 @@
import PassepartoutKit
import SwiftUI
-struct OpenVPNView: View {
+struct OpenVPNView: View, ModuleDraftEditing {
+
+ @Environment(\.navigationPath)
+ private var path
@ObservedObject
- private var editor: ProfileEditor
+ var editor: ProfileEditor
+
+ let module: OpenVPNModule.Builder
private let isServerPushed: Bool
- @Binding
- private var draft: OpenVPNModule.Builder
-
init(serverConfiguration: OpenVPN.Configuration) {
let module = OpenVPNModule.Builder(configurationBuilder: serverConfiguration.builder())
let editor = ProfileEditor(modules: [module])
self.editor = editor
- _draft = .constant(module)
+ self.module = module
isServerPushed = true
}
init(editor: ProfileEditor, module: OpenVPNModule.Builder) {
self.editor = editor
- _draft = editor.binding(forModule: module)
+ self.module = module
isServerPushed = false
}
var body: some View {
contentView
- .themeAnimation(on: draft, category: .modules)
- .moduleView(editor: editor, draft: draft, withName: !isServerPushed)
+ .moduleView(editor: editor, draft: draft.wrappedValue, withName: !isServerPushed)
.navigationDestination(for: Subroute.self, destination: destination)
}
}
@@ -63,7 +64,7 @@ struct OpenVPNView: View {
private extension OpenVPNView {
var configuration: OpenVPN.Configuration.Builder {
- draft.configurationBuilder ?? .init(withFallbacks: true)
+ draft.wrappedValue.configurationBuilder ?? .init(withFallbacks: true)
}
@ViewBuilder
@@ -80,7 +81,8 @@ private extension OpenVPNView {
VPNProviderContentModifier(
providerId: providerId,
selectedEntity: providerEntity,
- isRequired: draft.configurationBuilder == nil,
+ isRequired: draft.wrappedValue.configurationBuilder == nil,
+ entityDestination: Subroute.providerServer,
providerRows: {
moduleGroup(for: providerAccountRows)
}
@@ -88,11 +90,11 @@ private extension OpenVPNView {
}
var providerId: Binding {
- editor.binding(forProviderOf: draft.id)
+ editor.binding(forProviderOf: module.id)
}
var providerEntity: Binding?> {
- editor.binding(forProviderEntityOf: draft.id)
+ editor.binding(forProviderEntityOf: module.id)
}
var providerAccountRows: [ModuleRow]? {
@@ -101,6 +103,11 @@ private extension OpenVPNView {
}
private extension OpenVPNView {
+ func onSelectServer(server: VPNServer, preset: VPNPreset) {
+ providerEntity.wrappedValue = VPNEntity(server: server, preset: preset)
+ path.wrappedValue.removeLast()
+ }
+
func importConfiguration(from url: URL) {
// TODO: #657, import draft from external URL
}
@@ -110,16 +117,27 @@ private extension OpenVPNView {
private extension OpenVPNView {
enum Subroute: Hashable {
+ case providerServer
+
case credentials
}
@ViewBuilder
func destination(for route: Subroute) -> some View {
switch route {
+ case .providerServer:
+ providerId.wrappedValue.map {
+ VPNProviderServerView(
+ providerId: $0,
+ configurationType: OpenVPN.Configuration.self,
+ onSelect: onSelectServer
+ )
+ }
+
case .credentials:
CredentialsView(
- isInteractive: $draft.isInteractive,
- credentials: $draft.credentials
+ isInteractive: draft.isInteractive,
+ credentials: draft.credentials
)
}
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift
index 3c71e18d..0d7cf516 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift
@@ -28,41 +28,16 @@ import PassepartoutKit
import PassepartoutWireGuardGo
import SwiftUI
-struct WireGuardView: View {
- private enum Subroute: Hashable {
- case providerServer(id: ProviderID)
- }
+struct WireGuardView: View, ModuleDraftEditing {
@ObservedObject
- private var editor: ProfileEditor
+ var editor: ProfileEditor
- @Binding
- private var draft: WireGuardModule.Builder
-
-// @Binding
-// private var providerId: ProviderID?
-//
-// @State
-// private var providerServer: VPNServer?
-
- init(editor: ProfileEditor, module: WireGuardModule.Builder) {
- self.editor = editor
- _draft = editor.binding(forModule: module)
-// _providerId = editor.binding(forProviderOf: module.id)
- }
+ let module: WireGuardModule.Builder
var body: some View {
contentView
-// .modifier(providerModifier)
- .moduleView(editor: editor, draft: draft)
-// .navigationDestination(for: Subroute.self) {
-// switch $0 {
-// case .providerServer(let id):
-// VPNProviderServerView(providerId: id) {
-// providerServer = $1
-// }
-// }
-// }
+ .moduleView(editor: editor, draft: draft.wrappedValue)
}
}
@@ -70,7 +45,7 @@ struct WireGuardView: View {
private extension WireGuardView {
var configuration: WireGuard.Configuration.Builder {
- draft.configurationBuilder ?? .default
+ draft.wrappedValue.configurationBuilder ?? .default
}
@ViewBuilder
@@ -81,17 +56,6 @@ private extension WireGuardView {
moduleSection(for: peersRows(for: peer), header: Strings.Modules.Wireguard.peer(index + 1))
}
}
-
-// var providerModifier: some ViewModifier {
-// ProviderPanelModifier(
-// providerId: $providerId,
-// selectedServer: $providerServer,
-// configurationType: WireGuard.Configuration.self,
-// serverRoute: {
-// Subroute.providerServer(id: $0)
-// }
-// )
-// }
}
// MARK: - Subviews
diff --git a/Passepartout/Library/Sources/AppUI/Views/Profile/iOS/ProfileEditView+iOS.swift b/Passepartout/Library/Sources/AppUI/Views/Profile/iOS/ProfileEditView+iOS.swift
index c390c0e5..a6c15b37 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Profile/iOS/ProfileEditView+iOS.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Profile/iOS/ProfileEditView+iOS.swift
@@ -71,6 +71,7 @@ struct ProfileEditView: View, Routable {
.navigationTitle(Strings.Global.profile)
.navigationBarBackButtonHidden(true)
.navigationDestination(for: NavigationRoute.self, destination: pushDestination)
+ .environment(\.navigationPath, $path)
}
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Profile/macOS/ProfileSplitView+macOS.swift b/Passepartout/Library/Sources/AppUI/Views/Profile/macOS/ProfileSplitView+macOS.swift
index 2e9eeca0..793f75d5 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Profile/macOS/ProfileSplitView+macOS.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Profile/macOS/ProfileSplitView+macOS.swift
@@ -36,6 +36,9 @@ struct ProfileSplitView: View, Routable {
var flow: ProfileCoordinator.Flow?
+ @State
+ private var detailPath = NavigationPath()
+
@State
private var selectedModuleId: UUID? = ModuleListView.generalModuleId
@@ -52,7 +55,7 @@ struct ProfileSplitView: View, Routable {
flow: flow
)
} detail: {
- NavigationStack {
+ NavigationStack(path: $detailPath) {
switch selectedModuleId {
case ModuleListView.generalModuleId:
detailView(for: .general)
@@ -62,6 +65,7 @@ struct ProfileSplitView: View, Routable {
}
}
.toolbar(content: toolbarContent)
+ .environment(\.navigationPath, $detailPath)
}
}
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ProfileEditor+UI.swift b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ProfileEditor+UI.swift
index a0bbbb62..cc0da83f 100644
--- a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ProfileEditor+UI.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ProfileEditor+UI.swift
@@ -51,7 +51,7 @@ extension ProfileEditor {
}
}
- func binding(forModule module: T) -> Binding where T: ModuleBuilder {
+ subscript(module: T) -> Binding where T: ModuleBuilder {
Binding { [weak self] in
guard let foundModule = self?.module(withId: module.id) else {
fatalError("Module not found in editor: \(module.id)")
diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift
index 4d3244f6..2373ae24 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift
@@ -28,8 +28,7 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-@MainActor
-struct VPNProviderContentModifier: ViewModifier where Configuration: ProviderConfigurationIdentifiable & Codable, ProviderRows: View {
+struct VPNProviderContentModifier: ViewModifier where Configuration: ProviderConfigurationIdentifiable & Codable, Destination: Hashable, ProviderRows: View {
var apis: [APIMapper] = API.shared
@@ -41,6 +40,8 @@ struct VPNProviderContentModifier: ViewModifier whe
let isRequired: Bool
+ let entityDestination: Destination
+
@ViewBuilder
let providerRows: ProviderRows
@@ -53,7 +54,7 @@ struct VPNProviderContentModifier: ViewModifier whe
entityType: VPNEntity.self,
isRequired: isRequired,
providerRows: {
- providerServerRow
+ providerEntityRow
providerRows
},
onSelectProvider: onSelectProvider
@@ -62,18 +63,8 @@ struct VPNProviderContentModifier: ViewModifier whe
}
private extension VPNProviderContentModifier {
- var providerServerRow: some View {
- NavigationLink {
- providerId.map {
- VPNProviderServerView(
- apis: apis,
- providerId: $0,
- configurationType: Configuration.self,
- selectedEntity: selectedEntity,
- onSelect: onSelectServer
- )
- }
- } label: {
+ var providerEntityRow: some View {
+ NavigationLink(value: entityDestination) {
HStack {
Text(Strings.Global.server)
if let selectedEntity {
@@ -92,25 +83,29 @@ private extension VPNProviderContentModifier {
selectedEntity = nil
}
}
-
- func onSelectServer(server: VPNServer, preset: VPNPreset) {
- selectedEntity = VPNEntity(server: server, preset: preset)
- }
}
// MARK: - Preview
#Preview {
- List {
- EmptyView()
- .modifier(VPNProviderContentModifier(
- providerId: .constant(.hideme),
- selectedEntity: .constant(nil as VPNEntity?),
- isRequired: false,
- providerRows: {
- Text("Other")
- }
- ))
+ NavigationStack {
+ List {
+ EmptyView()
+ .modifier(VPNProviderContentModifier(
+ apis: [API.bundled],
+ providerId: .constant(.hideme),
+ selectedEntity: .constant(nil as VPNEntity?),
+ isRequired: false,
+ entityDestination: "Destination",
+ providerRows: {
+ Text("Other")
+ }
+ ))
+ }
+ .navigationTitle("Preview")
+ .navigationDestination(for: String.self) {
+ Text($0)
+ }
}
.withMockEnvironment()
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift
index 0790a153..1ccbefc0 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift
@@ -33,10 +33,7 @@ struct VPNProviderServerView: View where Configuration: ProviderC
@EnvironmentObject
private var providerManager: ProviderManager
- @Environment(\.dismiss)
- private var dismiss
-
- let apis: [APIMapper]
+ var apis: [APIMapper] = API.shared
let providerId: ProviderID
@@ -112,14 +109,12 @@ extension VPNProviderServerView {
return
}
onSelect(server, preset)
- dismiss()
}
}
// MARK: - Preview
#Preview {
-
NavigationStack {
VPNProviderServerView(
apis: [API.bundled],