diff --git a/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj b/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj index e18609187f3..7b4cdf1358a 100644 --- a/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj +++ b/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj @@ -111,7 +111,6 @@ 6103F2BC2BE45990002D67F8 /* SavedPaymentMethodManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6103F2BB2BE45990002D67F8 /* SavedPaymentMethodManager.swift */; }; 614A8AE72BE53C6900E8688B /* SavedPaymentMethodManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6103F2BD2BE53737002D67F8 /* SavedPaymentMethodManagerTest.swift */; }; 6151DDC02B14FDCF00ED4F7E /* UpdateCardViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6151DDBF2B14FDCF00ED4F7E /* UpdateCardViewControllerSnapshotTests.swift */; }; - 6198AA6C2BED1AC000F39D3E /* CheckmarkCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6198AA6B2BED1AC000F39D3E /* CheckmarkCircleView.swift */; }; 6198AA6E2BED1C5A00F39D3E /* PaymentMethodRowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6198AA6D2BED1C5A00F39D3E /* PaymentMethodRowButton.swift */; }; 61C0D3B8C63EB4558AB74A7E /* StripePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A1C7CFA5C9C1A8A73CFA1C0 /* StripePayments.framework */; }; 61CB0BD02BED985100E24A4C /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61CBE6672BED97EE005F7FEB /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift */; }; @@ -433,7 +432,6 @@ 6151DDBF2B14FDCF00ED4F7E /* UpdateCardViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCardViewControllerSnapshotTests.swift; sourceTree = ""; }; 617C44F9338DE2E93E318291 /* PayWithLinkWebController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayWithLinkWebController.swift; sourceTree = ""; }; 6193FC5E14E1EC459E31B5F4 /* SheetNavigationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetNavigationButton.swift; sourceTree = ""; }; - 6198AA6B2BED1AC000F39D3E /* CheckmarkCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkCircleView.swift; sourceTree = ""; }; 6198AA6D2BED1C5A00F39D3E /* PaymentMethodRowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodRowButton.swift; sourceTree = ""; }; 61CBE6652BED9749005F7FEB /* VerticalSavedPaymentMethodsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalSavedPaymentMethodsViewController.swift; sourceTree = ""; }; 61CBE6672BED97EE005F7FEB /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift; sourceTree = ""; }; @@ -836,7 +834,6 @@ children = ( 61CBE6652BED9749005F7FEB /* VerticalSavedPaymentMethodsViewController.swift */, 6198AA6D2BED1C5A00F39D3E /* PaymentMethodRowButton.swift */, - 6198AA6B2BED1AC000F39D3E /* CheckmarkCircleView.swift */, ); path = "Vertical Saved Payment Method Screen"; sourceTree = ""; @@ -1704,7 +1701,6 @@ F42DEC1850964E75ACAC29AB /* CustomerSheet+API.swift in Sources */, 50C68C68B007A926BE99B2B8 /* CustomerSheet+PaymentMethodAvailability.swift in Sources */, DB8A4C5FC11D0EED55E8C975 /* CustomerSheet+SwiftUI.swift in Sources */, - 6198AA6C2BED1AC000F39D3E /* CheckmarkCircleView.swift in Sources */, 648FDD85FD6ECDA1BBC71D45 /* CustomerSheet.swift in Sources */, 9E77F1E9F801AE970F1A5BE1 /* CustomerSheetConfiguration.swift in Sources */, AB8E1556F008083257A99E91 /* CustomerSheetError.swift in Sources */, diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings b/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings index 831d4fa7405..53c0934e9a6 100644 --- a/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings +++ b/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings @@ -122,6 +122,9 @@ /* iDEAL bank section title for iDEAL form entry. */ "iDEAL Bank" = "iDEAL Bank"; +/* Title shown above a view containing the customer's payment methods that they can delete or update */ +"Manage payment methods" = "Manage payment methods"; + /* Title shown above a carousel containing the customer's payment methods */ "Manage your payment methods" = "Manage your payment methods"; diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift b/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift index 45cd2b8971a..47cb0bb61ec 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift @@ -254,4 +254,11 @@ extension String.Localized { "Select your payment method", "Title shown above a carousel containing the customer's payment methods") } + + static var manage_payment_methods: String { + STPLocalizedString( + "Manage payment methods", + "Title shown above a view containing the customer's payment methods that they can delete or update" + ) + } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift index 5728ae9f5fe..9972a085907 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift @@ -324,7 +324,7 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { self.navigationBar.additionalButton.removeTarget( self, action: #selector(didSelectEditSavedPaymentMethodsButton), for: .touchUpInside) - return shouldShowPaymentMethodCarousel ? .back : .close(showAdditionalButton: false) + return shouldShowPaymentMethodCarousel ? .back(showAdditionalButton: false) : .close(showAdditionalButton: false) } }()) } @@ -664,7 +664,6 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { // MARK: Helpers func configureEditSavedPaymentMethodsButton() { if savedPaymentOptionsViewController.isRemovingPaymentMethods { - navigationBar.additionalButton.setTitle(UIButton.doneButtonTitle, for: .normal) UIView.animate(withDuration: PaymentSheetUI.defaultAnimationDuration) { self.actionButton.setHiddenIfNecessary(true) self.updateBottomNotice() @@ -675,10 +674,8 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { self.actionButton.setHiddenIfNecessary(!showActionButton) self.updateBottomNotice() } - navigationBar.additionalButton.setTitle(UIButton.editButtonTitle, for: .normal) } - navigationBar.additionalButton.accessibilityIdentifier = "edit_saved_button" - navigationBar.additionalButton.titleLabel?.adjustsFontForContentSizeCategory = true + navigationBar.additionalButton.configureCommonEditButton(isEditingPaymentMethods: savedPaymentOptionsViewController.isRemovingPaymentMethods) navigationBar.additionalButton.addTarget( self, action: #selector(didSelectEditSavedPaymentMethodsButton), for: .touchUpInside) } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift index 9bc3e8d7bad..d068b44acba 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift @@ -402,6 +402,10 @@ extension SavedPaymentMethodCollectionView { } } + override var intrinsicContentSize: CGSize { + return CGSize(width: 20, height: 20) + } + required init(icon: Image, fillColor: UIColor) { imageView = UIImageView(image: icon.makeImage(template: true)) super.init(frame: .zero) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/CheckmarkCircleView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/CheckmarkCircleView.swift deleted file mode 100644 index 7a473d2e958..00000000000 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/CheckmarkCircleView.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// 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 { - - 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() - } -} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/PaymentMethodRowButton.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/PaymentMethodRowButton.swift index ba6864be560..9c8fdfe24d5 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/PaymentMethodRowButton.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/PaymentMethodRowButton.swift @@ -12,41 +12,68 @@ import Foundation import UIKit protocol PaymentMethodRowButtonDelegate: AnyObject { - func didSelectButton(_ button: PaymentMethodRowButton) - // TODO(porter) Add did delete and did update + func didSelectButton(_ button: PaymentMethodRowButton, with paymentMethod: STPPaymentMethod) + func didSelectRemoveButton(_ button: PaymentMethodRowButton, with paymentMethod: STPPaymentMethod) + func didSelectEditButton(_ button: PaymentMethodRowButton, with paymentMethod: STPPaymentMethod) } final class PaymentMethodRowButton: UIView { struct ViewModel { let appearance: PaymentSheet.Appearance - let text: String - let image: UIImage + let paymentMethod: STPPaymentMethod // TODO(porter) Add can remove and can update } + enum State { + case selected + case unselected + case editing + } + // MARK: Internal properties - // TODO(porter) Maybe expand this into an enum of (selected, unselected, editing) state + var state: State = .unselected { + didSet { + previousState = oldValue + + selectionTapGesture.isEnabled = !isEditing + shadowRoundedRect.isSelected = isSelected + circleView.isHidden = !isSelected + editButton.isHidden = !isEditing // TODO(porter) only show if we can edit + removeButton.isHidden = !isEditing // TOOD(porter) only show if we can remove + } + } + + private(set) var previousState: State = .unselected + var isSelected: Bool { - get { - return shadowRoundedRect.isSelected + switch state { + case .selected: + return true + case .unselected, .editing: + return false } + } - set { - shadowRoundedRect.isSelected = newValue - circleView.alpha = newValue ? 1.0 : 0.0 + var isEditing: Bool { + switch state { + case .selected, .unselected: + return false + case .editing: + return true } } weak var delegate: PaymentMethodRowButtonDelegate? // MARK: Private properties - private let viewModel: ViewModel + private let paymentMethod: STPPaymentMethod + private let appearance: PaymentSheet.Appearance // MARK: Private views private lazy var paymentMethodImageView: UIImageView = { - let imageView = UIImageView(image: viewModel.image) + let imageView = UIImageView(image: paymentMethod.makeSavedPaymentMethodRowImage()) imageView.contentMode = .scaleAspectFit // TODO(porter) Do we want to round the corners? return imageView @@ -54,22 +81,40 @@ final class PaymentMethodRowButton: UIView { private lazy var label: UILabel = { let label = UILabel() - label.text = viewModel.text - label.font = viewModel.appearance.scaledFont(for: viewModel.appearance.font.base.medium, + label.text = paymentMethod.paymentSheetLabel + label.font = appearance.scaledFont(for: 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 + // TODO(porter) Refactor CircleIconView out of SavedPaymentMethodCollectionView once it is deleted + private lazy var circleView: SavedPaymentMethodCollectionView.CircleIconView = { + let circleView = SavedPaymentMethodCollectionView.CircleIconView(icon: .icon_checkmark, + fillColor: appearance.colors.primary) + circleView.isHidden = true return circleView }() + lazy var removeButton: CircularButton = { + let removeButton = CircularButton(style: .remove, iconColor: .white) + removeButton.backgroundColor = appearance.colors.danger + removeButton.isHidden = true + removeButton.addTarget(self, action: #selector(handleRemoveButtonTapped), for: .touchUpInside) + return removeButton + }() + + private lazy var editButton: CircularButton = { + let editButton = CircularButton(style: .edit, iconColor: .white) + editButton.backgroundColor = appearance.colors.icon + editButton.isHidden = true + editButton.addTarget(self, action: #selector(handleEditButtonTapped), for: .touchUpInside) + return editButton + }() + private lazy var stackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [paymentMethodImageView, label, UIView.spacerView, circleView]) + let stackView = UIStackView(arrangedSubviews: [paymentMethodImageView, label, UIView.spacerView, circleView, editButton, removeButton]) stackView.axis = .horizontal stackView.alignment = .center stackView.translatesAutoresizingMaskIntoConstraints = false @@ -78,19 +123,25 @@ final class PaymentMethodRowButton: UIView { bottom: 12, trailing: PaymentSheetUI.defaultPadding) stackView.isLayoutMarginsRelativeArrangement = true - stackView.setCustomSpacing(12, after: paymentMethodImageView) // Hardcoded from figma + stackView.spacing = 12 // Hardcoded from figma + return stackView }() private lazy var shadowRoundedRect: ShadowedRoundedRectangle = { - let shadowRoundedRect = ShadowedRoundedRectangle(appearance: viewModel.appearance) + let shadowRoundedRect = ShadowedRoundedRectangle(appearance: appearance) shadowRoundedRect.translatesAutoresizingMaskIntoConstraints = false shadowRoundedRect.addAndPinSubview(stackView) return shadowRoundedRect }() - init(viewModel: ViewModel) { - self.viewModel = viewModel + private lazy var selectionTapGesture: UITapGestureRecognizer = { + return UITapGestureRecognizer(target: self, action: #selector(handleSelectionTap)) + }() + + init(paymentMethod: STPPaymentMethod, appearance: PaymentSheet.Appearance) { + self.paymentMethod = paymentMethod + self.appearance = appearance super.init(frame: .zero) addAndPinSubview(shadowRoundedRect) @@ -99,8 +150,7 @@ final class PaymentMethodRowButton: UIView { paymentMethodImageView.widthAnchor.constraint(equalToConstant: 25), ]) // TODO(porter) accessibility? - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - addGestureRecognizer(tapGesture) + addGestureRecognizer(selectionTapGesture) } required init?(coder aDecoder: NSCoder) { @@ -108,10 +158,17 @@ final class PaymentMethodRowButton: UIView { } // MARK: Tap handlers - @objc private func handleTap() { - shadowRoundedRect.isSelected = true - circleView.alpha = 1.0 - delegate?.didSelectButton(self) + @objc private func handleSelectionTap() { + state = .selected + delegate?.didSelectButton(self, with: paymentMethod) + } + + @objc private func handleEditButtonTapped() { + delegate?.didSelectEditButton(self, with: paymentMethod) + } + + @objc private func handleRemoveButtonTapped() { + delegate?.didSelectRemoveButton(self, with: paymentMethod) } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift index d1defd75007..2a10ec626b6 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift @@ -21,6 +21,31 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { private let configuration: PaymentSheet.Configuration private let paymentMethods: [STPPaymentMethod] + private var isEditingPaymentMethods: Bool = false { + didSet { + let additionalButtonTitle = isEditingPaymentMethods ? UIButton.doneButtonTitle : UIButton.editButtonTitle + navigationBar.additionalButton.setTitle(additionalButtonTitle, for: .normal) + headerLabel.text = headerText + + // If we are entering edit mode, put all buttons in an edit state, otherwise put back in their previous state + if isEditingPaymentMethods { + paymentMethodRows.forEach { $0.state = .editing } + } else { + paymentMethodRows.forEach { $0.state = $0.previousState } + } + // TODO(porter) Handle case where we delete the selected card + } + } + + private var headerText: String { + if isEditingPaymentMethods { + return .Localized.manage_payment_methods + } + + let nonCardPaymentMethods = paymentMethods.filter({ $0.type != .card }) + return nonCardPaymentMethods.isEmpty ? .Localized.select_card : .Localized.select_payment_method + } + // MARK: Internal properties weak var delegate: VerticalSavedPaymentMethodsViewControllerDelegate? @@ -29,32 +54,33 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { lazy var navigationBar: SheetNavigationBar = { let navBar = SheetNavigationBar(isTestMode: configuration.apiClient.isTestmode, appearance: configuration.appearance) - navBar.setStyle(.back) + // TODO(porter) Only show edit button if we should + navBar.setStyle(.back(showAdditionalButton: true)) navBar.delegate = self + navBar.additionalButton.configureCommonEditButton(isEditingPaymentMethods: isEditingPaymentMethods) + // TODO(porter) Read color from new secondary action color from appearance + navBar.additionalButton.setTitleColor(configuration.appearance.colors.primary, for: .normal) + navBar.additionalButton.setTitleColor(configuration.appearance.colors.primary.disabledColor, for: .disabled) + navBar.additionalButton.addTarget(self, action: #selector(didSelectEditSavedPaymentMethodsButton), for: .touchUpInside) return navBar }() private lazy var headerLabel: UILabel = { let label = PaymentSheetUI.makeHeaderLabel(appearance: configuration.appearance) - let nonCardPaymentMethods = paymentMethods.filter({ $0.type != .card }) - label.text = nonCardPaymentMethods.isEmpty ? .Localized.select_card : .Localized.select_payment_method + label.text = headerText return label }() - private lazy var paymentMethodRows: [(paymentMethod: STPPaymentMethod, button: PaymentMethodRowButton)] = { + private lazy var paymentMethodRows: [PaymentMethodRowButton] = { return paymentMethods.map { paymentMethod in - let button = PaymentMethodRowButton(viewModel: .init(appearance: configuration.appearance, - text: paymentMethod.paymentSheetLabel, - image: paymentMethod.makeSavedPaymentMethodRowImage())) + let button = PaymentMethodRowButton(paymentMethod: paymentMethod, appearance: configuration.appearance) button.delegate = self - return (paymentMethod, button) + return button } }() private lazy var stackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [headerLabel] + paymentMethodRows.map { $0.button }) - stackView.directionalLayoutMargins = PaymentSheetUI.defaultMargins - stackView.isLayoutMarginsRelativeArrangement = true + let stackView = UIStackView(arrangedSubviews: [headerLabel] + paymentMethodRows) stackView.axis = .vertical stackView.spacing = 12 stackView.setCustomSpacing(16, after: headerLabel) @@ -76,8 +102,12 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { view.backgroundColor = configuration.appearance.colors.background configuration.style.configure(self) // TODO(porter) Pipe in selected payment method, default to selecting first for now - paymentMethodRows.first?.button.isSelected = true - view.addAndPinSubviewToSafeArea(stackView, insets: PaymentSheetUI.defaultSheetMargins) + paymentMethodRows.first?.state = .selected + view.addAndPinSubview(stackView, insets: PaymentSheetUI.defaultSheetMargins) + } + + @objc func didSelectEditSavedPaymentMethodsButton() { + isEditingPaymentMethods = !isEditingPaymentMethods } } @@ -113,17 +143,14 @@ extension VerticalSavedPaymentMethodsViewController: SheetNavigationBarDelegate // MARK: - PaymentMethodRowButtonDelegate extension VerticalSavedPaymentMethodsViewController: PaymentMethodRowButtonDelegate { - func didSelectButton(_ button: PaymentMethodRowButton) { - guard let paymentMethod = paymentMethodRows.first(where: { $0.button === button })?.paymentMethod else { - // TODO(porter) Handle error - no matching payment method found - return - } + func didSelectButton(_ button: PaymentMethodRowButton, with paymentMethod: STPPaymentMethod) { // Deselect previous button - paymentMethodRows.first { $0.button != button && $0.button.isSelected }?.button.isSelected = false + paymentMethodRows.first { $0 != button && $0.isSelected }?.state = .unselected - // Disable interaction to prevent double selecting since we will be dismissing soon + // Disable interaction to prevent double selecting or entering edit mode since we will be dismissing soon self.view.isUserInteractionEnabled = false + self.navigationBar.isUserInteractionEnabled = false // Give time for new selected row to show it has been selected before dismissing // Makes UX feel a little nicer @@ -132,4 +159,12 @@ extension VerticalSavedPaymentMethodsViewController: PaymentMethodRowButtonDeleg self?.delegate?.didSelectPaymentMethod(paymentMethod) } } + + func didSelectRemoveButton(_ button: PaymentMethodRowButton, with paymentMethod: STPPaymentMethod) { + print("Remove payment method with id: \(paymentMethod.stripeId)") + } + + func didSelectEditButton(_ button: PaymentMethodRowButton, with paymentMethod: STPPaymentMethod) { + print("Edit payment method with id: \(paymentMethod.stripeId)") + } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/BottomSheet3DS2ViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/BottomSheet3DS2ViewController.swift index ed8331861c3..6a9b3ee887d 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/BottomSheet3DS2ViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/BottomSheet3DS2ViewController.swift @@ -24,7 +24,7 @@ class BottomSheet3DS2ViewController: UIViewController { lazy var navigationBar: SheetNavigationBar = { let navBar = SheetNavigationBar(isTestMode: isTestMode, appearance: appearance) - navBar.setStyle(.back) + navBar.setStyle(.back(showAdditionalButton: false)) navBar.delegate = self return navBar }() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift index 6299f4496f0..e631ca798bf 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift @@ -315,7 +315,7 @@ class PaymentSheetFlowControllerViewController: UIViewController, FlowController self, action: #selector(didSelectEditSavedPaymentMethodsButton), for: .touchUpInside) return savedPaymentOptionsViewController.hasPaymentOptions - ? .back : .close(showAdditionalButton: false) + ? .back(showAdditionalButton: false) : .close(showAdditionalButton: false) } }()) } @@ -594,14 +594,7 @@ extension PaymentSheetFlowControllerViewController: SavedPaymentOptionsViewContr // MARK: Helpers func configureEditSavedPaymentMethodsButton() { - if savedPaymentOptionsViewController.isRemovingPaymentMethods { - navigationBar.additionalButton.setTitle(UIButton.doneButtonTitle, for: .normal) - } else { - navigationBar.additionalButton.setTitle(UIButton.editButtonTitle, for: .normal) - } - navigationBar.additionalButton.accessibilityIdentifier = "edit_saved_button" - navigationBar.additionalButton.titleLabel?.font = configuration.appearance.font.base.medium - navigationBar.additionalButton.titleLabel?.adjustsFontForContentSizeCategory = true + navigationBar.additionalButton.configureCommonEditButton(isEditingPaymentMethods: savedPaymentOptionsViewController.isRemovingPaymentMethods) navigationBar.additionalButton.addTarget( self, action: #selector(didSelectEditSavedPaymentMethodsButton), for: .touchUpInside) } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift index e5300d0c85b..ba58e8af3ce 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift @@ -284,7 +284,7 @@ class PaymentSheetViewController: UIViewController, PaymentSheetViewControllerPr action: #selector(didSelectEditSavedPaymentMethodsButton), for: .touchUpInside ) - return !savedPaymentOptionsViewController.hasPaymentOptions ? .close(showAdditionalButton: false) : .back + return !savedPaymentOptionsViewController.hasPaymentOptions ? .close(showAdditionalButton: false) : .back(showAdditionalButton: false) } }() ) @@ -629,14 +629,11 @@ extension PaymentSheetViewController: SavedPaymentOptionsViewControllerDelegate // MARK: Helpers func configureEditSavedPaymentMethodsButton() { if savedPaymentOptionsViewController.isRemovingPaymentMethods { - navigationBar.additionalButton.setTitle(UIButton.doneButtonTitle, for: .normal) buyButton.update(state: .disabled) } else { buyButton.update(state: buyButtonEnabledForSavedPayments()) - navigationBar.additionalButton.setTitle(UIButton.editButtonTitle, for: .normal) } - navigationBar.additionalButton.accessibilityIdentifier = "edit_saved_button" - navigationBar.additionalButton.titleLabel?.adjustsFontForContentSizeCategory = true + navigationBar.additionalButton.configureCommonEditButton(isEditingPaymentMethods: savedPaymentOptionsViewController.isRemovingPaymentMethods) navigationBar.additionalButton.addTarget( self, action: #selector(didSelectEditSavedPaymentMethodsButton), diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PollingViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PollingViewController.swift index d42e0efad7b..69a5198d54e 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PollingViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PollingViewController.swift @@ -280,7 +280,7 @@ class PollingViewController: UIViewController { self.cancelButton.isHidden = true self.titleLabel.text = .Localized.payment_failed self.instructionLabel.text = .Localized.please_go_back - self.navigationBar.setStyle(.back) + self.navigationBar.setStyle(.back(showAdditionalButton: false)) self.intentPoller.suspendPolling() self.oneSecondTimer?.invalidate() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/UpdateCardViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/UpdateCardViewController.swift index 3ae1c0ff912..d4751d6fcfe 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/UpdateCardViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/UpdateCardViewController.swift @@ -42,7 +42,7 @@ final class UpdateCardViewController: UIViewController { let navBar = SheetNavigationBar(isTestMode: isTestMode, appearance: appearance) navBar.delegate = self - navBar.setStyle(.back) + navBar.setStyle(.back(showAdditionalButton: false)) return navBar }() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SheetNavigationBar.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SheetNavigationBar.swift index 57c1f8202f2..6c7d9c34712 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SheetNavigationBar.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/SheetNavigationBar.swift @@ -132,16 +132,19 @@ class SheetNavigationBar: UIView { // MARK: - enum Style { case close(showAdditionalButton: Bool) - case back + case back(showAdditionalButton: Bool) case none } func setStyle(_ style: Style) { switch style { - case .back: + case .back(let showAdditionalButton): closeButtonLeft.isHidden = true closeButtonRight.isHidden = true - additionalButton.isHidden = true + additionalButton.isHidden = !showAdditionalButton + if showAdditionalButton { + bringSubviewToFront(additionalButton) + } backButton.isHidden = false bringSubviewToFront(backButton) case .close(let showAdditionalButton): @@ -167,3 +170,12 @@ class SheetNavigationBar: UIView { layer.shadowOffset = CGSize(width: 0, height: 2) } } + +extension UIButton { + func configureCommonEditButton(isEditingPaymentMethods: Bool) { + let title = isEditingPaymentMethods ? UIButton.doneButtonTitle : UIButton.editButtonTitle + setTitle(title, for: .normal) + titleLabel?.adjustsFontForContentSizeCategory = true + accessibilityIdentifier = "edit_saved_button" + } +} diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift index e51bcffdeaf..fd22b903464 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift @@ -28,14 +28,19 @@ final class VerticalSavedPaymentMethodsViewControllerSnapshotTests: STPSnapshotT var configuration = PaymentSheet.Configuration() configuration.appearance = appearance let sut = VerticalSavedPaymentMethodsViewController(configuration: configuration, paymentMethods: generatePaymentMethods()) - let testWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: 428, height: 500)) + let bottomSheet = BottomSheetViewController(contentViewController: sut, appearance: appearance, isTestMode: true, didCancelNative3DS2: {}) + bottomSheet.view.autosizeHeight(width: 375) + + let testWindow = UIWindow(frame: CGRect(x: 0, + y: 0, + width: 375, + height: bottomSheet.view.frame.size.height + sut.view.frame.size.height)) testWindow.isHidden = false if darkMode { testWindow.overrideUserInterfaceStyle = .dark } - testWindow.rootViewController = sut - sut.view.autosizeHeight(width: 375) - STPSnapshotVerifyView(sut.view) + testWindow.rootViewController = bottomSheet + STPSnapshotVerifyView(bottomSheet.view) } private func generatePaymentMethods() -> [STPPaymentMethod] { diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsAppearance@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsAppearance@3x.png index d6a0fba1ab1..242eecdb6b0 100644 Binary files a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsAppearance@3x.png and b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsAppearance@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsLightMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsLightMode@3x.png index 8c4cb30eca8..bbbf0174b29 100644 Binary files a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsLightMode@3x.png and b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsLightMode@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentOptionsViewControllerSnapshotTestsDarkMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentOptionsViewControllerSnapshotTestsDarkMode@3x.png index 5285992a44b..1e45b061bd9 100644 Binary files a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentOptionsViewControllerSnapshotTestsDarkMode@3x.png and b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentOptionsViewControllerSnapshotTestsDarkMode@3x.png differ