mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2024-12-26 19:32:41 +00:00
6ede6f052a
Hosts only: - Add new - Edit/delete existing - Reorder Closes #206
300 lines
9.4 KiB
Swift
300 lines
9.4 KiB
Swift
//
|
|
// NetworkSettingsView.swift
|
|
// Passepartout
|
|
//
|
|
// Created by Davide De Rosa on 2/19/22.
|
|
// Copyright (c) 2023 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 PassepartoutLibrary
|
|
import SwiftUI
|
|
|
|
struct NetworkSettingsView: View {
|
|
@ObservedObject private var currentProfile: ObservableProfile
|
|
|
|
@State private var settings = Profile.NetworkSettings()
|
|
|
|
init(currentProfile: ObservableProfile) {
|
|
self.currentProfile = currentProfile
|
|
}
|
|
|
|
var body: some View {
|
|
debugChanges()
|
|
return List {
|
|
if vpnProtocol.supportsGateway {
|
|
gatewayView
|
|
}
|
|
if vpnProtocol.supportsDNS {
|
|
dnsView
|
|
}
|
|
if vpnProtocol.supportsProxy {
|
|
proxyView
|
|
}
|
|
if vpnProtocol.supportsMTU {
|
|
mtuView
|
|
}
|
|
}.navigationTitle(L10n.NetworkSettings.title)
|
|
.toolbar {
|
|
CopySavingButton(
|
|
original: $currentProfile.value.networkSettings,
|
|
copy: $settings,
|
|
mapping: \.stripped,
|
|
label: themeSaveButtonLabel
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
// MARK: Gateway
|
|
|
|
private extension NetworkSettingsView {
|
|
var gatewayView: some View {
|
|
Section {
|
|
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticGateway.themeAnimation())
|
|
|
|
if !settings.isAutomaticGateway {
|
|
Toggle(Unlocalized.Network.ipv4, isOn: $settings.gateway.isDefaultIPv4)
|
|
Toggle(Unlocalized.Network.ipv6, isOn: $settings.gateway.isDefaultIPv6)
|
|
}
|
|
} header: {
|
|
Text(L10n.NetworkSettings.Gateway.title)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: DNS
|
|
|
|
private extension NetworkSettingsView {
|
|
|
|
@ViewBuilder
|
|
var dnsView: some View {
|
|
Section {
|
|
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticDNS.themeAnimation())
|
|
|
|
if !settings.isAutomaticDNS {
|
|
themeTextPicker(
|
|
L10n.Global.Strings.configuration,
|
|
selection: $settings.dns.configurationType,
|
|
values: Network.DNSSettings.availableConfigurationTypes(forVPNProtocol: vpnProtocol),
|
|
description: \.localizedDescription
|
|
)
|
|
|
|
switch settings.dns.configurationType {
|
|
case .plain:
|
|
EmptyView()
|
|
|
|
case .https:
|
|
dnsManualHTTPSRow
|
|
|
|
case .tls:
|
|
dnsManualTLSRow
|
|
|
|
case .disabled:
|
|
EmptyView()
|
|
}
|
|
}
|
|
} header: {
|
|
Text(Unlocalized.Network.dns)
|
|
}
|
|
if !settings.isAutomaticDNS && settings.dns.configurationType != .disabled {
|
|
dnsManualServers
|
|
dnsManualDomainRow
|
|
dnsManualSearchDomains
|
|
}
|
|
}
|
|
|
|
var dnsManualHTTPSRow: some View {
|
|
TextField(Unlocalized.Placeholders.dohURL, text: $settings.dns.dnsHTTPSURL.toString())
|
|
.themeValidURL(settings.dns.dnsHTTPSURL?.absoluteString)
|
|
}
|
|
|
|
var dnsManualTLSRow: some View {
|
|
TextField(Unlocalized.Placeholders.dotServerName, text: $settings.dns.dnsTLSServerName ?? "")
|
|
.themeValidDNSOverTLSServerName(settings.dns.dnsTLSServerName)
|
|
}
|
|
|
|
var dnsManualServers: some View {
|
|
Section {
|
|
EditableTextList(
|
|
elements: $settings.dns.dnsServers ?? [],
|
|
allowsDuplicates: false,
|
|
mapping: mapNotEmpty
|
|
) {
|
|
TextField(
|
|
Unlocalized.Placeholders.dnsAddress,
|
|
text: $0.text,
|
|
onEditingChanged: $0.onEditingChanged,
|
|
onCommit: $0.onCommit
|
|
).themeValidIPAddress($0.text.wrappedValue)
|
|
} addLabel: {
|
|
Text(L10n.NetworkSettings.Items.AddDnsServer.caption)
|
|
} commitLabel: {
|
|
Text(L10n.Global.Strings.add)
|
|
}
|
|
}
|
|
}
|
|
|
|
var dnsManualDomainRow: some View {
|
|
TextField(L10n.Global.Strings.domain, text: $settings.dns.dnsDomain ?? "")
|
|
.themeValidDomainName(settings.dns.dnsDomain)
|
|
}
|
|
|
|
var dnsManualSearchDomains: some View {
|
|
Section {
|
|
EditableTextList(
|
|
elements: $settings.dns.dnsSearchDomains ?? [],
|
|
allowsDuplicates: false,
|
|
mapping: mapNotEmpty
|
|
) {
|
|
TextField(
|
|
Unlocalized.Placeholders.dnsDomain,
|
|
text: $0.text,
|
|
onEditingChanged: $0.onEditingChanged,
|
|
onCommit: $0.onCommit
|
|
).themeValidDomainName($0.text.wrappedValue)
|
|
} addLabel: {
|
|
Text(L10n.NetworkSettings.Items.AddDnsDomain.caption)
|
|
} commitLabel: {
|
|
Text(L10n.Global.Strings.add)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Proxy
|
|
|
|
private extension NetworkSettingsView {
|
|
|
|
@ViewBuilder
|
|
var proxyView: some View {
|
|
Section {
|
|
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticProxy.themeAnimation())
|
|
|
|
if !settings.isAutomaticProxy {
|
|
themeTextPicker(
|
|
L10n.Global.Strings.configuration,
|
|
selection: $settings.proxy.configurationType,
|
|
values: Network.ProxySettings.availableConfigurationTypes,
|
|
description: \.localizedDescription
|
|
)
|
|
|
|
switch settings.proxy.configurationType {
|
|
case .manual:
|
|
TextField(Unlocalized.Placeholders.address, text: $settings.proxy.proxyAddress ?? "")
|
|
.themeValidIPAddress(settings.proxy.proxyAddress)
|
|
.withLeadingText(L10n.Global.Strings.address)
|
|
|
|
TextField(Unlocalized.Placeholders.port, text: $settings.proxy.proxyPort.toString())
|
|
.themeValidSocketPort(settings.proxy.proxyPort?.description)
|
|
.withLeadingText(L10n.Global.Strings.port)
|
|
|
|
case .pac:
|
|
TextField(Unlocalized.Placeholders.pacURL, text: $settings.proxy.proxyAutoConfigurationURL.toString())
|
|
.themeValidURL(settings.proxy.proxyAutoConfigurationURL?.absoluteString)
|
|
|
|
case .disabled:
|
|
EmptyView()
|
|
}
|
|
}
|
|
} header: {
|
|
Text(L10n.Global.Strings.proxy)
|
|
}
|
|
if !settings.isAutomaticProxy && settings.proxy.configurationType == .manual {
|
|
proxyManualBypassDomains
|
|
}
|
|
}
|
|
|
|
var proxyManualBypassDomains: some View {
|
|
Section {
|
|
EditableTextList(
|
|
elements: $settings.proxy.proxyBypassDomains ?? [],
|
|
allowsDuplicates: false,
|
|
mapping: mapNotEmpty
|
|
) {
|
|
TextField(
|
|
Unlocalized.Placeholders.proxyBypassDomain,
|
|
text: $0.text,
|
|
onEditingChanged: $0.onEditingChanged,
|
|
onCommit: $0.onCommit
|
|
).themeValidWildcardDomainName($0.text.wrappedValue)
|
|
} addLabel: {
|
|
Text(L10n.NetworkSettings.Items.AddProxyBypass.caption)
|
|
} commitLabel: {
|
|
Text(L10n.Global.Strings.add)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: MTU
|
|
|
|
private extension NetworkSettingsView {
|
|
var mtuView: some View {
|
|
Section {
|
|
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticMTU.themeAnimation())
|
|
|
|
if !settings.isAutomaticMTU {
|
|
themeTextPicker(
|
|
L10n.Global.Strings.bytes,
|
|
selection: $settings.mtu.mtuBytes,
|
|
values: Network.MTUSettings.availableBytes,
|
|
description: { $0.localizedDescription(style: .mtu) }
|
|
)
|
|
}
|
|
} header: {
|
|
Text(Unlocalized.Network.mtu)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Global
|
|
|
|
private extension NetworkSettingsView {
|
|
var vpnProtocol: VPNProtocolType {
|
|
currentProfile.value.currentVPNProtocol
|
|
}
|
|
|
|
// EditButton()
|
|
// .disabled(!isAnythingManual)
|
|
|
|
var isAnythingManual: Bool {
|
|
// if settings.gateway.choice == .manual {
|
|
// return true
|
|
// }
|
|
if settings.dns.choice == .manual {
|
|
return true
|
|
}
|
|
if settings.proxy.choice == .manual {
|
|
return true
|
|
}
|
|
// if settings.mtu.choice == .manual {
|
|
// return true
|
|
// }
|
|
return false
|
|
}
|
|
|
|
func mapNotEmpty(elements: [IdentifiableString]) -> [IdentifiableString] {
|
|
elements
|
|
.filter { !$0.string.isEmpty }
|
|
}
|
|
}
|