-
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
[MBL-1815] PLOT plan selector - selected state #2213
Changes from 2 commits
7fda0e7
5169766
ed401ec
eff7fa4
b5aab6c
43bd0a5
5db01cb
2bcd664
8893498
8589d20
d38cfa3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like there may be some extra spacing after "Charge 1". I know we were having issues without iPad snapshots for this previously. If it's the same issue here, that's fine for now. I'd like to fix that, though, if possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course, we need to address how we handle snapshot tests. I’ll create a ticket for this purpose. You can follow this thread for more details. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,10 @@ protocol PledgePaymentPlanOptionViewDelegate: AnyObject { | |
_ optionView: PledgePaymentPlanOptionView, | ||
didSelectPlanType paymentPlanType: PledgePaymentPlansType | ||
) | ||
func pledgePaymentPlansViewController( | ||
_ optionView: PledgePaymentPlanOptionView, | ||
didTapTermsOfUseWith helpType: HelpType | ||
) | ||
} | ||
|
||
final class PledgePaymentPlanOptionView: UIView { | ||
|
@@ -16,6 +20,8 @@ final class PledgePaymentPlanOptionView: UIView { | |
private lazy var titleLabel = { UILabel(frame: .zero) }() | ||
private lazy var subtitleLabel = { UILabel(frame: .zero) }() | ||
private lazy var selectionIndicatorImageView: UIImageView = { UIImageView(frame: .zero) }() | ||
private lazy var termsOfUseButton: UIButton = { UIButton(frame: .zero) }() | ||
private lazy var paymentIncrementsStackView: UIStackView = { UIStackView(frame: .zero) }() | ||
|
||
private let viewModel: PledgePaymentPlansOptionViewModelType = PledgePaymentPlansOptionViewModel() | ||
|
||
|
@@ -27,7 +33,7 @@ final class PledgePaymentPlanOptionView: UIView { | |
self.bindViewModel() | ||
self.configureSubviews() | ||
self.setupConstraints() | ||
self.configureTapGesture() | ||
self.configureTapGestureAndActions() | ||
} | ||
|
||
@available(*, unavailable) | ||
|
@@ -43,7 +49,21 @@ final class PledgePaymentPlanOptionView: UIView { | |
self.contentView.addSubview(self.selectionIndicatorImageView) | ||
self.contentView.addSubview(self.optionDescriptorStackView) | ||
|
||
self.optionDescriptorStackView.addArrangedSubviews([self.titleLabel, self.subtitleLabel]) | ||
self.optionDescriptorStackView.addArrangedSubviews( | ||
self.titleLabel, | ||
self.subtitleLabel, | ||
self.termsOfUseButton, | ||
self.paymentIncrementsStackView | ||
) | ||
|
||
// TODO: add strings translations [MBL-1860](https://kickstarter.atlassian.net/browse/MBL-1860) | ||
self.termsOfUseButton.setAttributedTitle( | ||
NSAttributedString( | ||
string: "See our Terms of Use", | ||
attributes: [NSAttributedString.Key.font: UIFont.ksr_caption1()] | ||
), | ||
for: .normal | ||
) | ||
} | ||
|
||
private func setupConstraints() { | ||
|
@@ -85,11 +105,17 @@ final class PledgePaymentPlanOptionView: UIView { | |
]) | ||
} | ||
|
||
private func configureTapGesture() { | ||
private func configureTapGestureAndActions() { | ||
self.addGestureRecognizer(UITapGestureRecognizer( | ||
target: self, | ||
action: #selector(self.onOptionTapped) | ||
)) | ||
|
||
self.termsOfUseButton.addTarget( | ||
self, | ||
action: #selector(self.onTermsOfUseButtonTapped), | ||
for: .touchUpInside | ||
) | ||
} | ||
|
||
// MARK: - Styles | ||
|
@@ -98,9 +124,13 @@ final class PledgePaymentPlanOptionView: UIView { | |
super.bindStyles() | ||
|
||
applyOptionDescriptorStackViewStyle(self.optionDescriptorStackView) | ||
self.optionDescriptorStackView.setCustomSpacing(0, after: self.subtitleLabel) | ||
|
||
applyTitleLabelStyle(self.titleLabel) | ||
applySubtitleLabelStyle(self.subtitleLabel) | ||
applySelectionIndicatorImageViewStyle(self.selectionIndicatorImageView) | ||
applyTermsOfUseStyle(self.termsOfUseButton) | ||
applyPaymentIncrementsStackViewStyle(self.paymentIncrementsStackView) | ||
} | ||
|
||
// MARK: - View model | ||
|
@@ -126,6 +156,25 @@ final class PledgePaymentPlanOptionView: UIView { | |
|
||
self.delegate?.pledgePaymentPlanOptionView(self, didSelectPlanType: paymentPlan) | ||
} | ||
|
||
self.viewModel.outputs.notifyDelegateTermsOfUseTapped | ||
.observeForUI() | ||
.observeValues { [weak self] helpType in | ||
guard let self = self else { return } | ||
|
||
self.delegate?.pledgePaymentPlansViewController(self, didTapTermsOfUseWith: helpType) | ||
} | ||
|
||
self.termsOfUseButton.rac.hidden = self.viewModel.outputs.termsOfUseButtonHidden | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this output? When the option isn't selected the entire PledgePaymentPlanOptionView will be hidden anyway, right? I don't think there's a case where the terms button won't be shown when everything else is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I thought that there was a stackview just for the items that need to be hidden/shown at the correct time. |
||
self.paymentIncrementsStackView.rac.hidden = self.viewModel.outputs.paymentIncrementsHidden | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually same question about this stack view as the one above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a case where one element for PLOT would be shown and not another? If they're always grouped together, controlling them with one output makes more sense to me. But I'm not as familiar with the designs as you both are! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There’s no current case where one element would show and not another, except for the Ineligible state. In that case, we display the badge but hide the increments, description, and terms of use button. Given this, I think it makes sense to handle this cleanup as part of a tech-debt ticket. Let me know if that works for you. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, works for me. |
||
|
||
self.viewModel.outputs.paymentIncrements | ||
.observeForUI() | ||
.observeValues { [weak self] increments in | ||
guard let self = self else { return } | ||
|
||
self.setupIncrementsStackView(with: increments) | ||
} | ||
} | ||
|
||
func configureWith(value: PledgePaymentPlanOptionData) { | ||
|
@@ -141,20 +190,67 @@ final class PledgePaymentPlanOptionView: UIView { | |
@objc private func onOptionTapped() { | ||
self.viewModel.inputs.optionTapped() | ||
} | ||
|
||
@objc private func onTermsOfUseButtonTapped() { | ||
self.viewModel.inputs.termsOfUseTapped() | ||
} | ||
|
||
// MARK: - Functions | ||
|
||
private func setupIncrementsStackView(with increments: [PledgePaymentIncrementFormatted]) { | ||
increments.forEach { increment in | ||
let incrementStackView = UIStackView() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be cleaner if it was all pulled out into its own subview, like
and then all the label and formatting logic could live there as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense, and it would definitely help clean things up. I propose handling this as part of a tech-debt ticket so we can prioritize unblocking the API tasks for now. Let me know if that works for you! |
||
applyIncrementStackViewStyle(incrementStackView) | ||
|
||
let titleLabel = UILabel() | ||
applyIncrementTitleLabelStyle(titleLabel) | ||
titleLabel.text = increment.title | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Can we rename this to something like |
||
|
||
let detailsStackView = UIStackView() | ||
applyIncrementDetailsStackViewStyle(detailsStackView) | ||
|
||
let dateLabel = UILabel() | ||
applyDetailLabelStyle(dateLabel) | ||
dateLabel.text = increment.scheduledCollection | ||
|
||
let amountLabel = UILabel() | ||
applyDetailLabelStyle(amountLabel) | ||
amountLabel.text = increment.amount | ||
amountLabel.setContentCompressionResistancePriority(.required, for: .horizontal) | ||
|
||
detailsStackView.addArrangedSubviews(dateLabel, amountLabel) | ||
incrementStackView.addArrangedSubviews(titleLabel, detailsStackView) | ||
|
||
self.paymentIncrementsStackView.addArrangedSubview(incrementStackView) | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Styles helper | ||
|
||
private func applyDetailLabelStyle(_ label: UILabel) { | ||
label.font = UIFont.ksr_footnote() | ||
label.textColor = .ksr_support_400 | ||
label.textAlignment = .left | ||
label.adjustsFontForContentSizeCategory = true | ||
} | ||
|
||
private func applyOptionDescriptorStackViewStyle(_ stackView: UIStackView) { | ||
stackView.axis = .vertical | ||
stackView.spacing = Styles.grid(1) | ||
} | ||
|
||
private func applyPaymentIncrementsStackViewStyle(_ stackView: UIStackView) { | ||
stackView.axis = .vertical | ||
stackView.spacing = Styles.grid(2) | ||
} | ||
|
||
private func applyTitleLabelStyle(_ label: UILabel) { | ||
label.accessibilityTraits = UIAccessibilityTraits.header | ||
label.adjustsFontForContentSizeCategory = true | ||
label.numberOfLines = 0 | ||
label.font = UIFont.ksr_subhead().bolded | ||
label.textColor = .ksr_black | ||
} | ||
|
||
private func applySubtitleLabelStyle(_ label: UILabel) { | ||
|
@@ -168,3 +264,34 @@ private func applySubtitleLabelStyle(_ label: UILabel) { | |
private func applySelectionIndicatorImageViewStyle(_ imageView: UIImageView) { | ||
imageView.contentMode = .center | ||
} | ||
|
||
private func applyTermsOfUseStyle(_ button: UIButton) { | ||
button.configuration = { | ||
var config = UIButton.Configuration.borderless() | ||
config.contentInsets = NSDirectionalEdgeInsets(top: 1.0, leading: 0, bottom: 1.0, trailing: 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we could throw these inset values, and any others if we have any, in a Constants enum. Similarly to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, I’ll move these |
||
config.baseForegroundColor = .ksr_create_700 | ||
return config | ||
}() | ||
|
||
button.contentHorizontalAlignment = .leading | ||
button.setContentHuggingPriority(.required, for: .horizontal) | ||
} | ||
|
||
private func applyIncrementStackViewStyle(_ stackView: UIStackView) { | ||
stackView.axis = .vertical | ||
stackView.alignment = .leading | ||
stackView.spacing = Styles.gridHalf(1) | ||
} | ||
|
||
private func applyIncrementDetailsStackViewStyle(_ stackview: UIStackView) { | ||
stackview.axis = .horizontal | ||
stackview.spacing = Styles.grid(6) | ||
} | ||
|
||
private func applyIncrementTitleLabelStyle(_ label: UILabel) { | ||
label.font = UIFont.ksr_footnote().bolded | ||
label.textColor = .ksr_black | ||
label.textAlignment = .left | ||
label.adjustsFontForContentSizeCategory = true | ||
label.setContentCompressionResistancePriority(.required, for: .vertical) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,7 +101,10 @@ public class NoShippingPledgeViewModel: NoShippingPledgeViewModelType, NoShippin | |
.map { showPledgeOverTimeUI -> PledgePaymentPlansAndSelectionData? in | ||
guard showPledgeOverTimeUI else { return nil } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this guard statement could be replaced with a |
||
|
||
return PledgePaymentPlansAndSelectionData(selectedPlan: .pledgeInFull) | ||
return PledgePaymentPlansAndSelectionData( | ||
selectedPlan: .pledgeInFull, | ||
increments: mockPledgePaymentIncrement() | ||
) | ||
}.skipNil() | ||
|
||
self.pledgeAmountViewHidden = context.map { $0.pledgeAmountViewHidden } | ||
|
@@ -1244,3 +1247,17 @@ private func pledgeAmountSummaryViewData( | |
rewardIsLocalPickup: rewardIsLocalPickup | ||
) | ||
} | ||
|
||
private func mockPledgePaymentIncrement() -> [PledgePaymentIncrement] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Merging mock data into the main branch makes me nervous. It looks like it should be safe, since it's behind Can you put the mock code in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, Amy. I think using |
||
var increments: [PledgePaymentIncrement] = [] | ||
var timeStamp = Date().timeIntervalSince1970 | ||
for _ in 1...4 { | ||
timeStamp += 30 * 24 * 60 * 60 | ||
increments.append(PledgePaymentIncrement( | ||
amount: PledgePaymentIncrementAmount(amount: 250.0, currency: "USD"), | ||
scheduledCollection: timeStamp | ||
)) | ||
} | ||
|
||
return increments | ||
} |
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.
The spacing issue shown here is due to the test locking the layout to a specific screen size, which might not fully represent the component's dynamic behavior. In actual implementation, this component adapts its size based on the content and available screen dimensions, so the extra space seen in the test is expected.
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 you link the ticket you created here for reference? We should update how we use snapshot tests if we make a habit of testing individual views instead of the entire screen. (I'm a big fan of testing individual views, for the record; we just don't do that currently)
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 haven’t created a ticket for this yet, but I can definitely do so to track this issue and the potential update to how we approach snapshot testing. Let me know if that works!