Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PI-1738: Billing Form component: Standard #144

Closed
wants to merge 55 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a3ddaae
PI-1738: Billing form component: standard
ehab-al-cko Apr 26, 2022
1d5aae9
Fix logging issue
ehab-al-cko Apr 26, 2022
02641fb
Fix `DefaultFormStylePostcodedCell` typo
ehab-al-cko Apr 27, 2022
8bb68d2
Fix 'hight' typo
ehab-al-cko Apr 27, 2022
0201e01
Fix typo in folder name
ehab-al-cko Apr 27, 2022
0f918de
remove explicite `Bool`
ehab-al-cko Apr 27, 2022
4373e90
Fix 'BillForm' to be `BillingForm`
ehab-al-cko Apr 27, 2022
e2efe8f
Remove unused commented code
ehab-al-cko Apr 27, 2022
86fed89
fix `deleagte` typo
ehab-al-cko Apr 27, 2022
77e1998
Renaming some variables `doneButton` and `cancelButton`
ehab-al-cko Apr 27, 2022
3551409
Update UIColor+Extension
ehab-al-cko Apr 27, 2022
311a63a
remove unused code
ehab-al-cko Apr 27, 2022
7a61071
Renaming correction
ehab-al-cko Apr 27, 2022
88bc829
Following the same code style pattern, remove extra `self`
ehab-al-cko Apr 27, 2022
d0f46cf
Simplifying the code
ehab-al-cko Apr 27, 2022
f94e42a
Remove unused `import UIKit`
ehab-al-cko Apr 27, 2022
5b742d9
Use `XCTAssertFalse` or `XCTAssertTrue` instead of `XCTAssertEqual`
ehab-al-cko Apr 27, 2022
9e5b7a2
Clean code
ehab-al-cko Apr 27, 2022
ca3e7a9
Support localised strings
ehab-al-cko Apr 28, 2022
42a7f4e
Clean validators code
ehab-al-cko Apr 28, 2022
a7e0c31
Fix phone number clears after scrolling off of view (unable to click …
ehab-al-cko Apr 28, 2022
d56979d
Header is currently part of the scroll view and can be scrolled off o…
ehab-al-cko Apr 29, 2022
1674596
Update `updateRow` code to be without implicitly capturing `self`
ehab-al-cko May 9, 2022
ba10ffe
Update `UIColor` initialisation using `hex`
ehab-al-cko May 9, 2022
31c2a02
Rename `inActive` to `disabled`
ehab-al-cko May 9, 2022
44fe199
Update `phoneNumberKit` to be `private`
ehab-al-cko May 9, 2022
7b6b139
Update `partialFormatter` to be `lazy`
ehab-al-cko May 9, 2022
64559f6
Sorted `BillingFormPhoneNumberText` variables by their access level
ehab-al-cko May 9, 2022
fc01d9c
Update `delegate` to be `weak`
ehab-al-cko May 9, 2022
96ee26c
Add `keyboardType` to be `.phonePad` for `BillingFormPhoneNumberText`
ehab-al-cko May 9, 2022
f1dbfe5
Clean code
ehab-al-cko May 9, 2022
347c106
Update Localization
ehab-al-cko May 9, 2022
e77207e
Update Example test UI Test
ehab-al-cko May 9, 2022
52267b1
Fix Test cases
ehab-al-cko May 9, 2022
4045c05
Support Carthage
ehab-al-cko May 9, 2022
d554c26
Support Pods
ehab-al-cko May 9, 2022
3fa1922
Update `checkoutEventLoggerKitVersion` to `1.2.0`
ehab-al-cko May 9, 2022
db069fa
Change textfield color to red when it has error
ehab-al-cko May 9, 2022
d1df920
Fix screen leading and trailing
ehab-al-cko May 9, 2022
ecea7cd
Update country code
ehab-al-cko May 10, 2022
b366298
Expose `DefaultBillingFormViewModel` delegate
ehab-al-cko May 10, 2022
0634474
Rename `setupViews` to `setupViewsInOrder`
ehab-al-cko May 10, 2022
dbd2ef5
Clean test cases
ehab-al-cko May 10, 2022
dd4a5b2
clean code
ehab-al-cko May 10, 2022
0c01976
Join the guard statements for `isNewUI` and `getBillingFormViewContr…
ehab-al-cko May 10, 2022
5e74377
Fix Memory Leak
ehab-al-cko May 10, 2022
61b42c0
TODO: CountryCode
ehab-al-cko May 10, 2022
3b53468
//TODO: migrate to assets
ehab-al-cko May 11, 2022
07a18fd
revert to 3.3.3
ehab-al-cko May 11, 2022
bad5997
Revert `PhoneNumberKit` to 3.3.0
ehab-al-cko May 11, 2022
1dedcc8
Add required Fonts
ehab-al-cko May 11, 2022
a57c2d7
Update padding for header view
ehab-al-cko May 12, 2022
b538fde
Clean code
ehab-al-cko May 12, 2022
abd3e24
Fix Carthage build error
ehab-al-cko May 13, 2022
bbcdcba
Add: A merchant can choose if that field is optional or mandatory to …
ehab-al-cko May 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1 +1 @@
github "marmelroy/PhoneNumberKit" "3.3.3"
github "marmelroy/PhoneNumberKit" "3.3.7"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this review dragged longer than it was expected, but for visibility 3.4.0 was released just few hours ago 🙈 . It has added encoding and decoding strategies, so if that is something of interest we may create some separate maintenance task to review that functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> Revert to 3.3.3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be reverted to 3.3.3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

36 changes: 36 additions & 0 deletions Source/CKOColor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import UIKit

extension UIColor {
public static let brandeisBlue = UIColor(hex: "#0B5FF0")
public static let doveGray = UIColor(hex: "#636363")
public static let codGray = UIColor(hex: "#141414")
public static let mediumGray = UIColor(hex: "#8A8A8A")
public static let tallPoppyRed = UIColor(hex: "#AD283E")
}

extension UIColor {
convenience init(hex: String, alpha: CGFloat = 1.0) {
var cString = hex
.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: " ", with: "")
.uppercased()

if cString.hasPrefix("#") { cString.removeFirst() }

guard cString.count == 6 else {
self.init(hex: "ff0000") // gray color
return
}

var rgbValue: UInt64 = 0
Scanner(string: cString).scanHexInt64(&rgbValue)

self.init (
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}

}
61 changes: 61 additions & 0 deletions Source/CKOFont.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import UIKit

extension UIFont {

// load framework font in application
public static let loadAllFonts: () = {
for style in GraphikStyle.allCases {
style.fontName.register(for: CheckoutTheme.self, withExtension: "otf")
}
}()

}

extension UIFont {

enum GraphikStyle: String, CaseIterable {
case regularIt
case thin
case thinIt
case extrabldIt
case lightIt
case black
case medium
case extrabld
case boldIt
case regular
case blackIt
case mediumIt
case bold
case light
case semiboldIt
case semibold
var fontName: String {
return "GraphikLCG-\(self.rawValue.capitalized)"
}
}

convenience init(graphikStyle: GraphikStyle, size: CGFloat) {
self.init(name: graphikStyle.fontName, size: size)!
}

}


extension String {
//MARK: - Make custom font bundle register to framework

func register(for type: AnyClass, withExtension: String) {
let bundle = getBundle(forClass: type)

if let pathForResourceString = bundle.url(forResource: self, withExtension: withExtension),
let fontData = NSData(contentsOf: pathForResourceString), let dataProvider = CGDataProvider.init(data: fontData) {
let fontRef = CGFont.init(dataProvider)
var errorRef: Unmanaged<CFError>? = nil
if CTFontManagerRegisterGraphicsFont(fontRef!, &errorRef) == false {
print("Failed to register font - register graphics font failed - this font may have already been registered in the main bundle.")
}
}
}

}
37 changes: 29 additions & 8 deletions Source/Extensions/StringExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,45 @@ import Foundation
import UIKit

extension String {

private func getBundle(forClass: AnyClass) -> Foundation.Bundle {
#if SWIFT_PACKAGE
func getBundle(forClass: Swift.AnyClass) -> Foundation.Bundle {
#if SWIFT_PACKAGE
let baseBundle = Bundle.module
#else
#else
let baseBundle = Foundation.Bundle(for: forClass)
#endif
#endif
let path = baseBundle.path(forResource: "Frames", ofType: "bundle")
return path == nil ? baseBundle : Foundation.Bundle(path: path!)!
}

func localized(forClass: AnyClass, comment: String = "") -> String {
func localized(forClass: Swift.AnyClass, comment: String = "") -> String {
let bundle = getBundle(forClass: forClass)
return NSLocalizedString(self, bundle: bundle, comment: "")
}

func image(forClass: AnyClass) -> UIImage {
let bundle = getBundle(forClass: forClass)
return UIImage(named: self, in: bundle, compatibleWith: nil) ?? UIImage()
}

//TODO: migrate to assets
//https://www.hackingwithswift.com/example-code/core-graphics/how-to-render-a-pdf-to-an-image
func vectorPDFImage(forClass: AnyClass) -> UIImage? {
let bundle = getBundle(forClass: forClass)
guard let urlPath = bundle.url(forResource: self, withExtension: "pdf") else { return nil }
guard let document = CGPDFDocument(urlPath as CFURL) else { return nil }
guard let page = document.page(at: 1) else { return nil }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would document this page 1, as the method currently is slightly unclear. I'd expect that given a string, it will convert a pdf file from resources to an image.

Technically, we are converting one page at a time, and if I was to reuse this for an actual PDF document, not just an image file it would deliver wrong expectation

As I was documenting this comment, I realised using assets in SPM is more what you need and all of this could be avoided to begin with?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: a new ticket to refactor and test this change


let pageRect = page.getBoxRect(.mediaBox)
let renderer = UIGraphicsImageRenderer(size: pageRect.size)

return renderer.image {
UIColor.white.set()
$0.fill(pageRect)
$0.cgContext.translateBy(x: 0.0, y: pageRect.size.height)
$0.cgContext.scaleBy(x: 1.0, y: -1.0)
$0.cgContext.drawPDFPage(page)
}
}

}
1 change: 1 addition & 0 deletions Source/Extensions/UIViewControllerExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ extension UIViewController {
let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin
if !aRect.contains(activeTextFieldOrigin!) {
scrollView.scrollRectToVisible(activeTextFieldRect!, animated: true)

}
}
}
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14 changes: 13 additions & 1 deletion Source/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@

"missingBillingFormFullName" = "Missing Name";
"missingBillingFormAddressLine1" = "Missing Address Line 1";
"missingBillingFormAddressLine2" = "Missing Address Line 2";
"missingBillingFormCity" = "Missing City";
"missingBillingFormState" = "Missing State";
"missingBillingFormPostcode" = "Missing Postcode";
"missingBillingFormPhoneNumber" = "Missing Phone Number";
"missingBillingFormCountry" = "Missing Country";
"billingFormPhoneNumberHint" = "Phone Number";
"billingAddressTitle" = "Billing address";
"city" = "City";
"cancel" = "Cancel";
"done" = "Done";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a tracking option to ensure the localisation is being followed after this PR is merged?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be solved by @patrick-hoban-cko and @deepesh-vasthimal-cko

"cardNumber" = "Card Number*";
"cardholderName" = "Cardholder's name";
"cardholderNameRequired" = "Cardholder's name*";
Expand Down
Binary file added Source/Resources/warning/warning.pdf
Binary file not shown.
33 changes: 30 additions & 3 deletions Source/UI/Controllers/CardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ public class CardViewController: UIViewController,
var topConstraint: NSLayoutConstraint?

private var loggedForCurrentCorrelationID = false

public var isNewUI = false
// TODO: [Will updated in the next ticket].
private var countryCode = 0
ehab-al-cko marked this conversation as resolved.
Show resolved Hide resolved
// MARK: - Initialization



/// Returns a newly initialized view controller with the cardholder's name and billing details
/// state specified. You can specified the region using the Iso2 region code ("UK" for "United Kingdom")
public init(checkoutApiClient: CheckoutAPIClient, cardHolderNameState: InputState,
billingDetailsState: InputState, defaultRegionCode: String? = nil) {
UIFont.loadAllFonts
self.checkoutApiClient = checkoutApiClient
self.cardHolderNameState = cardHolderNameState
self.billingDetailsState = billingDetailsState
Expand Down Expand Up @@ -160,8 +164,14 @@ public class CardViewController: UIViewController,
}

@objc func onTapAddressView() {
navigationController?.pushViewController(addressViewController, animated: true)
checkoutApiClient?.logger.log(.billingFormPresented)

guard isNewUI,
let viewController = BillingFormFactory.getBillingFormViewController(delegate: self).1 else {
navigationController?.pushViewController(addressViewController, animated: true)
return
}
navigationController?.present(viewController, animated: true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside of the scope of this PR, but @harry-brown-cko , @deepesh-vasthimal-cko , I think the animated value should follow the decisions of the user of the SDK.

I would say we should offer it as a customisable option, that defaults from user device UIAccessibility.isReduceMotionEnabled. May be worth checking if maybe OS overrides anyway, but there are many very valid reasons for animations to be disabled for an app (mostly accessibility but not only), so the SDK consumer may appreciate us following their approach or even improving it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback from @patrick-hoban-cko

loggedForCurrentCorrelationID = true
}

Expand Down Expand Up @@ -339,3 +349,20 @@ public class CardViewController: UIViewController,
}
}
}


extension CardViewController: BillingFormViewModelDelegate {
func updateCountryCode(code: Int) {
countryCode = code
}

func onTapDoneButton(address: CkoAddress, phone: CkoPhoneNumber) {
billingDetailsAddress = address
billingDetailsPhone = phone
let value = "\(address.addressLine1 ?? ""), \(address.city ?? "")"
melting-snowman marked this conversation as resolved.
Show resolved Hide resolved
cardView.billingDetailsInputView.value.text = value
validateFieldsValues()
// return to CardViewController
self.topConstraint?.isActive = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import UIKit

struct DefaultBillingFormAddressLine1CellStyle : BillingFormTextFieldCellStyle {

var isOptinal: Bool
var backgroundColor: UIColor
var title: InputLabelStyle?
var hint: InputLabelStyle?
var textfield: TextFieldStyle
var error: ErrorInputLabelStyle

init(isOptinal: Bool = true,
backgroundColor: UIColor = .white,
header: InputLabelStyle = DefaultTitleLabelStyle(text: "addressLine1".localized(forClass: CheckoutTheme.self)),
hint: InputLabelStyle? = nil,
textfield: TextFieldStyle = DefaultTextField(),
error: ErrorInputLabelStyle = DefaultErrorInputLabelStyle(text: "missingBillingFormAddressLine1".localized(forClass: CheckoutTheme.self))) {
self.backgroundColor = backgroundColor
self.title = header
self.hint = hint
self.textfield = textfield
self.error = error
self.isOptinal = isOptinal
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import UIKit

struct DefaultBillingFormAddressLine2CellStyle : BillingFormTextFieldCellStyle {

var isOptinal: Bool
var backgroundColor: UIColor
var title: InputLabelStyle?
var hint: InputLabelStyle?
var textfield: TextFieldStyle
var error: ErrorInputLabelStyle

init(isOptinal: Bool = true,
backgroundColor: UIColor = .white,
header: InputLabelStyle = DefaultTitleLabelStyle(text: "addressLine2".localized(forClass: CheckoutTheme.self)),
hint: InputLabelStyle? = nil,
textfield: TextFieldStyle = DefaultTextField(),
error: ErrorInputLabelStyle = DefaultErrorInputLabelStyle(text: "missingBillingFormAddressLine2".localized(forClass: CheckoutTheme.self))) {
self.backgroundColor = backgroundColor
self.title = header
self.hint = hint
self.textfield = textfield
self.error = error
self.isOptinal = isOptinal
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import UIKit

struct DefaultBillingFormCityCellStyle : BillingFormTextFieldCellStyle {

var isOptinal: Bool
var backgroundColor: UIColor
var title: InputLabelStyle?
var hint: InputLabelStyle?
var textfield: TextFieldStyle
var error: ErrorInputLabelStyle

init(isOptinal: Bool = false,
backgroundColor: UIColor = .white,
header: InputLabelStyle = DefaultTitleLabelStyle(text: "city".localized(forClass: CheckoutTheme.self)),
hint: InputLabelStyle? = nil,
textfield: TextFieldStyle = DefaultTextField(),
error: ErrorInputLabelStyle = DefaultErrorInputLabelStyle(text: "missingBillingFormCity".localized(forClass: CheckoutTheme.self))) {
self.backgroundColor = backgroundColor
self.title = header
self.hint = hint
self.textfield = textfield
self.error = error
self.isOptinal = isOptinal
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import UIKit

struct DefaultBillingFormCountryCellStyle : BillingFormTextFieldCellStyle {

var isOptinal: Bool
var backgroundColor: UIColor
var title: InputLabelStyle?
var hint: InputLabelStyle?
var textfield: TextFieldStyle
var error: ErrorInputLabelStyle

init(isOptinal: Bool = false,
backgroundColor: UIColor = .white,
header: InputLabelStyle = DefaultTitleLabelStyle(text: "country".localized(forClass: CheckoutTheme.self)),
hint: InputLabelStyle? = nil,
textfield: TextFieldStyle = DefaultTextField(),
error: ErrorInputLabelStyle = DefaultErrorInputLabelStyle(text: "missingBillingFormCountry".localized(forClass: CheckoutTheme.self))) {
self.backgroundColor = backgroundColor
self.title = header
self.hint = hint
self.textfield = textfield
self.error = error
self.isOptinal = isOptinal
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import UIKit

struct DefaultBillingFormFullNameCellStyle : BillingFormTextFieldCellStyle {

var isOptinal: Bool
var backgroundColor: UIColor
var title: InputLabelStyle?
var hint: InputLabelStyle?
var textfield: TextFieldStyle
var error: ErrorInputLabelStyle

init(isOptinal: Bool = false,
backgroundColor: UIColor = .white,
header: InputLabelStyle = DefaultTitleLabelStyle(text: "name".localized(forClass: CheckoutTheme.self)),
hint: InputLabelStyle? = nil,
textfield: TextFieldStyle = DefaultTextField(),
error: ErrorInputLabelStyle = DefaultErrorInputLabelStyle(text: "missingBillingFormFullName".localized(forClass: CheckoutTheme.self))) {
self.backgroundColor = backgroundColor
self.title = header
self.hint = hint
self.textfield = textfield
self.error = error
self.isOptinal = isOptinal
}

}