From 10c55b12be709d291386ee24c9268308480442ea Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 3 Jul 2024 15:58:15 -0500 Subject: [PATCH] converted to VDS Signed-off-by: Matt Bruce --- .../Atomic/Atoms/Selectors/RadioButton.swift | 238 +++++++++--------- .../Atoms/Selectors/RadioButtonModel.swift | 69 ++--- 2 files changed, 131 insertions(+), 176 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift index e73a8d5c..9c0d9e46 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift @@ -7,37 +7,35 @@ // import UIKit -import VDSCoreTokens +import VDS -@objcMembers open class RadioButton: Control, MFButtonProtocol { - //-------------------------------------------------- +@objcMembers open class RadioButton: VDS.RadioButton, RadioButtonSelectionHelperProtocol, VDSMoleculeViewProtocol, MFButtonProtocol, MVMCoreUIViewConstrainingProtocol { + //------------------------------------------------------ // MARK: - Properties - //-------------------------------------------------- - - public var diameter: CGFloat = 20 { - didSet { widthConstraint?.constant = diameter } + //------------------------------------------------------ + open var viewModel: RadioButtonModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + + open var radioButtonModel: RadioButtonModel { + viewModel } - @objc public override var isSelected: Bool { + // Form Validation + var fieldKey: String? + var fieldValue: JSONValue? + var groupName: String? + + open override var isSelected: Bool { didSet { - radioModel?.state = isSelected - updateAccessibilityLabel() + viewModel.state = isSelected + if oldValue != isSelected { + sendActions(for: .valueChanged) + } } } - public var enabledColor: UIColor { - return radioModel?.inverted ?? false ? VDSColor.elementsPrimaryOndark : VDSColor.elementsPrimaryOnlight - } - public var disabledColor: UIColor { - return radioModel?.inverted ?? false ? VDSColor.interactiveDisabledOndark : VDSColor.interactiveDisabledOnlight - } - public var delegateObject: MVMCoreUIDelegateObject? - var additionalData: [AnyHashable: Any]? - - public var radioModel: RadioButtonModel? { - model as? RadioButtonModel - } - - lazy public var radioGroupName: String? = { radioModel?.fieldKey }() + + lazy public var radioGroupName: String? = { viewModel.fieldKey }() lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = { @@ -48,132 +46,120 @@ import VDSCoreTokens return radioButtonModel }() - public override var isEnabled: Bool { - didSet { - isUserInteractionEnabled = isEnabled - setNeedsDisplay() - } + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + override public init(frame: CGRect) { + super.init(frame: frame) } - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - public var widthConstraint: NSLayoutConstraint? - public var heightConstraint: NSLayoutConstraint? - - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - - open override func draw(_ rect: CGRect) { - guard let context = UIGraphicsGetCurrentContext() else { return } - - let color = isEnabled == false ? disabledColor.cgColor : enabledColor.cgColor - layer.cornerRadius = bounds.width * 0.5 - layer.borderColor = color - layer.borderWidth = bounds.width * 0.0333 - - if isSelected { - // Space around inner circle is 1/5 the size - context.addEllipse(in: CGRect(x: bounds.width * 0.2, - y: bounds.height * 0.2, - width: bounds.width * 0.6, - height: bounds.height * 0.6)) - context.setFillColor(color) - context.fillPath() - } + /// There is currently no intention on using xib files. + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("xib file is not implemented for Checkbox.") } + public convenience required init() { + self.init(frame:.zero) + } + //-------------------------------------------------- // MARK: - Validation //-------------------------------------------------- - /// The action performed when tapped. - func tapAction() { - if !isEnabled { - return + public func isValidField() -> Bool { isSelected } + + public func formFieldName() -> String? { + viewModel.fieldKey + } + + public func formFieldGroupName() -> String? { + viewModel.fieldKey + } + + public func formFieldValue() -> AnyHashable? { + guard let radioModel = viewModel, radioModel.enabled else { return nil } + return radioModel.fieldValue + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setup() { + super.setup() + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return nil } + if let message = MVMCoreUIUtility.hardcodedString(withKey: "radio_button"), + let selectedState = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "radio_selected_state" : "radio_not_selected_state") { + return message + selectedState + } else { + return nil + } } + accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint") + } + + open override func toggle() { + guard !isSelected, isEnabled else { return } + + //removed error + if showError && isSelected == false { + showError.toggle() + } + let wasPreviouslySelected = isSelected - if let radioButtonModel = radioButtonSelectionHelper { - radioButtonModel.selected(self) + if let radioButtonSelectionHelper { + radioButtonSelectionHelper.selected(self) } else { isSelected = !isSelected } - if let radioModel = radioModel, let actionModel = radioModel.action, isSelected, !wasPreviouslySelected { + if let actionModel = viewModel.action, isSelected, !wasPreviouslySelected { Task(priority: .userInitiated) { - try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioModel) + try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: viewModel) } } - _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) - setNeedsDisplay() - } - - public func isValidField() -> Bool { isSelected } - - public func formFieldName() -> String? { - radioModel?.fieldKey - } - - public func formFieldGroupName() -> String? { - radioModel?.fieldKey - } - - public func formFieldValue() -> AnyHashable? { - guard let radioModel = radioModel, radioModel.enabled else { return nil } - return radioModel.fieldValue + setNeedsUpdate() } //-------------------------------------------------- - // MARK: - Methods + // MARK: - Actions //-------------------------------------------------- - /// Adjust accessibility label based on state of RadioButton. - func updateAccessibilityLabel() { - if let message = MVMCoreUIUtility.hardcodedString(withKey: "radio_button"), - let selectedState = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "radio_selected_state" : "radio_not_selected_state") { - accessibilityLabel = message + selectedState + /// This will toggle the state of the Checkbox and execute the actionBlock if provided. + public func tapAction() { + toggle() + } + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + open func needsToBeConstrained() -> Bool { true } + + public func horizontalAlignment() -> UIStackView.Alignment { .leading } + + public func updateView(_ size: CGFloat) {} + + public func viewModelDidUpdate() { + + //events + viewModel.updateUI = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + let isValid = viewModel.isValid ?? true + showError = !isValid + isEnabled = viewModel.enabled + + }) } - } - - //-------------------------------------------------- - // MARK: - MVMViewProtocol - //-------------------------------------------------- - - open override func setupView() { - super.setupView() - - backgroundColor = .clear - clipsToBounds = true - widthConstraint = widthAnchor.constraint(equalToConstant: 20) - widthConstraint?.isActive = true - heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) - heightConstraint?.isActive = true - - addTarget(self, action: #selector(tapAction), for: .touchUpInside) - isAccessibilityElement = true - accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint") - accessibilityTraits = .button - updateAccessibilityLabel() - } - - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - self.delegateObject = delegateObject - self.additionalData = additionalData - - guard let model = model as? RadioButtonModel else { return } - - isSelected = model.state - isEnabled = model.enabled && !model.readOnly - RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) - } - - public override func reset() { - super.reset() - backgroundColor = .clear + + isSelected = viewModel.state + isEnabled = viewModel.enabled && !viewModel.readOnly + RadioButtonSelectionHelper.setupForRadioButtonGroup(viewModel, self, delegateObject: delegateObject) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift index 99b5cdb7..308ede2b 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift @@ -7,47 +7,31 @@ // import MVMCore +import VDS - -open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { +open class RadioButtonModel: FormFieldModel { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public static var identifier: String = "radioButton" - public var id: String = UUID().uuidString - - public var backgroundColor: Color? - public var accessibilityIdentifier: String? + public static override var identifier: String { "radioButton" } public var state: Bool = false - public var enabled: Bool = true - public var readOnly: Bool = false /// The specific value to send to server. TODO: update this to be more generic. public var fieldValue: String? - public var baseValue: AnyHashable? - public var groupName: String = FormValidator.defaultGroupName - public var fieldKey: String? public var action: ActionModelProtocol? public var inverted: Bool = false - + public var surface: Surface { inverted ? .dark : .light } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case id - case moleculeName - case backgroundColor - case accessibilityIdentifier case state - case enabled case fieldValue - case fieldKey - case groupName case action - case readOnly case inverted } @@ -56,49 +40,41 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- public init(_ state: Bool) { + super.init() self.state = state - baseValue = state + self.baseValue = state } //-------------------------------------------------- // MARK: - Validation //-------------------------------------------------- - public func formFieldValue() -> AnyHashable? { + public override func formFieldValue() -> AnyHashable? { guard enabled else { return nil } return fieldValue } - //-------------------------------------------------- - // MARK: - Server Value - //-------------------------------------------------- - open func formFieldServerValue() -> AnyHashable? { - return formFieldValue() + open override func setValidity(_ valid: Bool, errorMessage: String?) { + if let ruleErrorMessage = errorMessage, fieldKey != nil { + self.errorMessage = ruleErrorMessage + } + isValid = valid + updateUI?() } - + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - - id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString - if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { self.state = state } - - enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true - readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) baseValue = state - fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { - self.groupName = groupName - } fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) action = try typeContainer.decodeModelIfPresent(codingKey: .action) if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { @@ -106,17 +82,10 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { } } - public func encode(to encoder: Encoder) throws { + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) - try container.encode(id, forKey: .id) - try container.encode(moleculeName, forKey: .moleculeName) try container.encode(state, forKey: .state) - try container.encode(enabled, forKey: .enabled) - try container.encode(readOnly, forKey: .readOnly) - try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeModelIfPresent(action, forKey: .action) try container.encodeIfPresent(inverted, forKey: .inverted)