From 5caf6da23fa8dc51d45e3ebbb1647fb4ad16add7 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 5 Jan 2025 18:08:29 +0100 Subject: [PATCH] Edit OpenVPN remotes (#1052) In a further subview. Remotes are not editable in providers. --- .../Modules/OpenVPNView+Configuration.swift | 29 +++++----- .../Views/Modules/OpenVPNView+Remotes.swift | 56 +++++++++++++++++++ .../AppUIMain/Views/Modules/OpenVPNView.swift | 17 ++++++ .../UILibrary/L10n/Strings+Unlocalized.swift | 4 ++ .../UILibrary/L10n/SwiftGen+Strings.swift | 2 + .../Resources/de.lproj/Localizable.strings | 1 + .../Resources/el.lproj/Localizable.strings | 1 + .../Resources/en.lproj/Localizable.strings | 1 + .../Resources/es.lproj/Localizable.strings | 1 + .../Resources/fr.lproj/Localizable.strings | 1 + .../Resources/it.lproj/Localizable.strings | 1 + .../Resources/nl.lproj/Localizable.strings | 1 + .../Resources/pl.lproj/Localizable.strings | 1 + .../Resources/pt.lproj/Localizable.strings | 1 + .../Resources/ru.lproj/Localizable.strings | 1 + .../Resources/sv.lproj/Localizable.strings | 1 + .../Resources/uk.lproj/Localizable.strings | 1 + .../zh-Hans.lproj/Localizable.strings | 1 + .../UILibrary/Views/UI/EndpointCardView.swift | 46 +++++++++++++++ 19 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Remotes.swift create mode 100644 Library/Sources/UILibrary/Views/UI/EndpointCardView.swift diff --git a/Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Configuration.swift b/Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Configuration.swift index 630e32ce..5d01cc6b 100644 --- a/Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Configuration.swift +++ b/Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Configuration.swift @@ -36,6 +36,8 @@ extension OpenVPNView { let credentialsRoute: (any Hashable)? + let remotesRoute: (any Hashable)? + @ObservedObject var excludedEndpoints: ObservableList @@ -72,12 +74,17 @@ extension OpenVPNView { private extension OpenVPNView.ConfigurationView { var remotesSection: some View { configuration.remotes.map { remotes in - ForEach(remotes, id: \.rawValue) { remote in - SelectableRemoteButton( - remote: remote, - all: Set(remotes), - excludedEndpoints: excludedEndpoints - ) + Group { + ForEach(remotes, id: \.rawValue) { remote in + SelectableRemoteButton( + remote: remote, + all: Set(remotes), + excludedEndpoints: excludedEndpoints + ) + } + if let remotesRoute { + NavigationLink(Strings.Global.Actions.edit, value: remotesRoute) + } } .themeSection(header: Strings.Modules.Openvpn.remotes) } @@ -103,14 +110,7 @@ private struct SelectableRemoteButton: View { } } label: { HStack { - VStack(alignment: .leading) { - Text(remote.address.rawValue) - .font(.headline) - - Text("\(remote.proto.socketType.rawValue):\(remote.proto.port.description)") - .font(.subheadline) - .foregroundStyle(.secondary) - } + EndpointCardView(endpoint: remote) Spacer() ThemeImage(.marked) .opaque(!excludedEndpoints.contains(remote)) @@ -363,6 +363,7 @@ private extension OpenVPNView.ConfigurationView { isServerPushed: false, configuration: .forPreviews, credentialsRoute: nil, + remotesRoute: nil, excludedEndpoints: excludedEndpoints ) } diff --git a/Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Remotes.swift b/Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Remotes.swift new file mode 100644 index 00000000..fb0cb8b9 --- /dev/null +++ b/Library/Sources/AppUIMain/Views/Modules/OpenVPNView+Remotes.swift @@ -0,0 +1,56 @@ +// +// OpenVPNView+Remotes.swift +// Passepartout +// +// Created by Davide De Rosa on 1/5/25. +// 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 OpenVPNView { + struct RemotesView: View { + + @EnvironmentObject + private var theme: Theme + + @Binding + var remotes: [String] + + var body: some View { + Form { + theme.listSection( + Strings.Modules.Openvpn.remotes, + addTitle: Strings.Global.Actions.add, + originalItems: $remotes, + itemLabel: { + if $0 { + Text($1.wrappedValue) + } else { + ThemeTextField("", text: $1, placeholder: Strings.Unlocalized.OpenVPN.Placeholders.remote) + } + } + ) + } + .labelsHidden() + .themeForm() + } + } +} diff --git a/Library/Sources/AppUIMain/Views/Modules/OpenVPNView.swift b/Library/Sources/AppUIMain/Views/Modules/OpenVPNView.swift index 062973a5..5f0fe0a5 100644 --- a/Library/Sources/AppUIMain/Views/Modules/OpenVPNView.swift +++ b/Library/Sources/AppUIMain/Views/Modules/OpenVPNView.swift @@ -94,6 +94,7 @@ private extension OpenVPNView { isServerPushed: isServerPushed, configuration: configuration, credentialsRoute: Subroute.credentials, + remotesRoute: Subroute.editRemotes, excludedEndpoints: excludedEndpoints ) } else { @@ -147,6 +148,8 @@ private extension OpenVPNView { case providerConfiguration(OpenVPN.Configuration) case credentials + + case editRemotes } @ViewBuilder @@ -169,6 +172,7 @@ private extension OpenVPNView { isServerPushed: false, configuration: configuration.builder(), credentialsRoute: nil, + remotesRoute: nil, excludedEndpoints: excludedEndpoints ) } @@ -186,6 +190,9 @@ private extension OpenVPNView { .navigationTitle(Strings.Modules.Openvpn.credentials) .themeForm() .themeAnimation(on: draft.wrappedValue.isInteractive, category: .modules) + + case .editRemotes: + RemotesView(remotes: editableRemotesBinding) } } } @@ -197,6 +204,16 @@ private extension OpenVPNView { editor.excludedEndpoints(for: module.id, preferences: modulePreferences) } + var editableRemotesBinding: Binding<[String]> { + Binding { + draft.wrappedValue.configurationBuilder?.remotes?.map(\.rawValue) ?? [] + } set: { + draft.wrappedValue.configurationBuilder?.remotes = $0.compactMap { + ExtendedEndpoint(rawValue: $0) + } + } + } + func onSelectServer(server: VPNServer, preset: VPNPreset) { draft.wrappedValue.providerEntity = VPNEntity(server: server, preset: preset) resetExcludedEndpointsWithCurrentProviderEntity() diff --git a/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift b/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift index bd704b23..170e70f2 100644 --- a/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift +++ b/Library/Sources/UILibrary/L10n/Strings+Unlocalized.swift @@ -29,6 +29,10 @@ import PassepartoutKit extension Strings { public enum Unlocalized { public enum OpenVPN { + public enum Placeholders { + public static let remote = "1.1.1.1:UDP:2222" + } + public enum XOR: String { case xormask diff --git a/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift b/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift index 183ef099..cb0a9b5d 100644 --- a/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift +++ b/Library/Sources/UILibrary/L10n/SwiftGen+Strings.swift @@ -184,6 +184,8 @@ public enum Strings { } public enum Global { public enum Actions { + /// Add + public static let add = Strings.tr("Localizable", "global.actions.add", fallback: "Add") /// Cancel public static let cancel = Strings.tr("Localizable", "global.actions.cancel", fallback: "Cancel") /// Connect diff --git a/Library/Sources/UILibrary/Resources/de.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/de.lproj/Localizable.strings index 9a5b5284..2f92d11c 100644 --- a/Library/Sources/UILibrary/Resources/de.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/de.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Alle Anbieter"; "features.routing" = "Benutzerdefiniertes Routing"; "features.sharing" = "%@"; +"global.actions.add" = "Hinzufügen"; "global.actions.cancel" = "Abbrechen"; "global.actions.connect" = "Verbinden"; "global.actions.delete" = "Löschen"; diff --git a/Library/Sources/UILibrary/Resources/el.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/el.lproj/Localizable.strings index 8febf85a..0e6ee92e 100644 --- a/Library/Sources/UILibrary/Resources/el.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/el.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Όλοι οι πάροχοι"; "features.routing" = "Προσαρμοσμένη Δρομολόγηση"; "features.sharing" = "%@"; +"global.actions.add" = "Προσθήκη"; "global.actions.cancel" = "Ακύρωση"; "global.actions.connect" = "Σύνδεση"; "global.actions.delete" = "Διαγραφή"; diff --git a/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings index 407b28e7..a91e4f34 100644 --- a/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/en.lproj/Localizable.strings @@ -230,6 +230,7 @@ // MARK: Global (Actions) +"global.actions.add" = "Add"; "global.actions.cancel" = "Cancel"; "global.actions.connect" = "Connect"; "global.actions.delete" = "Delete"; diff --git a/Library/Sources/UILibrary/Resources/es.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/es.lproj/Localizable.strings index 470a41d2..ecb088c7 100644 --- a/Library/Sources/UILibrary/Resources/es.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/es.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Todos los proveedores"; "features.routing" = "Enrutamiento Personalizado"; "features.sharing" = "%@"; +"global.actions.add" = "Agregar"; "global.actions.cancel" = "Cancelar"; "global.actions.connect" = "Conectar"; "global.actions.delete" = "Eliminar"; diff --git a/Library/Sources/UILibrary/Resources/fr.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/fr.lproj/Localizable.strings index 8f2dd5b4..1a3061bb 100644 --- a/Library/Sources/UILibrary/Resources/fr.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/fr.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Tous les fournisseurs"; "features.routing" = "Routage Personnalisé"; "features.sharing" = "%@"; +"global.actions.add" = "Ajouter"; "global.actions.cancel" = "Annuler"; "global.actions.connect" = "Connecter"; "global.actions.delete" = "Supprimer"; diff --git a/Library/Sources/UILibrary/Resources/it.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/it.lproj/Localizable.strings index 92aaf50c..d1692232 100644 --- a/Library/Sources/UILibrary/Resources/it.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/it.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Tutti i provider"; "features.routing" = "Routing Personalizzato"; "features.sharing" = "%@"; +"global.actions.add" = "Aggiungi"; "global.actions.cancel" = "Annulla"; "global.actions.connect" = "Connetti"; "global.actions.delete" = "Elimina"; diff --git a/Library/Sources/UILibrary/Resources/nl.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/nl.lproj/Localizable.strings index 0c98dba5..ef51cc51 100644 --- a/Library/Sources/UILibrary/Resources/nl.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/nl.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Alle providers"; "features.routing" = "Aangepaste routering"; "features.sharing" = "%@"; +"global.actions.add" = "Toevoegen"; "global.actions.cancel" = "Annuleren"; "global.actions.connect" = "Verbinden"; "global.actions.delete" = "Verwijderen"; diff --git a/Library/Sources/UILibrary/Resources/pl.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/pl.lproj/Localizable.strings index 28aaa0f9..7843c17e 100644 --- a/Library/Sources/UILibrary/Resources/pl.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/pl.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Wszyscy dostawcy"; "features.routing" = "Niestandardowe trasowanie"; "features.sharing" = "%@"; +"global.actions.add" = "Dodaj"; "global.actions.cancel" = "Anuluj"; "global.actions.connect" = "Połącz"; "global.actions.delete" = "Usuń"; diff --git a/Library/Sources/UILibrary/Resources/pt.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/pt.lproj/Localizable.strings index ee34733f..02d39b1d 100644 --- a/Library/Sources/UILibrary/Resources/pt.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/pt.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Todos os provedores"; "features.routing" = "Roteamento Personalizado"; "features.sharing" = "%@"; +"global.actions.add" = "Adicionar"; "global.actions.cancel" = "Cancelar"; "global.actions.connect" = "Conectar"; "global.actions.delete" = "Excluir"; diff --git a/Library/Sources/UILibrary/Resources/ru.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/ru.lproj/Localizable.strings index b1192124..79dac734 100644 --- a/Library/Sources/UILibrary/Resources/ru.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/ru.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Все поставщики"; "features.routing" = "Пользовательская маршрутизация"; "features.sharing" = "%@"; +"global.actions.add" = "Добавить"; "global.actions.cancel" = "Отмена"; "global.actions.connect" = "Подключить"; "global.actions.delete" = "Удалить"; diff --git a/Library/Sources/UILibrary/Resources/sv.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/sv.lproj/Localizable.strings index 477fd612..5a588ed5 100644 --- a/Library/Sources/UILibrary/Resources/sv.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/sv.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Alla leverantörer"; "features.routing" = "Anpassad Ruttning"; "features.sharing" = "%@"; +"global.actions.add" = "Lägg till"; "global.actions.cancel" = "Avbryt"; "global.actions.connect" = "Anslut"; "global.actions.delete" = "Ta bort"; diff --git a/Library/Sources/UILibrary/Resources/uk.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/uk.lproj/Localizable.strings index 4722bf53..3954a4e3 100644 --- a/Library/Sources/UILibrary/Resources/uk.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/uk.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "Усі постачальники"; "features.routing" = "Індивідуальна маршрутизація"; "features.sharing" = "%@"; +"global.actions.add" = "Додати"; "global.actions.cancel" = "Скасувати"; "global.actions.connect" = "Підключитися"; "global.actions.delete" = "Видалити"; diff --git a/Library/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings b/Library/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings index 83ea247f..3d77e9e8 100644 --- a/Library/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings +++ b/Library/Sources/UILibrary/Resources/zh-Hans.lproj/Localizable.strings @@ -54,6 +54,7 @@ "features.providers" = "所有提供商"; "features.routing" = "自定义路由"; "features.sharing" = "%@"; +"global.actions.add" = "添加"; "global.actions.cancel" = "取消"; "global.actions.connect" = "连接"; "global.actions.delete" = "删除"; diff --git a/Library/Sources/UILibrary/Views/UI/EndpointCardView.swift b/Library/Sources/UILibrary/Views/UI/EndpointCardView.swift new file mode 100644 index 00000000..14d3e7dd --- /dev/null +++ b/Library/Sources/UILibrary/Views/UI/EndpointCardView.swift @@ -0,0 +1,46 @@ +// +// EndpointCardView.swift +// Passepartout +// +// Created by Davide De Rosa on 1/5/25. +// 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 + +public struct EndpointCardView: View { + private let endpoint: ExtendedEndpoint + + public init(endpoint: ExtendedEndpoint) { + self.endpoint = endpoint + } + + public var body: some View { + VStack(alignment: .leading) { + Text(endpoint.address.rawValue) + .font(.headline) + + Text("\(endpoint.proto.socketType.rawValue):\(endpoint.proto.port.description)") + .font(.subheadline) + .foregroundStyle(.secondary) + } + } +}