mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2024-12-24 10:22:42 +00:00
ee8ef34f06
A NavigationLink in VPNProviderContentModifier raised a few questions about the navigation approach in module views. It turned out that having a Binding to a local ObservedObject (ProfileEditor) is a recipe for disaster. Therefore: - We don't need a binding to the editor module (the draft), because by doing so we end up _observing_ the same changes from two properties, the binding and the editor. This seems to drive SwiftUI crazy and freezes the app once we navigate from the module to another view (e.g. in OpenVPN the credentials or the provider server). Use the module binding as a shortcut, but do not assign the binding to the view to avoid unnecessary observation. - Keep .navigationDestination() in the module view, and pass a known destination to VPNProviderContentModifier. This will save the modifier from creating a nested NavigationLink destination. The VPNProviderServerView is now openly instantiated by the module view when such destination is triggered by the NavigationLink in the modifier. - Do not implicitly dismiss VPNProviderServerView on selection, let the presenter take care. In order to do so, we add a .navigationPath environment key through which the module view can modify the current navigation stack.
112 lines
3.3 KiB
Swift
112 lines
3.3 KiB
Swift
//
|
|
// VPNProviderContentModifier.swift
|
|
// Passepartout
|
|
//
|
|
// Created by Davide De Rosa on 10/7/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 AppLibrary
|
|
import PassepartoutKit
|
|
import SwiftUI
|
|
import UtilsLibrary
|
|
|
|
struct VPNProviderContentModifier<Configuration, Destination, ProviderRows>: ViewModifier where Configuration: ProviderConfigurationIdentifiable & Codable, Destination: Hashable, ProviderRows: View {
|
|
|
|
var apis: [APIMapper] = API.shared
|
|
|
|
@Binding
|
|
var providerId: ProviderID?
|
|
|
|
@Binding
|
|
var selectedEntity: VPNEntity<Configuration>?
|
|
|
|
let isRequired: Bool
|
|
|
|
let entityDestination: Destination
|
|
|
|
@ViewBuilder
|
|
let providerRows: ProviderRows
|
|
|
|
func body(content: Content) -> some View {
|
|
debugChanges()
|
|
return content
|
|
.modifier(ProviderContentModifier(
|
|
apis: apis,
|
|
providerId: $providerId,
|
|
entityType: VPNEntity<Configuration>.self,
|
|
isRequired: isRequired,
|
|
providerRows: {
|
|
providerEntityRow
|
|
providerRows
|
|
},
|
|
onSelectProvider: onSelectProvider
|
|
))
|
|
}
|
|
}
|
|
|
|
private extension VPNProviderContentModifier {
|
|
var providerEntityRow: some View {
|
|
NavigationLink(value: entityDestination) {
|
|
HStack {
|
|
Text(Strings.Global.server)
|
|
if let selectedEntity {
|
|
Spacer()
|
|
Text(selectedEntity.server.hostname ?? selectedEntity.server.serverId)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension VPNProviderContentModifier {
|
|
func onSelectProvider(manager: ProviderManager, providerId: ProviderID?, isInitial: Bool) {
|
|
if !isInitial {
|
|
selectedEntity = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
List {
|
|
EmptyView()
|
|
.modifier(VPNProviderContentModifier(
|
|
apis: [API.bundled],
|
|
providerId: .constant(.hideme),
|
|
selectedEntity: .constant(nil as VPNEntity<OpenVPN.Configuration>?),
|
|
isRequired: false,
|
|
entityDestination: "Destination",
|
|
providerRows: {
|
|
Text("Other")
|
|
}
|
|
))
|
|
}
|
|
.navigationTitle("Preview")
|
|
.navigationDestination(for: String.self) {
|
|
Text($0)
|
|
}
|
|
}
|
|
.withMockEnvironment()
|
|
}
|