Commit e15ffd02 authored by Davide De Rosa's avatar Davide De Rosa
Browse files

Connect redeem controller to signup storyboard

Make Signup* use both signup and redeem requests. Reuse failure
vc with dynamic image/messages based on error parameter.

Pack signup metadata.

Model:

- Signup email
- Created user

Graphical:

- VC title
- Body image
- Body title
- Body subtitle (message)
parent e18c943f
......@@ -28,6 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Client.providers.accountProvider = mock
Client.useMockServerProvider()
Client.useMockVPNProvider()
Client.useMockInAppProvider()
Client.database.truncate()
Client.bootstrap()
......
......@@ -167,6 +167,7 @@
0EA8072D20A1C7A30033EC1A /* RedeemRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA8072B20A1C7A20033EC1A /* RedeemRequest.swift */; };
0EA8072F20A1E7C60033EC1A /* GlossRedeem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA8072E20A1E7C60033EC1A /* GlossRedeem.swift */; };
0EA8073020A1E7C60033EC1A /* GlossRedeem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA8072E20A1E7C60033EC1A /* GlossRedeem.swift */; };
0EA8073220A2F50A0033EC1A /* SignupMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA8073120A2F50A0033EC1A /* SignupMetadata.swift */; };
0EAA388B1F9CC4C4000149CF /* InAppProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAA38881F9CC4C4000149CF /* InAppProduct.swift */; };
0EAA388C1F9CC4C4000149CF /* InAppProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAA38891F9CC4C4000149CF /* InAppProvider.swift */; };
0EAA388D1F9CC4C4000149CF /* InAppTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAA388A1F9CC4C4000149CF /* InAppTransaction.swift */; };
......@@ -407,6 +408,7 @@
0EA8072620A1A0090033EC1A /* Redeem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Redeem.swift; sourceTree = "<group>"; };
0EA8072B20A1C7A20033EC1A /* RedeemRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemRequest.swift; sourceTree = "<group>"; };
0EA8072E20A1E7C60033EC1A /* GlossRedeem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlossRedeem.swift; sourceTree = "<group>"; };
0EA8073120A2F50A0033EC1A /* SignupMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignupMetadata.swift; sourceTree = "<group>"; };
0EAA38881F9CC4C4000149CF /* InAppProduct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppProduct.swift; sourceTree = "<group>"; };
0EAA38891F9CC4C4000149CF /* InAppProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppProvider.swift; sourceTree = "<group>"; };
0EAA388A1F9CC4C4000149CF /* InAppTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppTransaction.swift; sourceTree = "<group>"; };
......@@ -780,6 +782,7 @@
0EB8C0571F9CD38A005857E4 /* SignupFailureViewController.swift */,
0EB8C0581F9CD38A005857E4 /* SignupInProgressViewController.swift */,
0EB8C0591F9CD38A005857E4 /* SignupInternetUnreachableViewController.swift */,
0EA8073120A2F50A0033EC1A /* SignupMetadata.swift */,
0EB8C05A1F9CD38A005857E4 /* SignupSuccessViewController.swift */,
0E2215CE2008DFD900F5FB4D /* SwiftGen+Assets.swift */,
0ED1585B1FDC0D1B008F6522 /* SwiftGen+Storyboards.swift */,
......@@ -1559,6 +1562,7 @@
0EBBC6DC1F9F64E700B8BD21 /* Client+Environment.swift in Sources */,
0EB3D9821FF02FE5005B11F4 /* VPNAction.swift in Sources */,
0E2ADD371FE14F0000BB170C /* DefaultVPNProvider.swift in Sources */,
0EA8073220A2F50A0033EC1A /* SignupMetadata.swift in Sources */,
0E4D4E9F1FA4CA7A007DA6DA /* Restylable.swift in Sources */,
0EB8C06E1F9CD38B005857E4 /* SignupInternetUnreachableViewController.swift in Sources */,
0E75D8D31F9E3F9F00658D1E /* UserAccount.swift in Sources */,
......
......@@ -2,7 +2,6 @@
"images" : [
{
"idiom" : "universal",
"filename" : "image-account-failed.png",
"scale" : "1x"
},
{
......
......@@ -2,7 +2,6 @@
"images" : [
{
"idiom" : "universal",
"filename" : "image-no-internet.png",
"scale" : "1x"
},
{
......
......@@ -2,7 +2,6 @@
"images" : [
{
"idiom" : "universal",
"filename" : "image-purchase-success.png",
"scale" : "1x"
},
{
......
......@@ -2,7 +2,6 @@
"images" : [
{
"idiom" : "universal",
"filename" : "image-receipt-background.png",
"scale" : "1x"
},
{
......
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-claimed@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-claimed@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-expired@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-expired@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-invalid@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-invalid@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-success@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "image-redeem-success@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
......@@ -2,7 +2,6 @@
"images" : [
{
"idiom" : "universal",
"filename" : "nav-logo.png",
"scale" : "1x"
},
{
......
......@@ -1110,8 +1110,8 @@ You will not be charged during this process.</string>
<variation key="default">
<mask key="constraints">
<exclude reference="Cx0-9R-80o"/>
<exclude reference="wiL-Hp-aF4"/>
<exclude reference="gnC-Zx-jwS"/>
<exclude reference="wiL-Hp-aF4"/>
</mask>
</variation>
<variation key="heightClass=compact">
......@@ -1254,8 +1254,8 @@ You will not be charged during this process.</string>
<viewLayoutGuide key="safeArea" id="rwM-xJ-4Mg"/>
<variation key="default">
<mask key="constraints">
<exclude reference="nm6-jy-D7Z"/>
<exclude reference="hLc-Dd-43g"/>
<exclude reference="nm6-jy-D7Z"/>
</mask>
</variation>
<variation key="heightClass=compact">
......@@ -1277,6 +1277,7 @@ You will not be charged during this process.</string>
<outlet property="textCode" destination="pbJ-dg-A3t" id="3HI-fT-yNN"/>
<outlet property="textEmail" destination="3ne-g6-NDW" id="tUr-cH-df2"/>
<outlet property="viewFooter" destination="h4C-e9-6n1" id="xcl-1q-9mx"/>
<segue destination="9jt-8I-K8i" kind="presentation" identifier="SignupViaRedeemSegue" id="Knv-fa-JGO"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="r3v-al-5qU" userLabel="First Responder" sceneMemberID="firstResponder"/>
......@@ -1288,6 +1289,6 @@ You will not be charged during this process.</string>
<image name="nav-logo" width="354" height="36"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="EEe-nI-dHO"/>
<segue reference="Knv-fa-JGO"/>
</inferredMetricsTieBreakers>
</document>
......@@ -6,16 +6,26 @@
Copyright © 2017 London Trust Media. All rights reserved.
*/
"in_progress.title" = "Confirm sign-up";
"in_progress.message" = "We're confirming your purchase with our system. It could take a moment so hang in there.";
"in_progress.redeem.message" = "We're confirming your card PIN with our system. It could take a moment so hang in there.";
"success.title" = "Purchase complete";
"success.message_format" = "Thank you for signing up with us. We have sent your account username and password at your email address at %@";
"success.redeem.title" = "Completed!";
"success.redeem.message" = "You will receive an email shortly with your username and password.";
"success.username.caption" = "Username";
"success.password.caption" = "Password";
"success.submit" = "GET STARTED";
"failure.title" = "Account creation failed";
"failure.message" = "We're unable to create an account at this time. Please try again later. Reopening the app will re-attempt to create an account.";
"failure.redeem.invalid.title" = "Invalid card PIN";
"failure.redeem.invalid.message" = "Looks like you entered an invalid card PIN. Please try again.";
"failure.redeem.claimed.title" = "Card claimed already";
"failure.redeem.claimed.message" = "Looks like this card has already been claimed by another account. You can try entering a different PIN.";
"failure.redeem.expired.title" = "Card expired";
"failure.redeem.expired.message" = "Looks like this card has expired. You can try entering a different PIN.";
"failure.submit" = "GO BACK";
"unreachable.title" = "Whoops!";
......
......@@ -34,6 +34,8 @@
"redeem.submit" = "SUBMIT";
//"redeem.login.footer" = "Already have an account?";
//"redeem.login.button" = "Sign in!";
"redeem.error.title" = "Redeem";
"redeem.error.code" = "Code must be 16 numeric digits.";
"plan.monthly.title" = "Monthly";
"plan.yearly.title" = "Yearly";
......
......@@ -22,4 +22,14 @@ public class Validator {
public static func validate(email: String) -> Bool {
return NSPredicate(format: "SELF MATCHES %@", "^[^\\s]+@((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,}$").evaluate(with: email)
}
/**
Validates a gift code.
- Parameter giftCode: The gift code to validate.
- Returns: `true` if the code syntax is valid.
*/
public static func validate(giftCode: String) -> Bool {
return NSPredicate(format: "SELF MATCHES %@", "^\\d{16}$").evaluate(with: giftCode)
}
}
......@@ -164,7 +164,11 @@ class LoginViewController: AutolayoutViewController, WelcomeChild {
guard let email = signupEmail else {
fatalError("Signing up and signupEmail is not set")
}
vc.request = SignupRequest(email: email)
var metadata = SignupMetadata(email: email)
metadata.title = L10n.Signup.InProgress.title
metadata.bodySubtitle = L10n.Signup.InProgress.message
vc.metadata = metadata
vc.signupRequest = SignupRequest(email: email)
}
}
......
......@@ -212,7 +212,11 @@ public class PIAWelcomeViewController: AutolayoutViewController, WelcomeCompleti
guard let request = pendingSignupRequest else {
fatalError("Recovering signup and pendingSignupRequest is not set")
}
vc.request = request
var metadata = SignupMetadata(email: request.email)
metadata.title = L10n.Signup.InProgress.title
metadata.bodySubtitle = L10n.Signup.InProgress.message
vc.metadata = metadata
vc.signupRequest = request
}
}
......
......@@ -207,7 +207,11 @@ class PurchaseViewController: AutolayoutViewController, WelcomeChild {
guard let email = signupEmail else {
fatalError("Signing up and signupEmail is not set")
}
vc.request = SignupRequest(email: email, transaction: signupTransaction)
var metadata = SignupMetadata(email: email)
metadata.title = L10n.Signup.InProgress.title
metadata.bodySubtitle = L10n.Signup.InProgress.message
vc.metadata = metadata
vc.signupRequest = SignupRequest(email: email, transaction: signupTransaction)
vc.preset = preset
vc.completionDelegate = completionDelegate
}
......
......@@ -7,6 +7,9 @@
//
import UIKit
import SwiftyBeaver
private let log = SwiftyBeaver.self
class RedeemViewController: AutolayoutViewController, WelcomeChild {
@IBOutlet private weak var scrollView: UIScrollView!
......@@ -37,6 +40,10 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
var completionDelegate: WelcomeCompletionDelegate?
private var redeemEmail: String?
private var redeemCode: String?
override func viewDidLoad() {
super.viewDidLoad()
......@@ -77,9 +84,58 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
// MARK: Actions
@IBAction private func redeem(_ sender: Any?) {
//
guard !buttonRedeem.isRunningActivity else {
return
}
guard let email = textEmail.text, Validator.validate(email: email) else {
let alert = Macros.alert(
L10n.Welcome.Redeem.Error.title,
L10n.Welcome.Purchase.Error.validation
)
alert.addCancelAction(L10n.Ui.Global.ok)
present(alert, animated: true, completion: nil)
return
}
guard let code = textCode.text, Validator.validate(giftCode: code) else {
let alert = Macros.alert(
L10n.Welcome.Redeem.Error.title,
L10n.Welcome.Redeem.Error.code
)
alert.addCancelAction(L10n.Ui.Global.ok)
present(alert, animated: true, completion: nil)
return
}
log.debug("Redeeming...")
redeemEmail = email
redeemCode = code
perform(segue: StoryboardSegue.Welcome.signupViaRedeemSegue)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == StoryboardSegue.Welcome.signupViaRedeemSegue.rawValue) {
let nav = segue.destination as! UINavigationController
let vc = nav.topViewController as! SignupInProgressViewController
guard let email = redeemEmail else {
fatalError("Redeeming and redeemEmail is not set")
}
guard let code = redeemCode else {
fatalError("Redeeming and redeemCode is not set")
}
var metadata = SignupMetadata(email: email)
metadata.title = L10n.Welcome.Redeem.title
metadata.bodySubtitle = L10n.Signup.InProgress.Redeem.message
vc.metadata = metadata
vc.redeemRequest = RedeemRequest(email: email, code: code)
vc.preset = preset
vc.completionDelegate = completionDelegate
}
}
private func enableInteractions(_ enable: Bool) {
parent?.view.isUserInteractionEnabled = enable
if enable {
......
......@@ -16,6 +16,8 @@ class SignupFailureViewController: AutolayoutViewController {
@IBOutlet private weak var labelMessage: UILabel!
@IBOutlet private weak var buttonSubmit: ActivityButton!
var error: Error?
override func viewDidLoad() {
super.viewDidLoad()
......@@ -25,6 +27,35 @@ class SignupFailureViewController: AutolayoutViewController {
imvPicture.image = Asset.imageAccountFailed.image
labelTitle.text = L10n.Signup.Failure.title
labelMessage.text = L10n.Signup.Failure.message
if let clientError = error as? ClientError {
switch clientError {
case .redeemInvalid:
title = L10n.Welcome.Redeem.title
imvPicture.image = Asset.imageRedeemInvalid.image
labelTitle.text = L10n.Signup.Failure.Redeem.Invalid.title
labelMessage.text = L10n.Signup.Failure.Redeem.Invalid.message
break
case .redeemClaimed:
title = L10n.Welcome.Redeem.title
imvPicture.image = Asset.imageRedeemClaimed.image
labelTitle.text = L10n.Signup.Failure.Redeem.Claimed.title
labelMessage.text = L10n.Signup.Failure.Redeem.Claimed.message
break
case .redeemExpired:
title = L10n.Welcome.Redeem.title
imvPicture.image = Asset.imageRedeemExpired.image
labelTitle.text = L10n.Signup.Failure.Redeem.Expired.title
labelMessage.text = L10n.Signup.Failure.Redeem.Expired.message
break
default:
break
}
}
buttonSubmit.title = L10n.Signup.Failure.submit.uppercased()
}
......
......@@ -16,50 +16,93 @@ class SignupInProgressViewController: AutolayoutViewController {
@IBOutlet private weak var labelMessage: UILabel!
var request: SignupRequest!
var signupRequest: SignupRequest?
var redeemRequest: RedeemRequest?
var preset: PIAWelcomeViewController.Preset?
var metadata: SignupMetadata?
weak var completionDelegate: WelcomeCompletionDelegate?
private var user: UserAccount?
private var error: Error?
override func viewDidLoad() {
super.viewDidLoad()
labelMessage.text = L10n.Signup.InProgress.message
title = metadata?.title
labelMessage.text = metadata?.bodySubtitle
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
progressView.startAnimating()
performSignup()
if let request = signupRequest {
performSignup(with: request)
} else if let request = redeemRequest {
performRedeem(with: request)
}
}
private func performSignup() {
private func performSignup(with request: SignupRequest) {
log.debug("Signing up...")
preset?.accountProvider.signup(with: request) { (user, error) in
guard let user = user else {
self.user = nil
self.error = error
if let clientError = error as? ClientError, (clientError == .internetUnreachable) {
log.error("Failed to sign up: Internet is unreachable")
self.perform(segue: StoryboardSegue.Signup.internetUnreachableSegueIdentifier, sender: nil)
return
}
if let error = error {
log.error("Failed to sign up (error: \(error))")
} else {
if let error = error {
log.error("Failed to sign up (error: \(error))")
} else {
log.error("Failed to sign up")
}
self.perform(segue: StoryboardSegue.Signup.failureSegueIdentifier)
log.error("Failed to sign up")
}
self.perform(segue: StoryboardSegue.Signup.failureSegueIdentifier)
return
}
log.debug("Sign-up succeeded!")
self.user = user
self.error = nil
self.perform(segue: StoryboardSegue.Signup.successSegueIdentifier)
}
}
private func performRedeem(with request: RedeemRequest) {
log.debug("Redeeming...")
preset?.accountProvider.redeem(with: request) { (user, error) in
guard let user = user else {
self.user = nil
self.error = error
if let clientError = error as? ClientError, (clientError == .internetUnreachable) {
log.error("Failed to redeem: Internet is unreachable")
self.perform(segue: StoryboardSegue.Signup.internetUnreachableSegueIdentifier, sender: nil)