Commit 1e82000b authored by Jose Blaya's avatar Jose Blaya
Browse files

Show email view after purchase

parent d803b691
......@@ -301,7 +301,6 @@
DDD824E82189C0EE00151709 /* BrandableNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD824E62189C0E800151709 /* BrandableNavigationBar.swift */; };
DDD824EA2189CD5700151709 /* NavigationLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD824E92189CD5700151709 /* NavigationLogoView.swift */; };
DDE27E0422E1B1A700503A89 /* ProductTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE27E0322E1B1A700503A89 /* ProductTests.swift */; };
DDE93C5722F9847E0054FE28 /* PurchaseTrialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE93C5622F9847E0054FE28 /* PurchaseTrialViewController.swift */; };
DDF7F73F2405846800A671C7 /* PIAWGTunnelProvider+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF7F73E2405846800A671C7 /* PIAWGTunnelProvider+Profile.swift */; };
DDFCFAA821E924A70081F235 /* TileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFCFAA721E924A70081F235 /* TileProvider.swift */; };
DDFCFAA921E924AD0081F235 /* TileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFCFAA721E924A70081F235 /* TileProvider.swift */; };
......@@ -601,7 +600,6 @@
DDD824E62189C0E800151709 /* BrandableNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrandableNavigationBar.swift; sourceTree = "<group>"; };
DDD824E92189CD5700151709 /* NavigationLogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationLogoView.swift; sourceTree = "<group>"; };
DDE27E0322E1B1A700503A89 /* ProductTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProductTests.swift; path = "../../../../../../System/Volumes/Data/Users/ueshiba/Projects/PIA/client-library-apple/PIALibraryTests/ProductTests.swift"; sourceTree = "<group>"; };
DDE93C5622F9847E0054FE28 /* PurchaseTrialViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseTrialViewController.swift; sourceTree = "<group>"; };
DDF7F73E2405846800A671C7 /* PIAWGTunnelProvider+Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PIAWGTunnelProvider+Profile.swift"; sourceTree = "<group>"; };
DDFCFAA721E924A70081F235 /* TileProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileProvider.swift; sourceTree = "<group>"; };
DDFCFAAA21E925160081F235 /* DefaultTileProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTileProvider.swift; sourceTree = "<group>"; };
......@@ -1149,7 +1147,6 @@
0E48A8521FDAD60900B9A4C0 /* OptionsViewController.swift */,
0EB8C0531F9CD38A005857E4 /* PIAWelcomeViewController.swift */,
0EB8C0551F9CD38A005857E4 /* PurchaseViewController.swift */,
DDE93C5622F9847E0054FE28 /* PurchaseTrialViewController.swift */,
DDA184D022FC1F79003239CC /* TermsAndConditionsViewController.swift */,
DD0DE84F22366090002A6E82 /* GDPRViewController.swift */,
0EB8C0561F9CD38A005857E4 /* RestoreSignupViewController.swift */,
......@@ -1873,7 +1870,6 @@
0E392DA31FE3247E0002160D /* Endpoint.swift in Sources */,
0E53A8581FE5DA16000C2A18 /* MockInAppProvider.swift in Sources */,
DDD824E32189969400151709 /* Preset.swift in Sources */,
DDE93C5722F9847E0054FE28 /* PurchaseTrialViewController.swift in Sources */,
0E9D62721FDE83BD009A90CF /* GlossServersBundle.swift in Sources */,
0E492C6A1FE61485007F23DF /* Client+Database.swift in Sources */,
0E48A8531FDAD60900B9A4C0 /* OptionsViewController.swift in Sources */,
......
......@@ -105,10 +105,11 @@ public protocol AccountProvider: class {
- Postcondition:
- Posts `Notification.Name.PIAAccountDidUpdate` on success.
- Parameter request: The account update request.
- Parameter resetPassword: Boolean to reset the password after update the account.
- Parameter password: The credentials to perform the operation.
- Parameter callback: Returns the updated `AccountInfo`.
*/
func update(with request: UpdateAccountRequest, andPassword password: String, _ callback: LibraryCallback<AccountInfo>?)
func update(with request: UpdateAccountRequest, resetPassword reset: Bool, andPassword password: String, _ callback: LibraryCallback<AccountInfo>?)
/**
Logs out of the system.
......
......@@ -66,8 +66,6 @@ protocol PlainStore: class {
var isPersistentConnection: Bool? { get set }
var gdprTermsAccepted: Bool? { get set }
var shouldConnectForAllNetworks: Bool? { get set }
var useWiFiProtection: Bool? { get set }
......
......@@ -36,7 +36,7 @@ protocol WebServices: class {
func info(token: String, _ callback: LibraryCallback<AccountInfo>?)
func update(credentials: Credentials, email: String, _ callback: SuccessLibraryCallback?)
func update(credentials: Credentials, resetPassword reset: Bool, email: String, _ callback: SuccessLibraryCallback?)
/**
Invalidates the access token.
......
......@@ -290,13 +290,13 @@ class DefaultAccountProvider: AccountProvider, ConfigurationAccess, DatabaseAcce
}
}
func update(with request: UpdateAccountRequest, andPassword password: String, _ callback: ((AccountInfo?, Error?) -> Void)?) {
func update(with request: UpdateAccountRequest, resetPassword reset: Bool, andPassword password: String, _ callback: ((AccountInfo?, Error?) -> Void)?) {
guard let user = currentUser else {
preconditionFailure()
}
let credentials = Credentials(username: Client.providers.accountProvider.publicUsername ?? "",
password: password)
webServices.update(credentials: credentials, email: request.email) { (error) in
webServices.update(credentials: credentials, resetPassword: reset, email: request.email) { (error) in
if let _ = error {
callback?(nil, error)
return
......
......@@ -29,9 +29,7 @@ private protocol PreferencesStore: class {
var preferredServer: Server? { get set }
var isPersistentConnection: Bool { get set }
var gdprTermsAccepted: Bool { get set }
var mace: Bool { get set }
var useWiFiProtection: Bool { get set }
......@@ -71,7 +69,6 @@ private extension PreferencesStore {
func load(from source: PreferencesStore) {
preferredServer = source.preferredServer
gdprTermsAccepted = source.gdprTermsAccepted
isPersistentConnection = source.isPersistentConnection
mace = source.mace
useWiFiProtection = source.useWiFiProtection
......@@ -121,16 +118,6 @@ extension Client {
}
}
/// GDPR treatment data agreement.
public fileprivate(set) var gdprTermsAccepted: Bool {
get {
return accessedDatabase.plain.gdprTermsAccepted ?? defaults.gdprTermsAccepted
}
set {
accessedDatabase.plain.gdprTermsAccepted = newValue
}
}
/// Enables automatic VPN reconnection.
public fileprivate(set) var isPersistentConnection: Bool {
get {
......@@ -330,7 +317,6 @@ extension Client.Preferences {
fileprivate init() {
preferredServer = nil
isPersistentConnection = true
gdprTermsAccepted = false
mace = false
useWiFiProtection = true
trustCellularData = false
......@@ -375,9 +361,6 @@ extension Client.Preferences {
/// :nodoc:
public var isPersistentConnection: Bool
/// :nodoc:
public var gdprTermsAccepted: Bool
/// :nodoc:
public var mace: Bool
......
......@@ -54,8 +54,6 @@ class UserDefaultsStore: PlainStore, ConfigurationAccess {
static let persistentConnection = "PersistentConnection" // legacy
static let gdprTermsAccepted = "GDPRTermsAccepted"
static let mace = "MACE" // legacy
static let visibleTiles = "VisibleTiles"
......@@ -358,18 +356,6 @@ class UserDefaultsStore: PlainStore, ConfigurationAccess {
}
}
var gdprTermsAccepted: Bool? {
get {
guard let value = backend.object(forKey: Entries.gdprTermsAccepted) as? Bool else {
return nil
}
return value
}
set {
backend.set(newValue, forKey: Entries.gdprTermsAccepted)
}
}
var useWiFiProtection: Bool? {
get {
guard let value = backend.object(forKey: Entries.useWiFiProtection) as? Bool else {
......
......@@ -115,9 +115,9 @@ class PIAWebServices: WebServices, ConfigurationAccess {
})
}
func update(credentials: Credentials, email: String, _ callback: SuccessLibraryCallback?) {
func update(credentials: Credentials, resetPassword reset: Bool, email: String, _ callback: SuccessLibraryCallback?) {
let endpoint = ClientEndpoint.updateAccount
let parameters = ["email": email]
let parameters = ["email": email, "reset_password": reset] as [String : Any]
let status = [200]
req(credentials, .post, endpoint, useAuthToken: false, parameters, status, JSONRequestExecutor() { (json, status, error) in
......
......@@ -228,8 +228,8 @@ public class MockAccountProvider: AccountProvider, WebServicesConsumer {
}
/// :nodoc:
public func update(with request: UpdateAccountRequest, andPassword password: String, _ callback: LibraryCallback<AccountInfo>?) {
delegate.update(with: request, andPassword: password, callback)
public func update(with request: UpdateAccountRequest, resetPassword reset: Bool, andPassword password: String, _ callback: LibraryCallback<AccountInfo>?) {
delegate.update(with: request, resetPassword: reset, andPassword: password, callback)
}
/// :nodoc:
......
......@@ -55,7 +55,7 @@ class MockWebServices: WebServices {
callback?(result, error)
}
func update(credentials: Credentials, email: String, _ callback: SuccessLibraryCallback?) {
func update(credentials: Credentials, resetPassword reset: Bool, email: String, _ callback: SuccessLibraryCallback?) {
callback?(nil)
}
......
......@@ -25,8 +25,6 @@ internal enum StoryboardScene {
internal static let loginViewController = SceneType<PIALibrary.LoginViewController>(storyboard: Welcome.self, identifier: "LoginViewController")
internal static let purchaseViewController = SceneType<PIALibrary.PurchaseViewController>(storyboard: Welcome.self, identifier: "PurchaseViewController")
internal static let purchaseTrialViewController = SceneType<PIALibrary.PurchaseTrialViewController>(storyboard: Welcome.self, identifier: "PurchaseTrialViewController")
internal static let restoreViewController = SceneType<PIALibrary.RestoreSignupViewController>(storyboard: Welcome.self, identifier: "RestoreSignupViewController")
......
......@@ -15,23 +15,20 @@ internal enum StoryboardSegue {
internal enum Signup: String, SegueType {
case failureSegueIdentifier = "FailureSegueIdentifier"
case internetUnreachableSegueIdentifier = "InternetUnreachableSegueIdentifier"
case presentGDPRTermsSegue = "PresentGDPRTermsSegue"
case successSegueIdentifier = "SuccessSegueIdentifier"
case successShowCredentialsSegueIdentifier = "SuccessShowCredentialsSegueIdentifier"
case unwindFailureSegueIdentifier = "UnwindFailureSegueIdentifier"
case unwindInternetUnreachableSegueIdentifier = "UnwindInternetUnreachableSegueIdentifier"
}
internal enum Welcome: String, SegueType {
case confirmPurchaseVPNPlanSegue = "ConfirmPurchaseVPNPlanSegue"
case loginAccountSegue = "LoginAccountSegue"
case presentGDPRTermsSegue = "PresentGDPRTermsSegue"
case purchaseVPNPlanSegue = "PurchaseVPNPlanSegue"
case restoreLoginPurchaseSegue = "RestoreLoginPurchaseSegue"
case restorePurchaseSegue = "RestorePurchaseSegue"
case showTermsAndConditionsSegue = "ShowTermsAndConditionsSegue"
case signupViaPurchaseSegue = "SignupViaPurchaseSegue"
case signupViaRecoverSegue = "SignupViaRecoverSegue"
case signupViaRestoreSegue = "SignupViaRestoreSegue"
case subscribeNowVPNPlanSegue = "SubscribeNowVPNPlanSegue"
case viewMoreVPNPlansSegue = "ViewMoreVPNPlansSegue"
}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
......
......@@ -29,18 +29,17 @@ public class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNa
@IBOutlet private weak var buttonConfirm: PIAButton!
@IBOutlet private weak var textEmail: BorderedTextField!
@IBOutlet private weak var textAgreement: UITextView!
@IBOutlet private weak var labelTitle: UILabel!
@IBOutlet private weak var labelSubtitle: UILabel!
private var signupEmail: String?
private var signupTransaction: InAppTransaction?
var metadata: SignupMetadata!
weak var completionDelegate: WelcomeCompletionDelegate?
var omitsSiblingLink = false
var termsAndConditionsAgreed = false
var preset: Preset?
private var allPlans: [PurchasePlan] = [.dummy, .dummy]
private var selectedPlanIndex: Int?
deinit {
NotificationCenter.default.removeObserver(self)
......@@ -53,54 +52,19 @@ public class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNa
fatalError("Preset not propagated")
}
guard let planIndex = selectedPlanIndex else {
return
}
self.navigationItem.leftBarButtonItem = UIBarButtonItem(
image: Theme.current.palette.navigationBarBackIcon?.withRenderingMode(.alwaysOriginal),
style: .plain,
target: self,
action: #selector(back(_:))
)
self.navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Welcome.Redeem.Accessibility.back
navigationItem.hidesBackButton = true
labelTitle.text = L10n.Welcome.Purchase.Confirm.Form.email
let plan = allPlans[planIndex]
labelSubtitle.text = L10n.Welcome.Purchase.Email.why
var price = ""
for plan in allPlans {
if plan.plan == .yearly {
price = plan.priceString
}
}
textAgreement.attributedText = Theme.current.agreementText(
withMessage: L10n.Welcome.Agreement.message(price),
tos: L10n.Welcome.Agreement.Message.tos,
tosUrl: Client.configuration.tosUrl,
privacy: L10n.Welcome.Agreement.Message.privacy,
privacyUrl: Client.configuration.privacyUrl
)
textEmail.placeholder = L10n.Welcome.Purchase.Email.placeholder
textEmail.text = preset.purchaseEmail
self.styleConfirmButton()
}
/// Populate the view with the values from PurchaseViewController
/// - Parameters:
/// - plans: The available plans.
/// - selectedIndex: The selected plan from the previous screen.
func populateViewWith(plans: [PurchasePlan], andSelectedPlanIndex selectedIndex: Int) {
self.allPlans = plans
self.selectedPlanIndex = selectedIndex
}
@IBAction private func signUp(_ sender: Any?) {
guard let planIndex = selectedPlanIndex else {
return
}
guard let email = textEmail.text?.trimmed(), Validator.validate(email: email) else {
signupEmail = nil
Macros.displayImageNote(withImage: Asset.iconWarning.image,
......@@ -109,29 +73,63 @@ public class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNa
return
}
self.status = .restore(element: textEmail)
let plan = allPlans[planIndex]
guard !plan.isDummy else {
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: L10n.Welcome.Iap.Error.Message.unavailable)
guard termsAndConditionsAgreed else {
//present term and conditions
self.performSegue(withIdentifier: StoryboardSegue.Signup.presentGDPRTermsSegue.rawValue,
sender: nil)
return
}
self.status = .restore(element: textEmail)
disableInteractions()
preset?.accountProvider.isAPIEndpointAvailable({ [weak self] (isAvailable, error) in
self?.enableInteractions()
guard let isAvailable = isAvailable,
isAvailable else {
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: L10n.Welcome.Purchase.Error.Connectivity.description)
return
}
self?.startPurchaseProcessWithEmail(email,
andPlan: plan)
})
self.showLoadingAnimation()
self.disableInteractions()
log.debug("Account: Modifying account email...")
metadata.title = L10n.Signup.InProgress.title
metadata.bodyImage = Asset.imagePurchaseSuccess.image
metadata.bodyTitle = L10n.Signup.Success.title
metadata.bodySubtitle = L10n.Signup.Success.messageFormat(email)
let request = UpdateAccountRequest(email: email)
var password = ""
if let currentPassword = metadata.user?.credentials.password {
password = currentPassword
}
Client.providers.accountProvider.update(with: request,
resetPassword: false,
andPassword: password) { [weak self] (info, error) in
self?.hideLoadingAnimation()
self?.enableInteractions()
guard let _ = info else {
if let error = error {
log.error("Account: Failed to modify account email (error: \(error))")
} else {
log.error("Account: Failed to modify account email")
}
self?.textEmail.text = ""
/*
let alert = Macros.alert(L10n.Global.error, L10n.Account.Error.unauthorized)
alert.addDefaultAction(L10n.Global.close)
self?.present(alert, animated: true, completion: nil)
*/
let alert = Macros.alert("Error", "Error msg")
alert.addDefaultAction("Close")
self?.present(alert, animated: true, completion: nil)
return
}
log.debug("Account: Email successfully modified")
self?.textEmail.endEditing(true)
self?.perform(segue: StoryboardSegue.Signup.successShowCredentialsSegueIdentifier)
}
}
private func disableInteractions() {
......@@ -142,71 +140,25 @@ public class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNa
parent?.view.isUserInteractionEnabled = true
}
private func startPurchaseProcessWithEmail(_ email: String,
andPlan plan: PurchasePlan) {
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard !Client.store.hasUncreditedTransactions else {
let alert = Macros.alert(
nil,
L10n.Signup.Purchase.Uncredited.Alert.message
)
alert.addCancelAction(L10n.Signup.Purchase.Uncredited.Alert.Button.cancel)
alert.addActionWithTitle(L10n.Signup.Purchase.Uncredited.Alert.Button.recover) {
self.navigationController?.popToRootViewController(animated: true)
Macros.postNotification(.PIARecoverAccount)
}
present(alert, animated: true, completion: nil)
guard let identifier = segue.identifier, let segueType = StoryboardSegue.Signup(rawValue: identifier) else {
return
}
//textEmail.text = email
log.debug("Will purchase plan: \(plan.product)")
disableInteractions()
self.showLoadingAnimation()
preset?.accountProvider.purchase(plan: plan.plan) { (transaction, error) in
self.enableInteractions()
self.hideLoadingAnimation()
guard let transaction = transaction else {
if let error = error {
let message = error.localizedDescription
log.error("Purchase failed (error: \(error))")
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: message)
} else {
log.warning("Cancelled purchase")
}
return
}
log.debug("Purchased with transaction: \(transaction)")
self.signupEmail = email
self.signupTransaction = transaction
self.perform(segue: StoryboardSegue.Welcome.signupViaPurchaseSegue)
}
}
override public func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == StoryboardSegue.Welcome.signupViaPurchaseSegue.rawValue) {
let nav = segue.destination as! UINavigationController
let vc = nav.topViewController as! SignupInProgressViewController
guard let email = signupEmail else {
fatalError("Signing up and signupEmail is not set")
}
var metadata = SignupMetadata(email: email)
metadata.title = L10n.Signup.InProgress.title
metadata.bodySubtitle = L10n.Signup.InProgress.message
switch segueType {
case .successShowCredentialsSegueIdentifier:
let vc = segue.destination as! SignupSuccessViewController
vc.metadata = metadata
vc.signupRequest = SignupRequest(email: email, transaction: signupTransaction)
vc.preset = preset
vc.completionDelegate = completionDelegate
break
case .presentGDPRTermsSegue:
let gdprViewController = segue.destination as! GDPRViewController
gdprViewController.delegate = self
break
default:
break
}
}
// MARK: Restylable
......@@ -218,7 +170,6 @@ public class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNa
Theme.current.applyInput(textEmail)
Theme.current.applyTitle(labelTitle, appearance: .dark)
Theme.current.applySubtitle(labelSubtitle)
Theme.current.applyLinkAttributes(textAgreement)
}
private func styleConfirmButton() {
......@@ -229,3 +180,16 @@ public class ConfirmVPNPlanViewController: AutolayoutViewController, BrandableNa
}
}
extension ConfirmVPNPlanViewController: GDPRDelegate {
public func gdprViewWasAccepted() {
self.termsAndConditionsAgreed = true
self.signUp(nil)
}
public func gdprViewWasRejected() {
self.termsAndConditionsAgreed = false
}
}
......@@ -57,8 +57,6 @@ public class GetStartedViewController: PIAWelcomeViewController {
@IBOutlet private weak var buttonViewConstraintHeight: NSLayoutConstraint!
@IBOutlet private weak var hiddenButtonsConstraintHeight: NSLayoutConstraint!
var termsWerePresentedOnce = false
private var buttonViewIsExpanded = false {
didSet {
self.updateButtonView()
......@@ -233,10 +231,7 @@ public class GetStartedViewController: PIAWelcomeViewController {
public override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == StoryboardSegue.Welcome.presentGDPRTermsSegue.rawValue {
let gdprViewController = segue.destination as! GDPRViewController
gdprViewController.delegate = self
} else if (segue.identifier == StoryboardSegue.Welcome.signupViaPurchaseSegue.rawValue) {
if (segue.identifier == StoryboardSegue.Welcome.signupViaPurchaseSegue.rawValue) {
let nav = segue.destination as! UINavigationController
let vc = nav.topViewController as! SignupInProgressViewController
......@@ -295,20 +290,6 @@ public class GetStartedViewController: PIAWelcomeViewController {
} else {
disableInteractions(fully: false)
}
guard Client.preferences.gdprTermsAccepted else {
if !termsWerePresentedOnce {
//present term and conditions
termsWerePresentedOnce = true
self.performSegue(withIdentifier: StoryboardSegue.Welcome.presentGDPRTermsSegue.rawValue,
sender: nil)
return
}
return
}
}
private func styleButtons() {
......@@ -497,19 +478,3 @@ extension GetStartedViewController: UIScrollViewDelegate {
updateButtonsToCurrentPage()
}
}
extension GetStartedViewController: GDPRDelegate {
public func gdprViewWasAccepted() {
let preferences = Client.preferences.editable()
preferences.gdprTermsAccepted = true
preferences.commit()
}