-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PIA-882: Login feature for tvOS (#44)
* PIA-882: Add domain-app logic components for Login * PIA-882: Add presentation components for Login * PIA-882: Add data components for Login * PIA-882: Add LoginView * PIA-882: Add Login factory to create Login feature * PIA-882: Add unit and integration tests for Login feature on tvOs * PIA-882: Decoupled PIALibrary from presentation and domain layers
- Loading branch information
1 parent
c0fc9b5
commit d9f39a2
Showing
29 changed files
with
1,315 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// | ||
// LoginFactory.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 4/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import PIALibrary | ||
|
||
class LoginFactory { | ||
static func makeLoginView() -> LoginView { | ||
LoginView(viewModel: makeLoginViewModel()) | ||
} | ||
|
||
private static func makeLoginViewModel() -> LoginViewModel { | ||
LoginViewModel(loginWithCredentialsUseCase: makeLoginWithCredentialsUseCase(), | ||
checkLoginAvailability: CheckLoginAvailability(), | ||
validateLoginCredentials: ValidateCredentialsFormat(), | ||
errorMapper: LoginPresentableErrorMapper()) | ||
} | ||
|
||
private static func makeLoginWithCredentialsUseCase() -> LoginWithCredentialsUseCaseType { | ||
LoginWithCredentialsUseCase(loginProvider: makeLoginProvider(), | ||
errorMapper: LoginDomainErrorMapper()) | ||
} | ||
|
||
private static func makeLoginProvider() -> LoginProviderType { | ||
LoginProvider(accountProvider: Client.providers.accountProvider, | ||
userAccountMapper: UserAccountMapper()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// | ||
// LoginDomainErrorMapper.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 4/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import PIALibrary | ||
|
||
class LoginDomainErrorMapper: LoginDomainErrorMapperType { | ||
func map(error: Error?) -> LoginError { | ||
guard let clientError = error as? ClientError else { | ||
return .generic(message: error?.localizedDescription) | ||
} | ||
|
||
switch clientError { | ||
case .unauthorized: | ||
return .unauthorized | ||
|
||
case .throttled(retryAfter: let retryAfter): | ||
return .throttled(retryAfter: Double(retryAfter)) | ||
|
||
case .expired: | ||
return .expired | ||
default: | ||
return .generic(message: error?.localizedDescription) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// | ||
// LoginProvider.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 4/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import PIALibrary | ||
|
||
class LoginProvider: LoginProviderType { | ||
private let accountProvider: AccountProvider | ||
private let userAccountMapper: UserAccountMapper | ||
|
||
init(accountProvider: AccountProvider, userAccountMapper: UserAccountMapper) { | ||
self.accountProvider = accountProvider | ||
self.userAccountMapper = userAccountMapper | ||
} | ||
|
||
func login(with credentials: Credentials, completion: @escaping (Result<UserAccount, Error>) -> Void) { | ||
let pialibraryCredentials = PIALibrary.Credentials(username: credentials.username, password: credentials.password) | ||
let request = LoginRequest(credentials: pialibraryCredentials) | ||
|
||
accountProvider.login(with: request) { [weak self] userAccount, error in | ||
guard let self = self else { return } | ||
|
||
if let error = error { | ||
completion(.failure(error)) | ||
return | ||
} | ||
|
||
guard let userAccount = userAccount else { | ||
completion(.failure(ClientError.unexpectedReply)) | ||
return | ||
} | ||
|
||
completion(.success(userAccountMapper.map(userAccount: userAccount))) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// | ||
// UserAccountMapper.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 12/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import PIALibrary | ||
|
||
class UserAccountMapper { | ||
func map(userAccount: PIALibrary.UserAccount) -> UserAccount { | ||
let credentials = Credentials(username: userAccount.credentials.username, | ||
password: userAccount.credentials.password) | ||
|
||
guard let info = userAccount.info else { | ||
return UserAccount(credentials: credentials, info: nil) | ||
} | ||
|
||
let accountInfo = AccountInfo(email: info.email, | ||
username: info.username, | ||
plan: Plan.map(plan: info.plan), | ||
productId: info.productId, | ||
isRenewable: info.isRenewable, | ||
isRecurring: info.isRecurring, | ||
expirationDate: info.expirationDate, | ||
canInvite: info.canInvite, | ||
shouldPresentExpirationAlert: info.shouldPresentExpirationAlert, | ||
renewUrl: info.renewUrl) | ||
|
||
return UserAccount(credentials: credentials, info: accountInfo) | ||
} | ||
} | ||
|
||
extension Plan { | ||
static func map(plan: PIALibrary.Plan) -> Plan { | ||
switch plan { | ||
case .monthly: | ||
return .monthly | ||
case .yearly: | ||
return .yearly | ||
case .trial: | ||
return .trial | ||
case .other: | ||
return .other | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// | ||
// AccountInfo.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 12/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
enum Plan: String { | ||
case monthly | ||
case yearly | ||
case trial | ||
case other | ||
} | ||
|
||
struct AccountInfo { | ||
let email: String? | ||
let username: String | ||
let plan: Plan | ||
let productId: String? | ||
let isRenewable: Bool | ||
let isRecurring: Bool | ||
let expirationDate: Date | ||
let canInvite: Bool | ||
|
||
public var isExpired: Bool { | ||
return (expirationDate.timeIntervalSinceNow < 0) | ||
} | ||
|
||
public var dateComponentsBeforeExpiration: DateComponents { | ||
return Calendar.current.dateComponents([.day, .hour], from: Date(), to: expirationDate) | ||
} | ||
|
||
public let shouldPresentExpirationAlert: Bool | ||
public let renewUrl: URL? | ||
|
||
public func humanReadableExpirationDate(usingLocale locale: Locale = Locale.current) -> String { | ||
let dateFormatter = DateFormatter() | ||
dateFormatter.dateStyle = .long | ||
dateFormatter.timeStyle = .none | ||
dateFormatter.locale = locale | ||
return dateFormatter.string(from: self.expirationDate) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// | ||
// Credentials.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 12/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
struct Credentials { | ||
let username: String | ||
let password: String | ||
|
||
init(username: String, password: String) { | ||
self.username = username | ||
self.password = password | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// | ||
// LoginError.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 29/11/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
enum LoginError: Error { | ||
case unauthorized | ||
case throttled(retryAfter: Double) | ||
case expired | ||
case usernameWrongFormat | ||
case passwordWrongFormat | ||
case generic(message: String?) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// | ||
// UserAccount.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 12/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
struct UserAccount { | ||
let credentials: Credentials | ||
let info: AccountInfo? | ||
|
||
var isRenewable: Bool { | ||
return info?.isRenewable ?? false | ||
} | ||
|
||
init(credentials: Credentials, info: AccountInfo?) { | ||
self.credentials = credentials | ||
self.info = info | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
PIA VPN-tvOS/Login/Domain/Interfaces/LoginDomainErrorMapperType.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// LoginDomainErrorMapperType.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 4/12/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol LoginDomainErrorMapperType { | ||
func map(error: Error?) -> LoginError | ||
} |
13 changes: 13 additions & 0 deletions
13
PIA VPN-tvOS/Login/Domain/Interfaces/LoginProviderType.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// LoginProviderType.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 27/11/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol LoginProviderType { | ||
func login(with credentials: Credentials, completion: @escaping (Result<UserAccount, Error>) -> Void) | ||
} |
43 changes: 43 additions & 0 deletions
43
PIA VPN-tvOS/Login/Domain/Use cases/LoginWithCredentialsUseCase.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// LoginWithCredentialsUseCase.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 27/11/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol LoginWithCredentialsUseCaseType { | ||
func execute(username: String, password: String, completion: @escaping (Result<UserAccount, LoginError>) -> Void) | ||
} | ||
|
||
class LoginWithCredentialsUseCase: LoginWithCredentialsUseCaseType { | ||
private let loginProvider: LoginProviderType | ||
private let errorMapper: LoginDomainErrorMapperType | ||
|
||
init(loginProvider: LoginProviderType, errorMapper: LoginDomainErrorMapperType) { | ||
self.loginProvider = loginProvider | ||
self.errorMapper = errorMapper | ||
} | ||
|
||
func execute(username: String, password: String, completion: @escaping (Result<UserAccount, LoginError>) -> Void) { | ||
let credentials = Credentials(username: username, | ||
password: password) | ||
|
||
loginProvider.login(with: credentials) { [weak self] result in | ||
guard let self = self else { return } | ||
|
||
switch result { | ||
case .success(let userAccount): | ||
completion(.success(userAccount)) | ||
case .failure(let error): | ||
completion(.failure(errorMapper.map(error: error))) | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
|
||
|
30 changes: 30 additions & 0 deletions
30
PIA VPN-tvOS/Login/Presentation/CheckLoginAvailability.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// CheckLoginAvailability.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 29/11/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol CheckLoginAvailabilityType { | ||
func disableLoginFor(_ delay: Double) | ||
func callAsFunction() -> Result<Void, LoginError> | ||
} | ||
|
||
class CheckLoginAvailability: CheckLoginAvailabilityType { | ||
private var timeToRetryCredentials: TimeInterval? = nil | ||
|
||
func disableLoginFor(_ delay: Double) { | ||
timeToRetryCredentials = delay | ||
} | ||
|
||
func callAsFunction() -> Result<Void, LoginError> { | ||
if let timeUntilNextTry = timeToRetryCredentials?.timeSinceNow() { | ||
return .failure(.throttled(retryAfter: timeUntilNextTry)) | ||
} | ||
|
||
return .success(()) | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
PIA VPN-tvOS/Login/Presentation/LoginPresentableErrorMapper.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// LoginPresentableErrorMapper.swift | ||
// PIA VPN-tvOS | ||
// | ||
// Created by Said Rehouni on 28/11/23. | ||
// Copyright © 2023 Private Internet Access Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
class LoginPresentableErrorMapper { | ||
func map(error: LoginError) -> String? { | ||
return nil | ||
} | ||
} |
Oops, something went wrong.