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

Merge branch '49-qr-code-scanning' into 'develop'

Resolve "QR code scanning"

See merge request ios/client-library-apple!105
parents 55f41503 43d04807
......@@ -240,8 +240,13 @@
0EFEB4C420077AC900F81029 /* PIATunnelProvider+Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4BF2007784A00F81029 /* PIATunnelProvider+Profile.swift */; };
0EFEB4C520077AC900F81029 /* PIATunnelProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFEB4C02007784A00F81029 /* PIATunnelProfile.swift */; };
5EA54E484C7FD035341E021E /* Pods_PIALibrary_PIALibraryTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A22DD34A821A4EDAED288D2 /* Pods_PIALibrary_PIALibraryTests_iOS.framework */; };
841BE60D212AD0F3002EF2D1 /* ValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BE60C212AD0F3002EF2D1 /* ValidatorTests.swift */; };
841BE60F212AFE49002EF2D1 /* GiftCardUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BE60E212AFE49002EF2D1 /* GiftCardUtil.swift */; };
841BE610212AFE69002EF2D1 /* GiftCardUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BE60E212AFE49002EF2D1 /* GiftCardUtil.swift */; };
841BE612212AFFA7002EF2D1 /* GiftCardUtilTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841BE611212AFFA7002EF2D1 /* GiftCardUtilTests.swift */; };
843C67C22122E714005A3FDA /* AccountInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843C67C12122E714005A3FDA /* AccountInfoTests.swift */; };
843C67C32122EA13005A3FDA /* AccountInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843C67C12122E714005A3FDA /* AccountInfoTests.swift */; };
84D5DA702126CE2900F753F8 /* QRCameraScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D5DA6F2126CE2900F753F8 /* QRCameraScannerViewController.swift */; };
A56365AB3EAA4588B479616A /* Pods_PIALibrary_PIALibraryHost_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 476E8514A236C48F405ACA40 /* Pods_PIALibrary_PIALibraryHost_iOS.framework */; };
E07462773BC314E3183B240E /* Pods_PIALibrary_PIALibrary_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FC5BF5764146CA9C6104276 /* Pods_PIALibrary_PIALibrary_iOS.framework */; };
/* End PBXBuildFile section */
......@@ -481,7 +486,11 @@
378AC49FB4E07B2415156492 /* Pods-PIALibrary-PIALibraryTests-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIALibrary-PIALibraryTests-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-PIALibrary-PIALibraryTests-iOS/Pods-PIALibrary-PIALibraryTests-iOS.release.xcconfig"; sourceTree = "<group>"; };
476E8514A236C48F405ACA40 /* Pods_PIALibrary_PIALibraryHost_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIALibrary_PIALibraryHost_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
64C04C071DE1CB44DE7945D6 /* Pods-PIALibrary-PIALibrary-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIALibrary-PIALibrary-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PIALibrary-PIALibrary-iOS/Pods-PIALibrary-PIALibrary-iOS.debug.xcconfig"; sourceTree = "<group>"; };
841BE60C212AD0F3002EF2D1 /* ValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatorTests.swift; sourceTree = "<group>"; };
841BE60E212AFE49002EF2D1 /* GiftCardUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiftCardUtil.swift; sourceTree = "<group>"; };
841BE611212AFFA7002EF2D1 /* GiftCardUtilTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiftCardUtilTests.swift; sourceTree = "<group>"; };
843C67C12122E714005A3FDA /* AccountInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInfoTests.swift; sourceTree = "<group>"; };
84D5DA6F2126CE2900F753F8 /* QRCameraScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCameraScannerViewController.swift; sourceTree = "<group>"; };
9A22DD34A821A4EDAED288D2 /* Pods_PIALibrary_PIALibraryTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIALibrary_PIALibraryTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BD0536741E220873045CBCC7 /* Pods-PIALibrary-PIALibraryHost-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIALibrary-PIALibraryHost-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PIALibrary-PIALibraryHost-iOS/Pods-PIALibrary-PIALibraryHost-iOS.debug.xcconfig"; sourceTree = "<group>"; };
E865E7775CCB07BA4CE22DEE /* Pods_PIALibrary_PIALibrary_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIALibrary_PIALibrary_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
......@@ -674,6 +683,7 @@
children = (
0E4D4E9B1FA4CA7A007DA6DA /* Restylable.swift */,
0E4D4E9C1FA4CA7A007DA6DA /* Validator.swift */,
841BE60E212AFE49002EF2D1 /* GiftCardUtil.swift */,
);
path = Shared;
sourceTree = "<group>";
......@@ -769,6 +779,7 @@
0EB8C0541F9CD38A005857E4 /* PurchasePlanCell.swift */,
0EB8C0551F9CD38A005857E4 /* PurchaseViewController.swift */,
0EA8072320A18C6B0033EC1A /* RedeemViewController.swift */,
84D5DA6F2126CE2900F753F8 /* QRCameraScannerViewController.swift */,
0EB8C0561F9CD38A005857E4 /* RestoreSignupViewController.swift */,
0EB8C0571F9CD38A005857E4 /* SignupFailureViewController.swift */,
0EB8C0581F9CD38A005857E4 /* SignupInProgressViewController.swift */,
......@@ -914,6 +925,8 @@
0EA4C4411FDDFD830041C3D8 /* ServerTests.swift */,
0E2ADD441FE1583A00BB170C /* VPNTests.swift */,
843C67C12122E714005A3FDA /* AccountInfoTests.swift */,
841BE60C212AD0F3002EF2D1 /* ValidatorTests.swift */,
841BE611212AFFA7002EF2D1 /* GiftCardUtilTests.swift */,
0EE78B011F818A20002E4CDD /* Info-iOS.plist */,
0E2ADD201FE13B8600BB170C /* Info-macOS.plist */,
);
......@@ -1377,6 +1390,7 @@
0E7BC6F51F96B1120035C8B2 /* Credentials.swift in Sources */,
0E2ADCCB1FE06D7A00BB170C /* Array+Math.swift in Sources */,
0E7BC6EE1F96B1120035C8B2 /* AccountProvider.swift in Sources */,
841BE610212AFE69002EF2D1 /* GiftCardUtil.swift in Sources */,
0E7BC6F61F96B1120035C8B2 /* AccountInfo.swift in Sources */,
0E3D13D71F9E272B00434A48 /* GlossPayment.swift in Sources */,
0E2ADD491FE1595300BB170C /* IPSecProfile.swift in Sources */,
......@@ -1498,6 +1512,7 @@
0EB9667E1FDF36490086ABC2 /* GlossParser.swift in Sources */,
0EAA38991F9CC7E4000149CF /* AppStoreTransaction.swift in Sources */,
0EA8072720A1A0090033EC1A /* Redeem.swift in Sources */,
84D5DA702126CE2900F753F8 /* QRCameraScannerViewController.swift in Sources */,
0E392D951FE316860002160D /* DebugLog.swift in Sources */,
0EAA38981F9CC7E4000149CF /* AppStoreProduct.swift in Sources */,
0E1743131F82E1A4001E7DD6 /* Client.swift in Sources */,
......@@ -1537,6 +1552,7 @@
0E492C6A1FE61485007F23DF /* Client+Database.swift in Sources */,
0E48A8531FDAD60900B9A4C0 /* OptionsViewController.swift in Sources */,
0EB8C06C1F9CD38B005857E4 /* SignupFailureViewController.swift in Sources */,
841BE60F212AFE49002EF2D1 /* GiftCardUtil.swift in Sources */,
0EB8C0681F9CD38B005857E4 /* PIAWelcomeViewController.swift in Sources */,
0E2ADD481FE1595300BB170C /* IPSecProfile.swift in Sources */,
0EFB51271F82A5F80033B81F /* Notification+Core.swift in Sources */,
......@@ -1571,11 +1587,13 @@
buildActionMask = 2147483647;
files = (
0E53A85A1FE5E3CD000C2A18 /* MockProviders.swift in Sources */,
841BE60D212AD0F3002EF2D1 /* ValidatorTests.swift in Sources */,
843C67C22122E714005A3FDA /* AccountInfoTests.swift in Sources */,
0EE78B001F818A20002E4CDD /* AccountTests.swift in Sources */,
0EA4C4421FDDFD840041C3D8 /* ServerTests.swift in Sources */,
0EB8C0461F9CCB7C005857E4 /* AccountSignupTests.swift in Sources */,
0E2ADD451FE1583A00BB170C /* VPNTests.swift in Sources */,
841BE612212AFFA7002EF2D1 /* GiftCardUtilTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "close_button@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "close_button@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",
......
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_qr_code@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_qr_code@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VHM-bG-giz">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="VHM-bG-giz">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
......@@ -1139,6 +1139,17 @@ 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">
<rect key="frame" x="250" y="117" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="bCM-kI-tMy"/>
</constraints>
<inset key="imageEdgeInsets" minX="5" minY="5" maxX="5" maxY="5"/>
<state key="normal" image="qr-code"/>
<connections>
<action selector="showCameraToScanQRCodes:" destination="qgN-PX-Sir" eventType="touchUpInside" id="i22-HG-3Xe"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tea-DU-K60" customClass="ActivityButton" customModule="PIALibrary" customModuleProvider="target">
<rect key="frame" x="0.0" y="167" width="280" height="40"/>
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="calibratedWhite"/>
......@@ -1165,9 +1176,12 @@ You will not be charged during this process.</string>
<constraint firstAttribute="bottom" secondItem="xcj-pL-a9w" secondAttribute="bottom" id="4lI-EV-hFu"/>
<constraint firstItem="pbJ-dg-A3t" firstAttribute="top" secondItem="3ne-g6-NDW" secondAttribute="bottom" constant="20" id="5F7-D0-viR"/>
<constraint firstAttribute="trailing" secondItem="S5y-bL-bNg" secondAttribute="trailing" id="5RT-z4-UFq"/>
<constraint firstAttribute="trailing" secondItem="aoC-xB-Jhr" secondAttribute="trailing" id="925-8A-MSn"/>
<constraint firstItem="tea-DU-K60" firstAttribute="leading" secondItem="TnM-Pl-njO" secondAttribute="leading" id="FYy-Le-HYh"/>
<constraint firstItem="3ne-g6-NDW" firstAttribute="top" secondItem="S5y-bL-bNg" secondAttribute="bottom" constant="20" id="Hgd-B4-frq"/>
<constraint firstItem="pbJ-dg-A3t" firstAttribute="height" secondItem="3ne-g6-NDW" secondAttribute="height" id="IUJ-r9-crT"/>
<constraint firstItem="aoC-xB-Jhr" firstAttribute="top" secondItem="3ne-g6-NDW" secondAttribute="bottom" constant="20" id="JHV-oQ-Ico"/>
<constraint firstItem="aoC-xB-Jhr" firstAttribute="height" secondItem="pbJ-dg-A3t" secondAttribute="height" id="LdG-rX-omr"/>
<constraint firstItem="xcj-pL-a9w" firstAttribute="leading" secondItem="TnM-Pl-njO" secondAttribute="leading" id="Qng-Zc-Xvy"/>
<constraint firstItem="tea-DU-K60" firstAttribute="trailing" secondItem="3ne-g6-NDW" secondAttribute="trailing" id="R8D-e4-h8a"/>
<constraint firstItem="pbJ-dg-A3t" firstAttribute="leading" secondItem="3ne-g6-NDW" secondAttribute="leading" id="Sq9-EB-iDd"/>
......@@ -1176,9 +1190,10 @@ You will not be charged during this process.</string>
<constraint firstAttribute="width" priority="750" constant="280" id="aV8-Hz-Jvp"/>
<constraint firstItem="QEC-Qu-zkj" firstAttribute="top" secondItem="TnM-Pl-njO" secondAttribute="top" id="fcA-aA-3kt"/>
<constraint firstItem="tea-DU-K60" firstAttribute="leading" secondItem="3ne-g6-NDW" secondAttribute="leading" id="jTH-4f-ZLY"/>
<constraint firstItem="tea-DU-K60" firstAttribute="top" secondItem="aoC-xB-Jhr" secondAttribute="bottom" constant="20" id="pbY-F6-mLe"/>
<constraint firstAttribute="trailing" secondItem="pbJ-dg-A3t" secondAttribute="trailing" id="qhK-Wr-Ika"/>
<constraint firstAttribute="trailing" secondItem="xcj-pL-a9w" secondAttribute="trailing" id="qzi-8l-LNR"/>
<constraint firstItem="S5y-bL-bNg" firstAttribute="top" secondItem="QEC-Qu-zkj" secondAttribute="bottom" constant="10" id="scj-YN-W64"/>
<constraint firstItem="pbJ-dg-A3t" firstAttribute="trailing" secondItem="3ne-g6-NDW" secondAttribute="trailing" id="toJ-5G-R2t"/>
<constraint firstItem="S5y-bL-bNg" firstAttribute="leading" secondItem="TnM-Pl-njO" secondAttribute="leading" id="x2Y-Ul-kQx"/>
</constraints>
</view>
......@@ -1338,6 +1353,7 @@ You will not be charged during this process.</string>
</view>
<connections>
<outlet property="buttonRedeem" destination="tea-DU-K60" id="5tX-8w-A1k"/>
<outlet property="cameraButton" destination="aoC-xB-Jhr" id="oNJ-qt-hbR"/>
<outlet property="labelLogin1" destination="Ovb-tk-w2q" id="43z-6a-sg0"/>
<outlet property="labelLogin2" destination="ecj-b6-crK" id="a8l-fb-1Ph"/>
<outlet property="labelSubtitle" destination="S5y-bL-bNg" id="hzd-5h-ycZ"/>
......@@ -1349,17 +1365,56 @@ You will not be charged during this process.</string>
<outlet property="viewFooter" destination="h4C-e9-6n1" id="xcl-1q-9mx"/>
<outlet property="viewLogin" destination="h4C-e9-6n1" id="9QP-x0-OOS"/>
<segue destination="9jt-8I-K8i" kind="presentation" identifier="SignupViaRedeemSegue" id="Knv-fa-JGO"/>
<segue destination="hSc-7t-RTF" kind="presentation" identifier="SignupQRCameraScannerSegue" id="xx6-S9-E8H"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="r3v-al-5qU" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2375" y="-1899"/>
<point key="canvasLocation" x="2373.5999999999999" y="-1899.400299850075"/>
</scene>
<!--Camera Scanner View Controller-->
<scene sceneID="0ML-Wj-AS6">
<objects>
<viewController id="hSc-7t-RTF" customClass="QRCameraScannerViewController" customModule="PIALibrary" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="ep7-uG-1hN">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xjW-oD-kRx">
<rect key="frame" x="5" y="25" width="44" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="mnt-Sa-70o"/>
<constraint firstAttribute="width" constant="44" id="uGL-95-tvI"/>
</constraints>
<inset key="imageEdgeInsets" minX="10" minY="10" maxX="10" maxY="10"/>
<state key="normal" image="close-icon"/>
<connections>
<action selector="dismissView:" destination="hSc-7t-RTF" eventType="touchUpInside" id="6rL-tJ-P3p"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="xjW-oD-kRx" firstAttribute="top" secondItem="bb6-gq-GN1" secondAttribute="top" constant="5" id="PSn-O5-oZX"/>
<constraint firstItem="xjW-oD-kRx" firstAttribute="leading" secondItem="bb6-gq-GN1" secondAttribute="leading" constant="5" id="e3d-hl-FCD"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bb6-gq-GN1"/>
</view>
<connections>
<outlet property="closeButton" destination="xjW-oD-kRx" id="nl4-yD-KQR"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="GlS-xa-MJS" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3238" y="-1901"/>
</scene>
</scenes>
<resources>
<image name="close-icon" width="50" height="50"/>
<image name="nav-logo" width="354" height="36"/>
<image name="qr-code" width="325" height="325"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="EEe-nI-dHO"/>
<segue reference="Knv-fa-JGO"/>
</inferredMetricsTieBreakers>
</document>
......@@ -38,6 +38,7 @@
"redeem.submit" = "SUBMIT";
"redeem.error.title" = "Redeem";
"redeem.error.code" = "Code must be %lu numeric digits.";
"redeem.error.qrcode.invalid" = "Invalid QR code sequence. Please try again.";
"plan.monthly.title" = "Monthly";
"plan.yearly.title" = "Yearly";
......@@ -57,3 +58,7 @@
"agreement.message" = "Signing up constitutes acceptance of the $1 and the $2.";
"agreement.message.tos" = "Terms of Service";
"agreement.message.privacy" = "Privacy Policy";
"camera.access.error.title" = "Camera not available";
"camera.access.error.message" = "Your device does not support scanning QR codes. Please use a device with a camera.";
"camera.access.denied.message" = "Enable camera access so you can scan QR codes to redeem a gift card.";
//
// GiftCardUtil.swift
// PIALibrary-iOS
//
// Created by Jose Antonio Blaya Garcia on 20/8/18.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import Foundation
public class GiftCardUtil {
private static let rxCodeGrouping: NSRegularExpression = try! NSRegularExpression(pattern: "\\d{4}(?=\\d)", options: [])
private static let sixteenDigitRegex: NSRegularExpression = try! NSRegularExpression(pattern: "\\d{16}", options: [])
private static let strippedSixteenDigitRegex: NSRegularExpression = try! NSRegularExpression(pattern: "\\b\\d{4}(| |-)\\d{4}\\1\\d{4}\\1\\d{4}\\b", options: [])
/**
Add the stripped format to a given gift card code.
- Parameter code: The code to be formatted.
- Returns: String the code with the stripped format.
*/
public static func friendlyRedeemCode(_ code: String) -> String {
return GiftCardUtil.rxCodeGrouping.stringByReplacingMatches(in: code,
options: [],
range: NSMakeRange(0, code.count),
withTemplate: "$0-")
}
/**
Remove all stripped occurrences.
- Parameter code: The code to replace.
- Returns: String without the found occurrences.
*/
public static func strippedRedeemCode(_ code: String) -> String {
return code.replacingOccurrences(of: "-", with: "")
}
/**
This function extracts the redeem code from any String.
- Parameter code: The redeem code string.
- Parameter stripped: Option to remove - characters after extract the redeem code.
- Returns: The 16-digit redeem code .
*/
public static func extractRedeemCode(_ code: String,
strippedFormat stripped: Bool = false) -> String? {
if let finalResult = findBySixteenDigitOnly(code),
finalResult.count > 0 {
if let result = finalResult.first {
if stripped {
return strippedRedeemCode(result)
}
return result
}
} else if let finalResult = findBySixteenDigitStrippedOnly(code),
finalResult.count > 0 {
if let result = finalResult.first {
if stripped {
return strippedRedeemCode(result)
}
return result
}
}
return nil
}
private static func findBySixteenDigitOnly(_ code: String) -> [String]? {
let matches = sixteenDigitRegex.matches(in: code,
options: [],
range: NSMakeRange(0, code.count))
return self.findOccurrencesInRegex(matches,
forCode: code)
}
private static func findBySixteenDigitStrippedOnly(_ code: String) -> [String]? {
let matches = strippedSixteenDigitRegex.matches(in: code,
options: [],
range: NSMakeRange(0, code.count))
return self.findOccurrencesInRegex(matches,
forCode: code)
}
private static func findOccurrencesInRegex(_ matches: [NSTextCheckingResult],
forCode code: String) -> [String]? {
var finalResult = [String]()
if matches.count > 0 {
finalResult = matches.map {
if let range = Range($0.range,
in: code) {
return String(code[range])
}
return ""
}
}
return finalResult
}
}
//
// QRCameraScannerViewController.swift
// PIALibrary-iOS
//
// Created by Jose Antonio Blaya Garcia on 17/8/18.
// Copyright © 2018 London Trust Media. All rights reserved.
//
import AVFoundation
import UIKit
class QRCameraScannerViewController: AutolayoutViewController {
@IBOutlet private weak var closeButton: UIButton!
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
weak var delegate: RedeemScannerDelegate!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if captureSession == nil {
setupCaptureSession()
} else {
if (captureSession?.isRunning == false) {
captureSession.startRunning()
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if (captureSession?.isRunning == true) {
captureSession.stopRunning()
}
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
@IBAction private func dismissView(_ sender: Any?) {
dismissModal()
}
private func setupCaptureSession() {
captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else {
failed()
return
}
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
failed()
return
}
if (captureSession.canAddInput(videoInput)) {
captureSession.addInput(videoInput)
} else {
failed()
return
}
let metadataOutput = AVCaptureMetadataOutput()
if (captureSession.canAddOutput(metadataOutput)) {
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self,
queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr]
} else {
failed()
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = UIScreen.main.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
view.bringSubview(toFront: closeButton)
captureSession.startRunning()
}
private func failed() {
captureSession = nil
dismiss(animated: true) { [weak self] in
self?.delegate.errorFound()
}
}
}
extension QRCameraScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
captureSession.stopRunning()
var code = ""
if let metadataObject = metadataObjects.first {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else {
return
}
guard let stringValue = readableObject.stringValue else {
return
}
code = stringValue
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
}
dismiss(animated: true) { [weak self] in
self?.delegate.giftCardCodeFound(withCode: code)
}
}
}
......@@ -8,14 +8,18 @@
import UIKit
import SwiftyBeaver
import AVFoundation
private let log = SwiftyBeaver.self
protocol RedeemScannerDelegate: class {
func giftCardCodeFound(withCode code: String)
func errorFound()
}
class RedeemViewController: AutolayoutViewController, WelcomeChild {
private static let codeInvalidSet = CharacterSet.decimalDigits.inverted
private static let rxCodeGrouping: NSRegularExpression = try! NSRegularExpression(pattern: "\\d{4}(?=\\d)", options: [])
private static let codePlaceholder = "1234-5678-9012-3456"
private static let codeLength = 16
......@@ -41,6 +45,8 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
@IBOutlet private weak var labelLogin1: UILabel!
@IBOutlet private weak var labelLogin2: UILabel!
@IBOutlet private weak var cameraButton: UIButton!
var preset: PIAWelcomeViewController.Preset?
......@@ -56,7 +62,7 @@ class RedeemViewController: AutolayoutViewController, WelcomeChild {
textCode.text = nil
return
}
textCode.text = friendlyRedeemCode(code)
textCode.text = GiftCardUtil.friendlyRedeemCode(code)
}
}