Commit ea245e90 authored by Jose Blaya's avatar Jose Blaya
Browse files

Redeem View revamp

parent 302f3ba8
......@@ -47,6 +47,7 @@ Pod::Spec.new do |s|
p.resources = "PIALibrary/Resources/UI/Shared/**/*"
p.dependency "PIALibrary/Library"
p.dependency "SwiftyBeaver"
p.dependency "SwiftEntryKit", "0.7.2"
p.ios.source_files = "PIALibrary/Sources/UI/iOS/**/*.swift"
p.ios.resources = "PIALibrary/Resources/UI/iOS/**/*"
......
......@@ -1314,7 +1314,9 @@
"${BUILT_PRODUCTS_DIR}/Gloss/Gloss.framework",
"${PODS_ROOT}/OpenSSL-Apple/frameworks/iPhone/openssl.framework",
"${BUILT_PRODUCTS_DIR}/PIATunnel/PIATunnel.framework",
"${BUILT_PRODUCTS_DIR}/QuickLayout/QuickLayout.framework",
"${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework",
"${BUILT_PRODUCTS_DIR}/SwiftEntryKit/SwiftEntryKit.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework",
);
name = "[CP] Embed Pods Frameworks";
......@@ -1325,7 +1327,9 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Gloss.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PIATunnel.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/QuickLayout.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftEntryKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyBeaver.framework",
);
runOnlyForDeploymentPostprocessing = 0;
......@@ -1368,7 +1372,9 @@
"${BUILT_PRODUCTS_DIR}/Gloss/Gloss.framework",
"${PODS_ROOT}/OpenSSL-Apple/frameworks/iPhone/openssl.framework",
"${BUILT_PRODUCTS_DIR}/PIATunnel/PIATunnel.framework",
"${BUILT_PRODUCTS_DIR}/QuickLayout/QuickLayout.framework",
"${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework",
"${BUILT_PRODUCTS_DIR}/SwiftEntryKit/SwiftEntryKit.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework",
);
name = "[CP] Embed Pods Frameworks";
......@@ -1379,7 +1385,9 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Gloss.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PIATunnel.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/QuickLayout.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftEntryKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyBeaver.framework",
);
runOnlyForDeploymentPostprocessing = 0;
......
{
"images" : [
{
"idiom" : "universal",
"filename" : "scanIcon.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "warningIcon.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
......@@ -1189,11 +1189,8 @@ You will not be charged during this process.</string>
<outlet property="delegate" destination="qgN-PX-Sir" id="dW1-A9-fEp"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aoC-xB-Jhr" customClass="PIAButton" customModule="PIALibrary" customModuleProvider="target">
<rect key="frame" x="200" y="149" width="120" height="40"/>
<constraints>
<constraint firstAttribute="width" constant="120" id="uze-cQ-L1w"/>
</constraints>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aoC-xB-Jhr" customClass="PIAButton" customModule="PIALibrary" customModuleProvider="target">
<rect key="frame" x="290" y="149" width="30" height="40"/>
<inset key="imageEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
<connections>
<action selector="showCameraToScanQRCodes:" destination="qgN-PX-Sir" eventType="touchUpInside" id="i22-HG-3Xe"/>
......@@ -1448,6 +1445,6 @@ You will not be charged during this process.</string>
<image name="logo-light" width="298" height="39"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="eeI-Xa-Rq6"/>
<segue reference="Knv-fa-JGO"/>
</inferredMetricsTieBreakers>
</document>
......@@ -40,6 +40,7 @@
"redeem.error.code" = "Code must be %lu numeric digits.";
"redeem.error.qrcode.invalid" = "Invalid QR code sequence. Please try again.";
"redeem.accessibility.back" = "Back";
"redeem.scanqr" = "Scan QR";
"plan.monthly.title" = "Monthly";
"plan.yearly.title" = "Yearly";
......
......@@ -130,6 +130,10 @@ public class BorderedTextField: UITextField {
return UIEdgeInsetsInsetRect(bounds,
UIEdgeInsetsMake(0, 16, 0, 16))
}
override public func editingRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds,
UIEdgeInsetsMake(0, 16, 0, 16))
}
override public func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return UIEdgeInsetsInsetRect(bounds,
UIEdgeInsetsMake(0, 16, 0, 16))
......
......@@ -8,6 +8,7 @@
import Foundation
import UIKit
import SwiftEntryKit
extension Macros {
......@@ -95,6 +96,37 @@ extension Macros {
public static func alert(_ title: String?, _ message: String?) -> UIAlertController {
return UIAlertController(title: title, message: message, preferredStyle: .alert)
}
/**
Shortcut to display an `EKImageNoteMessageView`.
- Parameter image: The note image
- Parameter message: The note message
- Parameter duration: Optional duration of the note
*/
public static func displayImageNote(withImage image: UIImage,
message: String,
andDuration duration: Double? = nil) {
var attributes = EKAttributes()
attributes = .topToast
attributes.hapticFeedbackType = .success
attributes.entryBackground = .color(color: UIColor.piaRed)
if let duration = duration {
attributes.displayDuration = duration
}
let labelContent = EKProperty.LabelContent(text: message,
style: .init(font: TextStyle.textStyle7.font!,
color: TextStyle.textStyle7.color!))
let imageContent = EKProperty.ImageContent(image: image)
let contentView = EKImageNoteMessageView(with: labelContent,
imageContent: imageContent)
SwiftEntryKit.display(entry: contentView,
using: attributes)
}
/**
Shortcut to create an `UIAlertController` with `.actionSheet` preferred style.
......
......@@ -9,9 +9,25 @@
import Foundation
import UIKit
public enum PIAButtonStatus {
case normal
case error
}
// UIButton with rounded corners and border to be used throughout PIA application
public class PIAButton: UIButton {
private var isButtonImage = false
private var edgesHaveBeenSet = false
private var renderingModeHasBeenSet = false
private var borderColor: UIColor!
private var style: TextStyle!
public var status: PIAButtonStatus = .normal {
didSet { reloadButtonStatus() }
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
......@@ -35,14 +51,87 @@ public class PIAButton: UIButton {
func setBorder(withSize size: CGFloat,
andColor color: UIColor) {
self.layer.borderWidth = size
self.layer.borderColor = color.cgColor
self.borderColor = color
self.layer.borderColor = borderColor.cgColor
clipsToBounds = true
}
func setPlain() {
func setBorder(withSize size: CGFloat,
andStyle style: TextStyle) {
self.layer.borderWidth = size
self.style = style
if let color = style.color {
self.borderColor = color
self.layer.borderColor = color.cgColor
}
clipsToBounds = true
}
func resetButton() {
self.isButtonImage = false
self.edgesHaveBeenSet = false
self.renderingModeHasBeenSet = false
self.layer.cornerRadius = 0.0
self.layer.borderWidth = 0.0
self.layer.borderColor = UIColor.clear.cgColor
clipsToBounds = true
}
func setButtonImage() {
self.isButtonImage = true
}
private func reloadButtonStatus() {
checkRenderingMode()
if status == .error {
self.edgesHaveBeenSet = false
if let errorColor = TextStyle.textStyle10.color {
self.layer.borderColor = errorColor.cgColor
self.tintColor = errorColor
style(style: TextStyle.textStyle10,
for: [])
}
} else {
if let color = style.color {
self.layer.borderColor = color.cgColor
self.tintColor = color
style(style: style,
for: [])
} else {
self.layer.borderColor = borderColor.cgColor
self.tintColor = borderColor
}
}
}
private func checkRenderingMode() {
if !self.renderingModeHasBeenSet,
let imageView = imageView,
let image = imageView.image {
self.renderingModeHasBeenSet = true
imageView.image = image.withRenderingMode(.alwaysTemplate)
}
}
}
extension PIAButton {
override public func layoutSubviews() {
super.layoutSubviews()
if self.isButtonImage,
!self.edgesHaveBeenSet,
let imageView = imageView {
imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: imageView.frame.width + 10)
titleEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 5)
self.edgesHaveBeenSet = true
for const in self.constraints {
if const.firstAttribute == .width {
const.constant += imageView.frame.width
self.layoutIfNeeded()
}
}
}
}
}
......@@ -21,6 +21,8 @@ internal enum Asset {
internal static let centeredDarkMap = ImageAsset(name: "centered-dark-map")
internal static let centeredLightMap = ImageAsset(name: "centered-light-map")
internal static let iconBack = ImageAsset(name: "icon-back")
internal static let iconCamera = ImageAsset(name: "icon-camera")
internal static let iconWarning = ImageAsset(name: "icon-warning")
internal static let logoDark = ImageAsset(name: "logo-dark")
internal static let logoLight = ImageAsset(name: "logo-light")
internal static let closeIcon = ImageAsset(name: "close-icon")
......
......@@ -233,6 +233,8 @@ internal enum L10n {
}
}
internal enum Redeem {
/// Scan QR
internal static let scanqr = L10n.tr("Welcome", "redeem.scanqr")
/// SUBMIT
internal static let submit = L10n.tr("Welcome", "redeem.submit")
/// Type in your email address and the %lu digit PIN from your gift card or trial card below.
......
......@@ -16,10 +16,13 @@ extension Theme.Palette {
let palette = Theme.Palette()
palette.appearance = Theme.Appearance.light
palette.logo = Asset.navLogo.image
palette.secondaryColor = .white
palette.textfieldButtonBackgroundColor = .white
palette.navigationBarBackIcon = Asset.iconBack.image
palette.brandBackground = Macros.color(hex: 0x009a18, alpha: 0xff)
palette.lightBackground = .groupTableViewBackground
palette.lineColor = .piaGreenDark20
palette.subtitleColor = .piaGrey8
palette.emphasis = Macros.color(hex: 0x29cc41, alpha: 0xff)
palette.accent1 = Macros.color(hex: 0xf7941d, alpha: 0xff)
palette.accent2 = Macros.color(hex: 0xe60924, alpha: 0xff)
......
......@@ -37,6 +37,12 @@ public class Theme {
/// The navigation bar back image.
public var navigationBarBackIcon: UIImage?
/// The light background color.
public var secondaryColor: UIColor
/// The background color of the buttons inside textfields.
public var textfieldButtonBackgroundColor: UIColor
/// The brand background color.
public var brandBackground: UIColor
......@@ -45,6 +51,9 @@ public class Theme {
/// The solid light background color.
public var solidLightBackground: UIColor
/// The color for the subtitle Labels in forms and views
public var subtitleColor: UIColor
// public var primary: UIColor
......@@ -70,6 +79,9 @@ public class Theme {
/// The divider color.
public var divider: UIColor
/// The error color.
public var errorColor: UIColor
/// The overlay alpha value.
public var overlayAlpha: CGFloat
......@@ -81,8 +93,11 @@ public class Theme {
public init() {
appearance = .light
brandBackground = .green
secondaryColor = .piaGrey10
lightBackground = .piaGrey1
solidLightBackground = .white
subtitleColor = .piaGrey8
textfieldButtonBackgroundColor = .white
lineColor = .piaGreenDark20
// primary = .black
emphasis = .green
......@@ -103,6 +118,7 @@ public class Theme {
]
solidButtonText = .white
divider = .piaGrey1
errorColor = .piaRed
overlayAlpha = 0.3
}
......@@ -499,6 +515,7 @@ public class Theme {
public func applyInput(_ textField: UITextField) { // hint is placeholder
textField.style(style: TextStyle.textStyle8)
textField.backgroundColor = Theme.current.palette.secondaryColor
if let borderedTextField = textField as? BorderedTextField {
borderedTextField.borderColor = palette.divider
......@@ -506,6 +523,20 @@ public class Theme {
borderedTextField.highlightsWhileEditing = true
}
}
/// :nodoc:
public func applyInputError(_ textField: UITextField) { // hint is placeholder
textField.style(style: TextStyle.textStyle8)
textField.backgroundColor = Theme.current.palette.secondaryColor
if let borderedTextField = textField as? BorderedTextField {
borderedTextField.borderColor = palette.errorColor
borderedTextField.highlightedBorderColor = palette.errorColor
borderedTextField.highlightsWhileEditing = true
}
}
/// :nodoc:
public func applyActionButton(_ button: ActivityButton) {
......
......@@ -9,6 +9,7 @@
import UIKit
import SwiftyBeaver
import AVFoundation
import SwiftEntryKit
private let log = SwiftyBeaver.self
......@@ -17,6 +18,12 @@ protocol RedeemScannerDelegate: class {
func errorFound()
}
enum RedeemViewStatus {
case initial
case restore(element: UIView)
case error(element: UIView)
}
class RedeemViewController: AutolayoutViewController, WelcomeChild {
private static let codeInvalidSet = CharacterSet.decimalDigits.inverted
......@@ -65,6 +72,10 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
textCode.text = GiftCardUtil.friendlyRedeemCode(code)
}
}
var status: RedeemViewStatus = .initial {
didSet { reloadFormElements() }
}
override func viewDidLoad() {
......@@ -100,8 +111,7 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
super.viewDidLoad()
labelSubtitle.textAlignment = .center
cameraButton.setRounded()
cameraButton.style(style: TextStyle.Buttons.piaPlainTextButton)
configureCameraButton()
}
......@@ -123,18 +133,25 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
//}
guard let email = textEmail.text?.trimmed(), Validator.validate(email: email) else {
presentAlertWith(title: L10n.Welcome.Redeem.Error.title,
andMessage: L10n.Welcome.Purchase.Error.validation,
andButtonTitle: L10n.Ui.Global.ok)
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: L10n.Welcome.Purchase.Error.validation)
self.status = .error(element: textEmail)
return
}
self.status = .restore(element: textEmail)
guard let code = redeemCode?.trimmed(), Validator.validate(giftCode: code) else {
presentAlertWith(title: L10n.Welcome.Redeem.Error.title,
andMessage: L10n.Welcome.Redeem.Error.code(RedeemViewController.codeLength),
andButtonTitle: L10n.Ui.Global.ok)
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: L10n.Welcome.Redeem.Error.code(RedeemViewController.codeLength))
self.status = .error(element: textCode)
self.cameraButton.status = .error
return
}
self.status = .initial
self.cameraButton.status = .normal
textEmail.text = email
textCode.text = GiftCardUtil.friendlyRedeemCode(code)
log.debug("Redeeming...")
......@@ -200,6 +217,48 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
}
private func configureCameraButton() {
cameraButton.setButtonImage()
cameraButton.setRounded()
cameraButton.setBorder(withSize: 1,
andStyle: TextStyle.textStyle8)
cameraButton.style(style: TextStyle.textStyle8,
for: [])
cameraButton.setTitle(L10n.Welcome.Redeem.scanqr.uppercased(),
for: [])
cameraButton.tintColor = TextStyle.textStyle8.color
cameraButton.backgroundColor = Theme.current.palette.textfieldButtonBackgroundColor
cameraButton.setImage(Asset.iconCamera.image, for: [])
}
private func reloadFormElements() {
switch status {
case .initial:
resetFormElementBorders()
case .restore(let element):
restoreFormElementBorder(element)
case .error(let element):
updateFormElementBorder(element)
}
}
private func resetFormElementBorders() {
Theme.current.applyInput(textEmail)
Theme.current.applyInput(textCode)
}
private func restoreFormElementBorder(_ element: UIView) {
if let element = element as? UITextField {
Theme.current.applyInput(element)
}
}
private func updateFormElementBorder(_ element: UIView) {
if let element = element as? UITextField {
Theme.current.applyInputError(element)
}
}
private func styleRedeemButton() {
buttonRedeem.setRounded()
buttonRedeem.style(style: TextStyle.Buttons.piaGreenButton)
......@@ -209,24 +268,10 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
private func presentUnauthorizeCameraError() {
DispatchQueue.main.async {
self.presentAlertWith(title: L10n.Welcome.Camera.Access.Error.title,
andMessage: L10n.Welcome.Camera.Access.Denied.message,
andButtonTitle: L10n.Ui.Global.close)
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: L10n.Welcome.Camera.Access.Denied.message)
}
}
private func presentAlertWith(title: String,
andMessage message: String,
andButtonTitle buttonTitle: String ) {
let alert = Macros.alert(title,
message)
alert.addCancelAction(buttonTitle)
present(alert,
animated: true,
completion: nil)
}
private func enableInteractions(_ enable: Bool) {
parent?.view.isUserInteractionEnabled = enable
......@@ -308,16 +353,14 @@ extension RedeemViewController: RedeemScannerDelegate {
textCode.text = GiftCardUtil.friendlyRedeemCode(redeemCode)
self.redeemCode = redeemCode
} else {
presentAlertWith(title: L10n.Welcome.Redeem.Error.title,
andMessage: L10n.Welcome.Redeem.Error.Qrcode.invalid,
andButtonTitle:L10n.Ui.Global.ok)
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: L10n.Welcome.Redeem.Error.Qrcode.invalid)
}
}
func errorFound() {
presentAlertWith(title: L10n.Welcome.Camera.Access.Error.title,
andMessage: L10n.Welcome.Camera.Access.Error.message,
andButtonTitle:L10n.Ui.Global.close)
Macros.displayImageNote(withImage: Asset.iconWarning.image,
message: L10n.Welcome.Camera.Access.Error.message)
}
}
......@@ -6,6 +6,7 @@ abstract_target 'PIALibrary' do
pod 'Gloss', '~> 2'
pod 'Alamofire', '~> 4'
pod 'ReachabilitySwift'
pod 'SwiftEntryKit', '0.7.2'
pod 'PIATunnel', :path => '/Users/ueshiba/Desktop/PIA/tunnel-apple'
#pod 'PIATunnel', '~> 1.1.7'
......
......@@ -11,7 +11,10 @@ PODS:
- PIATunnel/Core (1.1.7):
- OpenSSL-Apple (~> 1.1.0h)
- SwiftyBeaver
- QuickLayout (2.0.2)
- ReachabilitySwift (4.3.0)
- SwiftEntryKit (0.7.2):
- QuickLayout (= 2.0.2)
- SwiftyBeaver (1.6.1)
DEPENDENCIES:
......@@ -19,6 +22,7 @@ DEPENDENCIES:
- Gloss (~> 2)