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

PIA-1219: Separate Onboarding and UserAuthenticated flows #63

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions PIA VPN-tvOS/Login/CompositionRoot/LoginFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ class LoginFactory {
checkLoginAvailability: CheckLoginAvailability(),
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: makeLoginViewModelErrorHandler(),
appRouter: AppRouter.shared,
successDestination: OnboardingDestinations.installVPNProfile)

onSuccessAction: .navigate(router: AppRouter.shared, destination: OnboardingDestinations.installVPNProfile))
}

private static func makeLoginWithCredentialsUseCase() -> LoginWithCredentialsUseCaseType {
Expand Down
11 changes: 4 additions & 7 deletions PIA VPN-tvOS/Login/Presentation/LoginViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,18 @@ class LoginViewModel: ObservableObject {
private let checkLoginAvailability: CheckLoginAvailabilityType
private let validateLoginCredentials: ValidateCredentialsFormatType
private let errorHandler: LoginViewModelErrorHandlerType
private let appRouter: AppRouterType

private let successDestination: any Destinations
private let onSuccessAction: AppRouter.Actions

@Published var isAccountExpired = false
@Published var shouldShowErrorMessage = false
@Published var loginStatus: LoginStatus = .none

init(loginWithCredentialsUseCase: LoginWithCredentialsUseCaseType, checkLoginAvailability: CheckLoginAvailabilityType, validateLoginCredentials: ValidateCredentialsFormatType, errorHandler: LoginViewModelErrorHandlerType, appRouter: AppRouterType, successDestination: any Destinations) {
init(loginWithCredentialsUseCase: LoginWithCredentialsUseCaseType, checkLoginAvailability: CheckLoginAvailabilityType, validateLoginCredentials: ValidateCredentialsFormatType, errorHandler: LoginViewModelErrorHandlerType, onSuccessAction: AppRouter.Actions) {
self.loginWithCredentialsUseCase = loginWithCredentialsUseCase
self.checkLoginAvailability = checkLoginAvailability
self.validateLoginCredentials = validateLoginCredentials
self.errorHandler = errorHandler
self.appRouter = appRouter
self.successDestination = successDestination
self.onSuccessAction = onSuccessAction
}

func login(username: String, password: String) {
Expand All @@ -54,7 +51,7 @@ class LoginViewModel: ObservableObject {
case .success(let userAccount):
Task { @MainActor in
self.loginStatus = .succeeded(userAccount: userAccount)
self.appRouter.navigate(to: self.successDestination)
self.onSuccessAction()
}

case .failure(let error):
Expand Down
8 changes: 3 additions & 5 deletions PIA VPN-tvOS/Navigation/AppRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ protocol AppRouterType {
func goBackToRoot()
}


// AppRouter enables any component to navigate the user to any screen defined within Destinations
class AppRouter: ObservableObject, AppRouterType {

Expand Down Expand Up @@ -38,19 +39,17 @@ class AppRouter: ObservableObject, AppRouterType {
func goBackToRoot() {
path.removeLast(path.count)
}

}


extension AppRouter {

enum Actions: Equatable {

case pop(router: AppRouterType)
case goBackToRoot(router: AppRouterType)
case navigate(router: AppRouterType, destination: any Destinations)

func execute() {
func callAsFunction() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

switch self {
case .pop(let router):
router.pop()
Expand All @@ -60,7 +59,7 @@ extension AppRouter {
router.navigate(to: destination)
}
}

static func == (lhs: AppRouter.Actions, rhs: AppRouter.Actions) -> Bool {
switch (lhs, rhs) {
case (.pop, .pop):
Expand All @@ -74,5 +73,4 @@ extension AppRouter {
}

}

}
1 change: 0 additions & 1 deletion PIA VPN-tvOS/Navigation/Destinations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import Foundation

typealias Destinations = Hashable


enum DashboardDestinations: Destinations {
case home
}
Expand Down
3 changes: 0 additions & 3 deletions PIA VPN-tvOS/Navigation/Routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ enum AuthenticationDestinations: Destinations {

enum OnboardingDestinations: Destinations {
case installVPNProfile
case dashboard
}

public extension View {
Expand All @@ -33,8 +32,6 @@ public extension View {
switch destination {
case .installVPNProfile:
VPNConfigurationInstallingFactory.makeVPNConfigurationInstallingView()
case .dashboard:
UserActivatedContainerFactory.makeUSerActivatedContainerView()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class RegionsContainerViewModel: ObservableObject {

func navigate(to route: RegionSelectionSideMenuItems) {
if route == .search {
onSearchSelectedAction.execute()
onSearchSelectedAction()
} else {
selectedSideMenuItem = route
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ class RegionsListViewModel: ObservableObject {

func didSelectRegionServer(_ server: ServerType) {
useCase.select(server: server)
onServerSelectedRouterAction.execute()
onServerSelectedRouterAction()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ class RootContainerFactory {
guard let defaultAccountProvider = Client.providers.accountProvider as? DefaultAccountProvider else {
fatalError("Incorrect account provider type")
}
return RootContainerViewModel(accountProvider: defaultAccountProvider,
return RootContainerViewModel(accountProvider: defaultAccountProvider,
vpnConfigurationAvailability: VPNConfigurationAvailability(),
bootstrap: BootstraperFactory.makeBootstrapper(),
userAuthenticationStatusMonitor: StateMonitorsFactory.makeUserAuthenticationStatusMonitor())
userAuthenticationStatusMonitor: StateMonitorsFactory.makeUserAuthenticationStatusMonitor(),
appRouter: AppRouterFactory.makeAppRouter())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,29 @@ class RootContainerViewModel: ObservableObject {
private let vpnConfigurationAvailability: VPNConfigurationAvailabilityType
private let bootstrap: BootstraperType
private let userAuthenticationStatusMonitor: UserAuthenticationStatusMonitorType
private let appRouter: AppRouterType
private var cancellables = Set<AnyCancellable>()

init(accountProvider: AccountProviderType, notificationCenter: NotificationCenterType = NotificationCenter.default, vpnConfigurationAvailability: VPNConfigurationAvailabilityType, bootstrap: BootstraperType, userAuthenticationStatusMonitor: UserAuthenticationStatusMonitorType) {
init(accountProvider: AccountProviderType, notificationCenter: NotificationCenterType = NotificationCenter.default, vpnConfigurationAvailability: VPNConfigurationAvailabilityType, bootstrap: BootstraperType, userAuthenticationStatusMonitor: UserAuthenticationStatusMonitorType, appRouter: AppRouterType) {

self.accountProvider = accountProvider
self.notificationCenter = notificationCenter
self.vpnConfigurationAvailability = vpnConfigurationAvailability
self.bootstrap = bootstrap
self.userAuthenticationStatusMonitor = userAuthenticationStatusMonitor
updateState()
self.appRouter = appRouter

subscribeToAccountUpdates()
setup()
}

deinit {
notificationCenter.removeObserver(self)
}

func phaseDidBecomeActive() {
private func setup() {
bootstrap()
isBootstrapped = true
updateState()
}

private func updateState() {
@objc private func updateState() {
guard isBootstrapped else {
return
}
Expand All @@ -57,12 +56,16 @@ class RootContainerViewModel: ObservableObject {
// logged in, vpn profile not installed
case (true, false):
state = .activatedNotOnboarded
appRouter.navigate(to: OnboardingDestinations.installVPNProfile)
// not logged in, any
case (false, _):
state = .notActivated
}
}


deinit {
notificationCenter.removeObserver(self)
}
}

// Combine subscriptions
Expand All @@ -72,5 +75,10 @@ extension RootContainerViewModel {
userAuthenticationStatusMonitor.getStatus().sink { status in
self.updateState()
}.store(in: &cancellables)

notificationCenter.addObserver(self,
selector: #selector(updateState),
name: .DidInstallVPNProfile,
object: nil)
}
}
6 changes: 1 addition & 5 deletions PIA VPN-tvOS/RootContainer/UI/RootContainerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@ struct RootContainerView: View {
LoginFactory.makeLoginView()
.withAuthenticationRoutes()
.withOnboardingRoutes()
case .activatedNotOnboarded:
VPNConfigurationInstallingFactory.makeVPNConfigurationInstallingView()
.withOnboardingRoutes()
case .activated:
case .activatedNotOnboarded, .activated:
UserActivatedContainerFactory.makeUSerActivatedContainerView()
}
}.onChange(of: scenePhase) { _, newPhase in
if newPhase == .active {
NSLog(">>> Active")
viewModel.phaseDidBecomeActive()
} else if newPhase == .inactive {
NSLog(">>> Inactive")
} else if newPhase == .background {
Expand Down
4 changes: 4 additions & 0 deletions PIA VPN-tvOS/Shared/Utils/Foundation+Protocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ protocol NotificationCenterType {
}

extension NotificationCenter: NotificationCenterType {}

extension Notification.Name {
public static let DidInstallVPNProfile = Notification.Name("DidInstallVPNProfile")
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ class VPNConfigurationInstallingFactory {
private static func makeVPNConfigurationInstallingViewModel() -> VPNConfigurationInstallingViewModel {
VPNConfigurationInstallingViewModel(installVPNConfiguration:
makeInstallVPNConfigurationUseCase(),
errorMapper: VPNConfigurationInstallingErrorMapper(),
appRouter: AppRouter.shared,
successDestination: OnboardingDestinations.dashboard)
errorMapper: VPNConfigurationInstallingErrorMapper()) {
AppRouter.Actions.goBackToRoot(router: AppRouter.shared)()
NotificationCenter.default.post(name: .DidInstallVPNProfile, object: nil)
kp-juan-docal marked this conversation as resolved.
Show resolved Hide resolved
}
}

private static func makeInstallVPNConfigurationUseCase() -> InstallVPNConfigurationUseCaseType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ class InstallVpnConfigurationProvider: InstallVPNConfigurationUseCaseType {
return
}

continuation.resume()
vpnConfigurationAvailability.set(value: true)

continuation.resume()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,16 @@ import Foundation
class VPNConfigurationInstallingViewModel: ObservableObject {
private let installVPNConfiguration: InstallVPNConfigurationUseCaseType
private let errorMapper: VPNConfigurationInstallingErrorMapper
private let appRouter: AppRouterType
private let onSuccessAction: () -> Void

@Published var shouldShowErrorMessage = false
@Published var installingStatus: VPNConfigurationInstallingStatus = .none
var errorMessage: String?

private let successDestination: any Destinations

init(installVPNConfiguration: InstallVPNConfigurationUseCaseType, errorMapper: VPNConfigurationInstallingErrorMapper, appRouter: AppRouterType, successDestination: any Destinations) {
init(installVPNConfiguration: InstallVPNConfigurationUseCaseType, errorMapper: VPNConfigurationInstallingErrorMapper, onSuccessAction: @escaping () -> Void) {
self.installVPNConfiguration = installVPNConfiguration
self.errorMapper = errorMapper
self.appRouter = appRouter
self.successDestination = successDestination
self.onSuccessAction = onSuccessAction
}

func install() {
Expand All @@ -38,7 +35,7 @@ class VPNConfigurationInstallingViewModel: ObservableObject {
try await installVPNConfiguration()
Task { @MainActor in
installingStatus = .succeeded
appRouter.navigate(to: successDestination)
onSuccessAction()
}
} catch {
errorMessage = errorMapper.map(error: error)
Expand Down
1 change: 1 addition & 0 deletions PIA VPN-tvOSTests/Common/Mocks/AppRouterSpy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
@testable import PIA_VPN_tvOS

class AppRouterSpy: AppRouterType {

enum Request: Equatable {
static func == (lhs: AppRouterSpy.Request, rhs: AppRouterSpy.Request) -> Bool {
switch (lhs, rhs) {
Expand Down
2 changes: 1 addition & 1 deletion PIA VPN-tvOSTests/Dashboard/DashboardViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import SwiftUI
class DashboardViewModelTests: XCTestCase {
class Fixture {
let accountProviderMock = AccountProviderTypeMock()
let appRouter = AppRouter.shared
let appRouter = AppRouter(with: NavigationPath())
}

var fixture: Fixture!
Expand Down
8 changes: 3 additions & 5 deletions PIA VPN-tvOSTests/Login/LoginIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ final class LoginIntegrationTests: XCTestCase {
checkLoginAvailability: CheckLoginAvailability(),
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouter,
successDestination: OnboardingDestinations.installVPNProfile)
onSuccessAction: .navigate(router: appRouter, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for didLoginSuccessfully property to be updated")
Expand Down Expand Up @@ -104,9 +103,8 @@ final class LoginIntegrationTests: XCTestCase {
let sut = LoginViewModel(loginWithCredentialsUseCase: loginWithCredentialsUseCase,
checkLoginAvailability: CheckLoginAvailability(),
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouter,
successDestination: OnboardingDestinations.installVPNProfile)
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
onSuccessAction: .navigate(router: appRouter, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for isAccountExpired property to be updated")
Expand Down
18 changes: 6 additions & 12 deletions PIA VPN-tvOSTests/Login/LoginViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ final class LoginViewModelTests: XCTestCase {
checkLoginAvailability: checkLoginAvailabilityMock,
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouterSpy,
successDestination: OnboardingDestinations.installVPNProfile)
onSuccessAction: .navigate(router: appRouterSpy, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for shouldShowErrorMessage property to be updated")
Expand Down Expand Up @@ -72,8 +71,7 @@ final class LoginViewModelTests: XCTestCase {
checkLoginAvailability: checkLoginAvailabilityMock,
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouterSpy,
successDestination: OnboardingDestinations.installVPNProfile)
onSuccessAction: .navigate(router: appRouterSpy, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for shouldShowErrorMessage property to be updated")
Expand Down Expand Up @@ -117,8 +115,7 @@ final class LoginViewModelTests: XCTestCase {
checkLoginAvailability: checkLoginAvailabilityMock,
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouterSpy,
successDestination: OnboardingDestinations.installVPNProfile)
onSuccessAction: .navigate(router: appRouterSpy, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for shouldShowErrorMessage property to be updated")
Expand Down Expand Up @@ -163,8 +160,7 @@ final class LoginViewModelTests: XCTestCase {
checkLoginAvailability: checkLoginAvailabilityMock,
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouterSpy,
successDestination: OnboardingDestinations.installVPNProfile)
onSuccessAction: .navigate(router: appRouterSpy, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for didLoginSuccessfully property to be updated")
Expand Down Expand Up @@ -205,8 +201,7 @@ final class LoginViewModelTests: XCTestCase {
checkLoginAvailability: checkLoginAvailabilityMock,
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouterSpy,
successDestination: OnboardingDestinations.installVPNProfile)
onSuccessAction: .navigate(router: appRouterSpy, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for isAccountExpired property to be updated")
Expand Down Expand Up @@ -249,8 +244,7 @@ final class LoginViewModelTests: XCTestCase {
checkLoginAvailability: checkLoginAvailabilityMock,
validateLoginCredentials: ValidateCredentialsFormat(),
errorHandler: LoginViewModelErrorHandler(errorMapper: LoginPresentableErrorMapper()),
appRouter: appRouterSpy,
successDestination: OnboardingDestinations.installVPNProfile)
onSuccessAction: .navigate(router: appRouterSpy, destination: OnboardingDestinations.installVPNProfile))

var cancellables = Set<AnyCancellable>()
let expectation = expectation(description: "Waiting for shouldShowErrorMessage property to be updated")
Expand Down
Loading
Loading