// // TextEntryField.swift // VDS // // Created by Matt Bruce on 10/3/22. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine public class TextEntryField: TextEntryFieldBase{} open class TextEntryFieldBase: EntryField { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- internal var containerStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .horizontal $0.distribution = .fill $0.spacing = 12 } }() //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @Proxy(\.model.type) public var type: TextEntryFieldType @Proxy(\.model.showSuccess) open var showSuccess: Bool @Proxy(\.model.successText) open var successText: String? @Proxy(\.model.helperTextPlacement) open var helperTextPlacement: HelperTextPlacement private var successLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) } internal var minWidthConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setup() { super.setup() minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 0) minWidthConstraint?.isActive = true stackView.addArrangedSubview(successLabel) stackView.setCustomSpacing(8, after: successLabel) successLabel.textColorConfiguration = primaryColorConfig.eraseToAnyColorable() } open override func getContainer() -> UIView { containerStackView.addArrangedSubview(containerView) return containerStackView } open override func getBackgroundConfig() -> AnyColorable { return TextEntryFieldColorConfiguration().with { $0.enabled.lightColor = VDSFormControlsColor.backgroundOnlight $0.enabled.darkColor = VDSFormControlsColor.backgroundOndark $0.disabled.lightColor = VDSFormControlsColor.backgroundOnlight $0.disabled.darkColor = VDSFormControlsColor.backgroundOndark //error/success doesn't care enabled/disable $0.error.lightColor = VDSColor.feedbackErrorBackgroundOnlight $0.error.darkColor = VDSColor.feedbackErrorBackgroundOndark $0.success.lightColor = VDSColor.feedbackSuccessBackgroundOnlight $0.success.darkColor = VDSColor.feedbackSuccessBackgroundOndark }.eraseToAnyColorable() } open override func getBorderConfig() -> AnyColorable { return TextEntryFieldColorConfiguration().with { $0.enabled.lightColor = VDSFormControlsColor.borderOnlight $0.enabled.darkColor = VDSFormControlsColor.borderOnlight $0.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.disabled.darkColor = VDSColor.interactiveDisabledOndark //error/success doesn't care enabled/disable $0.error.lightColor = VDSColor.feedbackErrorOnlight $0.error.darkColor = VDSColor.feedbackErrorOndark $0.success.lightColor = VDSColor.feedbackSuccessOnlight $0.success.darkColor = VDSColor.feedbackSuccessOndark }.eraseToAnyColorable() } //-------------------------------------------------- // MARK: - State //-------------------------------------------------- open override func updateView(viewModel: ModelType) { super.updateView(viewModel: viewModel) //show error or success if viewModel.showError, let _ = viewModel.errorLabelModel { successLabel.isHidden = true } else if viewModel.showSuccess, let successLabelModel = viewModel.successLabelModel { successLabel.set(with: successLabelModel) successLabel.isHidden = false errorLabel.isHidden = true } else { successLabel.isHidden = true } //set the width constraints if let width = viewModel.width, width > viewModel.type.width { widthConstraint?.constant = width widthConstraint?.isActive = true minWidthConstraint?.isActive = false } else { minWidthConstraint?.constant = viewModel.type.width widthConstraint?.isActive = false minWidthConstraint?.isActive = true } } open override func updateHelperLabel(viewModel: ModelType){ helperLabel.removeFromSuperview() //set the helper label position if let helperLabelModel = viewModel.helperLabelModel { if viewModel.helperTextPlacement == .right { containerStackView.spacing = 12 containerStackView.distribution = .fillEqually containerStackView.addArrangedSubview(helperLabel) } else { containerStackView.spacing = 0 containerStackView.distribution = .fill stackView.addArrangedSubview(helperLabel) } helperLabel.set(with: helperLabelModel) helperLabel.isHidden = false } else { helperLabel.isHidden = true } } internal class TextEntryFieldColorConfiguration: DisabledSurfaceColorable { var success = SurfaceColorConfiguration() var error = SurfaceColorConfiguration() var disabled = SurfaceColorConfiguration() var enabled = SurfaceColorConfiguration() required init(){} func getColor(_ viewModel: ModelType) -> UIColor { //only show error is enabled and showError == true let showErrorColor = !viewModel.disabled && viewModel.showError let showSuccessColor = !viewModel.disabled && viewModel.showSuccess if showErrorColor { return error.getColor(viewModel) } else if showSuccessColor { return success.getColor(viewModel) } else { return getDisabledColor(viewModel) } } } } extension TextEntryFieldType { var width: CGFloat { switch self { case .inlineAction: return 102 case .password: return 62.0 case .creditCard: return 288.0 case .tel: return 176.0 case .date: return 114.0 case .securityCode: return 88.0 default: return 40.0 } } }