From 87c7d6367848d81f27b4a344241391da62c349e7 Mon Sep 17 00:00:00 2001 From: Davide Date: Sun, 13 Oct 2024 11:36:34 +0200 Subject: [PATCH] Redo provider managers lifecycle (#732) Update library with more efficient choices for interacting with the providers API. Fixes #731 --- .../xcshareddata/swiftpm/Package.resolved | 3 +- Passepartout/Library/Package.swift | 4 +- .../AppLibrary/Business/ProviderFactory.swift | 39 ------ .../Sources/AppUI/Business/AppContext.swift | 6 +- .../AppUI/L10n/PassepartoutKit+L10n.swift | 6 +- .../Library/Sources/AppUI/Mock/Mock.swift | 18 +-- .../AppUI/Views/Modules/OpenVPNView.swift | 55 +++++--- .../Views/ProfileEditor/ModuleSection.swift | 10 +- .../Provider/ProviderPanelModifier.swift | 128 +++++++++++++----- .../Views/Provider/VPNFiltersModifier.swift | 6 +- .../AppUI/Views/Provider/VPNFiltersView.swift | 47 +------ .../Provider/VPNProviderServerView.swift | 79 +---------- .../Provider/iOS/VPNFiltersModifier+iOS.swift | 2 +- .../iOS/VPNProviderServerView+iOS.swift | 10 +- .../macOS/VPNFiltersModifier+macOS.swift | 2 +- .../macOS/VPNProviderServerView+macOS.swift | 14 +- .../AppUI/Views/UI/View+Environment.swift | 5 +- .../Sources/CommonLibrary/Shared.swift | 2 +- Passepartout/Shared/Shared+App.swift | 10 +- 19 files changed, 189 insertions(+), 257 deletions(-) delete mode 100644 Passepartout/Library/Sources/AppLibrary/Business/ProviderFactory.swift diff --git a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bb4f898d..6f43a034 100644 --- a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,7 @@ "kind" : "remoteSourceControl", "location" : "git@github.com:passepartoutvpn/passepartoutkit-source", "state" : { - "revision" : "ebb142e836e9e2a8a1867c7ae3d4f44b6b96e917", - "version" : "0.9.1" + "revision" : "aeb982951e2798863e28f55081dd25e2221083e3" } }, { diff --git a/Passepartout/Library/Package.swift b/Passepartout/Library/Package.swift index 7a239ef0..a4ddb0e4 100644 --- a/Passepartout/Library/Package.swift +++ b/Passepartout/Library/Package.swift @@ -27,8 +27,8 @@ let package = Package( ) ], dependencies: [ - .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.1"), -// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "0bfd4578b71a905584cdd5c9c39ab3087521af78"), +// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.0"), + .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "aeb982951e2798863e28f55081dd25e2221083e3"), // .package(path: "../../../passepartoutkit-source"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.9.1"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"), diff --git a/Passepartout/Library/Sources/AppLibrary/Business/ProviderFactory.swift b/Passepartout/Library/Sources/AppLibrary/Business/ProviderFactory.swift deleted file mode 100644 index 9f143e53..00000000 --- a/Passepartout/Library/Sources/AppLibrary/Business/ProviderFactory.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// ProviderFactory.swift -// Passepartout -// -// Created by Davide De Rosa on 10/8/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 . -// - -import Foundation -import PassepartoutKit - -@MainActor -public final class ProviderFactory: ObservableObject { - public let providerManager: ProviderManager - - public let vpnProviderManager: VPNProviderManager - - public init(providerManager: ProviderManager, vpnProviderManager: VPNProviderManager) { - self.providerManager = providerManager - self.vpnProviderManager = vpnProviderManager - } -} diff --git a/Passepartout/Library/Sources/AppUI/Business/AppContext.swift b/Passepartout/Library/Sources/AppUI/Business/AppContext.swift index 280bb1b1..5a88428f 100644 --- a/Passepartout/Library/Sources/AppUI/Business/AppContext.swift +++ b/Passepartout/Library/Sources/AppUI/Business/AppContext.swift @@ -46,7 +46,7 @@ public final class AppContext: ObservableObject { public let registry: Registry - public let providerFactory: ProviderFactory + public let providerManager: ProviderManager private let constants: Constants @@ -59,7 +59,7 @@ public final class AppContext: ObservableObject { tunnel: Tunnel, tunnelEnvironment: TunnelEnvironment, registry: Registry, - providerFactory: ProviderFactory, + providerManager: ProviderManager, constants: Constants ) { self.iapManager = iapManager @@ -73,7 +73,7 @@ public final class AppContext: ObservableObject { interval: constants.tunnel.refreshInterval ) self.registry = registry - self.providerFactory = providerFactory + self.providerManager = providerManager self.constants = constants subscriptions = [] diff --git a/Passepartout/Library/Sources/AppUI/L10n/PassepartoutKit+L10n.swift b/Passepartout/Library/Sources/AppUI/L10n/PassepartoutKit+L10n.swift index e88c7d34..a076507b 100644 --- a/Passepartout/Library/Sources/AppUI/L10n/PassepartoutKit+L10n.swift +++ b/Passepartout/Library/Sources/AppUI/L10n/PassepartoutKit+L10n.swift @@ -145,13 +145,13 @@ extension OnDemandModule.Policy: LocalizableEntity { } extension VPNServer { - public var sortableRegion: String { - [countryCodes.first?.localizedAsRegionCode, area] + public var region: String { + [provider.countryCodes.first?.localizedAsRegionCode, provider.area] .compactMap { $0 } .joined(separator: " - ") } - public var sortableAddresses: String { + public var address: String { if let hostname { return hostname } diff --git a/Passepartout/Library/Sources/AppUI/Mock/Mock.swift b/Passepartout/Library/Sources/AppUI/Mock/Mock.swift index d913c1ca..1315a310 100644 --- a/Passepartout/Library/Sources/AppUI/Mock/Mock.swift +++ b/Passepartout/Library/Sources/AppUI/Mock/Mock.swift @@ -62,9 +62,9 @@ extension AppContext { tunnel: Tunnel(strategy: FakeTunnelStrategy(environment: env)), tunnelEnvironment: env, registry: registry, - providerFactory: ProviderFactory( - providerManager: ProviderManager(repository: InMemoryProviderRepository()), - vpnProviderManager: VPNProviderManager(repository: InMemoryVPNProviderRepository()) + providerManager: ProviderManager( + repository: InMemoryProviderRepository(), + vpnRepository: InMemoryVPNProviderRepository() ), constants: .shared ) @@ -83,12 +83,6 @@ extension ProfileManager { } } -extension ProviderFactory { - public static var mock: ProviderFactory { - AppContext.mock.providerFactory - } -} - extension ProfileProcessor { public static var mock: ProfileProcessor { AppContext.mock.profileProcessor @@ -107,6 +101,12 @@ extension ConnectionObserver { } } +extension ProviderManager { + public static var mock: ProviderManager { + AppContext.mock.providerManager + } +} + // MARK: - Profile extension Profile { diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift index 45558b80..47314499 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Modules/OpenVPNView.swift @@ -60,6 +60,9 @@ struct OpenVPNView: View { @Binding private var providerEntity: VPNEntity? + @StateObject + private var vpnProviderManager = VPNProviderManager() + init(serverConfiguration: OpenVPN.Configuration) { let module = OpenVPNModule.Builder(configurationBuilder: serverConfiguration.builder()) let editor = ProfileEditor(modules: [module]) @@ -99,41 +102,35 @@ private extension OpenVPNView { var providerModifier: some ViewModifier { ProviderPanelModifier( isRequired: draft.configurationBuilder == nil, + entityType: VPNEntity.self, providerId: $providerId, - selectedEntity: $providerEntity, - providerContent: providerContentView + providerContent: providerContentView, + onSelectProvider: onSelectProvider ) } @ViewBuilder - var contentView: some View { - credentialsView - if providerId == nil { - manualView - } + func providerContentView(providerId: ProviderID) -> some View { + providerServerRow + moduleGroup(for: accountRows) } - @ViewBuilder - func providerContentView(providerId: ProviderID, entity: VPNEntity?) -> some View { - NavigationLink(value: Subroute.providerServer(id: providerId)) { + var providerServerRow: some View { + NavigationLink(value: Subroute.providerServer) { HStack { - Text("Server") - if let entity { + Text(Strings.Global.server) + if let providerEntity { Spacer() - Text(entity.server.hostname ?? entity.server.serverId) + Text(providerEntity.server.hostname ?? providerEntity.server.serverId) .foregroundStyle(.secondary) } } } - credentialsView - } - - var credentialsView: some View { - moduleSection(for: accountRows, header: Strings.Global.account) } @ViewBuilder - var manualView: some View { + var contentView: some View { + moduleSection(for: accountRows, header: Strings.Global.account) moduleSection(for: remotesRows, header: Strings.Modules.Openvpn.remotes) if !isServerPushed { moduleSection(for: pullRows, header: Strings.Modules.Openvpn.pull) @@ -159,6 +156,20 @@ private extension OpenVPNView { } private extension OpenVPNView { + func onSelectProvider(manager: ProviderManager) { + guard let providerId else { + return + } + vpnProviderManager.view = manager.vpnView( + withId: providerId, + initialParameters: .init(sorting: [ + .localizedCountry, + .area, + .hostname + ]) + ) + } + func onSelect(server: VPNServer, preset: VPNPreset) { providerEntity = VPNEntity(server: server, preset: preset) } @@ -172,7 +183,7 @@ private extension OpenVPNView { private extension OpenVPNView { enum Subroute: Hashable { - case providerServer(id: ProviderID) + case providerServer case credentials } @@ -180,9 +191,9 @@ private extension OpenVPNView { @ViewBuilder func destination(for route: Subroute) -> some View { switch route { - case .providerServer(let id): + case .providerServer: VPNProviderServerView( - providerId: id, + manager: vpnProviderManager, onSelect: onSelect ) diff --git a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift index 19608aaa..8c8a4ed2 100644 --- a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift +++ b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift @@ -71,12 +71,18 @@ struct HashableRoute: Hashable { } extension View { - func moduleSection(for rows: [ModuleRow]?, header: String) -> some View { + func moduleGroup(for rows: [ModuleRow]?) -> some View { rows.map { rows in Group { ForEach(rows, id: \.self, content: moduleRowView) } - .themeSection(header: header) + } + } + + func moduleSection(for rows: [ModuleRow]?, header: String) -> some View { + rows.map { rows in + moduleGroup(for: rows) + .themeSection(header: header) } } } diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderPanelModifier.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderPanelModifier.swift index e012ec31..f5b28695 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderPanelModifier.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderPanelModifier.swift @@ -26,6 +26,9 @@ import AppLibrary import PassepartoutKit import SwiftUI +import UtilsLibrary + +// FIXME: #703, providers UI, reorg subviews struct ProviderPanelModifier: ViewModifier where Entity: ProviderEntity, Entity.Configuration: ProviderConfigurationIdentifiable & Codable, ProviderContent: View { @@ -36,25 +39,31 @@ struct ProviderPanelModifier: ViewModifier where Entity let isRequired: Bool + let entityType: Entity.Type + @Binding var providerId: ProviderID? - @Binding - var selectedEntity: Entity? - @ViewBuilder - let providerContent: (ProviderID, Entity?) -> ProviderContent + let providerContent: (ProviderID) -> ProviderContent + + let onSelectProvider: (ProviderManager) -> Void func body(content: Content) -> some View { - providerPicker - .task { - await refreshIndex() - } + debugChanges() + return Group { + providerPicker + .onLoad(perform: loadCurrentProvider) - if let providerId { - providerContent(providerId, selectedEntity) - } else if !isRequired { - content + if let providerId { + providerContent(providerId) + .asSectionWithTrailingContent { + refreshButton + } + .disabled(providerManager.isLoading) + } else if !isRequired { + content + } } } } @@ -70,33 +79,91 @@ private extension ProviderPanelModifier { let hasProviders = !supportedProviders.isEmpty return Picker(Strings.Global.provider, selection: $providerId) { if hasProviders { - Text(Strings.Global.none) + // FIXME: #703, providers UI + Text("Select a provider") .tag(nil as ProviderID?) ForEach(supportedProviders, id: \.id) { Text($0.description) .tag($0.id as ProviderID?) } } else { - Text(" ") // enforce constant picker height on iOS + // enforce constant picker height on iOS + Text(providerManager.isLoading ? "..." : "Unavailable") .tag(providerId) // tag always exists } } - .onChange(of: providerId) { _ in - selectedEntity = nil + .onChange(of: providerId) { newId in + Task { + if let newId { + await refreshInfrastructure(for: newId) + } + onSelectProvider(providerManager) + } } .disabled(!hasProviders) } + + var refreshButton: some View { + Button { + guard let providerId else { + return + } + Task { + await refreshInfrastructure(for: providerId) + } + } label: { + HStack { + Text(Strings.Views.Provider.Vpn.refreshInfrastructure) +#if os(iOS) + if let providerId, providerManager.pendingServices.contains(.provider(providerId)) { + Spacer() + ProgressView() + } +#endif + } + } + .disabled(providerManager.isLoading || providerId == nil) + } } private extension ProviderPanelModifier { + func loadCurrentProvider() { + Task { + if let providerId { + async let index = await refreshIndex() + async let provider = await refreshInfrastructure(for: providerId) + _ = await (index, provider) + onSelectProvider(providerManager) + } else { + await refreshIndex() + } + } + } - // FIXME: #707, fetch bundled providers on launch - // FIXME: #704, rate-limit fetch - func refreshIndex() async { + @discardableResult + func refreshIndex() async -> Bool { do { try await providerManager.fetchIndex(from: apis) + return true } catch { pp_log(.app, .error, "Unable to fetch index: \(error)") + return false + } + } + + @discardableResult + func refreshInfrastructure(for providerId: ProviderID) async -> Bool { + do { + try await providerManager.fetchVPNInfrastructure( + from: apis, + for: providerId, + ofType: Entity.Configuration.self + ) + return true + } catch { + // FIXME: #703, alert unable to refresh infrastructure + pp_log(.app, .error, "Unable to refresh infrastructure: \(error)") + return false } } } @@ -110,26 +177,17 @@ private extension ProviderID { // MARK: - Preview #Preview { - @State - var providerId: ProviderID? = .hideme - - @State - var vpnEntity: VPNEntity? - - return List { + List { EmptyView() .modifier(ProviderPanelModifier( apis: [API.bundled], isRequired: false, - providerId: $providerId, - selectedEntity: $vpnEntity, - providerContent: { _, entity in - HStack { - Text("Server") - Spacer() - Text(entity?.server.serverId ?? "None") - } - } + entityType: VPNEntity.self, + providerId: .constant(.hideme), + providerContent: { _ in + Text("Server") + }, + onSelectProvider: { _ in } )) } .withMockEnvironment() diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersModifier.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersModifier.swift index 16a2daed..16b4c00c 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersModifier.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersModifier.swift @@ -31,16 +31,12 @@ struct VPNFiltersModifier: ViewModifier where Configuration: Deco @ObservedObject var manager: VPNProviderManager - let providerId: ProviderID - - let onRefresh: () async -> Void - @State var isFiltersPresented = false func body(content: Content) -> some View { contentView(with: content) - .onChange(of: manager.filters) { _ in + .onChange(of: manager.parameters.filters) { _ in Task { await manager.applyFilters() } diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersView.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersView.swift index 5d9ded5c..369cbae6 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNFiltersView.swift @@ -34,10 +34,6 @@ struct VPNFiltersView: View where Configuration: Decodable { @ObservedObject var manager: VPNProviderManager - let providerId: ProviderID - - let onRefresh: () async -> Void - @State private var isRefreshing = false @@ -54,22 +50,16 @@ struct VPNFiltersView: View where Configuration: Decodable { HStack { Spacer() clearFiltersButton - refreshButton } #endif } -#if os(iOS) - Section { - refreshButton - } -#endif } } } private extension VPNFiltersView { var categoryPicker: some View { - Picker("Category", selection: $manager.filters.categoryName) { + Picker("Category", selection: $manager.parameters.filters.categoryName) { Text("Any") .tag(nil as String?) ForEach(categories, id: \.self) { @@ -80,7 +70,7 @@ private extension VPNFiltersView { } var countryPicker: some View { - Picker("Country", selection: $manager.filters.countryCode) { + Picker("Country", selection: $manager.parameters.filters.countryCode) { Text("Any") .tag(nil as String?) ForEach(countries, id: \.code) { @@ -92,8 +82,8 @@ private extension VPNFiltersView { @ViewBuilder var presetPicker: some View { - if manager.anyPresets.count > 1 { - Picker("Preset", selection: $manager.filters.presetId) { + if manager.allPresets.count > 1 { + Picker("Preset", selection: $manager.parameters.filters.presetId) { Text("Any") .tag(nil as String?) ForEach(presets, id: \.presetId) { @@ -111,27 +101,6 @@ private extension VPNFiltersView { } } } - - var refreshButton: some View { - Button { - Task { - isRefreshing = true - await onRefresh() - isRefreshing = false - } - } label: { - HStack { - Text(Strings.Views.Provider.Vpn.refreshInfrastructure) -#if os(iOS) - if isRefreshing { - Spacer() - ProgressView() - } -#endif - } - } - .disabled(isRefreshing) - } } private extension VPNFiltersView { @@ -149,7 +118,7 @@ private extension VPNFiltersView { let allCodes = manager .allServers .values - .flatMap(\.countryCodes) + .flatMap(\.provider.countryCodes) return Set(allCodes) .map { @@ -171,10 +140,6 @@ private extension VPNFiltersView { #Preview { NavigationStack { - VPNFiltersView( - manager: ProviderFactory.mock.vpnProviderManager, - providerId: .hideme, - onRefresh: {} - ) + VPNFiltersView(manager: VPNProviderManager()) } } diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift index 6ef9b0aa..1a4e7f1b 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/VPNProviderServerView.swift @@ -29,66 +29,24 @@ import SwiftUI struct VPNProviderServerView: View where Configuration: ProviderConfigurationIdentifiable & Hashable & Codable { - @EnvironmentObject - private var providerManager: ProviderManager - - @EnvironmentObject - private var vpnProviderManager: VPNProviderManager - @Environment(\.dismiss) private var dismiss - var apis: [APIMapper] = API.shared - - let providerId: ProviderID + @ObservedObject + var manager: VPNProviderManager let onSelect: (_ server: VPNServer, _ preset: VPNPreset) -> Void - @State - private var isLoading = true - - @State - var sortOrder = [ - KeyPathComparator(\VPNServer.sortableRegion) - ] - - @State - var sortedServers: [VPNServer] = [] - - // FIXME: #703, flickers on appear var body: some View { serversView - .modifier(VPNFiltersModifier( - manager: vpnProviderManager, - providerId: providerId, - onRefresh: { - await refreshInfrastructure(for: providerId) - } - )) - .themeAnimation(on: isLoading, category: .providers) - .navigationTitle(providerMetadata?.description ?? Strings.Global.servers) - .task { - await loadInfrastructure(for: providerId) - } - .onReceive(vpnProviderManager.$filteredServers, perform: onFilteredServers) - } -} - -private extension VPNProviderServerView { - var providerMetadata: ProviderMetadata? { - providerManager.metadata(withId: providerId) + .modifier(VPNFiltersModifier(manager: manager)) + .navigationTitle(Strings.Global.servers) } } // MARK: - Actions extension VPNProviderServerView { - func onFilteredServers(_ servers: [String: VPNServer]) { - sortedServers = servers - .values - .sorted(using: sortOrder) - } - func selectServer(_ server: VPNServer) { guard let preset = compatiblePreset(with: server) else { // FIXME: #703, alert select a preset @@ -101,7 +59,7 @@ extension VPNProviderServerView { private extension VPNProviderServerView { func compatiblePreset(with server: VPNServer) -> VPNPreset? { - vpnProviderManager + manager .presets(ofType: Configuration.self) .first { if let supportedIds = server.provider.supportedPresetIds { @@ -110,38 +68,13 @@ private extension VPNProviderServerView { return true } } - - func loadInfrastructure(for providerId: ProviderID) async { - await vpnProviderManager.setProvider(providerId) - if await vpnProviderManager.lastUpdated() == nil { - await refreshInfrastructure(for: providerId) - } - isLoading = false - } - - // FIXME: #704, rate-limit fetch - func refreshInfrastructure(for providerId: ProviderID) async { - do { - isLoading = true - try await vpnProviderManager.fetchInfrastructure( - from: apis, - for: providerId, - ofType: Configuration.self - ) - isLoading = false - } catch { - // FIXME: #703, alert unable to refresh infrastructure - pp_log(.app, .error, "Unable to refresh infrastructure: \(error)") - isLoading = false - } - } } // MARK: - Preview #Preview { NavigationStack { - VPNProviderServerView(apis: [API.bundled], providerId: .protonvpn) { _, _ in + VPNProviderServerView(manager: VPNProviderManager()) { _, _ in } } .withMockEnvironment() diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNFiltersModifier+iOS.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNFiltersModifier+iOS.swift index 9835b676..1fb90575 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNFiltersModifier+iOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNFiltersModifier+iOS.swift @@ -43,7 +43,7 @@ extension VPNFiltersModifier { } .themeModal(isPresented: $isFiltersPresented) { NavigationStack { - VPNFiltersView(manager: manager, providerId: providerId, onRefresh: onRefresh) + VPNFiltersView(manager: manager) .navigationTitle("Filters") .navigationBarTitleDisplayMode(.inline) } diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNProviderServerView+iOS.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNProviderServerView+iOS.swift index b4f8f7bf..3bbfcfe1 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNProviderServerView+iOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/iOS/VPNProviderServerView+iOS.swift @@ -30,12 +30,12 @@ import SwiftUI // FIXME: #703, providers UI extension VPNProviderServerView { + + @ViewBuilder var serversView: some View { - sortedServers.nilIfEmpty.map { servers in - ForEach(sortedServers) { server in - Button("\(server.hostname ?? server.id) \(server.countryCodes)") { - selectServer(server) - } + ForEach(manager.filteredServers) { server in + Button("\(server.hostname ?? server.id) \(server.provider.countryCodes)") { + selectServer(server) } } } diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNFiltersModifier+macOS.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNFiltersModifier+macOS.swift index 8ac9e1db..1646cebb 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNFiltersModifier+macOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNFiltersModifier+macOS.swift @@ -30,7 +30,7 @@ import SwiftUI extension VPNFiltersModifier { func contentView(with content: Content) -> some View { VStack { - VPNFiltersView(manager: manager, providerId: providerId, onRefresh: onRefresh) + VPNFiltersView(manager: manager) .padding() content } diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNProviderServerView+macOS.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNProviderServerView+macOS.swift index 29116da1..2eafd2e4 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNProviderServerView+macOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/macOS/VPNProviderServerView+macOS.swift @@ -30,14 +30,18 @@ import SwiftUI // FIXME: #703, providers UI extension VPNProviderServerView { + + @ViewBuilder var serversView: some View { - Table(sortedServers, sortOrder: $sortOrder) { - TableColumn("Region", value: \.sortableRegion) - .width(max: 200.0) + Table(manager.filteredServers) { + TableColumn("Region") { server in + Text(server.region) + } + .width(max: 200.0) - TableColumn("Address", value: \.sortableAddresses) + TableColumn("Address", value: \.address) - TableColumn("", value: \.serverId) { server in + TableColumn("") { server in Button { selectServer(server) } label: { diff --git a/Passepartout/Library/Sources/AppUI/Views/UI/View+Environment.swift b/Passepartout/Library/Sources/AppUI/Views/UI/View+Environment.swift index 55caf1ad..a9642c42 100644 --- a/Passepartout/Library/Sources/AppUI/Views/UI/View+Environment.swift +++ b/Passepartout/Library/Sources/AppUI/Views/UI/View+Environment.swift @@ -31,10 +31,9 @@ extension View { public func withEnvironment(from context: AppContext, theme: Theme) -> some View { environmentObject(theme) .environmentObject(context.iapManager) - .environmentObject(context.connectionObserver) - .environmentObject(context.providerFactory.providerManager) - .environmentObject(context.providerFactory.vpnProviderManager) .environmentObject(context.profileProcessor) + .environmentObject(context.connectionObserver) + .environmentObject(context.providerManager) } public func withMockEnvironment() -> some View { diff --git a/Passepartout/Library/Sources/CommonLibrary/Shared.swift b/Passepartout/Library/Sources/CommonLibrary/Shared.swift index 82518295..43c7b44b 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Shared.swift +++ b/Passepartout/Library/Sources/CommonLibrary/Shared.swift @@ -63,7 +63,7 @@ extension API { #if DEBUG [API.bundled] #else - [API.remoteThenBundled] + API.remoteThenBundled #endif } diff --git a/Passepartout/Shared/Shared+App.swift b/Passepartout/Shared/Shared+App.swift index b6747de0..9d6474fb 100644 --- a/Passepartout/Shared/Shared+App.swift +++ b/Passepartout/Shared/Shared+App.swift @@ -40,7 +40,7 @@ extension AppContext { tunnel: .shared, tunnelEnvironment: .shared, registry: .shared, - providerFactory: .shared, + providerManager: .shared, constants: .shared ) } @@ -208,10 +208,10 @@ private extension ProfileManager { // MARK: - // FIXME: #705, store providers to Core Data -extension ProviderFactory { - static let shared = ProviderFactory( - providerManager: ProviderManager(repository: InMemoryProviderRepository()), - vpnProviderManager: VPNProviderManager(repository: InMemoryVPNProviderRepository()) +extension ProviderManager { + static let shared = ProviderManager( + repository: InMemoryProviderRepository(), + vpnRepository: InMemoryVPNProviderRepository() ) }