Use block versions of Section header/footer

This commit is contained in:
Davide De Rosa 2022-05-01 19:43:33 +02:00
parent d8b19b952a
commit 48d499569b
24 changed files with 173 additions and 167 deletions

View File

@ -69,22 +69,20 @@ struct AboutView: View {
} }
private var githubSubview: some View { private var githubSubview: some View {
Section( Section {
header: Text(Unlocalized.About.github)
) {
Button(Unlocalized.About.readme) { Button(Unlocalized.About.readme) {
URL.openURL(readmeURL) URL.openURL(readmeURL)
} }
Button(Unlocalized.About.changelog) { Button(Unlocalized.About.changelog) {
URL.openURL(changelogURL) URL.openURL(changelogURL)
} }
} header: {
Text(Unlocalized.About.github)
} }
} }
private var webSubview: some View { private var webSubview: some View {
Section( Section {
header: Text(L10n.About.Sections.Web.header)
) {
Button(L10n.About.Items.Website.caption) { Button(L10n.About.Items.Website.caption) {
URL.openURL(readmeURL) URL.openURL(readmeURL)
} }
@ -97,6 +95,8 @@ struct AboutView: View {
Button(L10n.About.Items.PrivacyPolicy.caption) { Button(L10n.About.Items.PrivacyPolicy.caption) {
URL.openURL(privacyURL) URL.openURL(privacyURL)
} }
} header: {
Text(L10n.About.Sections.Web.header)
} }
} }
} }

View File

@ -63,11 +63,7 @@ struct AccountView: View {
var body: some View { var body: some View {
List { List {
Section( Section {
footer: metadata?.localizedGuidanceString.map {
Text($0)
}
) {
TextField(usernamePlaceholder ?? L10n.Account.Items.Username.placeholder, text: $liveAccount.username) TextField(usernamePlaceholder ?? L10n.Account.Items.Username.placeholder, text: $liveAccount.username)
.textContentType(.username) .textContentType(.username)
.disableAutocorrection(true) .disableAutocorrection(true)
@ -85,6 +81,10 @@ struct AccountView: View {
.disableAutocorrection(true) .disableAutocorrection(true)
.autocapitalization(.none) .autocapitalization(.none)
.withLeadingText(L10n.Account.Items.Password.caption) .withLeadingText(L10n.Account.Items.Password.caption)
} footer: {
metadata?.localizedGuidanceString.map {
Text($0)
}
} }
if vpnProtocol == .openVPN { if vpnProtocol == .openVPN {
metadata?.openVPNGuidanceURL.map { guidanceURL in metadata?.openVPNGuidanceURL.map { guidanceURL in

View File

@ -109,26 +109,27 @@ extension AddHostView {
} }
private var encryptionSection: some View { private var encryptionSection: some View {
Section( Section {
header: Text(L10n.Global.Strings.encryption)
) {
SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) { SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) {
processProfile(replacingExisting: false) processProfile(replacingExisting: false)
} }
} header: {
Text(L10n.Global.Strings.encryption)
} }
} }
private var completeSection: some View { private var completeSection: some View {
Section( Section {
header: Text(L10n.AddProfile.Shared.title),
footer: themeErrorMessage(viewModel.errorMessage)
) {
Text(Unlocalized.Network.url) Text(Unlocalized.Network.url)
.withTrailingText(url.lastPathComponent) .withTrailingText(url.lastPathComponent)
viewModel.processedProfile.vpnProtocols.first.map { viewModel.processedProfile.vpnProtocols.first.map {
Text(L10n.Global.Strings.protocol) Text(L10n.Global.Strings.protocol)
.withTrailingText($0.description) .withTrailingText($0.description)
} }
} header: {
Text(L10n.AddProfile.Shared.title)
} footer: {
themeErrorMessage(viewModel.errorMessage)
} }
} }

View File

@ -39,12 +39,13 @@ enum AddProfileView {
let onCommit: () -> Void let onCommit: () -> Void
var body: some View { var body: some View {
Section( Section {
header: Text(L10n.Global.Strings.name),
footer: themeErrorMessage(errorMessage)
) {
TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit) TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit)
.themeValidProfileName() .themeValidProfileName()
} header: {
Text(L10n.Global.Strings.name)
} footer: {
themeErrorMessage(errorMessage)
} }
} }
} }
@ -55,10 +56,10 @@ enum AddProfileView {
@Binding var profileName: String @Binding var profileName: String
var body: some View { var body: some View {
Section( Section {
header: Text(L10n.AddProfile.Shared.Views.Existing.header)
) {
ForEach(headers, content: existingProfileButton) ForEach(headers, content: existingProfileButton)
} header: {
Text(L10n.AddProfile.Shared.Views.Existing.header)
} }
} }

View File

@ -84,9 +84,7 @@ struct AddProviderView: View {
} }
private var mainSection: some View { private var mainSection: some View {
Section( Section {
footer: Text(L10n.AddProfile.Provider.Sections.Vpn.footer)
) {
let protos = availableVPNProtocols let protos = availableVPNProtocols
if !protos.isEmpty { if !protos.isEmpty {
themeTextPicker( themeTextPicker(
@ -97,14 +95,16 @@ struct AddProviderView: View {
) )
} }
updateListButton updateListButton
} footer: {
Text(L10n.AddProfile.Provider.Sections.Vpn.footer)
} }
} }
private var providersSection: some View { private var providersSection: some View {
Section( Section {
footer: themeErrorMessage(viewModel.errorMessage)
) {
ForEach(providers, content: providerRow) ForEach(providers, content: providerRow)
} footer: {
themeErrorMessage(viewModel.errorMessage)
}.disabled(viewModel.isFetchingAnyProvider) }.disabled(viewModel.isFetchingAnyProvider)
} }

View File

@ -110,9 +110,7 @@ extension DiagnosticsView {
} }
private var debugLogSection: some View { private var debugLogSection: some View {
Section( Section {
footer: Text(L10n.Diagnostics.Sections.DebugLog.footer)
) {
let url = debugLogURL let url = debugLogURL
NavigationLink(L10n.DebugLog.title) { NavigationLink(L10n.DebugLog.title) {
url.map { url.map {
@ -123,6 +121,8 @@ extension DiagnosticsView {
} }
}.disabled(url == nil) }.disabled(url == nil)
Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $appManager.masksPrivateData) Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $appManager.masksPrivateData)
} footer: {
Text(L10n.Diagnostics.Sections.DebugLog.footer)
} }
} }

View File

@ -95,16 +95,17 @@ struct DonateView: View {
} }
private var productsSection: some View { private var productsSection: some View {
Section( Section {
header: Text(L10n.Donate.Sections.OneTime.header),
footer: Text(L10n.Donate.Sections.OneTime.footer)
.xxxThemeTruncation()
) {
if !productManager.isRefreshingProducts { if !productManager.isRefreshingProducts {
ForEach(productManager.donations, id: \.productIdentifier, content: productRow) ForEach(productManager.donations, id: \.productIdentifier, content: productRow)
} else { } else {
ProgressView() ProgressView()
} }
} header: {
Text(L10n.Donate.Sections.OneTime.header)
} footer: {
Text(L10n.Donate.Sections.OneTime.footer)
.xxxThemeTruncation()
} }
} }

View File

@ -63,9 +63,7 @@ extension EndpointAdvancedView {
extension EndpointAdvancedView.OpenVPNView { extension EndpointAdvancedView.OpenVPNView {
private var ipv4Section: some View { private var ipv4Section: some View {
builder.ipv4.map { cfg in builder.ipv4.map { cfg in
Section( Section {
header: Text(Unlocalized.Network.ipv4)
) {
themeLongContentLinkDefault( themeLongContentLinkDefault(
L10n.Global.Strings.address, L10n.Global.Strings.address,
content: .constant(builder.ipv4.localizedAddress) content: .constant(builder.ipv4.localizedAddress)
@ -81,15 +79,15 @@ extension EndpointAdvancedView.OpenVPNView {
content: .constant(route.localizedDescription) content: .constant(route.localizedDescription)
) )
} }
} header: {
Text(Unlocalized.Network.ipv4)
} }
} }
} }
private var ipv6Section: some View { private var ipv6Section: some View {
builder.ipv6.map { cfg in builder.ipv6.map { cfg in
Section( Section {
header: Text(Unlocalized.Network.ipv6)
) {
themeLongContentLinkDefault( themeLongContentLinkDefault(
L10n.Global.Strings.address, L10n.Global.Strings.address,
content: .constant(builder.ipv6.localizedAddress) content: .constant(builder.ipv6.localizedAddress)
@ -105,15 +103,15 @@ extension EndpointAdvancedView.OpenVPNView {
content: .constant(route.localizedDescription) content: .constant(route.localizedDescription)
) )
} }
} header: {
Text(Unlocalized.Network.ipv6)
} }
} }
} }
private func communicationSection(configuration: OpenVPN.Configuration) -> some View { private func communicationSection(configuration: OpenVPN.Configuration) -> some View {
configuration.communicationSettings.map { settings in configuration.communicationSettings.map { settings in
Section( Section {
header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header)
) {
settings.cipher.map { settings.cipher.map {
Text(L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption) Text(L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption)
.withTrailingText($0.localizedDescription) .withTrailingText($0.localizedDescription)
@ -126,14 +124,14 @@ extension EndpointAdvancedView.OpenVPNView {
Text(Unlocalized.VPN.xor) Text(Unlocalized.VPN.xor)
.withTrailingText($0.localizedDescriptionAsXOR) .withTrailingText($0.localizedDescriptionAsXOR)
} }
} header: {
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header)
} }
} }
} }
private var communicationEditableSection: some View { private var communicationEditableSection: some View {
Section( Section {
header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header)
) {
themeTextPicker( themeTextPicker(
L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption, L10n.Endpoint.Advanced.Openvpn.Items.Cipher.caption,
selection: $builder.cipher ?? OpenVPN.Configuration.Fallback.cipher, selection: $builder.cipher ?? OpenVPN.Configuration.Fallback.cipher,
@ -150,14 +148,14 @@ extension EndpointAdvancedView.OpenVPNView {
Text(Unlocalized.VPN.xor) Text(Unlocalized.VPN.xor)
.withTrailingText($0.localizedDescriptionAsXOR) .withTrailingText($0.localizedDescriptionAsXOR)
} }
} header: {
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Communication.header)
} }
} }
private func compressionSection(configuration: OpenVPN.Configuration) -> some View { private func compressionSection(configuration: OpenVPN.Configuration) -> some View {
configuration.compressionSettings.map { settings in configuration.compressionSettings.map { settings in
Section( Section {
header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header)
) {
settings.framing.map { settings.framing.map {
Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption) Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption)
.withTrailingText($0.localizedDescription) .withTrailingText($0.localizedDescription)
@ -166,14 +164,14 @@ extension EndpointAdvancedView.OpenVPNView {
Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionAlgorithm.caption) Text(L10n.Endpoint.Advanced.Openvpn.Items.CompressionAlgorithm.caption)
.withTrailingText($0.localizedDescription) .withTrailingText($0.localizedDescription)
} }
} header: {
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header)
} }
} }
} }
private var compressionEditableSection: some View { private var compressionEditableSection: some View {
Section( Section {
header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header)
) {
themeTextPicker( themeTextPicker(
L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption, L10n.Endpoint.Advanced.Openvpn.Items.CompressionFraming.caption,
selection: $builder.compressionFraming ?? OpenVPN.Configuration.Fallback.compressionFraming, selection: $builder.compressionFraming ?? OpenVPN.Configuration.Fallback.compressionFraming,
@ -186,14 +184,14 @@ extension EndpointAdvancedView.OpenVPNView {
values: OpenVPN.CompressionAlgorithm.available, values: OpenVPN.CompressionAlgorithm.available,
description: \.localizedDescription description: \.localizedDescription
).disabled(builder.compressionFraming == .disabled) ).disabled(builder.compressionFraming == .disabled)
} header: {
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Compression.header)
} }
} }
private func dnsSection(configuration: OpenVPN.Configuration) -> some View { private func dnsSection(configuration: OpenVPN.Configuration) -> some View {
configuration.dnsSettings.map { settings in configuration.dnsSettings.map { settings in
Section( Section {
header: Text(Unlocalized.Network.dns)
) {
ForEach(settings.servers, id: \.self) { ForEach(settings.servers, id: \.self) {
Text(L10n.Global.Strings.address) Text(L10n.Global.Strings.address)
.withTrailingText($0, copyOnTap: true) .withTrailingText($0, copyOnTap: true)
@ -202,15 +200,15 @@ extension EndpointAdvancedView.OpenVPNView {
Text(L10n.Global.Strings.domain) Text(L10n.Global.Strings.domain)
.withTrailingText($0, copyOnTap: true) .withTrailingText($0, copyOnTap: true)
} }
} header: {
Text(Unlocalized.Network.dns)
} }
} }
} }
private func proxySection(configuration: OpenVPN.Configuration) -> some View { private func proxySection(configuration: OpenVPN.Configuration) -> some View {
configuration.proxySettings.map { settings in configuration.proxySettings.map { settings in
Section( Section {
header: Text(L10n.Global.Strings.proxy)
) {
settings.proxy.map { settings.proxy.map {
Text(L10n.Global.Strings.address) Text(L10n.Global.Strings.address)
.withTrailingText($0.rawValue, copyOnTap: true) .withTrailingText($0.rawValue, copyOnTap: true)
@ -223,14 +221,14 @@ extension EndpointAdvancedView.OpenVPNView {
Text(L10n.NetworkSettings.Items.ProxyBypass.caption) Text(L10n.NetworkSettings.Items.ProxyBypass.caption)
.withTrailingText($0, copyOnTap: true) .withTrailingText($0, copyOnTap: true)
} }
} header: {
Text(L10n.Global.Strings.proxy)
} }
} }
} }
private var tlsSection: some View { private var tlsSection: some View {
Section( Section {
header: Text(Unlocalized.Network.tls)
) {
builder.ca.map { ca in builder.ca.map { ca in
themeLongContentLink( themeLongContentLink(
Unlocalized.VPN.certificateAuthority, Unlocalized.VPN.certificateAuthority,
@ -258,14 +256,14 @@ extension EndpointAdvancedView.OpenVPNView {
} }
Text(L10n.Endpoint.Advanced.Openvpn.Items.Eku.caption) Text(L10n.Endpoint.Advanced.Openvpn.Items.Eku.caption)
.withTrailingText(builder.checksEKU.localizedDescriptionAsEKU) .withTrailingText(builder.checksEKU.localizedDescriptionAsEKU)
} header: {
Text(Unlocalized.Network.tls)
} }
} }
private func otherSection(configuration: OpenVPN.Configuration) -> some View { private func otherSection(configuration: OpenVPN.Configuration) -> some View {
configuration.otherSettings.map { settings in configuration.otherSettings.map { settings in
Section( Section {
header: Text(L10n.Endpoint.Advanced.Openvpn.Sections.Other.header)
) {
settings.keepAlive.map { settings.keepAlive.map {
Text(L10n.Global.Strings.keepalive) Text(L10n.Global.Strings.keepalive)
.withTrailingText($0.localizedDescriptionAsKeepAlive) .withTrailingText($0.localizedDescriptionAsKeepAlive)
@ -278,6 +276,8 @@ extension EndpointAdvancedView.OpenVPNView {
Text(L10n.Endpoint.Advanced.Openvpn.Items.RandomEndpoint.caption) Text(L10n.Endpoint.Advanced.Openvpn.Items.RandomEndpoint.caption)
.withTrailingText($0.localizedDescriptionAsRandomizeEndpoint) .withTrailingText($0.localizedDescriptionAsRandomizeEndpoint)
} }
} header: {
Text(L10n.Endpoint.Advanced.Openvpn.Sections.Other.header)
} }
} }
} }

View File

@ -46,27 +46,25 @@ extension EndpointAdvancedView {
extension EndpointAdvancedView.WireGuardView { extension EndpointAdvancedView.WireGuardView {
private var keySection: some View { private var keySection: some View {
Section( Section {
header: Text(L10n.Global.Strings.interface)
) {
themeLongContentLink(L10n.Global.Strings.privateKey, content: .constant(builder.privateKey)) themeLongContentLink(L10n.Global.Strings.privateKey, content: .constant(builder.privateKey))
themeLongContentLink(L10n.Global.Strings.publicKey, content: .constant(builder.publicKey)) themeLongContentLink(L10n.Global.Strings.publicKey, content: .constant(builder.publicKey))
} header: {
Text(L10n.Global.Strings.interface)
} }
} }
private var addressesSection: some View { private var addressesSection: some View {
Section( Section {
header: Text(L10n.Global.Strings.addresses)
) {
ForEach(builder.addresses, id: \.self, content: Text.init) ForEach(builder.addresses, id: \.self, content: Text.init)
} header: {
Text(L10n.Global.Strings.addresses)
} }
} }
private func dnsSection(configuration: WireGuard.Configuration) -> some View { private func dnsSection(configuration: WireGuard.Configuration) -> some View {
configuration.dnsSettings.map { settings in configuration.dnsSettings.map { settings in
Section( Section {
header: Text(Unlocalized.Network.dns)
) {
ForEach(settings.servers, id: \.self) { ForEach(settings.servers, id: \.self) {
Text(L10n.Global.Strings.address) Text(L10n.Global.Strings.address)
.withTrailingText($0) .withTrailingText($0)
@ -75,6 +73,8 @@ extension EndpointAdvancedView.WireGuardView {
Text(L10n.Global.Strings.domain) Text(L10n.Global.Strings.domain)
.withTrailingText($0) .withTrailingText($0)
} }
} header: {
Text(Unlocalized.Network.dns)
} }
} }
} }

View File

@ -154,12 +154,12 @@ extension EndpointView.OpenVPNView {
} }
private var addressesSection: some View { private var addressesSection: some View {
Section( Section {
header: Text(L10n.Global.Strings.addresses)
) {
filteredRemotes.map { filteredRemotes.map {
ForEach($0, content: button(forEndpoint:)) ForEach($0, content: button(forEndpoint:))
} }
} header: {
Text(L10n.Global.Strings.addresses)
} }
} }

View File

@ -104,9 +104,7 @@ extension EndpointView.WireGuardView {
} }
private func section(forPeerAt peerIndex: Int) -> some View { private func section(forPeerAt peerIndex: Int) -> some View {
Section( Section {
header: Text(L10n.Endpoint.Wireguard.Items.Peer.caption)
) {
themeLongContentLink( themeLongContentLink(
L10n.Global.Strings.publicKey, L10n.Global.Strings.publicKey,
content: .constant(builder.publicKey(ofPeer: peerIndex)) content: .constant(builder.publicKey(ofPeer: peerIndex))
@ -129,6 +127,8 @@ extension EndpointView.WireGuardView {
Text(L10n.Global.Strings.keepalive) Text(L10n.Global.Strings.keepalive)
.withTrailingText(TimeInterval($0).localizedDescriptionAsKeepAlive) .withTrailingText(TimeInterval($0).localizedDescriptionAsKeepAlive)
} }
} header: {
Text(L10n.Endpoint.Wireguard.Items.Peer.caption)
} }
} }

View File

@ -97,15 +97,15 @@ struct NetworkSettingsView: View {
extension NetworkSettingsView { extension NetworkSettingsView {
private var gatewayView: some View { private var gatewayView: some View {
Section( Section {
header: Text(L10n.NetworkSettings.Gateway.title)
) {
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticGateway.themeAnimation()) Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticGateway.themeAnimation())
if !settings.isAutomaticGateway { if !settings.isAutomaticGateway {
Toggle(Unlocalized.Network.ipv4, isOn: $settings.gateway.isDefaultIPv4) Toggle(Unlocalized.Network.ipv4, isOn: $settings.gateway.isDefaultIPv4)
Toggle(Unlocalized.Network.ipv6, isOn: $settings.gateway.isDefaultIPv6) Toggle(Unlocalized.Network.ipv6, isOn: $settings.gateway.isDefaultIPv6)
} }
} header: {
Text(L10n.NetworkSettings.Gateway.title)
} }
} }
} }
@ -116,9 +116,7 @@ extension NetworkSettingsView {
@ViewBuilder @ViewBuilder
private var dnsView: some View { private var dnsView: some View {
Section( Section {
header: Text(Unlocalized.Network.dns)
) {
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticDNS.themeAnimation()) Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticDNS.themeAnimation())
if !settings.isAutomaticDNS { if !settings.isAutomaticDNS {
@ -143,6 +141,8 @@ extension NetworkSettingsView {
EmptyView() EmptyView()
} }
} }
} header: {
Text(Unlocalized.Network.dns)
} }
if !settings.isAutomaticDNS && settings.dns.configurationType != .disabled { if !settings.isAutomaticDNS && settings.dns.configurationType != .disabled {
dnsManualServers dnsManualServers
@ -209,9 +209,7 @@ extension NetworkSettingsView {
@ViewBuilder @ViewBuilder
private var proxyView: some View { private var proxyView: some View {
Section( Section {
header: Text(L10n.Global.Strings.proxy)
) {
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticProxy.themeAnimation()) Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticProxy.themeAnimation())
if !settings.isAutomaticProxy { if !settings.isAutomaticProxy {
@ -240,6 +238,8 @@ extension NetworkSettingsView {
EmptyView() EmptyView()
} }
} }
} header: {
Text(L10n.Global.Strings.proxy)
} }
if !settings.isAutomaticProxy && settings.proxy.configurationType == .manual { if !settings.isAutomaticProxy && settings.proxy.configurationType == .manual {
proxyManualBypassDomains proxyManualBypassDomains
@ -272,9 +272,7 @@ extension NetworkSettingsView {
extension NetworkSettingsView { extension NetworkSettingsView {
private var mtuView: some View { private var mtuView: some View {
Section( Section {
header: Text(Unlocalized.Network.mtu)
) {
Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticMTU.themeAnimation()) Toggle(L10n.Global.Strings.automatic, isOn: $settings.isAutomaticMTU.themeAnimation())
if !settings.isAutomaticMTU { if !settings.isAutomaticMTU {
@ -285,6 +283,8 @@ extension NetworkSettingsView {
description: \.localizedDescriptionAsMTU description: \.localizedDescriptionAsMTU
) )
} }
} header: {
Text(Unlocalized.Network.mtu)
} }
} }
} }

View File

@ -79,27 +79,27 @@ extension OnDemandView {
@ViewBuilder @ViewBuilder
private var mainView: some View { private var mainView: some View {
if Utils.hasCellularData() { if Utils.hasCellularData() {
Section( Section {
// TODO: on-demand, restore when "trusted networks" -> "on-demand"
// header: Text(L10n.Profile.Sections.Trusted.header)
) {
Toggle(L10n.OnDemand.Items.Mobile.caption, isOn: $onDemand.withMobileNetwork) Toggle(L10n.OnDemand.Items.Mobile.caption, isOn: $onDemand.withMobileNetwork)
} header: {
// TODO: on-demand, restore when "trusted networks" -> "on-demand"
// Text(L10n.Profile.Sections.Trusted.header)
} }
Section { Section {
SSIDList(withSSIDs: $onDemand.withSSIDs) SSIDList(withSSIDs: $onDemand.withSSIDs)
} }
} else { } else {
Section( Section {
// TODO: on-demand, restore when "trusted networks" -> "on-demand"
// header: Text(L10n.Profile.Sections.Trusted.header)
) {
SSIDList(withSSIDs: $onDemand.withSSIDs) SSIDList(withSSIDs: $onDemand.withSSIDs)
} header: {
// TODO: on-demand, restore when "trusted networks" -> "on-demand"
// Text(L10n.Profile.Sections.Trusted.header)
} }
} }
Section( Section {
footer: Text(L10n.OnDemand.Sections.Policy.footer)
) {
Toggle(L10n.OnDemand.Items.Policy.caption, isOn: $onDemand.disconnectsIfNotMatching) Toggle(L10n.OnDemand.Items.Policy.caption, isOn: $onDemand.disconnectsIfNotMatching)
} footer: {
Text(L10n.OnDemand.Sections.Policy.footer)
} }
} }

View File

@ -109,16 +109,17 @@ extension PaywallView {
} }
private var productsSection: some View { private var productsSection: some View {
Section( Section {
header: Text(L10n.Paywall.title),
footer: Text(L10n.Paywall.Sections.Products.footer)
) {
if !productManager.isRefreshingProducts { if !productManager.isRefreshingProducts {
ForEach(productRowModels, id: \.product.productIdentifier, content: productRow) ForEach(productRowModels, id: \.product.productIdentifier, content: productRow)
} else { } else {
ProgressView() ProgressView()
} }
restoreRow restoreRow
} header: {
Text(L10n.Paywall.title)
} footer: {
Text(L10n.Paywall.Sections.Products.footer)
} }
} }

View File

@ -49,9 +49,7 @@ extension ProfileView {
} }
var body: some View { var body: some View {
Section( Section {
header: Text(L10n.Global.Strings.configuration)
) {
if currentProfile.value.vpnProtocols.count > 1 { if currentProfile.value.vpnProtocols.count > 1 {
themeTextPicker( themeTextPicker(
L10n.Global.Strings.protocol, L10n.Global.Strings.protocol,
@ -109,6 +107,8 @@ extension ProfileView {
onDemandRow onDemandRow
} }
} }
} header: {
Text(L10n.Global.Strings.configuration)
} }
} }

View File

@ -52,9 +52,7 @@ extension ProfileView {
} }
var body: some View { var body: some View {
Section( Section {
header: Text(L10n.Profile.Sections.Feedback.header)
) {
if isActiveProfile { if isActiveProfile {
NavigationLink { NavigationLink {
DiagnosticsView( DiagnosticsView(
@ -70,6 +68,8 @@ extension ProfileView {
} label: { } label: {
Label(Unlocalized.About.faq, systemImage: themeFAQImage) Label(Unlocalized.About.faq, systemImage: themeFAQImage)
} }
} header: {
Text(L10n.Profile.Sections.Feedback.header)
} }
} }
} }

View File

@ -36,22 +36,22 @@ extension ProfileView {
var body: some View { var body: some View {
if currentProfile.value.isProvider { if currentProfile.value.isProvider {
Section( Section {
footer: Text(L10n.Profile.Sections.VpnResolvesHostname.footer)
) {
Toggle( Toggle(
L10n.Profile.Items.VpnResolvesHostname.caption, L10n.Profile.Items.VpnResolvesHostname.caption,
isOn: $currentProfile.value.networkSettings.resolvesHostname isOn: $currentProfile.value.networkSettings.resolvesHostname
) )
} footer: {
Text(L10n.Profile.Sections.VpnResolvesHostname.footer)
} }
} }
Section( Section {
footer: Text(L10n.Profile.Sections.VpnSurvivesSleep.footer)
) {
Toggle( Toggle(
L10n.Profile.Items.VpnSurvivesSleep.caption, L10n.Profile.Items.VpnSurvivesSleep.caption,
isOn: $currentProfile.value.networkSettings.keepsAliveOnSleep isOn: $currentProfile.value.networkSettings.keepsAliveOnSleep
) )
} footer: {
Text(L10n.Profile.Sections.VpnSurvivesSleep.footer)
} }
} }
} }

View File

@ -57,12 +57,7 @@ extension ProfileView {
} }
private var mainView: some View { private var mainView: some View {
Section( Section {
header: Text(currentProvider.fullName),
footer: lastInfrastructureUpdate.map {
Text(L10n.Profile.Sections.ProviderInfrastructure.footer($0))
}
) {
NavigationLink(isActive: $isProviderLocationPresented) { NavigationLink(isActive: $isProviderLocationPresented) {
ProviderLocationView( ProviderLocationView(
currentProfile: currentProfile, currentProfile: currentProfile,
@ -85,6 +80,12 @@ extension ProfileView {
Button(action: refreshInfrastructure) { Button(action: refreshInfrastructure) {
Text(L10n.Profile.Items.Provider.Refresh.caption) Text(L10n.Profile.Items.Provider.Refresh.caption)
}.withTrailingProgress(when: isRefreshingInfrastructure) }.withTrailingProgress(when: isRefreshingInfrastructure)
} header: {
Text(currentProvider.fullName)
} footer: {
lastInfrastructureUpdate.map {
Text(L10n.Profile.Sections.ProviderInfrastructure.footer($0))
}
} }
} }

View File

@ -45,12 +45,12 @@ extension ProfileView {
var body: some View { var body: some View {
List { List {
Section( Section {
header: Text(L10n.Profile.Alerts.Rename.title)
) {
TextField(L10n.Global.Placeholders.profileName, text: $newName, onCommit: commitRenaming) TextField(L10n.Global.Placeholders.profileName, text: $newName, onCommit: commitRenaming)
.themeValidProfileName() .themeValidProfileName()
.onAppear(perform: loadCurrentName) .onAppear(perform: loadCurrentName)
} header: {
Text(L10n.Profile.Alerts.Rename.title)
} }
}.themeSecondaryView() }.themeSecondaryView()
.navigationTitle(currentProfile.value.header.name) .navigationTitle(currentProfile.value.header.name)

View File

@ -80,11 +80,7 @@ extension ProfileView {
} }
private var activeView: some View { private var activeView: some View {
Section( Section {
header: headerView,
footer: Text(L10n.Profile.Sections.Vpn.footer)
.xxxThemeTruncation()
) {
VPNToggle(rateLimit: Constants.RateLimit.vpnToggle) { VPNToggle(rateLimit: Constants.RateLimit.vpnToggle) {
// eligibility: donate intents if eligible for Siri // eligibility: donate intents if eligible for Siri
@ -105,13 +101,16 @@ extension ProfileView {
withErrors: true, withErrors: true,
dataCountIfAvailable: true dataCountIfAvailable: true
)) ))
} header: {
headerView
} footer: {
Text(L10n.Profile.Sections.Vpn.footer)
.xxxThemeTruncation()
} }
} }
private var inactiveSubview: some View { private var inactiveSubview: some View {
Section( Section {
header: headerView
) {
Button(L10n.Profile.Items.UseProfile.caption) { Button(L10n.Profile.Items.UseProfile.caption) {
Task { Task {
@ -127,14 +126,16 @@ extension ProfileView {
} }
} }
} }
} header: {
headerView
} }
} }
private var loadingView: some View { private var loadingView: some View {
Section( Section {
header: headerView
) {
ProgressView() ProgressView()
} header: {
headerView
} }
} }
} }

View File

@ -134,9 +134,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
} }
private func categorySection(_ category: ProviderCategory) -> some View { private func categorySection(_ category: ProviderCategory) -> some View {
Section( Section {
header: !category.name.isEmpty ? Text(category.name) : nil
) {
ForEach(filteredLocations(for: category)) { location in ForEach(filteredLocations(for: category)) { location in
if isEditable, #available(iOS 15, *) { if isEditable, #available(iOS 15, *) {
locationRow(location) locationRow(location)
@ -147,6 +145,8 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
locationRow(location) locationRow(location)
} }
} }
} header: {
!category.name.isEmpty ? Text(category.name) : nil
} }
} }
@ -185,9 +185,9 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
} }
private var emptyFavoritesSection: some View { private var emptyFavoritesSection: some View {
Section( Section {
footer: Text(L10n.Provider.Location.Sections.EmptyFavorites.footer) } footer: {
) { Text(L10n.Provider.Location.Sections.EmptyFavorites.footer)
} }
} }

View File

@ -69,9 +69,7 @@ struct ProviderPresetView: View {
} }
private func presetSection(_ preset: ProviderServer.Preset) -> some View { private func presetSection(_ preset: ProviderServer.Preset) -> some View {
Section( Section {
header: Text(preset.name)
) {
Button { Button {
selectedPreset = preset selectedPreset = preset
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
@ -89,6 +87,8 @@ struct ProviderPresetView: View {
).navigationTitle(preset.name) ).navigationTitle(preset.name)
} }
} }
} header: {
Text(preset.name)
} }
} }

View File

@ -49,24 +49,24 @@ extension ShortcutsView {
ZStack { ZStack {
hiddenProviderLocationLink hiddenProviderLocationLink
List { List {
Section( Section {
header: Text(Unlocalized.VPN.vpn)
) {
addConnectView addConnectView
Button(L10n.Shortcuts.Add.Items.EnableVpn.caption, action: addEnableVPN) Button(L10n.Shortcuts.Add.Items.EnableVpn.caption, action: addEnableVPN)
Button(L10n.Shortcuts.Add.Items.DisableVpn.caption, action: addDisableVPN) Button(L10n.Shortcuts.Add.Items.DisableVpn.caption, action: addDisableVPN)
} header: {
Text(Unlocalized.VPN.vpn)
} }
Section( Section {
header: Text(L10n.Shortcuts.Add.Sections.Wifi.header)
) {
Button(L10n.Shortcuts.Add.Items.TrustCurrentWifi.caption, action: addTrustWiFi) Button(L10n.Shortcuts.Add.Items.TrustCurrentWifi.caption, action: addTrustWiFi)
Button(L10n.Shortcuts.Add.Items.UntrustCurrentWifi.caption, action: addUntrustWiFi) Button(L10n.Shortcuts.Add.Items.UntrustCurrentWifi.caption, action: addUntrustWiFi)
} header: {
Text(L10n.Shortcuts.Add.Sections.Wifi.header)
} }
Section( Section {
header: Text(L10n.Shortcuts.Add.Sections.Cellular.header)
) {
Button(L10n.Shortcuts.Add.Items.TrustCellular.caption, action: addTrustCellular) Button(L10n.Shortcuts.Add.Items.TrustCellular.caption, action: addTrustCellular)
Button(L10n.Shortcuts.Add.Items.UntrustCellular.caption, action: addUntrustCellular) Button(L10n.Shortcuts.Add.Items.UntrustCellular.caption, action: addUntrustCellular)
} header: {
Text(L10n.Shortcuts.Add.Sections.Cellular.header)
} }
} }
}.navigationTitle(L10n.Shortcuts.Add.title) }.navigationTitle(L10n.Shortcuts.Add.title)

View File

@ -85,10 +85,10 @@ struct ShortcutsView: View {
} }
private var shortcutsSection: some View { private var shortcutsSection: some View {
Section( Section {
header: Text(L10n.Shortcuts.Edit.Sections.All.header)
) {
ForEach(relevantShortcuts, content: rowView) ForEach(relevantShortcuts, content: rowView)
} header: {
Text(L10n.Shortcuts.Edit.Sections.All.header)
} }
} }
@ -99,9 +99,7 @@ struct ShortcutsView: View {
} }
private var addSection: some View { private var addSection: some View {
Section( Section {
footer: Text(L10n.Shortcuts.Edit.Sections.Add.footer)
) {
NavigationLink(isActive: $isNavigationPresented) { NavigationLink(isActive: $isNavigationPresented) {
AddView( AddView(
target: target, target: target,
@ -110,6 +108,8 @@ struct ShortcutsView: View {
} label: { } label: {
Text(L10n.Shortcuts.Edit.Items.AddShortcut.caption) Text(L10n.Shortcuts.Edit.Items.AddShortcut.caption)
} }
} footer: {
Text(L10n.Shortcuts.Edit.Sections.Add.footer)
} }
} }