-
Notifications
You must be signed in to change notification settings - Fork 957
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
Show payment methods in vertical saved list #3573
Changes from all commits
be5deff
d0e5edd
c70c42f
5a43ed5
a61229c
4691297
19dd9e5
3c5d206
131d669
7d3c03c
c8a5810
35206f1
c2a6da0
d20b668
246d877
553d453
946a613
4b08cc3
ee46679
1b64726
67dbc25
2fd92cc
a99ccea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// | ||
// CheckmarkCircleView.swift | ||
// StripePaymentSheet | ||
// | ||
// Created by Nick Porter on 5/9/24. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
/// Draws a circle with the desired fill color with a white checkmark in the center | ||
final class CheckmarkCircleView: UIView { | ||
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. Omitted snapshot tests for this class and relying on 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. 👍 I don't think this needs its own tests |
||
|
||
let checkmarkColor: UIColor = .white | ||
let fillColor: UIColor | ||
|
||
init(fillColor: UIColor) { | ||
self.fillColor = fillColor | ||
super.init(frame: .zero) | ||
self.backgroundColor = .clear | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
override var intrinsicContentSize: CGSize { | ||
return CGSize(width: 20, height: 20) | ||
} | ||
|
||
override func draw(_ rect: CGRect) { | ||
super.draw(rect) | ||
drawCircle() | ||
drawCheckmark() | ||
} | ||
|
||
private func drawCircle() { | ||
let path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: intrinsicContentSize)) | ||
fillColor.setFill() | ||
path.fill() | ||
} | ||
|
||
private func drawCheckmark() { | ||
let path = UIBezierPath() | ||
path.lineWidth = max(2, intrinsicContentSize.width * 0.06) | ||
path.move(to: CGPoint(x: intrinsicContentSize.width * 0.28, y: intrinsicContentSize.height * 0.53)) | ||
path.addLine(to: CGPoint(x: intrinsicContentSize.width * 0.42, y: intrinsicContentSize.height * 0.66)) | ||
path.addLine(to: CGPoint(x: intrinsicContentSize.width * 0.72, y: intrinsicContentSize.height * 0.36)) | ||
|
||
checkmarkColor.setStroke() | ||
path.stroke() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// | ||
// PaymentMethodRowButton.swift | ||
// StripePaymentSheet | ||
// | ||
// Created by Nick Porter on 5/9/24. | ||
// | ||
|
||
import Foundation | ||
@_spi(STP) import StripeCore | ||
@_spi(STP) import StripePaymentsUI | ||
@_spi(STP) import StripeUICore | ||
import UIKit | ||
|
||
protocol PaymentMethodRowButtonDelegate: AnyObject { | ||
func didSelectButton(_ button: PaymentMethodRowButton) | ||
// TODO(porter) Add did delete and did update | ||
} | ||
|
||
final class PaymentMethodRowButton: UIView { | ||
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, omitted snapshot tests for this view and relied on |
||
|
||
struct ViewModel { | ||
let appearance: PaymentSheet.Appearance | ||
let text: String | ||
let image: UIImage | ||
// TODO(porter) Add can remove and can update | ||
} | ||
Comment on lines
+21
to
+26
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. what's the idea behind putting this 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. Probably not totally needed right now, but I anticipate this will scale better and am hoping in the future to create view models for extension STPPaymentMethod {
var rowButtonViewModel: PaymentMethodRowButton.ViewModel {...}
}
// Somewhere else
let rowButtons = savedPaymentMethods.map{PaymentMethodRowButton(appearance: appearance, viewModel: $0.rowButtonViewModel)} |
||
|
||
// MARK: Internal properties | ||
// TODO(porter) Maybe expand this into an enum of (selected, unselected, editing) state | ||
var isSelected: Bool { | ||
get { | ||
return shadowRoundedRect.isSelected | ||
} | ||
|
||
set { | ||
shadowRoundedRect.isSelected = newValue | ||
circleView.alpha = newValue ? 1.0 : 0.0 | ||
} | ||
} | ||
|
||
weak var delegate: PaymentMethodRowButtonDelegate? | ||
|
||
// MARK: Private properties | ||
private let viewModel: ViewModel | ||
|
||
// MARK: Private views | ||
|
||
private lazy var paymentMethodImageView: UIImageView = { | ||
let imageView = UIImageView(image: viewModel.image) | ||
imageView.contentMode = .scaleAspectFit | ||
// TODO(porter) Do we want to round the corners? | ||
return imageView | ||
}() | ||
|
||
private lazy var label: UILabel = { | ||
let label = UILabel() | ||
label.text = viewModel.text | ||
label.font = viewModel.appearance.scaledFont(for: viewModel.appearance.font.base.medium, | ||
style: .callout, | ||
maximumPointSize: 25) | ||
label.adjustsFontForContentSizeCategory = true | ||
return label | ||
}() | ||
|
||
private lazy var circleView: CheckmarkCircleView = { | ||
let circleView = CheckmarkCircleView(fillColor: viewModel.appearance.colors.primary) | ||
circleView.alpha = 0.0 | ||
return circleView | ||
}() | ||
|
||
private lazy var stackView: UIStackView = { | ||
let stackView = UIStackView(arrangedSubviews: [paymentMethodImageView, label, UIView.spacerView, circleView]) | ||
stackView.axis = .horizontal | ||
stackView.alignment = .center | ||
stackView.translatesAutoresizingMaskIntoConstraints = false | ||
stackView.directionalLayoutMargins = .init(top: 12, // Hardcoded from figma | ||
leading: PaymentSheetUI.defaultPadding, | ||
bottom: 12, | ||
trailing: PaymentSheetUI.defaultPadding) | ||
stackView.isLayoutMarginsRelativeArrangement = true | ||
stackView.setCustomSpacing(12, after: paymentMethodImageView) // Hardcoded from figma | ||
return stackView | ||
}() | ||
|
||
private lazy var shadowRoundedRect: ShadowedRoundedRectangle = { | ||
let shadowRoundedRect = ShadowedRoundedRectangle(appearance: viewModel.appearance) | ||
shadowRoundedRect.translatesAutoresizingMaskIntoConstraints = false | ||
shadowRoundedRect.addAndPinSubview(stackView) | ||
return shadowRoundedRect | ||
}() | ||
|
||
init(viewModel: ViewModel) { | ||
self.viewModel = viewModel | ||
super.init(frame: .zero) | ||
|
||
addAndPinSubview(shadowRoundedRect) | ||
NSLayoutConstraint.activate([ | ||
paymentMethodImageView.heightAnchor.constraint(equalToConstant: 20), // Hardcoded from figma | ||
paymentMethodImageView.widthAnchor.constraint(equalToConstant: 25), | ||
]) | ||
// TODO(porter) accessibility? | ||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) | ||
addGestureRecognizer(tapGesture) | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
// MARK: Tap handlers | ||
@objc private func handleTap() { | ||
shadowRoundedRect.isSelected = true | ||
circleView.alpha = 1.0 | ||
delegate?.didSelectButton(self) | ||
} | ||
|
||
} | ||
|
||
// MARK: Helper extensions | ||
extension UIView { | ||
static var spacerView: UIView { | ||
let view = UIView() | ||
view.isUserInteractionEnabled = false | ||
view.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal) | ||
view.setContentCompressionResistancePriority(.fittingSizeLevel, for: .horizontal) | ||
return view | ||
} | ||
} |
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 not reuse
makeSavedPaymentMethodCellImage
?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.
makeSavedPaymentMethodCellImage
uses carousel images for card brands and for SEPA. We need the images that are used in the PAN field for card brand rather than the carousel images. Here the diff if we usemakeSavedPaymentMethodCellImage
, notice visa & SEPA look a little different than what is laid out in the mocks. The differences would be more sark in dark mode but I don't think we have mocks for dark mode.Mock
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.
Ohh right, we have 'carousel' versions of these. I guess we can delete those once we finish LPM visibility and the horizontal saved pm carousel goes away.