diff --git a/VDSSample/ViewControllers/IconViewController.swift b/VDSSample/ViewControllers/IconViewController.swift index c2d4433..5d6c08c 100644 --- a/VDSSample/ViewControllers/IconViewController.swift +++ b/VDSSample/ViewControllers/IconViewController.swift @@ -57,14 +57,12 @@ class IconViewController: BaseViewController { func setupModel() { let name = Icon.Name.accessibility - let color = UIColor.VDSColor.paletteBlack - component.color = color.uiColor component.name = name //setup UI surfacePickerSelectorView.text = component.surface.rawValue sizePickerSelectorView.text = component.size.rawValue - colorPickerSelectorView.text = color.rawValue + colorPickerSelectorView.text = UIColor.VDSColor.paletteBlack.rawValue namePickerSelectorView.text = name.rawValue } func setupPicker(){ diff --git a/VDSSample/ViewControllers/InputFieldViewController.swift b/VDSSample/ViewControllers/InputFieldViewController.swift index 9ca4875..881870e 100644 --- a/VDSSample/ViewControllers/InputFieldViewController.swift +++ b/VDSSample/ViewControllers/InputFieldViewController.swift @@ -261,8 +261,9 @@ class InputFieldViewController: BaseViewController { } inputTypePickerSelectorView.onPickerDidSelect = { [weak self] item in - self?.component.fieldType = item + let _ = self?.component.resignFirstResponder() self?.component.text = "" + self?.component.fieldType = item self?.updateFormSections() } @@ -319,7 +320,6 @@ extension InputFieldViewController: ComponentSampleable { static func makeSample() -> ComponentSample { let component = Self.makeComponent() component.fieldType = .text - component.width = 328 component.labelText = "Street Address" component.helperText = "For example: 123 Verizon St" component.errorText = "Enter a valid address." @@ -328,3 +328,425 @@ extension InputFieldViewController: ComponentSampleable { return ComponentSample(component: component, trailingPinningType: .lessThanOrEqual) } } + + + +import VDSTokens +import Combine + +/// Base Class used to build out a Input controls. +open class TEntryFieldBase: Control, Changeable, FormFieldInternalValidatable { + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + required public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + //-------------------------------------------------- + // MARK: - Enums + //-------------------------------------------------- + /// Enum used to describe the position of the helper text. + public enum HelperTextPlacement: String, CaseIterable { + case bottom, right + } + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var primaryStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fill + $0.alignment = .leading + } + }() + + /// This is the veritcal stack view that has 2 rows, the containerView and the return view + /// of the getBottomContainer() method, by default returns the bottomContainerStackView. + internal let secondaryStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fill + } + + /// This is the view that will be wrapped with the border for userInteraction. + /// The only subview of this view is the fieldStackView + internal var containerView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + }() + + /// This is a horizontal Stack View that is placed inside the containterView (bordered view) + /// The first arrangedView will be the view from getFieldContainer() + /// The second view is the statusIcon. + internal var fieldStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.distribution = .fill + $0.alignment = .top + } + }() + + /// This is a vertical stack used for the errorLabel and helperLabel. + internal var bottomContainerStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fill + $0.spacing = VDSLayout.space2X + } + }() + + open var rules = [AnyRule]() + + //-------------------------------------------------- + // MARK: - Configuration Properties + //-------------------------------------------------- + // Sizes are from InVision design specs. + internal var maxWidth: CGFloat { frame.size.width } + internal var minWidth: CGFloat { containerSize.width } + internal var containerSize: CGSize { CGSize(width: minWidth, height: 44) } + + internal let primaryColorConfiguration = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) + } + + internal let secondaryColorConfiguration = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false) + } + + internal var backgroundColorConfiguration = ControlColorConfiguration().with { + $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .normal) + $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .disabled) + $0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: .error) + $0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: [.error, .focused]) + } + + internal var borderColorConfiguration = ControlColorConfiguration().with { + $0.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOndark, forState: .normal) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forState: .focused) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forState: [.focused, .error]) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) + $0.setSurfaceColors(VDSColor.feedbackErrorOnlight, VDSColor.feedbackErrorOndark, forState: .error) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.disabled,.error]) + } + + internal let iconColorConfiguration = ControlColorConfiguration().with { + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .error) + } + + internal var readOnlyBorderColorConfiguration = ControlColorConfiguration().with { + $0.setSurfaceColors(VDSFormControlsColor.borderReadonlyOnlight, VDSFormControlsColor.borderReadonlyOndark, forState: .normal) + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var onChangeSubscriber: AnyCancellable? + + open var titleLabel = Label().with { + $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.textStyle = .bodySmall + } + + open var errorLabel = Label().with { + $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.textStyle = .bodySmall + $0.accessibilityValue = "error" + } + + open var helperLabel = Label().with { + $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.textStyle = .bodySmall + } + + open var statusIcon: Icon = Icon().with { + $0.size = .medium + } + + open var labelText: String? { didSet { setNeedsUpdate() } } + + open var helperText: String? { didSet { setNeedsUpdate() } } + + /// Whether not to show the error. + open var showError: Bool = false { didSet { setNeedsUpdate() } } + + /// FormFieldValidator + open var validator: (any FormFieldValidatorable)? + + /// Override UIControl state to add the .error state if showError is true. + open override var state: UIControl.State { + get { + var state = super.state + if showError || hasInternalError { + state.insert(.error) + } + return state + } + } + + open var errorText: String? { didSet { setNeedsUpdate() } } + + open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } } + + open var transparentBackground: Bool = false { didSet { setNeedsUpdate() } } + + open var width: CGFloat? { didSet { setNeedsUpdate() } } + + open var inputId: String? { didSet { setNeedsUpdate() } } + + /// The text of this textField. + open var value: String? { + get { fatalError("must be read from subclass")} + } + + open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } } + + open var isRequired: Bool = false { didSet { setNeedsUpdate() } } + + open var isReadOnly: Bool = false { didSet { setNeedsUpdate() } } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + internal var heightConstraint: NSLayoutConstraint? + internal var widthConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. + open override func setup() { + super.setup() + + isAccessibilityElement = false + addSubview(primaryStackView) + + //create the wrapping view + heightConstraint = containerView.heightGreaterThanEqualTo(constant: containerSize.height) + widthConstraint = containerView.width(constant: frame.size.width) + + secondaryStackView.addArrangedSubview(containerView) + secondaryStackView.setCustomSpacing(8, after: containerView) + + //add ContainerStackView + //this is the horizontal stack that contains + //the left, InputContainer, Icons, Buttons + containerView.addSubview(fieldStackView) + fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X)) + + let fieldContainerView = getFieldContainer() + fieldContainerView.translatesAutoresizingMaskIntoConstraints = false + + //add the view to add input fields + fieldStackView.addArrangedSubview(fieldContainerView) + fieldStackView.addArrangedSubview(statusIcon) + fieldStackView.setCustomSpacing(VDSLayout.space3X, after: fieldContainerView) + + //get the container this is what show helper text, error text + //can include other for character count, max length + let bottomContainer = getBottomContainer() + + //this is the vertical stack that contains error text, helper text + bottomContainerStackView.addArrangedSubview(errorLabel) + bottomContainerStackView.addArrangedSubview(helperLabel) + + primaryStackView.addArrangedSubview(titleLabel) + primaryStackView.addArrangedSubview(secondaryStackView) + secondaryStackView.addArrangedSubview(bottomContainer) + + primaryStackView.setCustomSpacing(4, after: titleLabel) + + primaryStackView + .pinTop() + .pinLeading() + .pinTrailing(0, .defaultHigh) + .pinBottom(0, .defaultHigh) + + titleLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() + errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() + helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable() + } + + /// Resets to default settings. + open override func reset() { + super.reset() + titleLabel.reset() + errorLabel.reset() + helperLabel.reset() + + titleLabel.textStyle = .bodySmall + errorLabel.textStyle = .bodySmall + helperLabel.textStyle = .bodySmall + + labelText = nil + helperText = nil + showError = false + errorText = nil + tooltipModel = nil + transparentBackground = false + width = nil + inputId = nil + defaultValue = nil + isRequired = false + isReadOnly = false + onChange = nil + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + super.updateView() + updateContainerView() + updateTitleLabel() + updateErrorLabel() + updateHelperLabel() + updateContainerWidth() + } + + open func validate(){ + updateRules() + validator = FormFieldValidator(field: self, rules: rules) + validator?.validate() + setNeedsUpdate() + } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + internal func updateContainerView() { + containerView.backgroundColor = backgroundColorConfiguration.getColor(self) + containerView.layer.borderColor = isReadOnly ? readOnlyBorderColorConfiguration.getColor(self).cgColor : borderColorConfiguration.getColor(self).cgColor + containerView.layer.borderWidth = VDSFormControls.borderWidth + containerView.layer.cornerRadius = VDSFormControls.borderRadius + } + + //-------------------------------------------------- + // MARK: - Public Methods + //-------------------------------------------------- + open func updateContainerWidth() { + if let width, width > minWidth && width < maxWidth { + widthConstraint?.constant = width + } else { + widthConstraint?.constant = maxWidth >= minWidth ? maxWidth : minWidth + } + widthConstraint?.activate() + } + + /// Container for the area in which the user interacts. + open func getFieldContainer() -> UIView { + fatalError("Subclass must return the view that contains the field/view the user will interact with.") + } + + /// Container for the area in which helper or error text presents. + open func getBottomContainer() -> UIView { + return bottomContainerStackView + } + + internal func updateRules() { + rules.removeAll() + if self.isRequired { + let rule = RequiredRule() + if let errorText, !errorText.isEmpty { + rule.errorMessage = errorText + } else if let labelText{ + rule.errorMessage = "You must enter a \(labelText)" + } else { + rule.errorMessage = "You must enter a value" + } + rules.append(.init(rule)) + } + } + + open func updateTitleLabel() { + + //update the local vars for the label since we no + //long have a model + var attributes: [any LabelAttributeModel] = [] + var updatedLabelText = labelText + + //dealing with the "Optional" addition to the text + if let oldText = updatedLabelText, !isRequired, !oldText.hasSuffix("Optional") { + if isEnabled { + let optionColorAttr = ColorLabelAttribute(location: oldText.count + 2, + length: 8, + color: VDSColor.elementsSecondaryOnlight) + + attributes.append(optionColorAttr) + } + updatedLabelText = "\(oldText) Optional" + } + + if let tooltipModel { + attributes.append(TooltipLabelAttribute(surface: surface, model: tooltipModel, presenter: self)) + } + + //set the titleLabel + titleLabel.text = updatedLabelText + titleLabel.attributes = attributes + titleLabel.surface = surface + titleLabel.isEnabled = isEnabled + } + + open func updateErrorLabel(){ + if showError, let errorText { + errorLabel.text = errorText + errorLabel.surface = surface + errorLabel.isEnabled = isEnabled + errorLabel.isHidden = false + statusIcon.name = .error + statusIcon.surface = surface + statusIcon.isHidden = !isEnabled || state.contains(.focused) + } else if hasInternalError, let internalErrorText { + errorLabel.text = internalErrorText + errorLabel.surface = surface + errorLabel.isEnabled = isEnabled + errorLabel.isHidden = false + statusIcon.name = .error + statusIcon.surface = surface + statusIcon.isHidden = !isEnabled || state.contains(.focused) + } else { + statusIcon.isHidden = true + errorLabel.isHidden = true + } + statusIcon.color = iconColorConfiguration.getColor(self) + } + + open func updateHelperLabel(){ + //set the helper label position + if let helperText { + helperLabel.text = helperText + helperLabel.surface = surface + helperLabel.isEnabled = isEnabled + helperLabel.isHidden = false + } else { + helperLabel.isHidden = true + } + } +} + +class RequiredRule: Rule { + var maxLength: Int? + var errorMessage: String = "This field is required." + + func isValid(value: String?) -> Bool { + guard let value, !value.isEmpty, value.count > 0 else { return false } + return true + } +} diff --git a/VDSSample/ViewControllers/MenuViewController.swift b/VDSSample/ViewControllers/MenuViewController.swift index 90cb912..6854a79 100644 --- a/VDSSample/ViewControllers/MenuViewController.swift +++ b/VDSSample/ViewControllers/MenuViewController.swift @@ -66,6 +66,8 @@ class MenuCell: UITableViewCell { class MenuViewController: UITableViewController, TooltipLaunchable { static let items: [MenuComponent] = [ + MenuComponent(title: "Width", completed: true, viewController: WidthViewController.self), + MenuComponent(title: "CircleIcon", completed: true, viewController: CircleIconViewController.self), MenuComponent(title: "Test", completed: true, viewController: TestViewController.self), MenuComponent(title: "DropShadow Tester", completed: true, viewController: DropShadowViewController.self), MenuComponent(title: "TableView Tester", completed: true, viewController: TableViewTestController.self), diff --git a/VDSSample/ViewControllers/Test.swift b/VDSSample/ViewControllers/Test.swift index 167d991..1849c5c 100644 --- a/VDSSample/ViewControllers/Test.swift +++ b/VDSSample/ViewControllers/Test.swift @@ -27,6 +27,7 @@ class TestViewController: BaseViewController { var helperTextField = TextField() var showErrorSwitch = Toggle() var showSuccessSwitch = Toggle() + var widthTextField = NumericField() override func viewDidLoad() { super.viewDidLoad() @@ -45,6 +46,7 @@ class TestViewController: BaseViewController { } general.addFormRow(label: "Surface", view: surfacePickerSelectorView) + general.addFormRow(label: "Width", view: widthTextField) general.addFormRow(label: "Label Text", view: labelTextField) general.addFormRow(label: "Helper Text Placement", view: helperTextPositionPickerSelectorView) general.addFormRow(label: "Helper Text", view: helperTextField) @@ -89,6 +91,11 @@ class TestViewController: BaseViewController { self?.component.errorText = text }.store(in: &subscribers) + widthTextField + .numberPublisher + .sink { [weak self] number in + self?.component.width = number?.cgFloatValue + }.store(in: &subscribers) } @@ -203,13 +210,18 @@ open class EntryFieldBase2: Control, Changeable, FormFieldInternalValidatable { /// This is set by a local method. internal var bottomContainerView: UIView! + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + internal var widthConstraint: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- // Sizes are from InVision design specs. internal var maxWidth: CGFloat { frame.size.width } internal var minWidth: CGFloat { containerSize.width } - internal var containerSize: CGSize { CGSize(width: minWidth, height: 44) } + internal var containerSize: CGSize { CGSize(width: 150, height: 44) } internal let primaryColorConfiguration = ViewColorConfiguration().with { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) @@ -329,12 +341,25 @@ open class EntryFieldBase2: Control, Changeable, FormFieldInternalValidatable { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - // Configure Labels - titleLabel.translatesAutoresizingMaskIntoConstraints = false - + // Add mainStackView to the view + addSubview(mainStackView) + + let layoutGuide = UILayoutGuide() + addLayoutGuide(layoutGuide) + layoutGuide + .pinTop() + .pinLeading() + .pinBottom() + .pinTrailingLessThanOrEqualTo(anchor: trailingAnchor, constant: 0, priority: .defaultHigh) + + addSubview(mainStackView) + mainStackView.pin(layoutGuide) + + widthConstraint = layoutGuide.width(constant: containerSize.width).deactivate() + //add ContainerStackView //this is the horizontal stack that contains - //the left, InputContainer, Icons, Buttons + //InputContainer, Icons, Buttons containerView.addSubview(fieldStackView) fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X)) @@ -361,12 +386,7 @@ open class EntryFieldBase2: Control, Changeable, FormFieldInternalValidatable { // Add arranged subviews to mainStackView mainStackView.addArrangedSubview(titleLabel) mainStackView.addArrangedSubview(contentStackView) - - // Add mainStackView to the view - addSubview(mainStackView) - - mainStackView.pinToSuperView() - + // Initial position of the helper label updateHelperTextPosition() @@ -386,7 +406,8 @@ open class EntryFieldBase2: Control, Changeable, FormFieldInternalValidatable { updateTitleLabel() updateErrorLabel() updateHelperLabel() - + updateContainerWidth() + let imageName = InputField.CreditCardType.generic.imageName(surface: surface) creditCardImageView.image = BundleManager.shared.image(for: imageName) } @@ -421,7 +442,7 @@ open class EntryFieldBase2: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- /// Container for the area in which the user interacts. open func getFieldContainer() -> UIView { - return 2textField + return textField //fatalError("Subclass must return the view that contains the field/view the user will interact with.") } @@ -503,6 +524,15 @@ open class EntryFieldBase2: Control, Changeable, FormFieldInternalValidatable { } } + open func updateContainerWidth() { + if let width, width >= minWidth, width <= maxWidth, helperTextPlacement != .right { + widthConstraint?.constant = width + widthConstraint?.activate() + } else { + widthConstraint?.deactivate() + } + } + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- @@ -585,3 +615,236 @@ extension UIStackView { arrangedSubviews.forEach { removeArrangedSubview($0) } } } + +class WidthView: View { + + let stackView = UIStackView() + let customView = UIView() + var customViewWidthConstraint: NSLayoutConstraint! + + override func setup() { + super.setup() + + setupStackView() + setupCustomView() + } + + func setupStackView() { + stackView.axis = .vertical + stackView.distribution = .fillProportionally + stackView.alignment = .fill + stackView.translatesAutoresizingMaskIntoConstraints = false + + addSubview(stackView) + stackView.pinToSuperView() + } + + func setupCustomView() { + customView.translatesAutoresizingMaskIntoConstraints = false + customView.backgroundColor = .red + + let otherView = View() + otherView.backgroundColor = .green + + stackView.addArrangedSubview(otherView) + otherView.addSubview(customView) + customView.pinTop().pinBottom().pinLeading().pinTrailingLessThanOrEqualTo() + + // Add width constraint with lower priority + customViewWidthConstraint = customView.widthAnchor.constraint(equalToConstant: 60) + customViewWidthConstraint.priority = UILayoutPriority(999) + // Initially activate or deactivate based on your requirement + customViewWidthConstraint.isActive = false + } + + func setCustomWidth(to width: CGFloat) { + customViewWidthConstraint.constant = width + customViewWidthConstraint.isActive = true + layoutIfNeeded() + } + + func resetWidthToFill() { + customViewWidthConstraint.isActive = false + layoutIfNeeded() + } +} + +class WidthViewController: BaseViewController { + var widthTextField = NumericField() + + override func viewDidLoad() { + super.viewDidLoad() + addContentTopView(view: component) + component.height(150) + } + + override func setupForm(){ + super.setupForm() + + addFormRow(label: "Width", view: widthTextField) + + widthTextField + .numberPublisher + .sink { [weak self] number in + if let width = number?.cgFloatValue, width > 50, width < 250 { + self?.component.setCustomWidth(to: width) + } else { + self?.component.resetWidthToFill() + } + }.store(in: &subscribers) + } +} + + +class CircleIconViewController: BaseViewController { + + lazy var usePickerSelectorView = { + PickerSelectorView(title: "", + picker: self.picker, + items: [.primary, .secondary]) + }() + + lazy var colorPickerSelectorView = { + PickerSelectorView(title: "", + picker: self.picker, + items: UIColor.VDSColor.allCases) + }() + + lazy var namePickerSelectorView = { + PickerSelectorView(title: "", + picker: self.picker, + items: Icon.Name.all.sorted{ $0.rawValue < $1.rawValue }) + }() + + lazy var sizePickerSelectorView = { + PickerSelectorView(title: "", + picker: self.picker, + items: Icon.Size.allCases) + }() + + override func viewDidLoad() { + super.viewDidLoad() + addContentTopView(view: .makeWrapper(for: component)) + setupPicker() + setupModel() + } + + override func setupForm(){ + super.setupForm() + addFormRow(label: "Surface", view: surfacePickerSelectorView) + addFormRow(label: "Use", view: usePickerSelectorView) + addFormRow(label: "Name", view: namePickerSelectorView) + addFormRow(label: "Size", view: sizePickerSelectorView) + } + + func setupModel() { + //setup UI + surfacePickerSelectorView.text = component.surface.rawValue + sizePickerSelectorView.text = component.iconModel.size.rawValue + namePickerSelectorView.text = component.iconModel.name.rawValue + usePickerSelectorView.text = component.use.rawValue + } + + func setupPicker(){ + + surfacePickerSelectorView.onPickerDidSelect = { [weak self] item in + self?.component.surface = item + self?.contentTopView.backgroundColor = item.color + } + + sizePickerSelectorView.onPickerDidSelect = { [weak self] item in + self?.updateIconModel() + } + + usePickerSelectorView.onPickerDidSelect = { [weak self] item in + self?.component.use = item + } + + namePickerSelectorView.onPickerDidSelect = { [weak self] item in + self?.updateIconModel() + } + } + + func updateIconModel() { + let model = CircleIcon.IconModel(name: namePickerSelectorView.selectedItem, size: sizePickerSelectorView.selectedItem) + component.iconModel = model + } +} + +open class CircleIcon: View { + + open var use: Use = .primary { didSet { setNeedsUpdate() } } + + open var icon = Icon() + + open var iconModel = IconModel() { didSet { setNeedsUpdate() } } + + private var contentView = View() + + open override func setup() { + super.setup() + addSubview(contentView) + + contentView.addSubview(icon) + + icon.pinToSuperView(.uniform(VDSLayout.space1X)) + .pinCenterX() + .pinCenterY() + + contentView.pinTop() + contentView.pinLeading() + contentView.pinTrailingLessThanOrEqualTo() + contentView.pinBottomLessThanOrEqualTo() + + } + + open override var intrinsicContentSize: CGSize { contentView.frame.size } + + private let primaryIconColor = SurfaceColorConfiguration(VDSColor.paletteWhite, VDSColor.paletteWhite) + private let primaryBackgroundColor = SurfaceColorConfiguration(VDSColor.paletteRed, VDSColor.paletteRed) + + private let secondaryIconColor = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite) + private let secondaryBackgroundColor = SurfaceColorConfiguration(VDSColor.paletteGray65, VDSColor.paletteGray44) + + private var iconColor: UIColor { + use == .primary + ? primaryIconColor.getColor(surface) + : secondaryIconColor.getColor(surface) + } + + private var layerBackgroundColor: UIColor { + use == .primary + ? primaryBackgroundColor.getColor(surface) + : secondaryBackgroundColor.getColor(surface) + } + + open override func updateView() { + super.updateView() + icon.surface = .dark + icon.name = iconModel.name + icon.size = iconModel.size + icon.color = iconColor + contentView.layer.backgroundColor = layerBackgroundColor.cgColor + } + + open override func layoutSubviews() { + super.layoutSubviews() + contentView.layer.cornerRadius = min(bounds.width, bounds.height) / 2 + contentView.layer.masksToBounds = true + } + + /// Model that represents the options available for the descriptive icon. + public struct IconModel { + /// A representation that will be used to render the icon with corresponding name. + public var name: Icon.Name + + /// Enum for a preset height and width for the icon. + public var size: Icon.Size + + public init(name: Icon.Name = .threedAd, size: Icon.Size = .XLarge) { + self.name = name + self.size = size + } + } +} +