import Foundation import UIKit import ComponentFlow import Display import Stripe public final class CreditCardInputComponent: Component { public enum DataType { case cardNumber case expirationDate } public let dataType: DataType public let text: String public let textColor: UIColor public let errorTextColor: UIColor public let placeholder: String public let placeholderColor: UIColor public let updated: (String) -> Void public init( dataType: DataType, text: String, textColor: UIColor, errorTextColor: UIColor, placeholder: String, placeholderColor: UIColor, updated: @escaping (String) -> Void ) { self.dataType = dataType self.text = text self.textColor = textColor self.errorTextColor = errorTextColor self.placeholder = placeholder self.placeholderColor = placeholderColor self.updated = updated } public static func ==(lhs: CreditCardInputComponent, rhs: CreditCardInputComponent) -> Bool { if lhs.dataType != rhs.dataType { return false } if lhs.text != rhs.text { return false } if lhs.textColor != rhs.textColor { return false } if lhs.errorTextColor != rhs.errorTextColor { return false } if lhs.placeholder != rhs.placeholder { return false } if lhs.placeholderColor != rhs.placeholderColor { return false } return true } public final class View: UIView, STPFormTextFieldDelegate, UITextFieldDelegate { private let textField: STPFormTextField private var component: CreditCardInputComponent? private let viewModel: STPPaymentCardTextFieldViewModel override init(frame: CGRect) { self.textField = STPFormTextField(frame: CGRect()) self.viewModel = STPPaymentCardTextFieldViewModel() super.init(frame: frame) self.textField.backgroundColor = .clear self.textField.keyboardType = .phonePad self.textField.formDelegate = self self.textField.validText = true self.addSubview(self.textField) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func textFieldChanged(_ textField: UITextField) { self.component?.updated(self.textField.text ?? "") } public func formTextFieldDidBackspace(onEmpty formTextField: STPFormTextField) { } public func formTextField(_ formTextField: STPFormTextField, modifyIncomingTextChange input: NSAttributedString) -> NSAttributedString { guard let component = self.component else { return input } switch component.dataType { case .cardNumber: self.viewModel.cardNumber = input.string return NSAttributedString(string: self.viewModel.cardNumber ?? "", attributes: self.textField.defaultTextAttributes) case .expirationDate: self.viewModel.rawExpiration = input.string return NSAttributedString(string: self.viewModel.rawExpiration ?? "", attributes: self.textField.defaultTextAttributes) } } public func formTextFieldTextDidChange(_ textField: STPFormTextField) { guard let component = self.component else { return } component.updated(self.textField.text ?? "") let state: STPCardValidationState switch component.dataType { case .cardNumber: state = self.viewModel.validationState(for: .number) case .expirationDate: state = self.viewModel.validationState(for: .expiration) } self.textField.validText = true switch state { case .invalid: self.textField.validText = false case .incomplete: break case .valid: break @unknown default: break } } func update(component: CreditCardInputComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { switch component.dataType { case .cardNumber: self.textField.autoFormattingBehavior = .cardNumbers case .expirationDate: self.textField.autoFormattingBehavior = .expiration } self.textField.font = UIFont.systemFont(ofSize: 17.0) self.textField.defaultColor = component.textColor self.textField.errorColor = .red self.textField.placeholderColor = component.placeholderColor if self.textField.text != component.text { self.textField.text = component.text } self.textField.attributedPlaceholder = NSAttributedString(string: component.placeholder, font: self.textField.font, textColor: component.placeholderColor) let size = CGSize(width: availableSize.width, height: 44.0) transition.setFrame(view: self.textField, frame: CGRect(origin: CGPoint(), size: size), completion: nil) self.component = component return size } } public func makeView() -> View { return View(frame: CGRect()) } public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } }