Skip to content

Commit

Permalink
Misc. update fixes (#4177)
Browse files Browse the repository at this point in the history
## Summary
- [Don't fade in/out embedded if there was no height
change](dfdf88b)
- [Disable embedded and playground while updating and loading,
respectively](7c9831b)


## Testing
Add test for the disable functionality.

## Changelog
Not user facing
  • Loading branch information
yuki-stripe authored Oct 24, 2024
1 parent 5ae6f6f commit c8f86df
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ import Foundation
import UIKit

class EmbeddedPlaygroundViewController: UIViewController {
var isLoading: Bool = false {
didSet {
if isLoading {
view.bringSubviewToFront(loadingIndicator)
loadingIndicator.startAnimating()
view.isUserInteractionEnabled = false
} else {
loadingIndicator.stopAnimating()
view.isUserInteractionEnabled = true
}
}
}
private let appearance: PaymentSheet.Appearance

private let configuration: EmbeddedPaymentElement.Configuration
Expand Down Expand Up @@ -119,7 +131,7 @@ class EmbeddedPlaygroundViewController: UIViewController {
scrollView.contentLayoutGuide.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
scrollView.contentLayoutGuide.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
scrollView.contentLayoutGuide.widthAnchor.constraint(equalTo: view.widthAnchor),
checkoutButton.heightAnchor.constraint(equalToConstant: 45)
checkoutButton.heightAnchor.constraint(equalToConstant: 45),
])
paymentOptionView.configure(with: embeddedPaymentElement.paymentOption, showMandate: !configuration.embeddedViewDisplaysMandateText)
}
Expand Down Expand Up @@ -226,7 +238,7 @@ private class EmbeddedPaymentOptionView: UIView {
verticalStackView.topAnchor.constraint(equalTo: self.topAnchor, constant: 15),
verticalStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -15),
imageView.widthAnchor.constraint(equalToConstant: 25),
imageView.heightAnchor.constraint(equalToConstant: 25)
imageView.heightAnchor.constraint(equalToConstant: 25),
])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ extension PlaygroundController {
addressViewController = nil
paymentSheet = nil
lastPaymentResult = nil
embeddedPlaygroundViewController?.isLoading = true
isLoading = true
let settingsToLoad = self.settings

Expand Down Expand Up @@ -729,7 +730,8 @@ extension PlaygroundController {
}
case .embedded:
guard !shouldUpdateEmbeddedInsteadOfRecreating else {
// Update embedded rather than re-creating it
// Update embedded rather than re-creating it
self.embeddedPlaygroundViewController?.isLoading = false
self.updateEmbedded()
self.currentlyRenderedSettings = self.settings
return
Expand Down Expand Up @@ -827,7 +829,7 @@ extension PlaygroundController {
else {
if let data = data,
(response as? HTTPURLResponse)?.statusCode == 400 {
let errorMessage = String(decoding: data, as: UTF8.self)
let errorMessage = String(data: data, encoding: .utf8)!
// read the error message
intentCreationCallback(.failure(ConfirmHandlerError.confirmError(errorMessage)))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public final class EmbeddedPaymentElement {
public func update(
intentConfiguration: IntentConfiguration
) async -> UpdateResult {
embeddedPaymentMethodsView.isUserInteractionEnabled = false
// Cancel the old task and let it finish so that merchants receive update results in order
currentUpdateTask?.cancel()
_ = await currentUpdateTask?.value
Expand Down Expand Up @@ -158,7 +159,9 @@ public final class EmbeddedPaymentElement {
return .succeeded
}
self.currentUpdateTask = currentUpdateTask
return await currentUpdateTask.value
let updateResult = await currentUpdateTask.value
embeddedPaymentMethodsView.isUserInteractionEnabled = true
return updateResult
}

/// Completes the payment or setup.
Expand Down Expand Up @@ -237,7 +240,7 @@ public final class EmbeddedPaymentElement {

self.analyticsHelper = analyticsHelper
analyticsHelper.logInitialized()
self.containerView.updateSuperviewHeight = { [weak self] in
self.containerView.needsUpdateSuperviewHeight = { [weak self] in
guard let self else { return }
self.delegate?.embeddedPaymentElementDidUpdateHeight(embeddedPaymentElement: self)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import UIKit

/// The view that's vended to the merchant, containing the embedded view. We use this to be able to swap out the embedded view with an animation when `update` is called.
class EmbeddedPaymentElementContainerView: UIView {
var updateSuperviewHeight: () -> Void = {}
private var view: EmbeddedPaymentMethodsView
var needsUpdateSuperviewHeight: () -> Void = {}
private var contentView: EmbeddedPaymentMethodsView
private var bottomAnchorConstraint: NSLayoutConstraint!

init(embeddedPaymentMethodsView: EmbeddedPaymentMethodsView) {
self.view = embeddedPaymentMethodsView
self.contentView = embeddedPaymentMethodsView
super.init(frame: .zero)
addInitialView(view)
addInitialView(contentView)
}

required init?(coder: NSCoder) {
Expand All @@ -37,42 +37,50 @@ class EmbeddedPaymentElementContainerView: UIView {
func updateEmbeddedPaymentMethodsView(_ embeddedPaymentMethodsView: EmbeddedPaymentMethodsView) {
guard frame.size != .zero else {
// A zero frame means we haven't been laid out yet. Simply replace the old view to avoid laying out before the view is ready and breaking constraints.
self.view.removeFromSuperview()
self.view = embeddedPaymentMethodsView
contentView.removeFromSuperview()
contentView = embeddedPaymentMethodsView
addInitialView(embeddedPaymentMethodsView)
return
}
let oldView = view
let oldViewHeight = frame.height
// Add the new view w/ 0 alpha
let oldContentView = contentView

// Add the new view
embeddedPaymentMethodsView.translatesAutoresizingMaskIntoConstraints = false
addSubview(embeddedPaymentMethodsView)
view = embeddedPaymentMethodsView
embeddedPaymentMethodsView.alpha = 0
contentView = embeddedPaymentMethodsView
NSLayoutConstraint.activate([
embeddedPaymentMethodsView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
embeddedPaymentMethodsView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
embeddedPaymentMethodsView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
// Omit the bottom anchor so that the height is still fixed to the old view height
])
// important that the view is already laid out before the animation block so that it doesn't animate from zero size.

// Lay the new view out before the animation block so that it doesn't animate from zero size.
layoutIfNeeded()

// Calculate heights of old and new content views to determine if height will change
let oldContentViewHeight = oldContentView.frame.size.height
let newContentViewHeight = embeddedPaymentMethodsView.frame.size.height
let heightWillChange = oldContentViewHeight != newContentViewHeight

// Fade the old view out and the new view in if the height will change
if heightWillChange {
embeddedPaymentMethodsView.alpha = 0
}
UIView.animate(withDuration: 0.2) {
// Re-pin bottom anchor to the new view, thus updating our height
self.bottomAnchorConstraint.isActive = false
self.bottomAnchorConstraint = embeddedPaymentMethodsView.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor)
self.bottomAnchorConstraint.isActive = true
self.layoutIfNeeded()
// Fade old view out and new view in
oldView.alpha = 0
embeddedPaymentMethodsView.alpha = 1
if oldViewHeight != self.systemLayoutSizeFitting(.zero).height {
// Invoke EmbeddedPaymentElement delegate method so that height does not jump
self.updateSuperviewHeight()
if heightWillChange {
oldContentView.alpha = 0
embeddedPaymentMethodsView.alpha = 1
// Invoke EmbeddedPaymentElement delegate method so that height of our superview does not jump
self.needsUpdateSuperviewHeight()
}
} completion: { _ in
oldView.removeFromSuperview()
oldContentView.removeFromSuperview()
}
}
}

0 comments on commit c8f86df

Please sign in to comment.