Skip to content

Commit

Permalink
Public Release 1.6.1.
Browse files Browse the repository at this point in the history
Public Release 1.6.1.
  • Loading branch information
dmytrokhl authored Jul 27, 2020
2 parents b2a1b58 + 8235ace commit a4abc17
Show file tree
Hide file tree
Showing 115 changed files with 3,816 additions and 131 deletions.
1 change: 1 addition & 0 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ custom_categories:
children:
- VGSTextField
- VGSCardTextField
- VGSExpDateTextField
- VGSTextFieldDelegate
- VGSConfiguration
- FieldType
Expand Down
30 changes: 30 additions & 0 deletions MIGRATING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
## Migration Guides

### Migrating from versions < v1.6.0
#### Updated namings

`SwiftLuhn` -> `VGSPaymentCards`
`SwiftLuhn.CardType` -> `VGSPaymentCards.CardBrand`

#### Setup card number format pattern for each specific CardBrand
From now `.formatPattern` that was defined through `VGSConfiguration` will be ignored for fields with type `.cardNumber`. To be more flexible we released dynamic format patterns for each Payment Card Brand. Check our default format patterns in `VGSPaymentCards.swift` file, and update it if needed for each specific Card Model:

Before:
```
let cardConfiguration = VGSConfiguration(collector: vgsCollect, fieldName: "card_number")
cardConfiguration.type = .cardNumber
cardConfiguration.formatPattern = "#### #### #### ####"
```

Now:
```
VGSPaymentCards.amex.formatPattern = "#### ###### #####"
VGSPaymentCards.visa.formatPattern = "#### #### #### ####"
```

### Migrating from versions < v1.5.2
#### Is Secure field default attribute for CVC FieldType
Now default `.isSecureTextEntry` value for `.cvc` field type is `false`. If you need secure entry, you can set it via `VGSTextField` attribute:

```
cvcTextField.isSecureTextEntry = true
```

### Migrating from versions < v1.5.0
#### Removed `Alamofire` dependecy, changed API methods

Expand Down
25 changes: 25 additions & 0 deletions Sources/VGSCollectSDK/Core/Calendar+extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Calendar+extension.swift
// VGSCollectSDK
//
// Created by Dima on 22.07.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import Foundation

internal extension Calendar {

/// For card numbers we should use `gregorian` calendar. Note: Callendar.current could retern different calendars!!!
static var currentMonth: Int {
return Calendar(identifier: .gregorian).component(.month, from: Date())
}

static var currentYear: Int {
return Calendar(identifier: .gregorian).component(.year, from: Date())
}

static var currentYearShort: Int {
return Calendar(identifier: .gregorian).component(.year, from: Date()) - 2000
}
}
151 changes: 151 additions & 0 deletions Sources/VGSCollectSDK/UIElements/Text Field/VGSExpDateTextField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// Copyright © 2020 VGS. All rights reserved.
//

import Foundation
import UIKit

/// An object that displays an editable text area. Can be use instead of a `VGSTextField` when need to show picker view with Card Number Expiration Month and Year.
public final class VGSExpDateTextField: VGSTextField {

// MARK: - Enums
/// Available Month Label formats in `UIPickerView`
public enum MonthFormat {
/// Short month name, e.g.: `Jan`
case shortSymbols
/// Long month name, e.g.: `January`
case longSymbols
/// Month number: e.g.: `01`
case numbers
}

/// Available Year Label formats in `UIPickerView`
public enum YearFormat {
/// Two digits year format, e.g.: `21`
case short
/// Four digits year format:, e.g.:`2021`
case long
}

// MARK: - Attributes
/// UIPickerView Month Label format
public var monthPickerFormat: MonthFormat = .longSymbols {
didSet {
updateMonthsDataSource()
}
}

/// UIPickerView Year Label format
public var yearPickeFormat: YearFormat = .long {
didSet {
updateYearsDataSource()
}
}

///:nodoc:
public override var configuration: VGSConfiguration? {
didSet {
fieldType = .expDate
}
}

/// Visual month data source
internal var monthsDataSource = [String]()
/// Visual year data source
internal var yearsDataSource = [String]()
/// Valid months range
internal lazy var months = Array(1...12)
/// Valid years range
internal lazy var years: [Int] = {
let current = Calendar.currentYear
return Array(current...(current + validYearsCount))
}()
internal let monthPickerComponent = 0
internal let yearPickerComponent = 1
internal let validYearsCount = 20
internal lazy var picker = self.makePicker()

// MARK: - Initialization

override func mainInitialization() {
super.mainInitialization()
textField.inputView = picker
updateYearsDataSource()
updateMonthsDataSource()
scrollToCurrentMonth(animated: false)
textField.inputAccessoryView = UIView()
}

private func updateTextField() {
let month = months[picker.selectedRow(inComponent: monthPickerComponent)]
let year = years[picker.selectedRow(inComponent: yearPickerComponent)]
let format = textField.formatPattern.components(separatedBy: "/").last ?? FieldType.expDate.defaultFormatPattern
let yearString = (format.count == 4) ? String(year) : String(year - 2000)
let monthString = String(format: "%02d", month)
self.setText("\(monthString)\(yearString)")
}
}

extension VGSExpDateTextField: UIPickerViewDelegate, UIPickerViewDataSource {
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}

public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch component {
case monthPickerComponent:
return monthsDataSource.count
default:
return yearsDataSource.count
}
}

public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch component {
case monthPickerComponent:
return monthsDataSource[row]
default:
return yearsDataSource[row]
}
}

public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
/// check that date is not before current month
let currentMonthIndex = Calendar(identifier: .gregorian).component(.month, from: Date()) - 1
if pickerView.selectedRow(inComponent: yearPickerComponent) == 0 && pickerView.selectedRow(inComponent: monthPickerComponent) < currentMonthIndex {
pickerView.selectRow(currentMonthIndex, inComponent: monthPickerComponent, animated: true)
}
updateTextField()
}
}

private extension VGSExpDateTextField {
func makePicker() -> UIPickerView {
let picker = UIPickerView()
picker.delegate = self
picker.dataSource = self
return picker
}

func updateMonthsDataSource() {
switch monthPickerFormat {
case .shortSymbols:
monthsDataSource = DateFormatter().shortMonthSymbols
case .longSymbols:
monthsDataSource = DateFormatter().monthSymbols
case .numbers:
monthsDataSource = months.map{ (String(format: "%02d", $0))}
}
}

func updateYearsDataSource() {
let suffixLength = yearPickeFormat == .short ? 2 : 4
yearsDataSource = years.map{ String(String($0).suffix(suffixLength))}
}

func scrollToCurrentMonth(animated: Bool) {
let currentMonthIndex = Calendar.currentMonth - 1
picker.selectRow(currentMonthIndex, inComponent: monthPickerComponent, animated: animated)
picker.selectRow(0, inComponent: yearPickerComponent, animated: animated)
}
}
12 changes: 12 additions & 0 deletions Sources/VGSCollectSDK/UIElements/Text Field/VGSTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ public class VGSTextField: UIView {
public var adjustsFontForContentSizeCategory: Bool = false {
didSet { textField.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory }
}

/// Input Accessory View
public var keyboardAccessoryView: UIView? {
didSet { textField.inputAccessoryView = keyboardAccessoryView }
}

/// Determines whether autocorrection is enabled or disabled during typing.
public var autocorrectionType: UITextAutocorrectionType = .default {
didSet {
textField.autocorrectionType = autocorrectionType
}
}

// MARK: - Functional Attributes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,15 @@ extension VGSValidationRuleCardExpirationDate: VGSRuleValidator {

let mm = input.prefix(mmChars)
let yy = input.suffix(yyChars)

let today = Date()
let formatter = DateFormatter()

formatter.dateFormat = self.dateFormat == .longYear ? "yyyy" : "yy"
let todayYY = Int(formatter.string(from: today)) ?? 0

let todayYY = Calendar(identifier: .gregorian).component(.year, from: Date())
let todayMM = Calendar(identifier: .gregorian).component(.month, from: Date())

formatter.dateFormat = "MM"
let todayMM = Int(formatter.string(from: today)) ?? 0

guard let inputMM = Int(mm), let inputYY = Int(yy) else {
guard let inputMM = Int(mm), var inputYY = Int(yy) else {
return false
}

///convert input year to long format if needed
inputYY = self.dateFormat.yearCharacters == 2 ? (inputYY + 2000) : inputYY
if inputYY < todayYY || inputYY > (todayYY + 20) {
return false
}
Expand Down
76 changes: 76 additions & 0 deletions Tests/FrameworkTests/Text Fields Tests/ExpDateTextField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// ExpDateTextField.swift
// FrameworkTests
//
// Created by Vitalii Obertynskyi on 17.06.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import XCTest
@testable import VGSCollectSDK

class ExpDateTextField: XCTestCase {

var collector: VGSCollect!
var textField: VGSExpDateTextField!

override func setUp() {
collector = VGSCollect(id: "any")
textField = VGSExpDateTextField()
let config = VGSConfiguration(collector: collector, fieldName: "textField")
config.formatPattern = "##/####"
textField.configuration = config
}

override func tearDown() {
collector = nil
textField = nil
}

func testMonthFormat() {
textField.monthPickerFormat = .longSymbols
XCTAssertTrue(textField.monthsDataSource.first == "January")
textField.monthPickerFormat = .shortSymbols
XCTAssertTrue(textField.monthsDataSource.first == "Jan")
textField.monthPickerFormat = .numbers
XCTAssertTrue(textField.monthsDataSource.last == "12")
}

func testYearFormat() {
textField.yearPickeFormat = .long
XCTAssertTrue(textField.yearsDataSource.first == String(Calendar.currentYear))
textField.yearPickeFormat = .short
XCTAssertTrue(textField.yearsDataSource.first == String(Calendar.currentYear - 2000))
}

func testSelectDate() {
let monthSelected = 10
let yearSelected = 5
textField.picker.selectRow(monthSelected, inComponent: 0, animated: false)
textField.pickerView(textField.picker, didSelectRow: monthSelected, inComponent: 0)
textField.picker.selectRow(yearSelected, inComponent: 1, animated: false)
textField.pickerView(textField.picker, didSelectRow: yearSelected, inComponent: 1)

let currentValue = textField.textField.secureText
let monthComponent = currentValue?.components(separatedBy: "/").first ?? "0"
let yearComponent = currentValue?.components(separatedBy: "/").last ?? "0"

XCTAssertTrue(Int(monthComponent) == monthSelected + 1)
XCTAssertTrue(Int(yearComponent) == Calendar.currentYear + yearSelected)
}

func testSelectWrongDate() {
textField.picker.selectRow(0, inComponent: 1, animated: false)
textField.picker.selectRow(0, inComponent: 0, animated: false)
textField.pickerView(textField.picker, didSelectRow: 0, inComponent: 0)
let currentValue = textField.textField.secureText

let currentMonth = Calendar.currentMonth
let currentYear = Calendar.currentYear
let monthComponent = currentValue?.components(separatedBy: "/").first ?? "0"
let yearComponent = currentValue?.components(separatedBy: "/").last ?? "0"

XCTAssertTrue(Int(monthComponent) == currentMonth)
XCTAssertTrue(Int(yearComponent) == currentYear)
}
}
2 changes: 1 addition & 1 deletion VGSCollectSDK.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'VGSCollectSDK'
spec.version = '1.6.0'
spec.version = '1.6.1'
spec.summary = 'VGS Collect - is a product suite that allows customers to collect information securely without possession of it.'
spec.swift_version = '5.0'
spec.description = <<-DESC
Expand Down
Loading

0 comments on commit a4abc17

Please sign in to comment.