diff --git a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index be4a2c01..18f6e455 100644
--- a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -41,7 +41,7 @@
"kind" : "remoteSourceControl",
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
"state" : {
- "revision" : "c0a615bc7a85d68a9b00d3703d0dae6efab9bdd2"
+ "revision" : "db02de5247d0231ff06fb3c4d166645a434255be"
}
},
{
diff --git a/Passepartout/Library/Package.swift b/Passepartout/Library/Package.swift
index e27bb2a4..52441ebc 100644
--- a/Passepartout/Library/Package.swift
+++ b/Passepartout/Library/Package.swift
@@ -44,7 +44,7 @@ let package = Package(
],
dependencies: [
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.11.0"),
- .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "c0a615bc7a85d68a9b00d3703d0dae6efab9bdd2"),
+ .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "db02de5247d0231ff06fb3c4d166645a434255be"),
// .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/AppUIMain/AppUIMain.swift b/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift
index ab87d5c6..d317a5b3 100644
--- a/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift
+++ b/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift
@@ -23,6 +23,7 @@
// along with Passepartout. If not, see .
//
+import CommonLibrary
import Foundation
import PassepartoutKit
@_exported import UILibrary
@@ -42,19 +43,23 @@ private extension AppUIMain {
.openVPN
]
ModuleType.allCases.forEach { moduleType in
- let builder = moduleType.newModule(with: registry)
- guard builder is any ModuleViewProviding else {
- fatalError("\(moduleType): is not ModuleViewProviding")
- }
- if providerModuleTypes.contains(moduleType) {
- do {
- let module = try builder.tryBuild()
+ do {
+ let builder = moduleType.newModule(with: registry)
+ let module = try builder.tryBuild()
+
+ // ModuleViewProviding
+ guard builder is any ModuleViewProviding else {
+ fatalError("\(moduleType): is not ModuleViewProviding")
+ }
+
+ // ProviderEntityViewProviding
+ if providerModuleTypes.contains(moduleType) {
guard module is any ProviderEntityViewProviding else {
fatalError("\(moduleType): is not ProviderEntityViewProviding")
}
- } catch {
- fatalError("\(moduleType): empty module is not buildable")
}
+ } catch {
+ fatalError("\(moduleType): empty module is not buildable: \(error)")
}
}
}
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift
index 799cd6ec..8e00bd99 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift
@@ -54,6 +54,9 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
@State
private var migrationPath = NavigationPath()
+ @State
+ private var paywallReason: PaywallReason?
+
@StateObject
private var errorHandler: ErrorHandler = .default()
@@ -72,6 +75,7 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
contentView
.toolbar(content: toolbarContent)
}
+ .modifier(PaywallModifier(reason: $paywallReason))
.themeModal(
item: $modalRoute,
size: modalRoute?.size ?? .large,
@@ -87,6 +91,8 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
extension AppCoordinator {
enum ModalRoute: Identifiable {
+ case about
+
case editProfile
case editProviderEntity(Profile, Module, SerializedProvider)
@@ -95,15 +101,13 @@ extension AppCoordinator {
case settings
- case about
-
var id: Int {
switch self {
- case .editProfile: return 1
- case .editProviderEntity: return 2
- case .migrateProfiles: return 3
- case .settings: return 4
- case .about: return 5
+ case .about: return 1
+ case .editProfile: return 2
+ case .editProviderEntity: return 3
+ case .migrateProfiles: return 4
+ case .settings: return 5
}
}
@@ -171,6 +175,11 @@ extension AppCoordinator {
},
onMigrateProfiles: {
modalRoute = .migrateProfiles
+ },
+ onPurchaseRequired: { features in
+ setLater(.purchase(features)) {
+ paywallReason = $0
+ }
}
)
)
@@ -197,6 +206,12 @@ extension AppCoordinator {
@ViewBuilder
func modalDestination(for item: ModalRoute?) -> some View {
switch item {
+ case .about:
+ AboutRouterView(
+ profileManager: profileManager,
+ tunnel: tunnel
+ )
+
case .editProfile:
ProfileCoordinator(
profileManager: profileManager,
@@ -205,9 +220,7 @@ extension AppCoordinator {
moduleViewFactory: DefaultModuleViewFactory(registry: registry),
modally: true,
path: $profilePath,
- onDismiss: {
- present(nil)
- }
+ onDismiss: onDismiss
)
case .editProviderEntity(let profile, let module, let provider):
@@ -230,12 +243,6 @@ extension AppCoordinator {
case .settings:
SettingsView(profileManager: profileManager)
- case .about:
- AboutRouterView(
- profileManager: profileManager,
- tunnel: tunnel
- )
-
default:
EmptyView()
}
@@ -256,11 +263,15 @@ extension AppCoordinator {
present(.editProfile)
}
+ func onDismiss() {
+ present(nil)
+ }
+
func present(_ route: ModalRoute?) {
+
// XXX: this is a workaround for #791 on iOS 16
- Task {
- try await Task.sleep(for: .milliseconds(50))
- modalRoute = route
+ setLater(route) {
+ modalRoute = $0
}
}
}
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift
index e2894acf..3ba3bcfb 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/InstalledProfileView.swift
@@ -201,7 +201,12 @@ private struct ToggleButton: View {
nextProfileId: $nextProfileId,
interactiveManager: interactiveManager,
errorHandler: errorHandler,
- onProviderEntityRequired: flow?.onEditProviderEntity,
+ onProviderEntityRequired: {
+ flow?.onEditProviderEntity($0)
+ },
+ onPurchaseRequired: {
+ flow?.onPurchaseRequired($0)
+ },
label: { _ in
ThemeImage(.tunnelToggle)
.scaleEffect(1.5, anchor: .trailing)
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
index 34d08e90..2d7929ac 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileContextMenu.swift
@@ -69,13 +69,20 @@ private extension ProfileContextMenu {
profile: profile,
nextProfileId: .constant(nil),
interactiveManager: interactiveManager,
- errorHandler: errorHandler
- ) {
- ThemeImageLabel(
- $0 ? Strings.Global.enable : Strings.Global.disable,
- $0 ? .tunnelEnable : .tunnelDisable
- )
- }
+ errorHandler: errorHandler,
+ onProviderEntityRequired: {
+ flow?.onEditProviderEntity($0)
+ },
+ onPurchaseRequired: {
+ flow?.onPurchaseRequired($0)
+ },
+ label: {
+ ThemeImageLabel(
+ $0 ? Strings.Global.enable : Strings.Global.disable,
+ $0 ? .tunnelEnable : .tunnelDisable
+ )
+ }
+ )
}
var providerConnectToButton: some View {
@@ -92,10 +99,14 @@ private extension ProfileContextMenu {
TunnelRestartButton(
tunnel: tunnel,
profile: profile,
- errorHandler: errorHandler
- ) {
- ThemeImageLabel(Strings.Global.restart, .tunnelRestart)
- }
+ errorHandler: errorHandler,
+ onPurchaseRequired: {
+ flow?.onPurchaseRequired($0)
+ },
+ label: {
+ ThemeImageLabel(Strings.Global.restart, .tunnelRestart)
+ }
+ )
}
var profileEditButton: some View {
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileFlow.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileFlow.swift
index 4739327d..ad5370d8 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileFlow.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileFlow.swift
@@ -23,6 +23,7 @@
// along with Passepartout. If not, see .
//
+import CommonLibrary
import Foundation
import PassepartoutKit
@@ -32,4 +33,6 @@ struct ProfileFlow {
let onEditProviderEntity: (Profile) -> Void
let onMigrateProfiles: () -> Void
+
+ let onPurchaseRequired: (Set) -> Void
}
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift
index 7c9f028f..56667b94 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift
@@ -122,7 +122,12 @@ private extension ProfileRowView {
nextProfileId: $nextProfileId,
interactiveManager: interactiveManager,
errorHandler: errorHandler,
- onProviderEntityRequired: flow?.onEditProviderEntity,
+ onProviderEntityRequired: {
+ flow?.onEditProviderEntity($0)
+ },
+ onPurchaseRequired: {
+ flow?.onPurchaseRequired($0)
+ },
label: { _ in
ProfileCardView(
style: style,
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/TunnelRestartButton.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/TunnelRestartButton.swift
index 2b27f3a5..a18739be 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/TunnelRestartButton.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/TunnelRestartButton.swift
@@ -37,6 +37,8 @@ struct TunnelRestartButton