Revisit overuse of EnvironmentObject (#794)
The biggest issue is the hidden and scattered use of both Tunnel and ConnectionObserver. Only use the latter, and rename it to ExtendedTunnel for being now a full wrapper around Tunnel (e.g. for .connectionStatus). In general, restrict the use of EnvironmentObject to: - Theme - IAPManager - ProfileProcessor - ProviderManager Always be explicit about: - ProfileManager - ExtendedTunnel Contextually, move some UI entities to the base AppUI target.
This commit is contained in:
parent
33d238270e
commit
590b2790fa
|
@ -83,10 +83,14 @@ extension PassepartoutApp {
|
||||||
.withEnvironment(from: context, theme: theme)
|
.withEnvironment(from: context, theme: theme)
|
||||||
}
|
}
|
||||||
MenuBarExtra {
|
MenuBarExtra {
|
||||||
AppMenu()
|
AppMenu(
|
||||||
|
profileManager: context.profileManager,
|
||||||
|
profileProcessor: context.profileProcessor,
|
||||||
|
tunnel: context.tunnel
|
||||||
|
)
|
||||||
.withEnvironment(from: context, theme: theme)
|
.withEnvironment(from: context, theme: theme)
|
||||||
} label: {
|
} label: {
|
||||||
AppMenuImage(connectionObserver: context.connectionObserver)
|
AppMenuImage(tunnel: context.tunnel)
|
||||||
.environmentObject(theme)
|
.environmentObject(theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,12 +38,10 @@ public final class AppContext: ObservableObject {
|
||||||
|
|
||||||
public let profileProcessor: ProfileProcessor
|
public let profileProcessor: ProfileProcessor
|
||||||
|
|
||||||
public let tunnel: Tunnel
|
public let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
public let tunnelEnvironment: TunnelEnvironment
|
public let tunnelEnvironment: TunnelEnvironment
|
||||||
|
|
||||||
public let connectionObserver: ConnectionObserver
|
|
||||||
|
|
||||||
public let registry: Registry
|
public let registry: Registry
|
||||||
|
|
||||||
public let providerManager: ProviderManager
|
public let providerManager: ProviderManager
|
||||||
|
@ -65,9 +63,8 @@ public final class AppContext: ObservableObject {
|
||||||
self.iapManager = iapManager
|
self.iapManager = iapManager
|
||||||
self.profileManager = profileManager
|
self.profileManager = profileManager
|
||||||
self.profileProcessor = profileProcessor
|
self.profileProcessor = profileProcessor
|
||||||
self.tunnel = tunnel
|
|
||||||
self.tunnelEnvironment = tunnelEnvironment
|
self.tunnelEnvironment = tunnelEnvironment
|
||||||
connectionObserver = ConnectionObserver(
|
self.tunnel = ExtendedTunnel(
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
environment: tunnelEnvironment,
|
environment: tunnelEnvironment,
|
||||||
interval: constants.tunnel.refreshInterval
|
interval: constants.tunnel.refreshInterval
|
||||||
|
@ -79,7 +76,7 @@ public final class AppContext: ObservableObject {
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
await iapManager.reloadReceipt()
|
await iapManager.reloadReceipt()
|
||||||
connectionObserver.observeObjects()
|
self.tunnel.observeObjects()
|
||||||
profileManager.observeObjects()
|
profileManager.observeObjects()
|
||||||
observeObjects()
|
observeObjects()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// ConnectionObserver.swift
|
// ExtendedTunnel.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 9/7/24.
|
// Created by Davide De Rosa on 9/7/24.
|
||||||
|
@ -29,8 +29,8 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class ConnectionObserver: ObservableObject {
|
public final class ExtendedTunnel: ObservableObject {
|
||||||
public let tunnel: Tunnel
|
private let tunnel: Tunnel
|
||||||
|
|
||||||
private let environment: TunnelEnvironment
|
private let environment: TunnelEnvironment
|
||||||
|
|
||||||
|
@ -40,14 +40,10 @@ public final class ConnectionObserver: ObservableObject {
|
||||||
environment.environmentValue(forKey: key)
|
environment.environmentValue(forKey: key)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var connectionStatus: ConnectionStatus? {
|
|
||||||
value(forKey: TunnelEnvironmentKeys.connectionStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
public private(set) var lastErrorCode: PassepartoutError.Code? {
|
public private(set) var lastErrorCode: PassepartoutError.Code? {
|
||||||
didSet {
|
didSet {
|
||||||
pp_log(.app, .info, "ConnectionObserver.lastErrorCode -> \(lastErrorCode?.rawValue ?? "nil")")
|
pp_log(.app, .info, "ExtendedTunnel.lastErrorCode -> \(lastErrorCode?.rawValue ?? "nil")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,3 +99,63 @@ public final class ConnectionObserver: ObservableObject {
|
||||||
.store(in: &subscriptions)
|
.store(in: &subscriptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ExtendedTunnel {
|
||||||
|
public var status: TunnelStatus {
|
||||||
|
tunnel.status
|
||||||
|
}
|
||||||
|
|
||||||
|
public var connectionStatus: TunnelStatus {
|
||||||
|
var status = tunnel.status
|
||||||
|
if status == .active, let environmentConnectionStatus {
|
||||||
|
if environmentConnectionStatus == .connected {
|
||||||
|
status = .active
|
||||||
|
} else {
|
||||||
|
status = .activating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
private var environmentConnectionStatus: ConnectionStatus? {
|
||||||
|
value(forKey: TunnelEnvironmentKeys.connectionStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ExtendedTunnel {
|
||||||
|
public var currentProfile: TunnelCurrentProfile? {
|
||||||
|
tunnel.currentProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
public func prepare(purge: Bool) async throws {
|
||||||
|
try await tunnel.prepare(purge: purge)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func install(_ profile: Profile, processor: ProfileProcessor) async throws {
|
||||||
|
let newProfile = try processor.processed(profile)
|
||||||
|
try await tunnel.install(newProfile, connect: false, title: processor.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func connect(with profile: Profile, processor: ProfileProcessor) async throws {
|
||||||
|
let newProfile = try processor.processed(profile)
|
||||||
|
try await tunnel.install(newProfile, connect: true, title: processor.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func disconnect() async throws {
|
||||||
|
try await tunnel.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func currentLog(parameters: Constants.Log) async -> [String] {
|
||||||
|
let output = try? await tunnel.sendMessage(.localLog(
|
||||||
|
sinceLast: parameters.sinceLast,
|
||||||
|
maxLevel: parameters.maxLevel
|
||||||
|
))
|
||||||
|
switch output {
|
||||||
|
case .debugLog(let log):
|
||||||
|
return log.lines.map(parameters.formatter.formattedLine)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,23 +27,26 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class InteractiveManager: ObservableObject {
|
public final class InteractiveManager: ObservableObject {
|
||||||
typealias CompletionBlock = (Profile) async throws -> Void
|
public typealias CompletionBlock = (Profile) async throws -> Void
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var isPresented = false
|
public var isPresented = false
|
||||||
|
|
||||||
private(set) var editor = ProfileEditor()
|
public private(set) var editor = ProfileEditor()
|
||||||
|
|
||||||
private var onComplete: CompletionBlock?
|
private var onComplete: CompletionBlock?
|
||||||
|
|
||||||
func present(with profile: Profile, onComplete: CompletionBlock?) {
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func present(with profile: Profile, onComplete: CompletionBlock?) {
|
||||||
editor = ProfileEditor(profile: profile)
|
editor = ProfileEditor(profile: profile)
|
||||||
self.onComplete = onComplete
|
self.onComplete = onComplete
|
||||||
isPresented = true
|
isPresented = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func complete() async throws {
|
public func complete() async throws {
|
||||||
isPresented = false
|
isPresented = false
|
||||||
let newProfile = try editor.build()
|
let newProfile = try editor.build()
|
||||||
try await onComplete?(newProfile)
|
try await onComplete?(newProfile)
|
|
@ -1,55 +0,0 @@
|
||||||
//
|
|
||||||
// Tunnel+Extensions.swift
|
|
||||||
// Passepartout
|
|
||||||
//
|
|
||||||
// Created by Davide De Rosa on 8/11/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 CommonLibrary
|
|
||||||
import Foundation
|
|
||||||
import PassepartoutKit
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
extension Tunnel {
|
|
||||||
public func install(_ profile: Profile, processor: ProfileProcessor) async throws {
|
|
||||||
let newProfile = try processor.processed(profile)
|
|
||||||
try await install(newProfile, connect: false, title: processor.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func connect(with profile: Profile, processor: ProfileProcessor) async throws {
|
|
||||||
let newProfile = try processor.processed(profile)
|
|
||||||
try await install(newProfile, connect: true, title: processor.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func currentLog(parameters: Constants.Log) async -> [String] {
|
|
||||||
let output = try? await sendMessage(.localLog(
|
|
||||||
sinceLast: parameters.sinceLast,
|
|
||||||
maxLevel: parameters.maxLevel
|
|
||||||
))
|
|
||||||
switch output {
|
|
||||||
case .debugLog(let log):
|
|
||||||
return log.lines.map(parameters.formatter.formattedLine)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,12 +30,9 @@ import SwiftUI
|
||||||
extension View {
|
extension View {
|
||||||
public func withEnvironment(from context: AppContext, theme: Theme) -> some View {
|
public func withEnvironment(from context: AppContext, theme: Theme) -> some View {
|
||||||
environmentObject(theme)
|
environmentObject(theme)
|
||||||
.environmentObject(context.connectionObserver)
|
|
||||||
.environmentObject(context.iapManager)
|
.environmentObject(context.iapManager)
|
||||||
.environmentObject(context.profileManager)
|
|
||||||
.environmentObject(context.profileProcessor)
|
.environmentObject(context.profileProcessor)
|
||||||
.environmentObject(context.providerManager)
|
.environmentObject(context.providerManager)
|
||||||
.environmentObject(context.tunnel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withMockEnvironment() -> some View {
|
public func withMockEnvironment() -> some View {
|
||||||
|
|
|
@ -88,18 +88,12 @@ extension ProfileProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Tunnel {
|
extension ExtendedTunnel {
|
||||||
public static var mock: Tunnel {
|
public static var mock: ExtendedTunnel {
|
||||||
AppContext.mock.tunnel
|
AppContext.mock.tunnel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConnectionObserver {
|
|
||||||
public static var mock: ConnectionObserver {
|
|
||||||
AppContext.mock.connectionObserver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ProviderManager {
|
extension ProviderManager {
|
||||||
public static var mock: ProviderManager {
|
public static var mock: ProviderManager {
|
||||||
AppContext.mock.providerManager
|
AppContext.mock.providerManager
|
||||||
|
|
|
@ -30,7 +30,7 @@ import PassepartoutKit
|
||||||
public protocol AppCoordinatorConforming {
|
public protocol AppCoordinatorConforming {
|
||||||
init(
|
init(
|
||||||
profileManager: ProfileManager,
|
profileManager: ProfileManager,
|
||||||
tunnel: Tunnel,
|
tunnel: ExtendedTunnel,
|
||||||
registry: Registry
|
registry: Registry
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// TunnelContextProviding.swift
|
|
||||||
// Passepartout
|
|
||||||
//
|
|
||||||
// Created by Davide De Rosa on 9/5/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 Foundation
|
|
||||||
import PassepartoutKit
|
|
||||||
|
|
||||||
public protocol TunnelContextProviding {
|
|
||||||
var connectionObserver: ConnectionObserver { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
extension TunnelContextProviding {
|
|
||||||
public var tunnelConnectionStatus: TunnelStatus {
|
|
||||||
var status = connectionObserver.tunnel.status
|
|
||||||
if status == .active, let connectionStatus = connectionObserver.connectionStatus {
|
|
||||||
if connectionStatus == .connected {
|
|
||||||
status = .active
|
|
||||||
} else {
|
|
||||||
status = .activating
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,7 +30,7 @@ import PassepartoutKit
|
||||||
public protocol TunnelInstallationProviding {
|
public protocol TunnelInstallationProviding {
|
||||||
var profileManager: ProfileManager { get }
|
var profileManager: ProfileManager { get }
|
||||||
|
|
||||||
var tunnel: Tunnel { get }
|
var tunnel: ExtendedTunnel { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|
|
@ -27,32 +27,34 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ConnectionStatusView: View, TunnelContextProviding, ThemeProviding {
|
public struct ConnectionStatusView: View, ThemeProviding {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var theme: Theme
|
public var theme: Theme
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
var connectionObserver: ConnectionObserver
|
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tunnel: Tunnel
|
private var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
var body: some View {
|
public init(tunnel: ExtendedTunnel) {
|
||||||
|
self.tunnel = tunnel
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
Text(statusDescription)
|
Text(statusDescription)
|
||||||
.foregroundStyle(tunnelStatusColor)
|
.font(.headline)
|
||||||
|
.foregroundStyle(tunnel.statusColor(theme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ConnectionStatusView {
|
private extension ConnectionStatusView {
|
||||||
var statusDescription: String {
|
var statusDescription: String {
|
||||||
if let lastErrorCode = connectionObserver.lastErrorCode {
|
if let lastErrorCode = tunnel.lastErrorCode {
|
||||||
return lastErrorCode.localizedDescription
|
return lastErrorCode.localizedDescription
|
||||||
}
|
}
|
||||||
let status = tunnelConnectionStatus
|
let status = tunnel.connectionStatus
|
||||||
switch status {
|
switch status {
|
||||||
case .active:
|
case .active:
|
||||||
if let dataCount = connectionObserver.dataCount {
|
if let dataCount = tunnel.dataCount {
|
||||||
let down = dataCount.received.descriptionAsDataUnit
|
let down = dataCount.received.descriptionAsDataUnit
|
||||||
let up = dataCount.sent.descriptionAsDataUnit
|
let up = dataCount.sent.descriptionAsDataUnit
|
||||||
return "↓\(down) ↑\(up)"
|
return "↓\(down) ↑\(up)"
|
||||||
|
@ -75,7 +77,7 @@ private extension ConnectionStatusView {
|
||||||
#Preview("Connected") {
|
#Preview("Connected") {
|
||||||
ConnectionStatusView(tunnel: .mock)
|
ConnectionStatusView(tunnel: .mock)
|
||||||
.task {
|
.task {
|
||||||
try? await Tunnel.mock.connect(with: .mock, processor: .mock)
|
try? await ExtendedTunnel.mock.connect(with: .mock, processor: .mock)
|
||||||
}
|
}
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
|
@ -94,7 +96,7 @@ private extension ConnectionStatusView {
|
||||||
}
|
}
|
||||||
return ConnectionStatusView(tunnel: .mock)
|
return ConnectionStatusView(tunnel: .mock)
|
||||||
.task {
|
.task {
|
||||||
try? await Tunnel.mock.connect(with: profile, processor: .mock)
|
try? await ExtendedTunnel.mock.connect(with: profile, processor: .mock)
|
||||||
}
|
}
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// TunnelContextProviding+Theme.swift
|
// ExtendedTunnel+Theme.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 9/6/24.
|
// Created by Davide De Rosa on 9/6/24.
|
||||||
|
@ -26,11 +26,12 @@
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
extension ExtendedTunnel {
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension TunnelContextProviding where Self: ThemeProviding {
|
public func statusColor(_ theme: Theme) -> Color {
|
||||||
var tunnelStatusColor: Color {
|
if lastErrorCode != nil {
|
||||||
if connectionObserver.lastErrorCode != nil {
|
switch status {
|
||||||
switch connectionObserver.tunnel.status {
|
|
||||||
case .inactive:
|
case .inactive:
|
||||||
return theme.inactiveColor
|
return theme.inactiveColor
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ extension TunnelContextProviding where Self: ThemeProviding {
|
||||||
return theme.errorColor
|
return theme.errorColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch tunnelConnectionStatus {
|
switch connectionStatus {
|
||||||
case .active:
|
case .active:
|
||||||
return theme.activeColor
|
return theme.activeColor
|
||||||
|
|
|
@ -27,18 +27,15 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct TunnelToggleButton<Label>: View, TunnelContextProviding, ThemeProviding where Label: View {
|
public struct TunnelToggleButton<Label>: View, ThemeProviding where Label: View {
|
||||||
enum Style {
|
public enum Style {
|
||||||
case plain
|
case plain
|
||||||
|
|
||||||
case color
|
case color
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var theme: Theme
|
public var theme: Theme
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
var connectionObserver: ConnectionObserver
|
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var iapManager: IAPManager
|
private var iapManager: IAPManager
|
||||||
|
@ -46,25 +43,45 @@ struct TunnelToggleButton<Label>: View, TunnelContextProviding, ThemeProviding w
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var profileProcessor: ProfileProcessor
|
private var profileProcessor: ProfileProcessor
|
||||||
|
|
||||||
var style: Style = .plain
|
private let style: Style
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tunnel: Tunnel
|
private var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let profile: Profile?
|
private let profile: Profile?
|
||||||
|
|
||||||
@Binding
|
@Binding
|
||||||
var nextProfileId: Profile.ID?
|
private var nextProfileId: Profile.ID?
|
||||||
|
|
||||||
let interactiveManager: InteractiveManager
|
private let interactiveManager: InteractiveManager
|
||||||
|
|
||||||
let errorHandler: ErrorHandler
|
private let errorHandler: ErrorHandler
|
||||||
|
|
||||||
var onProviderEntityRequired: ((Profile) -> Void)?
|
private let onProviderEntityRequired: ((Profile) -> Void)?
|
||||||
|
|
||||||
let label: (Bool) -> Label
|
private let label: (Bool) -> Label
|
||||||
|
|
||||||
var body: some View {
|
public init(
|
||||||
|
style: Style = .plain,
|
||||||
|
tunnel: ExtendedTunnel,
|
||||||
|
profile: Profile?,
|
||||||
|
nextProfileId: Binding<Profile.ID?>,
|
||||||
|
interactiveManager: InteractiveManager,
|
||||||
|
errorHandler: ErrorHandler,
|
||||||
|
onProviderEntityRequired: ((Profile) -> Void)? = nil,
|
||||||
|
label: @escaping (Bool) -> Label
|
||||||
|
) {
|
||||||
|
self.style = style
|
||||||
|
self.tunnel = tunnel
|
||||||
|
self.profile = profile
|
||||||
|
_nextProfileId = nextProfileId
|
||||||
|
self.interactiveManager = interactiveManager
|
||||||
|
self.errorHandler = errorHandler
|
||||||
|
self.onProviderEntityRequired = onProviderEntityRequired
|
||||||
|
self.label = label
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
Button(action: tryPerform) {
|
Button(action: tryPerform) {
|
||||||
label(canConnect)
|
label(canConnect)
|
||||||
}
|
}
|
||||||
|
@ -92,7 +109,7 @@ private extension TunnelToggleButton {
|
||||||
return .primary
|
return .primary
|
||||||
|
|
||||||
case .color:
|
case .color:
|
||||||
return tunnelStatusColor
|
return tunnel.statusColor(theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,7 +39,7 @@ extension Issue {
|
||||||
|
|
||||||
let purchasedProducts: Set<AppProduct>
|
let purchasedProducts: Set<AppProduct>
|
||||||
|
|
||||||
let tunnel: Tunnel
|
let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let urlForTunnelLog: URL
|
let urlForTunnelLog: URL
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import UtilsLibrary
|
||||||
struct InstalledProfileView: View, Routable {
|
struct InstalledProfileView: View, Routable {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var theme: Theme
|
private var theme: Theme
|
||||||
|
|
||||||
let layout: ProfilesLayout
|
let layout: ProfilesLayout
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ struct InstalledProfileView: View, Routable {
|
||||||
|
|
||||||
let profile: Profile?
|
let profile: Profile?
|
||||||
|
|
||||||
let tunnel: Tunnel
|
@ObservedObject
|
||||||
|
var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let interactiveManager: InteractiveManager
|
let interactiveManager: InteractiveManager
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import UtilsLibrary
|
||||||
struct ProfileContextMenu: View, Routable {
|
struct ProfileContextMenu: View, Routable {
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
|
||||||
let tunnel: Tunnel
|
let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let header: ProfileHeader
|
let header: ProfileHeader
|
||||||
|
|
||||||
|
|
|
@ -28,21 +28,18 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct ProfileRowView: View, Routable, TunnelContextProviding {
|
struct ProfileRowView: View, Routable {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
var connectionObserver: ConnectionObserver
|
|
||||||
|
|
||||||
let style: ProfileCardView.Style
|
let style: ProfileCardView.Style
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var profileManager: ProfileManager
|
var profileManager: ProfileManager
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tunnel: Tunnel
|
var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let header: ProfileHeader
|
let header: ProfileHeader
|
||||||
|
|
||||||
|
@ -116,7 +113,7 @@ private extension ProfileRowView {
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusImage: Theme.ImageName {
|
var statusImage: Theme.ImageName {
|
||||||
switch tunnelConnectionStatus {
|
switch tunnel.connectionStatus {
|
||||||
case .active:
|
case .active:
|
||||||
return .marked
|
return .marked
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ struct TunnelRestartButton<Label>: View where Label: View {
|
||||||
private var profileProcessor: ProfileProcessor
|
private var profileProcessor: ProfileProcessor
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tunnel: Tunnel
|
var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let profile: Profile?
|
let profile: Profile?
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ struct AboutRouterView: View {
|
||||||
|
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
|
||||||
let tunnel: Tunnel
|
let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
@State
|
@State
|
||||||
var navigationRoute: NavigationRoute?
|
var navigationRoute: NavigationRoute?
|
||||||
|
@ -63,7 +63,10 @@ extension AboutRouterView {
|
||||||
DonateView()
|
DonateView()
|
||||||
|
|
||||||
case .diagnostics:
|
case .diagnostics:
|
||||||
DiagnosticsView()
|
DiagnosticsView(
|
||||||
|
profileManager: profileManager,
|
||||||
|
tunnel: tunnel
|
||||||
|
)
|
||||||
|
|
||||||
case .appDebugLog(let title):
|
case .appDebugLog(let title):
|
||||||
DebugLogView.withApp(parameters: Constants.shared.log)
|
DebugLogView.withApp(parameters: Constants.shared.log)
|
||||||
|
|
|
@ -35,7 +35,7 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
|
||||||
|
|
||||||
private let profileManager: ProfileManager
|
private let profileManager: ProfileManager
|
||||||
|
|
||||||
private let tunnel: Tunnel
|
private let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
private let registry: Registry
|
private let registry: Registry
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
profileManager: ProfileManager,
|
profileManager: ProfileManager,
|
||||||
tunnel: Tunnel,
|
tunnel: ExtendedTunnel,
|
||||||
registry: Registry
|
registry: Registry
|
||||||
) {
|
) {
|
||||||
self.profileManager = profileManager
|
self.profileManager = profileManager
|
||||||
|
|
|
@ -28,12 +28,12 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UtilsLibrary
|
import UtilsLibrary
|
||||||
|
|
||||||
struct ProfileContainerView: View, Routable, TunnelInstallationProviding {
|
struct ProfileContainerView: View, Routable {
|
||||||
let layout: ProfilesLayout
|
let layout: ProfilesLayout
|
||||||
|
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
|
||||||
let tunnel: Tunnel
|
let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let registry: Registry
|
let registry: Registry
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ struct ProfileGridView: View, Routable, TunnelInstallationProviding {
|
||||||
var profileManager: ProfileManager
|
var profileManager: ProfileManager
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tunnel: Tunnel
|
var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let interactiveManager: InteractiveManager
|
let interactiveManager: InteractiveManager
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ struct ProfileListView: View, Routable, TunnelInstallationProviding {
|
||||||
var profileManager: ProfileManager
|
var profileManager: ProfileManager
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tunnel: Tunnel
|
var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let interactiveManager: InteractiveManager
|
let interactiveManager: InteractiveManager
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ struct ProviderEntitySelector: View {
|
||||||
var profileManager: ProfileManager
|
var profileManager: ProfileManager
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var tunnel: Tunnel
|
var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let profile: Profile
|
let profile: Profile
|
||||||
|
|
||||||
|
|
|
@ -32,19 +32,22 @@ import SwiftUI
|
||||||
|
|
||||||
public struct AppMenu: View {
|
public struct AppMenu: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@ObservedObject
|
||||||
private var profileManager: ProfileManager
|
private var profileManager: ProfileManager
|
||||||
|
|
||||||
@EnvironmentObject
|
@ObservedObject
|
||||||
private var profileProcessor: ProfileProcessor
|
private var profileProcessor: ProfileProcessor
|
||||||
|
|
||||||
@EnvironmentObject
|
@ObservedObject
|
||||||
private var tunnel: Tunnel
|
private var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
private var model = Model()
|
private var model = Model()
|
||||||
|
|
||||||
public init() {
|
public init(profileManager: ProfileManager, profileProcessor: ProfileProcessor, tunnel: ExtendedTunnel) {
|
||||||
|
self.profileManager = profileManager
|
||||||
|
self.profileProcessor = profileProcessor
|
||||||
|
self.tunnel = tunnel
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
|
|
@ -28,17 +28,17 @@
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct AppMenuImage: View, TunnelContextProviding {
|
public struct AppMenuImage: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
public var connectionObserver: ConnectionObserver
|
private var tunnel: ExtendedTunnel
|
||||||
|
|
||||||
public init(connectionObserver: ConnectionObserver) {
|
public init(tunnel: ExtendedTunnel) {
|
||||||
self.connectionObserver = connectionObserver
|
self.tunnel = tunnel
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
ThemeMenuImage(tunnelConnectionStatus.imageName)
|
ThemeMenuImage(tunnel.connectionStatus.imageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ extension DebugLogView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func withTunnel(_ tunnel: Tunnel, parameters: Constants.Log) -> DebugLogView {
|
static func withTunnel(_ tunnel: ExtendedTunnel, parameters: Constants.Log) -> DebugLogView {
|
||||||
DebugLogView {
|
DebugLogView {
|
||||||
await tunnel.currentLog(parameters: parameters)
|
await tunnel.currentLog(parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import AppLibrary
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -41,15 +42,16 @@ struct DiagnosticsView: View {
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var connectionObserver: ConnectionObserver
|
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var iapManager: IAPManager
|
private var iapManager: IAPManager
|
||||||
|
|
||||||
@AppStorage(AppPreference.logsPrivateData.key, store: .appGroup)
|
@AppStorage(AppPreference.logsPrivateData.key, store: .appGroup)
|
||||||
private var logsPrivateData = false
|
private var logsPrivateData = false
|
||||||
|
|
||||||
|
let profileManager: ProfileManager
|
||||||
|
|
||||||
|
let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
var availableTunnelLogs: () async -> [LogEntry] = {
|
var availableTunnelLogs: () async -> [LogEntry] = {
|
||||||
await Task.detached {
|
await Task.detached {
|
||||||
PassepartoutConfiguration.shared.availableLogs(at: BundleConfiguration.urlForTunnelLog)
|
PassepartoutConfiguration.shared.availableLogs(at: BundleConfiguration.urlForTunnelLog)
|
||||||
|
@ -128,7 +130,7 @@ private extension DiagnosticsView {
|
||||||
}
|
}
|
||||||
|
|
||||||
var openVPNSection: some View {
|
var openVPNSection: some View {
|
||||||
connectionObserver.value(forKey: TunnelEnvironmentKeys.OpenVPN.serverConfiguration)
|
tunnel.value(forKey: TunnelEnvironmentKeys.OpenVPN.serverConfiguration)
|
||||||
.map { cfg in
|
.map { cfg in
|
||||||
Group {
|
Group {
|
||||||
NavigationLink(Strings.Views.Diagnostics.Openvpn.Rows.serverConfiguration) {
|
NavigationLink(Strings.Views.Diagnostics.Openvpn.Rows.serverConfiguration) {
|
||||||
|
@ -143,7 +145,8 @@ private extension DiagnosticsView {
|
||||||
var reportIssueSection: some View {
|
var reportIssueSection: some View {
|
||||||
Section {
|
Section {
|
||||||
ReportIssueButton(
|
ReportIssueButton(
|
||||||
tunnel: connectionObserver.tunnel,
|
profileManager: profileManager,
|
||||||
|
tunnel: tunnel,
|
||||||
title: Strings.Views.Diagnostics.ReportIssue.title,
|
title: Strings.Views.Diagnostics.ReportIssue.title,
|
||||||
purchasedProducts: iapManager.purchasedProducts,
|
purchasedProducts: iapManager.purchasedProducts,
|
||||||
isUnableToEmail: $isPresentingUnableToEmail
|
isUnableToEmail: $isPresentingUnableToEmail
|
||||||
|
@ -190,7 +193,7 @@ private extension DiagnosticsView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
DiagnosticsView {
|
DiagnosticsView(profileManager: .mock, tunnel: .mock) {
|
||||||
[
|
[
|
||||||
.init(date: Date(), url: URL(string: "http://one.com")!),
|
.init(date: Date(), url: URL(string: "http://one.com")!),
|
||||||
.init(date: Date().addingTimeInterval(-60), url: URL(string: "http://two.com")!),
|
.init(date: Date().addingTimeInterval(-60), url: URL(string: "http://two.com")!),
|
||||||
|
|
|
@ -29,13 +29,13 @@ import SwiftUI
|
||||||
|
|
||||||
struct ReportIssueButton {
|
struct ReportIssueButton {
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var profileManager: ProfileManager
|
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var providerManager: ProviderManager
|
private var providerManager: ProviderManager
|
||||||
|
|
||||||
let tunnel: Tunnel
|
@ObservedObject
|
||||||
|
var profileManager: ProfileManager
|
||||||
|
|
||||||
|
let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
let title: String
|
let title: String
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@ import SwiftUI
|
||||||
struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity: ProviderEntity, Entity.Configuration: ProviderConfigurationIdentifiable & Codable, ProviderRows: View {
|
struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity: ProviderEntity, Entity.Configuration: ProviderConfigurationIdentifiable & Codable, ProviderRows: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var providerManager: ProviderManager
|
private var iapManager: IAPManager
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var iapManager: IAPManager
|
private var providerManager: ProviderManager
|
||||||
|
|
||||||
let apis: [APIMapper]
|
let apis: [APIMapper]
|
||||||
|
|
||||||
|
|
|
@ -32,11 +32,11 @@ import SwiftUI
|
||||||
public struct AppCoordinator: View, AppCoordinatorConforming {
|
public struct AppCoordinator: View, AppCoordinatorConforming {
|
||||||
private let profileManager: ProfileManager
|
private let profileManager: ProfileManager
|
||||||
|
|
||||||
private let tunnel: Tunnel
|
private let tunnel: ExtendedTunnel
|
||||||
|
|
||||||
private let registry: Registry
|
private let registry: Registry
|
||||||
|
|
||||||
public init(profileManager: ProfileManager, tunnel: Tunnel, registry: Registry) {
|
public init(profileManager: ProfileManager, tunnel: ExtendedTunnel, registry: Registry) {
|
||||||
self.profileManager = profileManager
|
self.profileManager = profileManager
|
||||||
self.tunnel = tunnel
|
self.tunnel = tunnel
|
||||||
self.registry = registry
|
self.registry = registry
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// ConnectionObserverTests.swift
|
// ExtendedTunnelTests.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 9/12/24.
|
// Created by Davide De Rosa on 9/12/24.
|
||||||
|
@ -28,15 +28,15 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class ConnectionObserverTests: XCTestCase {
|
final class ExtendedTunnelTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension ConnectionObserverTests {
|
extension ExtendedTunnelTests {
|
||||||
func test_givenTunnel_whenDisconnectWithError_thenPublishesLastErrorCode() async throws {
|
func test_givenTunnel_whenDisconnectWithError_thenPublishesLastErrorCode() async throws {
|
||||||
let env = InMemoryEnvironment()
|
let env = InMemoryEnvironment()
|
||||||
let tunnel = Tunnel(strategy: FakeTunnelStrategy(environment: env))
|
let tunnel = Tunnel(strategy: FakeTunnelStrategy(environment: env))
|
||||||
let sut = ConnectionObserver(tunnel: tunnel, environment: env, interval: 0.1)
|
let sut = ExtendedTunnel(tunnel: tunnel, environment: env, interval: 0.1)
|
||||||
sut.observeObjects()
|
sut.observeObjects()
|
||||||
|
|
||||||
let profile = try Profile.Builder().tryBuild()
|
let profile = try Profile.Builder().tryBuild()
|
||||||
|
@ -51,7 +51,7 @@ extension ConnectionObserverTests {
|
||||||
func test_givenTunnel_whenConnect_thenPublishesDataCount() async throws {
|
func test_givenTunnel_whenConnect_thenPublishesDataCount() async throws {
|
||||||
let env = InMemoryEnvironment()
|
let env = InMemoryEnvironment()
|
||||||
let tunnel = Tunnel(strategy: FakeTunnelStrategy(environment: env))
|
let tunnel = Tunnel(strategy: FakeTunnelStrategy(environment: env))
|
||||||
let sut = ConnectionObserver(tunnel: tunnel, environment: env, interval: 0.1)
|
let sut = ExtendedTunnel(tunnel: tunnel, environment: env, interval: 0.1)
|
||||||
sut.observeObjects()
|
sut.observeObjects()
|
||||||
|
|
||||||
let profile = try Profile.Builder().tryBuild()
|
let profile = try Profile.Builder().tryBuild()
|
Loading…
Reference in New Issue