Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle TV requirements on connection #922

Merged
merged 10 commits into from
Nov 24, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,15 @@ extension AppCoordinator {
}
enterDetail(of: profile)
},
onEditProviderEntity: {
onMigrateProfiles: {
modalRoute = .migrateProfiles
},
onProviderEntityRequired: {
guard let pair = $0.selectedProvider else {
return
}
present(.editProviderEntity($0, pair.module, pair.selection))
},
onMigrateProfiles: {
modalRoute = .migrateProfiles
},
onPurchaseRequired: { features in
setLater(.purchase(features)) {
paywallReason = $0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private extension InstalledProfileView {
.selectedProvider
.map { _, selection in
Button {
flow?.onEditProviderEntity(profile!)
flow?.onProviderEntityRequired(profile!)
} label: {
providerSelectorLabel(with: selection)
}
Expand Down Expand Up @@ -202,7 +202,7 @@ private struct ToggleButton: View {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
onProviderEntityRequired: {
flow?.onEditProviderEntity($0)
flow?.onProviderEntityRequired($0)
},
onPurchaseRequired: {
flow?.onPurchaseRequired($0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private extension ProfileContainerView {
InteractiveCoordinator(style: .modal, manager: interactiveManager) {
errorHandler.handle(
$0,
title: Strings.Global.Nouns.connection,
title: interactiveManager.editor.profile.name,
message: Strings.Views.App.Errors.tunnel
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private extension ProfileContextMenu {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
onProviderEntityRequired: {
flow?.onEditProviderEntity($0)
flow?.onProviderEntityRequired($0)
},
onPurchaseRequired: {
flow?.onPurchaseRequired($0)
Expand All @@ -90,7 +90,7 @@ private extension ProfileContextMenu {
.selectedProvider
.map { _ in
Button(Strings.Views.App.ProfileContext.connectTo) {
flow?.onEditProviderEntity(profile!)
flow?.onProviderEntityRequired(profile!)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ import PassepartoutKit
struct ProfileFlow {
let onEditProfile: (ProfilePreview) -> Void

let onEditProviderEntity: (Profile) -> Void

let onMigrateProfiles: () -> Void

let onProviderEntityRequired: (Profile) -> Void

let onPurchaseRequired: (Set<AppFeature>) -> Void
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private extension ProfileRowView {
interactiveManager: interactiveManager,
errorHandler: errorHandler,
onProviderEntityRequired: {
flow?.onEditProviderEntity($0)
flow?.onProviderEntityRequired($0)
},
onPurchaseRequired: {
flow?.onPurchaseRequired($0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct TunnelRestartButton<Label>: View where Label: View {
} catch {
errorHandler.handle(
error,
title: Strings.Global.Nouns.connection,
title: profile.name,
message: Strings.Views.App.Errors.tunnel
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,13 @@ struct ProfileCoordinator: View {
var body: some View {
contentView
.modifier(PaywallModifier(reason: $paywallReason))
.alert(Strings.Views.Profile.Alerts.Purchase.title, isPresented: $requiresPurchase) {
Button(Strings.Global.Actions.purchase) {
paywallReason = .purchase(requiredFeatures, nil)
}
Button(Strings.Views.Profile.Alerts.Purchase.Buttons.ok, action: onDismiss)
Button(Strings.Global.Actions.cancel, role: .cancel, action: {})
} message: {
Text(purchaseMessage)
}
.modifier(PurchaseAlertModifier(
isPresented: $requiresPurchase,
paywallReason: $paywallReason,
requiredFeatures: requiredFeatures,
okTitle: Strings.Views.Profile.Alerts.Purchase.Buttons.ok,
okAction: onDismiss
))
.withErrorHandler(errorHandler)
}
}
Expand Down Expand Up @@ -117,13 +115,6 @@ private extension ProfileCoordinator {
)
#endif
}

var purchaseMessage: String {
let msg = Strings.Views.Profile.Alerts.Purchase.message
return msg + "\n\n" + requiredFeatures
.map(\.localizedDescription)
.joined(separator: "\n")
}
}

private extension ProfileCoordinator {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
//

import CommonLibrary
import CommonUtils
import PassepartoutKit
import SwiftUI
import UILibrary

public struct AppCoordinator: View, AppCoordinatorConforming {
private let profileManager: ProfileManager
Expand All @@ -34,6 +36,18 @@ public struct AppCoordinator: View, AppCoordinatorConforming {

private let registry: Registry

@State
private var requiresPurchase = false

@State
private var requiredFeatures: Set<AppFeature> = []

@State
private var paywallReason: PaywallReason?

@StateObject
private var errorHandler: ErrorHandler = .default()

public init(profileManager: ProfileManager, tunnel: ExtendedTunnel, registry: Registry) {
self.profileManager = profileManager
self.tunnel = tunnel
Expand All @@ -60,13 +74,28 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
}
}
.navigationDestination(for: AppCoordinatorRoute.self, destination: pushDestination)
.withErrorHandler(errorHandler)
.modifier(PaywallModifier(reason: $paywallReason))
.modifier(PurchaseAlertModifier(
isPresented: $requiresPurchase,
paywallReason: $paywallReason,
requiredFeatures: requiredFeatures
))
}
}
}

private extension AppCoordinator {
var profileView: some View {
ProfileView(profileManager: profileManager, tunnel: tunnel)
ProfileView(
profileManager: profileManager,
tunnel: tunnel,
errorHandler: errorHandler,
flow: .init(
onProviderEntityRequired: onProviderEntityRequired,
onPurchaseRequired: onPurchaseRequired
)
)
}

// var searchView: some View {
Expand Down Expand Up @@ -109,6 +138,20 @@ private extension AppCoordinator {
}
}

private extension AppCoordinator {
func onProviderEntityRequired(_ profile: Profile) {
errorHandler.handle(
title: profile.name,
message: Strings.Alerts.Providers.MissingServer.message
)
}

func onPurchaseRequired(_ features: Set<AppFeature>) {
requiredFeatures = features
requiresPurchase = true
}
}

// MARK: -

#Preview {
Expand Down
34 changes: 34 additions & 0 deletions Passepartout/Library/Sources/AppUITV/Views/App/AppFlow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// AppFlow.swift
// Passepartout
//
// Created by Davide De Rosa on 11/23/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

struct AppFlow {
let onProviderEntityRequired: (Profile) -> Void

let onPurchaseRequired: (Set<AppFeature>) -> Void
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ struct ActiveProfileView: View {
@ObservedObject
var errorHandler: ErrorHandler

var flow: AppFlow?

var body: some View {
VStack(spacing: .zero) {
VStack {
Expand Down Expand Up @@ -167,15 +169,13 @@ private extension ActiveProfileView {
}
}

// MARK: -

private extension ActiveProfileView {
func onProviderEntityRequired(_ profile: Profile) {
// FIXME: #913, TV missing provider entity
flow?.onProviderEntityRequired(profile)
}

func onPurchaseRequired(_ features: Set<AppFeature>) {
// FIXME: #913, TV purchase required
flow?.onPurchaseRequired(features)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ struct ProfileListView: View {
@ObservedObject
var errorHandler: ErrorHandler

var flow: AppFlow?

var body: some View {
VStack {
headerView
Expand Down Expand Up @@ -100,15 +102,13 @@ private extension ProfileListView {
}
}

// MARK: -

private extension ProfileListView {
func onProviderEntityRequired(_ profile: Profile) {
// FIXME: #913, TV missing provider entity
flow?.onProviderEntityRequired(profile)
}

func onPurchaseRequired(_ features: Set<AppFeature>) {
// FIXME: #913, TV purchase required
flow?.onPurchaseRequired(features)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import PassepartoutKit
import SwiftUI
import UILibrary

struct ProfileView: View, TunnelInstallationProviding {
struct ProfileView: View, Routable, TunnelInstallationProviding {
enum Field: Hashable {
case connect

Expand All @@ -47,6 +47,11 @@ struct ProfileView: View, TunnelInstallationProviding {
@ObservedObject
var tunnel: ExtendedTunnel

@ObservedObject
var errorHandler: ErrorHandler

var flow: AppFlow?

@State
var showsSidePanel = false

Expand All @@ -56,9 +61,6 @@ struct ProfileView: View, TunnelInstallationProviding {
@StateObject
private var interactiveManager = InteractiveManager()

@StateObject
private var errorHandler: ErrorHandler = .default()

var body: some View {
GeometryReader { geo in
HStack(spacing: .zero) {
Expand All @@ -80,7 +82,6 @@ struct ProfileView: View, TunnelInstallationProviding {
.ignoresSafeArea(edges: .horizontal)
.background(theme.primaryColor.opacity(0.6).gradient)
.themeAnimation(on: showsSidePanel, category: .profiles)
.withErrorHandler(errorHandler)
.defaultFocus($focusedField, .switchProfile)
.onChange(of: tunnel.status, onTunnelStatus)
.onChange(of: tunnel.currentProfile, onTunnelCurrentProfile)
Expand All @@ -104,7 +105,8 @@ private extension ProfileView {
isSwitching: $showsSidePanel,
focusedField: $focusedField,
interactiveManager: interactiveManager,
errorHandler: errorHandler
errorHandler: errorHandler,
flow: flow
)
}

Expand All @@ -126,7 +128,7 @@ private extension ProfileView {
InteractiveCoordinator(style: .inline(withCancel: false), manager: interactiveManager) {
errorHandler.handle(
$0,
title: Strings.Global.Nouns.connection,
title: interactiveManager.editor.profile.name,
message: Strings.Views.App.Errors.tunnel
)
}
Expand All @@ -144,7 +146,8 @@ private extension ProfileView {
tunnel: tunnel,
focusedField: $focusedField,
interactiveManager: interactiveManager,
errorHandler: errorHandler
errorHandler: errorHandler,
flow: flow
)
}
}
Expand Down Expand Up @@ -189,6 +192,7 @@ private extension ProfileView {
ProfileView(
profileManager: .mock,
tunnel: .mock,
errorHandler: .default(),
showsSidePanel: true
)
.withMockEnvironment()
Expand All @@ -198,6 +202,7 @@ private extension ProfileView {
ProfileView(
profileManager: ProfileManager(profiles: []),
tunnel: .mock,
errorHandler: .default(),
showsSidePanel: true
)
.withMockEnvironment()
Expand Down
Loading