Skip to content

Commit

Permalink
[NT-204] Manage pledge payment method section (#851)
Browse files Browse the repository at this point in the history
* Added ManagePledgePaymentMethodView, created PaymentSource type

* Added ManagePledgePaymentMethodView, created PaymentSource type

* Fixed CreditCard decoding bug / View styling / Added view to ManageViewPledgeViewController

* Fixed / incremented tests

* Code cleanup / Added missing tests

* Swiftformat

* Alphabetization

* Fixed margins of pledge summary view / Updated configureVIews function

* Set numberOfLines to 0 for title label

* Renamed stackView / Used PledgeCreditCardViewModel to bind card infos

* Added explicit object type while defining styles

* Added closure variable name / removed unecessary style wrapper

* Removed line that broke VoiceOver

* Reverted commented out line

* Swiftformat

* [NT-204] Additional fixes (#861)

* Rename label and styles

* Refactor styles

* Add vc to parent properly

* Flatten the style
  • Loading branch information
Scollaco authored and dusi committed Oct 1, 2019
1 parent 32de6d1 commit 4d936fb
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 33 deletions.
4 changes: 4 additions & 0 deletions Kickstarter-iOS/Views/Cells/PledgeCreditCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ final class PledgeCreditCardView: UIView {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Configuration

private func configureSubviews() {
_ = self
|> \.accessibilityElements .~ self.subviews
Expand Down Expand Up @@ -101,6 +103,8 @@ final class PledgeCreditCardView: UIView {
|> blackButtonStyle
}

// MARK: - View model

override func bindViewModel() {
super.bindViewModel()

Expand Down
133 changes: 133 additions & 0 deletions Kickstarter-iOS/Views/Controllers/ManagePledgePaymentMethodView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import KsApi
import Library
import Prelude
import UIKit

final class ManagePledgePaymentMethodView: UIView {
// MARK: - Properties

private lazy var cardImageView: UIImageView = { UIImageView(frame: .zero) }()
private lazy var cardLabelsStackView: UIStackView = { UIStackView(frame: .zero) }()
private lazy var expirationDateLabel: UILabel = { UILabel(frame: .zero) }()
private lazy var lastFourDigitsLabel: UILabel = { UILabel(frame: .zero) }()
private lazy var paymentMethodAdaptableStackView: UIStackView = { UIStackView(frame: .zero) }()
private lazy var rootStackView: UIStackView = { UIStackView(frame: .zero) }()
private lazy var titleLabel: UILabel = { UILabel(frame: .zero) }()

private let viewModel: PledgeCreditCardViewModelType = PledgeCreditCardViewModel()

// MARK: - Lifecycle

override init(frame: CGRect) {
super.init(frame: frame)

self.configureViews()
self.setupConstraints()
self.bindViewModel()
}

required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Configuration

public func configure(with card: GraphUserCreditCard.CreditCard) {
self.viewModel.inputs.configureWith(value: card)
}

private func configureViews() {
_ = ([self.lastFourDigitsLabel, self.expirationDateLabel], self.cardLabelsStackView)
|> ksr_addArrangedSubviewsToStackView()

_ = ([self.cardImageView, self.cardLabelsStackView], self.paymentMethodAdaptableStackView)
|> ksr_addArrangedSubviewsToStackView()

_ = ([self.titleLabel, self.paymentMethodAdaptableStackView], self.rootStackView)
|> ksr_addArrangedSubviewsToStackView()

_ = (self.rootStackView, self)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()
}

// MARK: - Styles

override func bindStyles() {
super.bindStyles()

_ = self.cardImageView
|> cardImageViewStyle

_ = self.cardLabelsStackView
|> verticalStackViewStyle

_ = self.expirationDateLabel
|> expirationDateLabelStyle

_ = self.lastFourDigitsLabel
|> lastFourDigitsLabelStyle

_ = self.paymentMethodAdaptableStackView
|> checkoutAdaptableStackViewStyle(
self.traitCollection.preferredContentSizeCategory.isAccessibilityCategory
)
|> paymentMethodAdaptableStackViewStyle

_ = self.rootStackView
|> checkoutCardStackViewStyle

_ = self.titleLabel
|> checkoutTitleLabelStyle
|> \.text %~ { _ in Strings.Payment_method() }
}

// MARK: - View model

override func bindViewModel() {
super.bindViewModel()

self.expirationDateLabel.rac.text = self.viewModel.outputs.expirationDateText
self.lastFourDigitsLabel.rac.text = self.viewModel.outputs.cardNumberTextShortStyle

self.viewModel.outputs.cardImage
.observeForUI()
.observeValues { [weak self] image in
_ = self?.cardImageView
?|> \.image .~ image
}
}

// MARK: - Functions

private func setupConstraints() {
NSLayoutConstraint.activate([
self.cardImageView.widthAnchor.constraint(
equalToConstant: CheckoutConstants.PaymentSource.ImageView.width
)
])
}
}

// MARK: - Styles

private let expirationDateLabelStyle: LabelStyle = { label in
label
|> \.adjustsFontForContentSizeCategory .~ true
|> \.font .~ UIFont.ksr_caption1().bolded
|> \.numberOfLines .~ 0
|> \.textColor .~ UIColor.ksr_text_dark_grey_500
}

private let lastFourDigitsLabelStyle: LabelStyle = { label in
label
|> \.adjustsFontForContentSizeCategory .~ true
|> \.font .~ UIFont.ksr_subhead().bolded
|> \.numberOfLines .~ 0
|> \.textColor .~ UIColor.ksr_soft_black
}

private let paymentMethodAdaptableStackViewStyle: StackViewStyle = { stackView in
stackView
|> \.spacing .~ Styles.grid(2)
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ final class ManagePledgeSummaryView: UIView {
private func configureViews() {
_ = (self.rootStackView, self)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToMarginsInParent()
|> ksr_constrainViewToEdgesInParent()

_ = ([self.backerNumberLabel, self.backingDateLabel], self.backerInfoStackView)
|> ksr_addArrangedSubviewsToStackView()
Expand Down
45 changes: 18 additions & 27 deletions Kickstarter-iOS/Views/Controllers/ManagePledgeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ final class ManagePledgeViewController: UIViewController {

private lazy var pledgeSummaryView: ManagePledgeSummaryView = { ManagePledgeSummaryView(frame: .zero) }()

private lazy var paymentMethodView: ManagePledgePaymentMethodView = {
ManagePledgePaymentMethodView(frame: .zero)
}()

private lazy var rewardReceivedViewController: ManageViewPledgeRewardReceivedViewController = {
ManageViewPledgeRewardReceivedViewController.instantiate()
}()
Expand All @@ -40,7 +44,7 @@ final class ManagePledgeViewController: UIViewController {
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}()

private let viewModel = ManagePledgeViewModel()
private let viewModel: ManagePledgeViewModelType = ManagePledgeViewModel()

static func instantiate(with project: Project, reward: Reward) -> ManagePledgeViewController {
let manageViewPledgeVC = ManagePledgeViewController.instantiate()
Expand All @@ -58,16 +62,7 @@ final class ManagePledgeViewController: UIViewController {
?|> \.leftBarButtonItem .~ self.closeButton
?|> \.rightBarButtonItem .~ self.menuButton

_ = (self.rootScrollView, self.view)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

_ = (self.rootStackView, self.rootScrollView)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

self.configureViews()
self.configureChildViewControllers()
self.setupConstraints()

self.viewModel.inputs.viewDidLoad()
Expand Down Expand Up @@ -110,7 +105,9 @@ final class ManagePledgeViewController: UIViewController {

self.viewModel.outputs.configurePaymentMethodView
.observeForUI()
.observeValues { _ in }
.observeValues { [weak self] card in
self?.paymentMethodView.configure(with: card)
}

self.viewModel.outputs.configurePledgeSummaryView
.observeForUI()
Expand Down Expand Up @@ -161,21 +158,6 @@ final class ManagePledgeViewController: UIViewController {

// MARK: - Configuration

private func configureChildViewControllers() {
let childViewControllers = [
self.rewardReceivedViewController
]

childViewControllers.forEach { viewController in
self.addChild(viewController)

_ = ([viewController.view], self.rootStackView)
|> ksr_addArrangedSubviewsToStackView()

viewController.didMove(toParent: self)
}
}

func configureWith(project: Project, reward: Reward) {
self.viewModel.inputs.configureWith(project, reward: reward)
}
Expand All @@ -197,8 +179,17 @@ final class ManagePledgeViewController: UIViewController {
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

_ = ([self.pledgeSummaryView], self.rootStackView)
_ = ([self.pledgeSummaryView, self.paymentMethodView], self.rootStackView)
|> ksr_addArrangedSubviewsToStackView()

[self.rewardReceivedViewController].forEach { viewController in
self.addChild(viewController)

_ = ([viewController.view], self.rootStackView)
|> ksr_addArrangedSubviewsToStackView()

viewController.didMove(toParent: self)
}
}

// MARK: Actions
Expand Down
7 changes: 7 additions & 0 deletions Kickstarter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@
D6534D3C22E7898B00E9D279 /* PledgePaymentMethodsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6534D3B22E7898B00E9D279 /* PledgePaymentMethodsViewModel.swift */; };
D6534D3E22E789B900E9D279 /* PledgePaymentMethodsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6534D3D22E789B900E9D279 /* PledgePaymentMethodsViewModelTests.swift */; };
D6560C2A2182361800CD24BC /* PaymentMethodsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65E8F8821821EF500AB9412 /* PaymentMethodsDataSourceTests.swift */; };
D65BF3552334050100B15B25 /* ManagePledgePaymentMethodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65BF3542334050100B15B25 /* ManagePledgePaymentMethodView.swift */; };
D65BF34F232C1A1C00B15B25 /* ManagePledgeSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65BF34E232C1A1C00B15B25 /* ManagePledgeSummaryView.swift */; };
D65BF351232FE88300B15B25 /* ManagePledgeSummaryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65BF350232FE88300B15B25 /* ManagePledgeSummaryViewModel.swift */; };
D65BF353233023E500B15B25 /* ManagePledgeSummaryViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65BF352233023E400B15B25 /* ManagePledgeSummaryViewModelTests.swift */; };
Expand Down Expand Up @@ -2376,6 +2377,9 @@
D6534D3922E7878D00E9D279 /* CreditCard+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreditCard+Utils.swift"; sourceTree = "<group>"; };
D6534D3B22E7898B00E9D279 /* PledgePaymentMethodsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgePaymentMethodsViewModel.swift; sourceTree = "<group>"; };
D6534D3D22E789B900E9D279 /* PledgePaymentMethodsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgePaymentMethodsViewModelTests.swift; sourceTree = "<group>"; };
D65BF3542334050100B15B25 /* M

anagePledgePaymentMethodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagePledgePaymentMethodView.swift; sourceTree = "<group>"; };
D65BF34E232C1A1C00B15B25 /* ManagePledgeSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagePledgeSummaryView.swift; sourceTree = "<group>"; };
D65BF350232FE88300B15B25 /* ManagePledgeSummaryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagePledgeSummaryViewModel.swift; sourceTree = "<group>"; };
D65BF352233023E400B15B25 /* ManagePledgeSummaryViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagePledgeSummaryViewModelTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2991,6 +2995,8 @@
D65BF34E232C1A1C00B15B25 /* ManagePledgeSummaryView.swift */,
37CA16AC23300376006044F9 /* ManageViewPledgeRewardReceivedViewController.swift */,
37CA16AE2330038E006044F9 /* ManageViewPledgeRewardReceivedViewControllerTests.swift */,
D65BF3542334050100B15B25 /* ManagePledgePaymentMethodView.swift */,
D6A45DB62319A7E1006DDB01 /* ManageViewPledgeViewController.swift */,
D6A45DB62319A7E1006DDB01 /* ManagePledgeViewController.swift */,
D61440FF23200FD3002A6507 /* ManageViewPledgeViewControllerTests.swift */,
D6E7DAF922089F3900689BD6 /* MessageBannerViewController.swift */,
Expand Down Expand Up @@ -5192,6 +5198,7 @@
778CCC5222822B5D00FB8D35 /* SheetOverlayTransitionAnimator.swift in Sources */,
D04F48D41E0313FB00EDC98A /* ActivityProjectStatusCell.swift in Sources */,
A773531F1D5E8AEF0017E239 /* MostPopularSearchProjectCell.swift in Sources */,
D65BF3552334050100B15B25 /* ManagePledgePaymentMethodView.swift in Sources */,
37E9E2A0225EABB000D29DD7 /* AmountInputView.swift in Sources */,
A77352ED1D5E70FC0017E239 /* MostPopularCell.swift in Sources */,
D79F0F3721028C2600D3B32C /* SettingsPrivacyRecommendationCell.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions KsApi/models/Backing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public struct Backing {
public let id: Int
public let locationId: Int?
public let locationName: String?
public let paymentSource: GraphUserCreditCard.CreditCard?
public let pledgedAt: TimeInterval
public let projectCountry: String
public let projectId: Int
Expand Down Expand Up @@ -46,6 +47,7 @@ extension Backing: Argo.Decodable {
let tmp2 = tmp1
<*> json <|? "location_id"
<*> json <|? "location_name"
<*> json <|? "payment_source"
<*> json <| "pledged_at"
<*> json <| "project_country"
<*> json <| "project_id"
Expand Down
15 changes: 15 additions & 0 deletions KsApi/models/BackingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ final class BackingTests: XCTestCase {
"id": 1,
"location_id": 1,
"location_name": "United States",
"payment_source": [
"expiration_date": "2019-09-23",
"id": 20,
"last_four": "1234",
"payment_type": "CREDIT_CARD",
"state": "ACTIVE",
"type": "VISA"
],
"pledged_at": 1_000,
"project_country": "US",
"project_id": 1,
Expand All @@ -20,6 +28,13 @@ final class BackingTests: XCTestCase {
XCTAssertEqual(1.0, backing.value?.amount)
XCTAssertEqual(1, backing.value?.backerId)
XCTAssertEqual(1, backing.value?.id)
XCTAssertEqual("2019-09-23", backing.value?.paymentSource?.expirationDate)
// id is converted to a base64 encoded string to keep graphQL compatibility (used in other API calls).
XCTAssertEqual("VXNlci0yMA==", backing.value?.paymentSource?.id)
XCTAssertEqual("1234", backing.value?.paymentSource?.lastFour)
XCTAssertEqual("CREDIT_CARD", backing.value?.paymentSource?.paymentType)
XCTAssertEqual("ACTIVE", backing.value?.paymentSource?.state)
XCTAssertEqual(GraphUserCreditCard.CreditCardType.visa, backing.value?.paymentSource?.type)
XCTAssertEqual(1, backing.value?.locationId)
XCTAssertEqual("United States", backing.value?.locationName)
XCTAssertEqual(1_000, backing.value?.pledgedAt)
Expand Down
26 changes: 25 additions & 1 deletion KsApi/models/GraphUserCreditCard.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import Argo
import Curry
import Runes

public struct GraphUserCreditCard: Swift.Decodable {
public var storedCards: CreditCardConnection

public struct CreditCard: Swift.Decodable, Equatable {
public var expirationDate: String
public var id: String
public var lastFour: String
public let paymentType: String?
public let state: String?
public var type: CreditCardType?

public var formattedExpirationDate: String {
Expand All @@ -21,7 +27,7 @@ public struct GraphUserCreditCard: Swift.Decodable {
}
}

public enum CreditCardType: String, Decodable, CaseIterable {
public enum CreditCardType: String, Swift.Decodable, CaseIterable {
case amex = "AMEX"
case diners = "DINERS"
case discover = "DISCOVER"
Expand Down Expand Up @@ -55,3 +61,21 @@ public struct GraphUserCreditCard: Swift.Decodable {
public let nodes: [CreditCard]
}
}

extension GraphUserCreditCard.CreditCard: Argo.Decodable {
public static func decode(_ json: JSON) -> Decoded<GraphUserCreditCard.CreditCard> {
return curry(GraphUserCreditCard.CreditCard.init)
<^> json <| "expiration_date"
<*> (json <| "id" <|> (json <| "id" >>- intToString))
<*> json <| "last_four"
<*> json <|? "payment_type"
<*> json <| "state"
<*> json <|? "type"
}
}

extension GraphUserCreditCard.CreditCardType: Argo.Decodable {}

private func intToString(_ input: Int) -> Decoded<String> {
return .success(Data("User-\(input)".utf8).base64EncodedString())
}
Loading

0 comments on commit 4d936fb

Please sign in to comment.