diff --git a/Passepartout/Library/Sources/AppDataProfiles/AppData+Profiles.swift b/Passepartout/Library/Sources/AppDataProfiles/AppData+Profiles.swift
index 4284eadc..3977f8e8 100644
--- a/Passepartout/Library/Sources/AppDataProfiles/AppData+Profiles.swift
+++ b/Passepartout/Library/Sources/AppDataProfiles/AppData+Profiles.swift
@@ -28,6 +28,8 @@ import CoreData
import Foundation
extension AppData {
+
+ @MainActor
public static let cdProfilesModel: NSManagedObjectModel = {
guard let model: NSManagedObjectModel = .mergedModel(from: [.module]) else {
fatalError("Unable to build Core Data model (Profiles v3)")
diff --git a/Passepartout/Library/Sources/AppUI/L10n/AppError+L10n.swift b/Passepartout/Library/Sources/AppUI/L10n/AppError+L10n.swift
index 33c9e246..d5c3e78e 100644
--- a/Passepartout/Library/Sources/AppUI/L10n/AppError+L10n.swift
+++ b/Passepartout/Library/Sources/AppUI/L10n/AppError+L10n.swift
@@ -56,6 +56,9 @@ extension PassepartoutError: LocalizedError {
return Strings.Errors.App.Passepartout.connectionModuleRequired
case .corruptProviderModule:
+ if let ppReason = reason as? PassepartoutError, ppReason.code == .notFound {
+ return Strings.Errors.App.missingProviderEntity
+ }
return Strings.Errors.App.Passepartout.corruptProviderModule(reason?.localizedDescription ?? "")
case .incompatibleModules:
diff --git a/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift b/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift
index f4476cbe..6b6480f0 100644
--- a/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift
+++ b/Passepartout/Library/Sources/AppUI/L10n/SwiftGen+Strings.swift
@@ -108,6 +108,8 @@ public enum Strings {
public static func malformedModule(_ p1: Any, _ p2: Any) -> String {
return Strings.tr("Localizable", "errors.app.malformed_module", String(describing: p1), String(describing: p2), fallback: "Module %@ is malformed. %@")
}
+ /// No provider server selected.
+ public static let missingProviderEntity = Strings.tr("Localizable", "errors.app.missing_provider_entity", fallback: "No provider server selected.")
public enum Passepartout {
/// Routing module can only be enabled together with a connection.
public static let connectionModuleRequired = Strings.tr("Localizable", "errors.app.passepartout.connection_module_required", fallback: "Routing module can only be enabled together with a connection.")
diff --git a/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings b/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings
index 3b3b0d00..42b9e822 100644
--- a/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings
+++ b/Passepartout/Library/Sources/AppUI/Resources/en.lproj/Localizable.strings
@@ -248,6 +248,7 @@
"errors.app.empty_profile_name" = "Profile name is empty.";
"errors.app.malformed_module" = "Module %@ is malformed. %@";
+"errors.app.missing_provider_entity" = "No provider server selected.";
"errors.app.default" = "Unable to complete operation.";
"errors.app.passepartout.connection_module_required" = "Routing module can only be enabled together with a connection.";
"errors.app.passepartout.corrupt_provider_module" = "Unable to connect to provider server (reason=%@).";
diff --git a/Passepartout/Library/Sources/AppUI/Views/App/AppInlineCoordinator.swift b/Passepartout/Library/Sources/AppUI/Views/App/AppInlineCoordinator.swift
index 7b4cb939..78c16d96 100644
--- a/Passepartout/Library/Sources/AppUI/Views/App/AppInlineCoordinator.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/App/AppInlineCoordinator.swift
@@ -85,12 +85,14 @@ private extension AppInlineCoordinator {
tunnel: tunnel,
registry: registry,
isImporting: $isImporting,
- onEdit: {
- guard let profile = profileManager.profile(withId: $0.id) else {
- return
+ flow: .init(
+ onEditProfile: {
+ guard let profile = profileManager.profile(withId: $0.id) else {
+ return
+ }
+ enterDetail(of: profile)
}
- enterDetail(of: profile)
- }
+ )
)
}
@@ -143,7 +145,10 @@ private extension AppInlineCoordinator {
}
func enterDetail(of profile: Profile) {
- profileEditor.editProfile(profile, isShared: profileManager.isRemotelyShared(profileWithId: profile.id))
+ profileEditor.editProfile(
+ profile,
+ isShared: profileManager.isRemotelyShared(profileWithId: profile.id)
+ )
push(.editProfile)
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/App/AppModalCoordinator.swift b/Passepartout/Library/Sources/AppUI/Views/App/AppModalCoordinator.swift
index 27e8da34..f86519db 100644
--- a/Passepartout/Library/Sources/AppUI/Views/App/AppModalCoordinator.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/App/AppModalCoordinator.swift
@@ -81,12 +81,14 @@ extension AppModalCoordinator {
tunnel: tunnel,
registry: registry,
isImporting: $isImporting,
- onEdit: {
- guard let profile = profileManager.profile(withId: $0.id) else {
- return
+ flow: .init(
+ onEditProfile: {
+ guard let profile = profileManager.profile(withId: $0.id) else {
+ return
+ }
+ enterDetail(of: profile)
}
- enterDetail(of: profile)
- }
+ )
)
}
@@ -135,7 +137,10 @@ extension AppModalCoordinator {
func enterDetail(of profile: Profile) {
profilePath = NavigationPath()
- profileEditor.editProfile(profile, isShared: profileManager.isRemotelyShared(profileWithId: profile.id))
+ profileEditor.editProfile(
+ profile,
+ isShared: profileManager.isRemotelyShared(profileWithId: profile.id)
+ )
modalRoute = .editProfile
}
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/App/ProfileContainerView.swift b/Passepartout/Library/Sources/AppUI/Views/App/ProfileContainerView.swift
index ac7a2643..561b31b5 100644
--- a/Passepartout/Library/Sources/AppUI/Views/App/ProfileContainerView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/App/ProfileContainerView.swift
@@ -28,7 +28,11 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct ProfileContainerView: View, TunnelInstallationProviding {
+struct ProfileContainerView: View, Routable, TunnelInstallationProviding {
+ struct Flow {
+ let onEditProfile: (ProfileHeader) -> Void
+ }
+
let layout: ProfilesLayout
let profileManager: ProfileManager
@@ -40,7 +44,7 @@ struct ProfileContainerView: View, TunnelInstallationProviding {
@Binding
var isImporting: Bool
- let onEdit: (ProfileHeader) -> Void
+ var flow: Flow?
@StateObject
private var interactiveManager = InteractiveManager()
@@ -77,7 +81,7 @@ private extension ProfileContainerView {
tunnel: tunnel,
interactiveManager: interactiveManager,
errorHandler: errorHandler,
- onEdit: onEdit
+ flow: flow
)
case .grid:
@@ -86,7 +90,7 @@ private extension ProfileContainerView {
tunnel: tunnel,
interactiveManager: interactiveManager,
errorHandler: errorHandler,
- onEdit: onEdit
+ flow: flow
)
}
}
@@ -149,8 +153,7 @@ private struct PreviewView: View {
profileManager: .mock,
tunnel: .mock,
registry: Registry(),
- isImporting: .constant(false),
- onEdit: { _ in }
+ isImporting: .constant(false)
)
}
.withMockEnvironment()
diff --git a/Passepartout/Library/Sources/AppUI/Views/App/ProfileGridView.swift b/Passepartout/Library/Sources/AppUI/Views/App/ProfileGridView.swift
index f8fde3db..226916e4 100644
--- a/Passepartout/Library/Sources/AppUI/Views/App/ProfileGridView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/App/ProfileGridView.swift
@@ -28,7 +28,7 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct ProfileGridView: View, TunnelInstallationProviding {
+struct ProfileGridView: View, Routable, TunnelInstallationProviding {
@Environment(\.isSearching)
private var isSearching
@@ -43,7 +43,7 @@ struct ProfileGridView: View, TunnelInstallationProviding {
let errorHandler: ErrorHandler
- let onEdit: (ProfileHeader) -> Void
+ var flow: ProfileContainerView.Flow?
@State
private var nextProfileId: Profile.ID?
@@ -94,9 +94,7 @@ private extension ProfileGridView {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
nextProfileId: $nextProfileId,
- flow: .init(
- onEditProfile: onEdit
- )
+ flow: flow
)
.contextMenu {
currentProfile.map {
@@ -107,7 +105,9 @@ private extension ProfileGridView {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
isInstalledProfile: true,
- onEdit: onEdit
+ onEdit: {
+ flow?.onEditProfile($0)
+ }
)
}
}
@@ -123,7 +123,9 @@ private extension ProfileGridView {
errorHandler: errorHandler,
nextProfileId: $nextProfileId,
withMarker: true,
- onEdit: onEdit
+ onEdit: {
+ flow?.onEditProfile($0)
+ }
)
.themeGridCell(isSelected: header.id == nextProfileId ?? tunnel.currentProfile?.id)
.contextMenu {
@@ -134,7 +136,9 @@ private extension ProfileGridView {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
isInstalledProfile: false,
- onEdit: onEdit
+ onEdit: {
+ flow?.onEditProfile($0)
+ }
)
}
.id(header.id)
@@ -148,8 +152,7 @@ private extension ProfileGridView {
profileManager: .mock,
tunnel: .mock,
interactiveManager: InteractiveManager(),
- errorHandler: .default(),
- onEdit: { _ in }
+ errorHandler: .default()
)
.themeWindow(width: 600, height: 300)
.withMockEnvironment()
diff --git a/Passepartout/Library/Sources/AppUI/Views/App/ProfileListView.swift b/Passepartout/Library/Sources/AppUI/Views/App/ProfileListView.swift
index f09cbd5f..8aec9eff 100644
--- a/Passepartout/Library/Sources/AppUI/Views/App/ProfileListView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/App/ProfileListView.swift
@@ -28,7 +28,7 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-struct ProfileListView: View, TunnelInstallationProviding {
+struct ProfileListView: View, Routable, TunnelInstallationProviding {
@Environment(\.horizontalSizeClass)
private var hsClass
@@ -49,7 +49,7 @@ struct ProfileListView: View, TunnelInstallationProviding {
let errorHandler: ErrorHandler
- let onEdit: (ProfileHeader) -> Void
+ var flow: ProfileContainerView.Flow?
@State
private var nextProfileId: Profile.ID?
@@ -90,9 +90,7 @@ private extension ProfileListView {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
nextProfileId: $nextProfileId,
- flow: .init(
- onEditProfile: onEdit
- )
+ flow: flow
)
.contextMenu {
currentProfile.map {
@@ -103,7 +101,9 @@ private extension ProfileListView {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
isInstalledProfile: true,
- onEdit: onEdit
+ onEdit: {
+ flow?.onEditProfile($0)
+ }
)
}
}
@@ -119,7 +119,9 @@ private extension ProfileListView {
errorHandler: errorHandler,
nextProfileId: $nextProfileId,
withMarker: true,
- onEdit: onEdit
+ onEdit: {
+ flow?.onEditProfile($0)
+ }
)
.contextMenu {
ProfileContextMenu(
@@ -129,7 +131,9 @@ private extension ProfileListView {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
isInstalledProfile: false,
- onEdit: onEdit
+ onEdit: {
+ flow?.onEditProfile($0)
+ }
)
}
.id(header.id)
@@ -153,8 +157,7 @@ private extension ProfileListView {
profileManager: .mock,
tunnel: .mock,
interactiveManager: InteractiveManager(),
- errorHandler: .default(),
- onEdit: { _ in }
+ errorHandler: .default()
)
.withMockEnvironment()
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift
index 599c66d5..7fcbfd82 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/DNSView.swift
@@ -28,13 +28,7 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-extension DNSModule.Builder: ModuleViewProviding {
- func moduleView(with editor: ProfileEditor) -> some View {
- DNSView(editor: editor, module: self)
- }
-}
-
-private struct DNSView: View {
+struct DNSView: View {
@EnvironmentObject
private var theme: Theme
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/DNSModule+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/DNSModule+Extensions.swift
new file mode 100644
index 00000000..45afa7cf
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/DNSModule+Extensions.swift
@@ -0,0 +1,33 @@
+//
+// DNSModule+Extensions.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 2/17/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
+
+extension DNSModule.Builder: ModuleViewProviding {
+ func moduleView(with editor: ProfileEditor) -> some View {
+ DNSView(editor: editor, module: self)
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/HTTPProxyModule+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/HTTPProxyModule+Extensions.swift
new file mode 100644
index 00000000..305a419a
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/HTTPProxyModule+Extensions.swift
@@ -0,0 +1,33 @@
+//
+// HTTPProxyModule+Extensions.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 2/17/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
+
+extension HTTPProxyModule.Builder: ModuleViewProviding {
+ func moduleView(with editor: ProfileEditor) -> some View {
+ HTTPProxyView(editor: editor, module: self)
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/IPModule+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/IPModule+Extensions.swift
new file mode 100644
index 00000000..4ab413fe
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/IPModule+Extensions.swift
@@ -0,0 +1,33 @@
+//
+// IPModule+Extensions.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 2/17/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
+
+extension IPModule.Builder: ModuleViewProviding {
+ func moduleView(with editor: ProfileEditor) -> some View {
+ IPView(editor: editor, module: self)
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OnDemandModule+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OnDemandModule+Extensions.swift
new file mode 100644
index 00000000..eb6b0e7b
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OnDemandModule+Extensions.swift
@@ -0,0 +1,33 @@
+//
+// OnDemandModule+Extensions.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 2/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
+
+extension OnDemandModule.Builder: ModuleViewProviding {
+ func moduleView(with editor: ProfileEditor) -> some View {
+ OnDemandView(editor: editor, module: self)
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OpenVPNModule+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OpenVPNModule+Extensions.swift
new file mode 100644
index 00000000..a2de730d
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/OpenVPNModule+Extensions.swift
@@ -0,0 +1,45 @@
+//
+// OpenVPNModule+Extensions.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 2/17/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
+
+extension OpenVPNModule.Builder: ModuleViewProviding {
+ func moduleView(with editor: ProfileEditor) -> some View {
+ OpenVPNView(editor: editor, module: self)
+ }
+}
+
+extension OpenVPNModule.Builder: InteractiveViewProviding {
+ func interactiveView(with editor: ProfileEditor) -> some View {
+ let draft = editor.binding(forModule: self)
+
+ return OpenVPNView.CredentialsView(
+ isInteractive: draft.isInteractive,
+ credentials: draft.credentials,
+ isAuthenticating: true
+ )
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/WireGuardModule+Extensions.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/WireGuardModule+Extensions.swift
new file mode 100644
index 00000000..1cdccd81
--- /dev/null
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/Extensions/WireGuardModule+Extensions.swift
@@ -0,0 +1,33 @@
+//
+// WireGuardModule+Extensions.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 7/31/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
+
+extension WireGuardModule.Builder: ModuleViewProviding {
+ func moduleView(with editor: ProfileEditor) -> some View {
+ WireGuardView(editor: editor, module: self)
+ }
+}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift
index d8796a78..63ac3e49 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/HTTPProxyView.swift
@@ -27,13 +27,7 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-extension HTTPProxyModule.Builder: ModuleViewProviding {
- func moduleView(with editor: ProfileEditor) -> some View {
- HTTPProxyView(editor: editor, module: self)
- }
-}
-
-private struct HTTPProxyView: View {
+struct HTTPProxyView: View {
@EnvironmentObject
private var theme: Theme
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift
index ac0358bd..937e6910 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView.swift
@@ -27,12 +27,6 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-extension IPModule.Builder: ModuleViewProviding {
- func moduleView(with editor: ProfileEditor) -> some View {
- IPView(editor: editor, module: self)
- }
-}
-
struct IPView: View {
@ObservedObject
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift
index 7b462426..79a29f1b 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/OnDemandView.swift
@@ -27,13 +27,7 @@ import PassepartoutKit
import SwiftUI
import UtilsLibrary
-extension OnDemandModule.Builder: ModuleViewProviding {
- func moduleView(with editor: ProfileEditor) -> some View {
- OnDemandView(editor: editor, module: self)
- }
-}
-
-private struct OnDemandView: View {
+struct OnDemandView: View {
@EnvironmentObject
private var theme: Theme
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift
index 98393604..5048e11c 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift
@@ -26,24 +26,6 @@
import PassepartoutKit
import SwiftUI
-extension OpenVPNModule.Builder: ModuleViewProviding {
- func moduleView(with editor: ProfileEditor) -> some View {
- OpenVPNView(editor: editor, module: self)
- }
-}
-
-extension OpenVPNModule.Builder: InteractiveViewProviding {
- func interactiveView(with editor: ProfileEditor) -> some View {
- let draft = editor.binding(forModule: self)
-
- return OpenVPNView.CredentialsView(
- isInteractive: draft.isInteractive,
- credentials: draft.credentials,
- isAuthenticating: true
- )
- }
-}
-
struct OpenVPNView: View {
@ObservedObject
@@ -96,9 +78,8 @@ private extension OpenVPNView {
var providerModifier: some ViewModifier {
VPNProviderContentModifier(
- providerId: editor.binding(forProviderOf: draft.id),
- selectedEntity: editor.binding(forProviderEntityOf: draft.id),
- configurationType: OpenVPN.Configuration.self,
+ providerId: providerId,
+ selectedEntity: providerEntity,
isRequired: draft.configurationBuilder == nil,
providerRows: {
moduleGroup(for: providerAccountRows)
@@ -106,6 +87,14 @@ private extension OpenVPNView {
)
}
+ var providerId: Binding {
+ editor.binding(forProviderOf: draft.id)
+ }
+
+ var providerEntity: Binding?> {
+ editor.binding(forProviderEntityOf: draft.id)
+ }
+
var providerAccountRows: [ModuleRow]? {
[.push(caption: Strings.Modules.Openvpn.credentials, route: HashableRoute(Subroute.credentials))]
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift
index 0b3df84e..3c71e18d 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Modules/WireGuardView.swift
@@ -28,13 +28,7 @@ import PassepartoutKit
import PassepartoutWireGuardGo
import SwiftUI
-extension WireGuardModule.Builder: ModuleViewProviding {
- func moduleView(with editor: ProfileEditor) -> some View {
- WireGuardView(editor: editor, module: self)
- }
-}
-
-private struct WireGuardView: View {
+struct WireGuardView: View {
private enum Subroute: Hashable {
case providerServer(id: ProviderID)
}
diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift
index 02d527a8..4d3244f6 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderContentModifier.swift
@@ -39,8 +39,6 @@ struct VPNProviderContentModifier: ViewModifier whe
@Binding
var selectedEntity: VPNEntity?
- let configurationType: Configuration.Type
-
let isRequired: Bool
@ViewBuilder
@@ -107,8 +105,7 @@ private extension VPNProviderContentModifier {
EmptyView()
.modifier(VPNProviderContentModifier(
providerId: .constant(.hideme),
- selectedEntity: .constant(nil),
- configurationType: OpenVPN.Configuration.self,
+ selectedEntity: .constant(nil as VPNEntity?),
isRequired: false,
providerRows: {
Text("Other")
diff --git a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift
index 7a6d2ad9..314e5981 100644
--- a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift
@@ -400,6 +400,29 @@ struct ThemeImageLabel: View {
}
}
+struct ThemeDisclosableMenu: View where NameContent: View, MenuContent: View {
+
+ @ViewBuilder
+ let name: NameContent
+
+ @ViewBuilder
+ let menu: () -> MenuContent
+
+ var body: some View {
+ Menu(content: menu) {
+ HStack(alignment: .firstTextBaseline) {
+ name
+ ThemeImage(.disclose)
+ }
+ .contentShape(.rect)
+ }
+ .foregroundStyle(.primary)
+#if os(macOS)
+ .buttonStyle(.plain)
+#endif
+ }
+}
+
struct ThemeCopiableText: View where Value: CustomStringConvertible, ValueView: View {
@EnvironmentObject
diff --git a/Passepartout/Library/Sources/AppUI/Views/UI/InstalledProfileView.swift b/Passepartout/Library/Sources/AppUI/Views/UI/InstalledProfileView.swift
index f371ac55..0676652e 100644
--- a/Passepartout/Library/Sources/AppUI/Views/UI/InstalledProfileView.swift
+++ b/Passepartout/Library/Sources/AppUI/Views/UI/InstalledProfileView.swift
@@ -33,10 +33,6 @@ struct InstalledProfileView: View, Routable {
@EnvironmentObject
var theme: Theme
- struct Flow {
- let onEditProfile: (ProfileHeader) -> Void
- }
-
let layout: ProfilesLayout
let profileManager: ProfileManager
@@ -52,7 +48,7 @@ struct InstalledProfileView: View, Routable {
@Binding
var nextProfileId: Profile.ID?
- var flow: Flow?
+ var flow: ProfileContainerView.Flow?
var body: some View {
debugChanges()
@@ -86,21 +82,16 @@ private extension InstalledProfileView {
}
var actionableNameView: some View {
- Menu(content: menuContent) {
- HStack(alignment: .firstTextBaseline) {
- nameView
- ThemeImage(.disclose)
- }
- .contentShape(.rect)
+ ThemeDisclosableMenu {
+ nameView
+ } menu: {
+ menuContent
}
- .foregroundStyle(.primary)
-#if os(macOS)
- .buttonStyle(.plain)
-#endif
}
var nameView: some View {
Text(profile?.name ?? Strings.Views.Profiles.Rows.notInstalled)
+ .fixedSize()
.font(.title2)
.fontWeight(theme.relevantWeight)
.themeTruncating(.tail)
@@ -128,7 +119,7 @@ private extension InstalledProfileView {
.opacity(installedOpacity)
}
- func menuContent() -> some View {
+ var menuContent: some View {
ProfileContextMenu(
profileManager: profileManager,
tunnel: tunnel,