-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[NT-364]Unsupported cards carousel #897
Conversation
…unsupported-cards-carousel
…unsupported-cards-carousel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of logic happening in the PledgePaymentMethodsViewController
which should probably be in a view model so it can be tested. I'm wondering if there's a reason we didn't just add the "card unavailable" label to the PledgeCreditCardView
and configured the PledgeCreditCardViewModel
with a boolean that lets it determine whether or not it should be an unavailable view. This would allow us to better test the logic around disabling unavailable card types, and might mitigate some of the complexity that doing this in the view controller is causing.
I also noticed a couple of bugs that might have to do with the way we're adding the card views:
After adding a new card, it looks like there's a duplicate set of unavailable card views in the list.
Also, the unavailable card label doesn't seem to be responding to dynamic type:
@@ -169,3 +174,16 @@ private let labelsStackViewStyle: StackViewStyle = { stackView in | |||
|> \.axis .~ .vertical | |||
|> \.spacing .~ Styles.gridHalf(1) | |||
} | |||
|
|||
private let selectButtonStyle: ButtonStyle = { button in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it necessary to create a custom style for this button? The blackButtonStyle
should have a .disabled
style, in which case we should be able to just set the button to disabled
to get the correct treatment 🤔
@@ -21,6 +21,8 @@ final class PledgePaymentMethodsViewController: UIViewController { | |||
private lazy var cardsStackView: UIStackView = { UIStackView(frame: .zero) }() | |||
internal weak var delegate: PledgePaymentMethodsViewControllerDelegate? | |||
internal weak var messageDisplayingDelegate: PledgeViewControllerMessageDisplaying? | |||
private lazy var pledgeButton: UIButton = { UIButton.init(type: .custom) }() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did this get accidentally re-added as part of a merge? I believe @justinswart refactored this button into the main PledgeViewController
@@ -98,10 +104,10 @@ final class PledgePaymentMethodsViewController: UIViewController { | |||
|
|||
self.viewModel.outputs.reloadPaymentMethods | |||
.observeForUI() | |||
.observeValues { [weak self] cards in | |||
.observeValues { [weak self] cardValues in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for this rename?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see below the naming has changed.
cardView.configureWith(value: card) | ||
|
||
guard let cardBrand = card.type?.rawValue else { return self.pledgeCardStackView } | ||
let isAvailableCardType = availableCardTypes.contains(cardBrand) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I feel like this logic should probably live in the view model.
|> \.axis .~ .vertical | ||
|> \.spacing .~ Styles.grid(2) | ||
|
||
let label = UILabel(frame: .zero) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we put this label within the PledgeCreditCardView
itself, it might be easier to manage it's hide/shown state from PledgeCreditCardViewModel
.
@@ -25,6 +28,9 @@ public protocol PledgeCreditCardViewModelOutputs { | |||
/// Emits a formatted string containing the card's last four digits with the format: Ending in 8844. | |||
var cardNumberTextShortStyle: Signal<String, Never> { get } | |||
|
|||
/// Emits whether or not the button is enabled. | |||
var disableButton: Signal<Bool, Never> { get } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually we try to match the output name with the property being modified: so here it would be var selectButtonEnabled: Signal<Bool, Never>
so that in the rac binding it would be:
self.selectButton.rac.enabled = self.viewModel.outputs.selectButtonEnabled
@@ -95,6 +103,11 @@ public final class PledgeCreditCardViewModel: PledgeCreditCardViewModelInputs, | |||
self.selectedCardProperty.value = creditCard | |||
} | |||
|
|||
private let disabledCardProperty = MutableProperty<Bool?>(nil) | |||
public func setDisabledCard(_ disabled: Bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would if we could have just wrapped up the card's disabled state as part of the configure
function:
typealias PledgeCreditCardValue = (card: GraphUserCreditCard.CreditCard, isEnabled: Bool)
fileprivate let creditCardProperty = MutableProperty<GraphUserCreditCard.CreditCard?>(nil)
public func configureWith(value: PledgeCreditCardValue) {
self.creditCardProperty.value = value
}
Then every card would be configured with whether or not it's enabled and we wouldn't need to do all that logic in the PledgePaymentMethodsViewController
.
@@ -4,9 +4,14 @@ import ReactiveExtensions | |||
import ReactiveSwift | |||
import UIKit | |||
|
|||
public typealias PledgeCreditCardValue = ( | |||
card: GraphUserCreditCard.CreditCard, | |||
isEnabled: Bool, projectCountry: String? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
projectCountry
probably doesn't have to be optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cdolm92 does this have to be optional?
} | ||
|
||
fileprivate let creditCardProperty = MutableProperty<GraphUserCreditCard.CreditCard?>(nil) | ||
public func configureWith(value: GraphUserCreditCard.CreditCard) { | ||
fileprivate let creditCardProperty = MutableProperty<PledgeCreditCardValue?>(nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we update the name of this property to match the fact that we now configure with PledgeCreditCardValue
? Maybe pledgeCreditCardValueProperty
?
project.map { $0.location.displayableName } | ||
).map { $0 as CardViewValues } | ||
|
||
self.reloadPaymentMethods = cardValues |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is where we could move even more logic out of the view controller – something we could do is instead of returning CardViewValues
, return an array of PledgeCreditCardValue
:
self.reloadPaymentMethods = Signal.combineLatest(
cards,
availableCardTypes,
project.map { $0.location.displayableName }
).map { cards, availableCardTypes, projectLocation in
return cards.map { card in
let isEnabled = availableCardTypes.contains(card.type.rawValue)
return (card, isEnabled, projectLocation)
}
}
Then we can test that given a list of stored cards, and a list of availableCardTypes
in a project, that the output is exactly what we're feeding into each PledgeCreditCardViewModel
to configure the card view.
…unsupported-cards-carousel
Generated by 🚫 Danger |
@@ -5,6 +5,10 @@ import ReactiveSwift | |||
import UIKit | |||
|
|||
public typealias PledgePaymentMethodsValue = (user: User, project: Project, applePayCapable: Bool) | |||
public typealias CardViewValues = ( | |||
cardAndIsAvailableCardType: [(cards: GraphUserCreditCard.CreditCard, cardTypeIsAvailable: Bool)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 | Line should be 110 characters or less: currently 122 charactersline_length PledgePaymentMethodsViewModel.swift:9 |
|
||
if let selectedCard = selectedCard { | ||
cardView.setSelectedCard(selectedCard) | ||
if isAvailableCardType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might be able to simplify this logic by doing:
let card = cardAndAvailableType.cards
let isAvailableCardType = cardAndAvailableType.cardTypeIsAvailable
let projectCountry = cardValues.projectCountry
cardView.configureWith(value: (card: card, isEnabled: isAvailableCardType, projectCountry: projectCountry))
if let selectedCard = selectedCard {
cardView.setSelectedCard(selectedCard)
}
@@ -229,6 +236,14 @@ final class PledgePaymentMethodsViewController: UIViewController { | |||
|> \.font .~ UIFont.ksr_caption1() | |||
|> \.textAlignment .~ .center | |||
} | |||
|
|||
private let cardRestrictionLabelStyle: LabelStyle = { label in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still being used in this file?
@@ -4,9 +4,14 @@ import ReactiveExtensions | |||
import ReactiveSwift | |||
import UIKit | |||
|
|||
public typealias PledgeCreditCardValue = ( | |||
card: GraphUserCreditCard.CreditCard, | |||
isEnabled: Bool, projectCountry: String? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cdolm92 does this have to be optional?
@@ -13,8 +13,12 @@ internal final class PledgeCreditCardViewModelTests: TestCase { | |||
private let cardNumberTextShortStyle = TestObserver<String, Never>() | |||
private let expirationDateText = TestObserver<String, Never>() | |||
private let notifyDelegateOfCardSelected = TestObserver<String, Never>() | |||
private let selectButtonEnable = TestObserver<Bool, Never>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small typo here, should be selectButtonEnabled
.
@@ -24,12 +28,16 @@ internal final class PledgeCreditCardViewModelTests: TestCase { | |||
self.vm.outputs.cardNumberTextShortStyle.observe(self.cardNumberTextShortStyle.observer) | |||
self.vm.outputs.expirationDateText.observe(self.expirationDateText.observer) | |||
self.vm.outputs.notifyDelegateOfCardSelected.observe(self.notifyDelegateOfCardSelected.observer) | |||
self.vm.outputs.selectButtonEnabled.observe(self.selectButtonEnable.observer) // |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some trailing slashes here 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh right, I still need to test those
@@ -5,6 +5,10 @@ import ReactiveSwift | |||
import UIKit | |||
|
|||
public typealias PledgePaymentMethodsValue = (user: User, project: Project, applePayCapable: Bool) | |||
public typealias CardViewValues = ( | |||
cardAndIsAvailableCardType: [(cards: GraphUserCreditCard.CreditCard, cardTypeIsAvailable: Bool)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this should be cardAndIsAvailableCardType: (card: GraphUserCreditCard.CreditCard, cardTypeIsAvailable: Bool)
(singular card
instead of cards
).
|
||
self.vm.inputs.configureWith((User.template, Project.template, false)) | ||
self.vm.inputs.viewDidLoad() | ||
|
||
self.scheduler.run() | ||
|
||
self.reloadPaymentMethods.assertValue(response.me.storedCards.nodes) | ||
self.reloadPaymentMethodsCards.assertValue(response.me.storedCards.nodes) | ||
self.reloadPaymentMethodsAvailableCardTypes.assertValues([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little difficult to follow these assertions when they're split up. Is it possible to do the assertions on the tuples? Something like this:
self.reloadPaymentMethodsCardsAndAvailable.assertValues([(GraphUserCreditCard.visa, true), (GraphUser CreditCard.masterCard, true)...etc])
…unsupported-cards-carousel
📲 What
We have disabled the select button, changed the button title to
No longer available
and provided copy below the unavailable credit card so that users understand that they cannot pledge with an unavailable card type.🤔 Why
Users want to clearly and easily see which of their saved payment methods may not be used to back a particular project in the Pledge screen so that they know to select or add a different payment method to complete their pledge.
🛠 How
We determine unavailable card types by checking if the user card is included in
project.availableCardTypes
.👀 See
Trello, screenshots, external resources?
✅ Acceptance criteria
No longer available
You can’t use this credit card to back a project from [project location].
Note: this will change to project country in an upcoming PR✍🏾 TO DO