Refactor a few things about provider flows (#748)
- Move disclosable menu from installed profile view to ThemeDisclosableMenu - Drop unnecessary configurationType modifier parameter - Reorg view-related module extensions to separate files - Reuse .flow fields instead of single blocks - Show specific error on missing provider server selection
This commit is contained in:
parent
39bdf145e8
commit
a94db35d01
|
@ -28,6 +28,8 @@ import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension AppData {
|
extension AppData {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
public static let cdProfilesModel: NSManagedObjectModel = {
|
public static let cdProfilesModel: NSManagedObjectModel = {
|
||||||
guard let model: NSManagedObjectModel = .mergedModel(from: [.module]) else {
|
guard let model: NSManagedObjectModel = .mergedModel(from: [.module]) else {
|
||||||
fatalError("Unable to build Core Data model (Profiles v3)")
|
fatalError("Unable to build Core Data model (Profiles v3)")
|
||||||
|
|
|
@ -56,6 +56,9 @@ extension PassepartoutError: LocalizedError {
|
||||||
return Strings.Errors.App.Passepartout.connectionModuleRequired
|
return Strings.Errors.App.Passepartout.connectionModuleRequired
|
||||||
|
|
||||||
case .corruptProviderModule:
|
case .corruptProviderModule:
|
||||||
|
if let ppReason = reason as? PassepartoutError, ppReason.code == .notFound {
|
||||||
|
return Strings.Errors.App.missingProviderEntity
|
||||||
|
}
|
||||||
return Strings.Errors.App.Passepartout.corruptProviderModule(reason?.localizedDescription ?? "")
|
return Strings.Errors.App.Passepartout.corruptProviderModule(reason?.localizedDescription ?? "")
|
||||||
|
|
||||||
case .incompatibleModules:
|
case .incompatibleModules:
|
||||||
|
|
|
@ -108,6 +108,8 @@ public enum Strings {
|
||||||
public static func malformedModule(_ p1: Any, _ p2: Any) -> String {
|
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. %@")
|
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 {
|
public enum Passepartout {
|
||||||
/// Routing module can only be enabled together with a connection.
|
/// 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.")
|
public static let connectionModuleRequired = Strings.tr("Localizable", "errors.app.passepartout.connection_module_required", fallback: "Routing module can only be enabled together with a connection.")
|
||||||
|
|
|
@ -248,6 +248,7 @@
|
||||||
|
|
||||||
"errors.app.empty_profile_name" = "Profile name is empty.";
|
"errors.app.empty_profile_name" = "Profile name is empty.";
|
||||||
"errors.app.malformed_module" = "Module %@ is malformed. %@";
|
"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.default" = "Unable to complete operation.";
|
||||||
"errors.app.passepartout.connection_module_required" = "Routing module can only be enabled together with a connection.";
|
"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=%@).";
|
"errors.app.passepartout.corrupt_provider_module" = "Unable to connect to provider server (reason=%@).";
|
||||||
|
|
|
@ -85,12 +85,14 @@ private extension AppInlineCoordinator {
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
registry: registry,
|
registry: registry,
|
||||||
isImporting: $isImporting,
|
isImporting: $isImporting,
|
||||||
onEdit: {
|
flow: .init(
|
||||||
guard let profile = profileManager.profile(withId: $0.id) else {
|
onEditProfile: {
|
||||||
return
|
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) {
|
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)
|
push(.editProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,12 +81,14 @@ extension AppModalCoordinator {
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
registry: registry,
|
registry: registry,
|
||||||
isImporting: $isImporting,
|
isImporting: $isImporting,
|
||||||
onEdit: {
|
flow: .init(
|
||||||
guard let profile = profileManager.profile(withId: $0.id) else {
|
onEditProfile: {
|
||||||
return
|
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) {
|
func enterDetail(of profile: Profile) {
|
||||||
profilePath = NavigationPath()
|
profilePath = NavigationPath()
|
||||||
profileEditor.editProfile(profile, isShared: profileManager.isRemotelyShared(profileWithId: profile.id))
|
profileEditor.editProfile(
|
||||||
|
profile,
|
||||||
|
isShared: profileManager.isRemotelyShared(profileWithId: profile.id)
|
||||||
|
)
|
||||||
modalRoute = .editProfile
|
modalRoute = .editProfile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,11 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct ProfileContainerView: View, TunnelInstallationProviding {
|
struct ProfileContainerView: View, Routable, TunnelInstallationProviding {
|
||||||
|
struct Flow {
|
||||||
|
let onEditProfile: (ProfileHeader) -> Void
|
||||||
|
}
|
||||||
|
|
||||||
let layout: ProfilesLayout
|
let layout: ProfilesLayout
|
||||||
|
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
@ -40,7 +44,7 @@ struct ProfileContainerView: View, TunnelInstallationProviding {
|
||||||
@Binding
|
@Binding
|
||||||
var isImporting: Bool
|
var isImporting: Bool
|
||||||
|
|
||||||
let onEdit: (ProfileHeader) -> Void
|
var flow: Flow?
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
private var interactiveManager = InteractiveManager()
|
private var interactiveManager = InteractiveManager()
|
||||||
|
@ -77,7 +81,7 @@ private extension ProfileContainerView {
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
onEdit: onEdit
|
flow: flow
|
||||||
)
|
)
|
||||||
|
|
||||||
case .grid:
|
case .grid:
|
||||||
|
@ -86,7 +90,7 @@ private extension ProfileContainerView {
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
onEdit: onEdit
|
flow: flow
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,8 +153,7 @@ private struct PreviewView: View {
|
||||||
profileManager: .mock,
|
profileManager: .mock,
|
||||||
tunnel: .mock,
|
tunnel: .mock,
|
||||||
registry: Registry(),
|
registry: Registry(),
|
||||||
isImporting: .constant(false),
|
isImporting: .constant(false)
|
||||||
onEdit: { _ in }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
|
|
|
@ -28,7 +28,7 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct ProfileGridView: View, TunnelInstallationProviding {
|
struct ProfileGridView: View, Routable, TunnelInstallationProviding {
|
||||||
|
|
||||||
@Environment(\.isSearching)
|
@Environment(\.isSearching)
|
||||||
private var isSearching
|
private var isSearching
|
||||||
|
@ -43,7 +43,7 @@ struct ProfileGridView: View, TunnelInstallationProviding {
|
||||||
|
|
||||||
let errorHandler: ErrorHandler
|
let errorHandler: ErrorHandler
|
||||||
|
|
||||||
let onEdit: (ProfileHeader) -> Void
|
var flow: ProfileContainerView.Flow?
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var nextProfileId: Profile.ID?
|
private var nextProfileId: Profile.ID?
|
||||||
|
@ -94,9 +94,7 @@ private extension ProfileGridView {
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
nextProfileId: $nextProfileId,
|
nextProfileId: $nextProfileId,
|
||||||
flow: .init(
|
flow: flow
|
||||||
onEditProfile: onEdit
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
currentProfile.map {
|
currentProfile.map {
|
||||||
|
@ -107,7 +105,9 @@ private extension ProfileGridView {
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
isInstalledProfile: true,
|
isInstalledProfile: true,
|
||||||
onEdit: onEdit
|
onEdit: {
|
||||||
|
flow?.onEditProfile($0)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,9 @@ private extension ProfileGridView {
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
nextProfileId: $nextProfileId,
|
nextProfileId: $nextProfileId,
|
||||||
withMarker: true,
|
withMarker: true,
|
||||||
onEdit: onEdit
|
onEdit: {
|
||||||
|
flow?.onEditProfile($0)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.themeGridCell(isSelected: header.id == nextProfileId ?? tunnel.currentProfile?.id)
|
.themeGridCell(isSelected: header.id == nextProfileId ?? tunnel.currentProfile?.id)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
|
@ -134,7 +136,9 @@ private extension ProfileGridView {
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
isInstalledProfile: false,
|
isInstalledProfile: false,
|
||||||
onEdit: onEdit
|
onEdit: {
|
||||||
|
flow?.onEditProfile($0)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.id(header.id)
|
.id(header.id)
|
||||||
|
@ -148,8 +152,7 @@ private extension ProfileGridView {
|
||||||
profileManager: .mock,
|
profileManager: .mock,
|
||||||
tunnel: .mock,
|
tunnel: .mock,
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default(),
|
errorHandler: .default()
|
||||||
onEdit: { _ in }
|
|
||||||
)
|
)
|
||||||
.themeWindow(width: 600, height: 300)
|
.themeWindow(width: 600, height: 300)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
|
|
|
@ -28,7 +28,7 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct ProfileListView: View, TunnelInstallationProviding {
|
struct ProfileListView: View, Routable, TunnelInstallationProviding {
|
||||||
|
|
||||||
@Environment(\.horizontalSizeClass)
|
@Environment(\.horizontalSizeClass)
|
||||||
private var hsClass
|
private var hsClass
|
||||||
|
@ -49,7 +49,7 @@ struct ProfileListView: View, TunnelInstallationProviding {
|
||||||
|
|
||||||
let errorHandler: ErrorHandler
|
let errorHandler: ErrorHandler
|
||||||
|
|
||||||
let onEdit: (ProfileHeader) -> Void
|
var flow: ProfileContainerView.Flow?
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var nextProfileId: Profile.ID?
|
private var nextProfileId: Profile.ID?
|
||||||
|
@ -90,9 +90,7 @@ private extension ProfileListView {
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
nextProfileId: $nextProfileId,
|
nextProfileId: $nextProfileId,
|
||||||
flow: .init(
|
flow: flow
|
||||||
onEditProfile: onEdit
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
currentProfile.map {
|
currentProfile.map {
|
||||||
|
@ -103,7 +101,9 @@ private extension ProfileListView {
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
isInstalledProfile: true,
|
isInstalledProfile: true,
|
||||||
onEdit: onEdit
|
onEdit: {
|
||||||
|
flow?.onEditProfile($0)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,9 @@ private extension ProfileListView {
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
nextProfileId: $nextProfileId,
|
nextProfileId: $nextProfileId,
|
||||||
withMarker: true,
|
withMarker: true,
|
||||||
onEdit: onEdit
|
onEdit: {
|
||||||
|
flow?.onEditProfile($0)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
ProfileContextMenu(
|
ProfileContextMenu(
|
||||||
|
@ -129,7 +131,9 @@ private extension ProfileListView {
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
isInstalledProfile: false,
|
isInstalledProfile: false,
|
||||||
onEdit: onEdit
|
onEdit: {
|
||||||
|
flow?.onEditProfile($0)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.id(header.id)
|
.id(header.id)
|
||||||
|
@ -153,8 +157,7 @@ private extension ProfileListView {
|
||||||
profileManager: .mock,
|
profileManager: .mock,
|
||||||
tunnel: .mock,
|
tunnel: .mock,
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default(),
|
errorHandler: .default()
|
||||||
onEdit: { _ in }
|
|
||||||
)
|
)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,13 +28,7 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension DNSModule.Builder: ModuleViewProviding {
|
struct DNSView: View {
|
||||||
func moduleView(with editor: ProfileEditor) -> some View {
|
|
||||||
DNSView(editor: editor, module: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct DNSView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import PassepartoutKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension DNSModule.Builder: ModuleViewProviding {
|
||||||
|
func moduleView(with editor: ProfileEditor) -> some View {
|
||||||
|
DNSView(editor: editor, module: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import PassepartoutKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HTTPProxyModule.Builder: ModuleViewProviding {
|
||||||
|
func moduleView(with editor: ProfileEditor) -> some View {
|
||||||
|
HTTPProxyView(editor: editor, module: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import PassepartoutKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension IPModule.Builder: ModuleViewProviding {
|
||||||
|
func moduleView(with editor: ProfileEditor) -> some View {
|
||||||
|
IPView(editor: editor, module: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import PassepartoutKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension OnDemandModule.Builder: ModuleViewProviding {
|
||||||
|
func moduleView(with editor: ProfileEditor) -> some View {
|
||||||
|
OnDemandView(editor: editor, module: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import PassepartoutKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension WireGuardModule.Builder: ModuleViewProviding {
|
||||||
|
func moduleView(with editor: ProfileEditor) -> some View {
|
||||||
|
WireGuardView(editor: editor, module: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,13 +27,7 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension HTTPProxyModule.Builder: ModuleViewProviding {
|
struct HTTPProxyView: View {
|
||||||
func moduleView(with editor: ProfileEditor) -> some View {
|
|
||||||
HTTPProxyView(editor: editor, module: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct HTTPProxyView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
|
@ -27,12 +27,6 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension IPModule.Builder: ModuleViewProviding {
|
|
||||||
func moduleView(with editor: ProfileEditor) -> some View {
|
|
||||||
IPView(editor: editor, module: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IPView: View {
|
struct IPView: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
|
|
|
@ -27,13 +27,7 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
extension OnDemandModule.Builder: ModuleViewProviding {
|
struct OnDemandView: View {
|
||||||
func moduleView(with editor: ProfileEditor) -> some View {
|
|
||||||
OnDemandView(editor: editor, module: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct OnDemandView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
|
@ -26,24 +26,6 @@
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
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 {
|
struct OpenVPNView: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
|
@ -96,9 +78,8 @@ private extension OpenVPNView {
|
||||||
|
|
||||||
var providerModifier: some ViewModifier {
|
var providerModifier: some ViewModifier {
|
||||||
VPNProviderContentModifier(
|
VPNProviderContentModifier(
|
||||||
providerId: editor.binding(forProviderOf: draft.id),
|
providerId: providerId,
|
||||||
selectedEntity: editor.binding(forProviderEntityOf: draft.id),
|
selectedEntity: providerEntity,
|
||||||
configurationType: OpenVPN.Configuration.self,
|
|
||||||
isRequired: draft.configurationBuilder == nil,
|
isRequired: draft.configurationBuilder == nil,
|
||||||
providerRows: {
|
providerRows: {
|
||||||
moduleGroup(for: providerAccountRows)
|
moduleGroup(for: providerAccountRows)
|
||||||
|
@ -106,6 +87,14 @@ private extension OpenVPNView {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var providerId: Binding<ProviderID?> {
|
||||||
|
editor.binding(forProviderOf: draft.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var providerEntity: Binding<VPNEntity<OpenVPN.Configuration>?> {
|
||||||
|
editor.binding(forProviderEntityOf: draft.id)
|
||||||
|
}
|
||||||
|
|
||||||
var providerAccountRows: [ModuleRow]? {
|
var providerAccountRows: [ModuleRow]? {
|
||||||
[.push(caption: Strings.Modules.Openvpn.credentials, route: HashableRoute(Subroute.credentials))]
|
[.push(caption: Strings.Modules.Openvpn.credentials, route: HashableRoute(Subroute.credentials))]
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,13 +28,7 @@ import PassepartoutKit
|
||||||
import PassepartoutWireGuardGo
|
import PassepartoutWireGuardGo
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension WireGuardModule.Builder: ModuleViewProviding {
|
struct WireGuardView: View {
|
||||||
func moduleView(with editor: ProfileEditor) -> some View {
|
|
||||||
WireGuardView(editor: editor, module: self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct WireGuardView: View {
|
|
||||||
private enum Subroute: Hashable {
|
private enum Subroute: Hashable {
|
||||||
case providerServer(id: ProviderID)
|
case providerServer(id: ProviderID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,6 @@ struct VPNProviderContentModifier<Configuration, ProviderRows>: ViewModifier whe
|
||||||
@Binding
|
@Binding
|
||||||
var selectedEntity: VPNEntity<Configuration>?
|
var selectedEntity: VPNEntity<Configuration>?
|
||||||
|
|
||||||
let configurationType: Configuration.Type
|
|
||||||
|
|
||||||
let isRequired: Bool
|
let isRequired: Bool
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -107,8 +105,7 @@ private extension VPNProviderContentModifier {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
.modifier(VPNProviderContentModifier(
|
.modifier(VPNProviderContentModifier(
|
||||||
providerId: .constant(.hideme),
|
providerId: .constant(.hideme),
|
||||||
selectedEntity: .constant(nil),
|
selectedEntity: .constant(nil as VPNEntity<OpenVPN.Configuration>?),
|
||||||
configurationType: OpenVPN.Configuration.self,
|
|
||||||
isRequired: false,
|
isRequired: false,
|
||||||
providerRows: {
|
providerRows: {
|
||||||
Text("Other")
|
Text("Other")
|
||||||
|
|
|
@ -400,6 +400,29 @@ struct ThemeImageLabel: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ThemeDisclosableMenu<NameContent, MenuContent>: 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<Value, ValueView>: View where Value: CustomStringConvertible, ValueView: View {
|
struct ThemeCopiableText<Value, ValueView>: View where Value: CustomStringConvertible, ValueView: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
|
|
|
@ -33,10 +33,6 @@ struct InstalledProfileView: View, Routable {
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var theme: Theme
|
var theme: Theme
|
||||||
|
|
||||||
struct Flow {
|
|
||||||
let onEditProfile: (ProfileHeader) -> Void
|
|
||||||
}
|
|
||||||
|
|
||||||
let layout: ProfilesLayout
|
let layout: ProfilesLayout
|
||||||
|
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
@ -52,7 +48,7 @@ struct InstalledProfileView: View, Routable {
|
||||||
@Binding
|
@Binding
|
||||||
var nextProfileId: Profile.ID?
|
var nextProfileId: Profile.ID?
|
||||||
|
|
||||||
var flow: Flow?
|
var flow: ProfileContainerView.Flow?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
debugChanges()
|
debugChanges()
|
||||||
|
@ -86,21 +82,16 @@ private extension InstalledProfileView {
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionableNameView: some View {
|
var actionableNameView: some View {
|
||||||
Menu(content: menuContent) {
|
ThemeDisclosableMenu {
|
||||||
HStack(alignment: .firstTextBaseline) {
|
nameView
|
||||||
nameView
|
} menu: {
|
||||||
ThemeImage(.disclose)
|
menuContent
|
||||||
}
|
|
||||||
.contentShape(.rect)
|
|
||||||
}
|
}
|
||||||
.foregroundStyle(.primary)
|
|
||||||
#if os(macOS)
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nameView: some View {
|
var nameView: some View {
|
||||||
Text(profile?.name ?? Strings.Views.Profiles.Rows.notInstalled)
|
Text(profile?.name ?? Strings.Views.Profiles.Rows.notInstalled)
|
||||||
|
.fixedSize()
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(theme.relevantWeight)
|
.fontWeight(theme.relevantWeight)
|
||||||
.themeTruncating(.tail)
|
.themeTruncating(.tail)
|
||||||
|
@ -128,7 +119,7 @@ private extension InstalledProfileView {
|
||||||
.opacity(installedOpacity)
|
.opacity(installedOpacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func menuContent() -> some View {
|
var menuContent: some View {
|
||||||
ProfileContextMenu(
|
ProfileContextMenu(
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
|
|
Loading…
Reference in New Issue