From 66255d9dc5329a4d2b482529ee20c00fe3a8b7ac Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 15:49:06 -0500 Subject: [PATCH 01/56] implemented initial vds toggle Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift | 343 +++--------------- .../Atomic/Atoms/Selectors/ToggleModel.swift | 60 +-- .../Atomic/Extensions/VDS-Enums+Codable.swift | 3 + 3 files changed, 91 insertions(+), 315 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index 4341614e..dba501ca 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -8,6 +8,7 @@ import MVMCore import UIKit +import VDS public typealias ActionBlockConfirmation = () -> (Bool) @@ -19,53 +20,35 @@ public typealias ActionBlockConfirmation = () -> (Bool) Container: The background of the toggle control. Knob: The circular indicator that slides on the container. */ -@objcMembers open class Toggle: Control, MVMCoreUIViewConstrainingProtocol { - //-------------------------------------------------- +@objcMembers open class Toggle: VDS.Toggle, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { + //------------------------------------------------------ // MARK: - Properties - //-------------------------------------------------- - - /// Holds the on and off colors for the container. - public var containerTintColor: (on: UIColor, off: UIColor) = (on: .mvmGreen, off: .mvmBlack) - - /// Holds the on and off colors for the knob. - public var knobTintColor: (on: UIColor, off: UIColor) = (on: .mvmWhite, off: .mvmWhite) - - /// Holds the on and off colors for the disabled state.. - public var disabledTintColor: (container: UIColor, knob: UIColor) = (container: .mvmCoolGray3, knob: .mvmWhite) - - /// Set this flag to false if you do not want to animate state changes. - public var isAnimated = true - - public var didToggleAction: ActionBlock? + //------------------------------------------------------ + open var viewModel: ToggleModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + + public var didToggleAction: ActionBlock? { + get { nil } + set { + if let action = newValue { + onChange = { _ in + action() + } + } else { + onChange = nil + } + } + } /// Executes logic before state change. If false, then toggle state will not change and the didToggleAction will not execute. public var shouldToggleAction: ActionBlockConfirmation? = { return { true } }() - // Sizes are from InVision design specs. - static let containerSize = CGSize(width: 51, height: 31) - static let knobSize = CGSize(width: 28, height: 28) - - private var knobView: View = { - let view = View() - view.backgroundColor = .white - view.layer.cornerRadius = Toggle.getKnobHeight() / 2.0 - return view - }() - //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- - - open override var isEnabled: Bool { - didSet { - isUserInteractionEnabled = isEnabled - changeStateNoAnimation(isEnabled ? isOn : false) - setToggleAppearanceFromState() - accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: isEnabled ? "AccToggleHint" : "AccDisabled") - } - } /// Simple means to prevent user interaction with the toggle. public var isLocked: Bool = false { @@ -73,83 +56,16 @@ public typealias ActionBlockConfirmation = () -> (Bool) } /// The state on the toggle. Default value: false. - open var isOn: Bool = false { + open override var isOn: Bool { didSet { - if isAnimated { - UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: { - if self.isOn { - self.knobView.backgroundColor = self.knobTintColor.on - self.backgroundColor = self.containerTintColor.on - - } else { - self.knobView.backgroundColor = self.knobTintColor.off - self.backgroundColor = self.containerTintColor.off - } - }, completion: nil) - - UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: { - self.constrainKnob() - self.knobWidthConstraint?.constant = Self.getKnobWidth() - self.layoutIfNeeded() - }, completion: nil) - - } else { - setToggleAppearanceFromState() - self.constrainKnob() - } - toggleModel?.selected = isOn _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) - accessibilityValue = isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") - setNeedsLayout() - layoutIfNeeded() } } public var toggleModel: ToggleModel? { - model as? ToggleModel + viewModel } - - //-------------------------------------------------- - // MARK: - Delegate - //-------------------------------------------------- - - private var delegateObject: MVMCoreUIDelegateObject? - - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - private var knobLeadingConstraint: NSLayoutConstraint? - private var knobTrailingConstraint: NSLayoutConstraint? - private var knobHeightConstraint: NSLayoutConstraint? - private var knobWidthConstraint: NSLayoutConstraint? - private var heightConstraint: NSLayoutConstraint? - private var widthConstraint: NSLayoutConstraint? - - private func constrainKnob() { - - knobLeadingConstraint?.isActive = false - knobTrailingConstraint?.isActive = false - - _ = isOn ? constrainKnobOn() : constrainKnobOff() - - knobTrailingConstraint?.isActive = true - knobLeadingConstraint?.isActive = true - } - - private func constrainKnobOn() { - - knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 2) - knobLeadingConstraint = knobView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor) - } - - private func constrainKnobOff() { - - knobTrailingConstraint = trailingAnchor.constraint(greaterThanOrEqualTo: knobView.trailingAnchor) - knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2) - } - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -158,7 +74,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) super.init(frame: frame) } - public convenience override init() { + public convenience required init() { self.init(frame: .zero) } @@ -171,7 +87,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) /// - parameter didToggleAction: A closure which is executed after the toggle changes states. public convenience init(isOn: Bool = false, didToggleAction: ActionBlock?) { self.init(frame: .zero) - changeStateNoAnimation(isOn) + self.isOn = isOn self.didToggleAction = didToggleAction } @@ -191,214 +107,71 @@ public typealias ActionBlockConfirmation = () -> (Bool) //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- - - public override func updateView(_ size: CGFloat) { - super.updateView(size) - - heightConstraint?.constant = Self.getContainerHeight() - widthConstraint?.constant = Self.getContainerWidth() - - knobHeightConstraint?.constant = Self.getKnobHeight() - knobWidthConstraint?.constant = Self.getKnobWidth() - - layer.cornerRadius = Self.getContainerHeight() / 2.0 - knobView.layer.cornerRadius = Self.getKnobHeight() / 2.0 - - changeStateNoAnimation(isOn) - } - - public override func setupView() { - super.setupView() - - isAccessibilityElement = true - accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccToggleHint") - accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") - accessibilityTraits = .button + public func updateView(_ size: CGFloat) { } - heightConstraint = heightAnchor.constraint(equalToConstant: Self.containerSize.height) - heightConstraint?.isActive = true + public override func setup() { + super.setup() + + bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return nil } + return MVMCoreUIUtility.hardcodedString(withKey: isEnabled ? "AccToggleHint" : "AccDisabled") + } - widthConstraint = widthAnchor.constraint(equalToConstant: Self.containerSize.width) - widthConstraint?.isActive = true - - layer.cornerRadius = Self.getContainerHeight() / 2.0 - backgroundColor = containerTintColor.off - - addSubview(knobView) - - knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: Self.knobSize.height) - knobHeightConstraint?.isActive = true - knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: Self.knobSize.width) - knobWidthConstraint?.isActive = true - knobView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - knobView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true - bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true - - constrainKnobOff() + bridge_accessibilityValueBlock = { [weak self] in + guard let self else { return nil } + return isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + } } public override func reset() { super.reset() - - backgroundColor = containerTintColor.off - knobView.backgroundColor = knobTintColor.off accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") - isAnimated = true didToggleAction = nil shouldToggleAction = { return true } } - class func getContainerWidth() -> CGFloat { - let containerWidth = Self.containerSize.width - return (MFSizeObject(standardSize: containerWidth, standardiPadPortraitSize: CGFloat(Self.containerSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerWidth - } - - class func getContainerHeight() -> CGFloat { - let containerHeight = Self.containerSize.height - return (MFSizeObject(standardSize: containerHeight, standardiPadPortraitSize: CGFloat(Self.containerSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerHeight - } - - class func getKnobWidth() -> CGFloat { - let knobWidth = Self.knobSize.width - return (MFSizeObject(standardSize: knobWidth, standardiPadPortraitSize: CGFloat(Self.knobSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobWidth - } - - class func getKnobHeight() -> CGFloat { - let knobHeight = Self.knobSize.width - return (MFSizeObject(standardSize: knobHeight, standardiPadPortraitSize: CGFloat(Self.knobSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobHeight - } - //-------------------------------------------------- // MARK: - Actions //-------------------------------------------------- - - open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { - super.sendAction(action, to: target, for: event) - toggleAndAction() - } - - open override func sendActions(for controlEvents: UIControl.Event) { - super.sendActions(for: controlEvents) - toggleAndAction() - } - /// This will toggle the state of the Toggle and execute the actionBlock if provided. public func toggleAndAction() { - + toggle() + } + + open override func toggle() { if let result = shouldToggleAction?(), result { - isOn.toggle() + super.toggle() didToggleAction?() } } - - private func changeStateNoAnimation(_ state: Bool) { - - // Hold state in case User wanted isAnimated to remain off. - let isAnimatedState = isAnimated - - isAnimated = false - isOn = state - isAnimated = isAnimatedState - } - - override open func accessibilityActivate() -> Bool { - // Hold state in case User wanted isAnimated to remain off. - guard isUserInteractionEnabled else { return false } - let isAnimatedState = isAnimated - isAnimated = false - sendActions(for: .touchUpInside) - isAnimated = isAnimatedState - return true - } - - //-------------------------------------------------- - // MARK: - UIResponder - //-------------------------------------------------- - - open override func touchesBegan(_ touches: Set, with event: UIEvent?) { - - UIView.animate(withDuration: 0.1, animations: { - self.knobWidthConstraint?.constant += PaddingOne - self.layoutIfNeeded() - }) - } - - public override func touchesEnded(_ touches: Set, with event: UIEvent?) { - - knobReformAnimation() - - // Action only occurs of the user lifts up from withing acceptable region of the toggle. - guard let coordinates = touches.first?.location(in: self), - coordinates.x > -20, - coordinates.x < bounds.width + 20, - coordinates.y > -20, - coordinates.y < bounds.height + 20 - else { return } - - sendActions(for: .touchUpInside) - } - - public func touchesCancelled(_ touches: Set, with event: UIEvent) { - - knobReformAnimation() - sendActions(for: .touchCancel) - } - - //-------------------------------------------------- - // MARK: - Animations - //-------------------------------------------------- - - public func setToggleAppearanceFromState() { - - backgroundColor = isEnabled ? isOn ? containerTintColor.on : containerTintColor.off : disabledTintColor.container - knobView.backgroundColor = isEnabled ? isOn ? knobTintColor.on : knobTintColor.off : disabledTintColor.knob - } - - public func knobReformAnimation() { - - if isAnimated { - UIView.animate(withDuration: 0.1, animations: { - self.knobWidthConstraint?.constant = Self.getKnobWidth() - self.layoutIfNeeded() - }, completion: nil) - - } else { - knobWidthConstraint?.constant = Self.getKnobWidth() - layoutIfNeeded() - } - } // MARK:- MoleculeViewProtocol - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - self.delegateObject = delegateObject + public func viewModelDidUpdate() { + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - guard let model = model as? ToggleModel else { return } + isOn = viewModel.selected + isAnimated = viewModel.animated + isEnabled = viewModel.enabled && !viewModel.readOnly + showText = viewModel.showText + onText = viewModel.onText + offText = viewModel.offText + textSize = viewModel.textSize + textWeight = viewModel.textWeight + textPosition = viewModel.textPosition - FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) - - containerTintColor.on = model.onTintColor.uiColor - containerTintColor.off = model.offTintColor.uiColor - knobTintColor.on = model.onKnobTintColor.uiColor - knobTintColor.off = model.offKnobTintColor.uiColor - isOn = model.selected - changeStateNoAnimation(isOn) - isAnimated = model.animated - isEnabled = model.enabled && !model.readOnly - - if let accessibileString = model.accessibilityText { + if let accessibileString = viewModel.accessibilityText { accessibilityLabel = accessibileString } - if model.action != nil || model.alternateAction != nil { + if viewModel.action != nil || viewModel.alternateAction != nil { didToggleAction = { [weak self] in guard let self = self else { return } if self.isOn { - if let action = model.action { + if let action = viewModel.action { MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: model, additionalData: additionalData, delegateObject: delegateObject) } } else { - if let action = model.alternateAction ?? model.action { + if let action = viewModel.alternateAction ?? viewModel.action { MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: model, additionalData: additionalData, delegateObject: delegateObject) } } @@ -406,8 +179,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) } } - public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - Self.getContainerHeight() + public class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return Self().intrinsicContentSize.height } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index f4ce9234..a96bddb8 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -5,7 +5,7 @@ // Created by Scott Pfeil on 1/14/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import VDS public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- @@ -24,10 +24,13 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { public var action: ActionModelProtocol? public var alternateAction: ActionModelProtocol? public var accessibilityText: String? - public var onTintColor: Color = Color(uiColor: .mvmGreen) - public var offTintColor: Color = Color(uiColor: .mvmBlack) - public var onKnobTintColor: Color = Color(uiColor: .mvmWhite) - public var offKnobTintColor: Color = Color(uiColor: .mvmWhite) + + public var showText: Bool = false + public var onText: String = "On" + public var offText: String = "Off" + public var textSize: VDS.Toggle.TextSize = .small + public var textWeight: VDS.Toggle.TextWeight = .regular + public var textPosition: VDS.Toggle.TextPosition = .left public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName @@ -49,10 +52,14 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { case accessibilityIdentifier case alternateAction case accessibilityText - case onTintColor - case offTintColor - case onKnobTintColor - case offKnobTintColor + + case showText + case onText + case offText + case textSize + case textWeight + case textPosition + case fieldKey case groupName } @@ -102,25 +109,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { action = try typeContainer.decodeModelIfPresent(codingKey: .action) alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) - if let onTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onTintColor) { - self.onTintColor = onTintColor - } - - if let offTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offTintColor) { - self.offTintColor = offTintColor - } - - if let onKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onKnobTintColor) { - self.onKnobTintColor = onKnobTintColor - } - - if let offKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offKnobTintColor) { - self.offKnobTintColor = offKnobTintColor - } - accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) baseValue = selected @@ -130,6 +120,13 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { } enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false + + showText = try typeContainer.decodeIfPresent(Bool.self, forKey: .showText) ?? false + onText = try typeContainer.decodeIfPresent(String.self, forKey: .onText) ?? "On" + offText = try typeContainer.decodeIfPresent(String.self, forKey: .offText) ?? "Off" + textSize = try typeContainer.decodeIfPresent(VDS.Toggle.TextSize.self, forKey: .textSize) ?? .small + textWeight = try typeContainer.decodeIfPresent(VDS.Toggle.TextWeight.self, forKey: .textWeight) ?? .regular + textPosition = try typeContainer.decodeIfPresent(VDS.Toggle.TextPosition.self, forKey: .textPosition) ?? .left } public func encode(to encoder: Encoder) throws { @@ -143,13 +140,16 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { try container.encode(selected, forKey: .state) try container.encode(animated, forKey: .animated) try container.encode(enabled, forKey: .enabled) - try container.encode(onTintColor, forKey: .onTintColor) - try container.encode(onKnobTintColor, forKey: .onKnobTintColor) - try container.encode(onKnobTintColor, forKey: .onKnobTintColor) - try container.encode(offKnobTintColor, forKey: .offKnobTintColor) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encode(readOnly, forKey: .readOnly) + + try container.encode(showText, forKey: .showText) + try container.encode(onText, forKey: .onText) + try container.encode(offText, forKey: .offText) + try container.encode(textSize, forKey: .textSize) + try container.encode(textWeight, forKey: .textWeight) + try container.encode(textPosition, forKey: .textPosition) } } diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index f4eb5500..1ae838e0 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -36,6 +36,9 @@ extension VDS.TextLinkCaret.IconPosition: Codable {} extension VDS.TileContainerBase.AspectRatio: Codable {} extension VDS.Tilelet.Padding: Codable {} extension VDS.TitleLockup.TextAlignment: Codable {} +extension VDS.Toggle.TextSize: Codable {} +extension VDS.Toggle.TextWeight: Codable {} +extension VDS.Toggle.TextPosition: Codable {} extension VDS.Tooltip.FillColor: Codable {} extension VDS.Tooltip.Size: Codable {} extension VDS.Line.Style: Codable {} From 04e11ee1fb28d7c8087b7dfdfaa5e99e05f066c8 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 28 Jun 2024 14:54:47 -0500 Subject: [PATCH 02/56] a few updates Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift | 21 ++++++++++++------- .../Atomic/Atoms/Selectors/ToggleModel.swift | 5 +++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index dba501ca..30bcd85a 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -27,6 +27,9 @@ public typealias ActionBlockConfirmation = () -> (Bool) open var viewModel: ToggleModel! open var delegateObject: MVMCoreUIDelegateObject? open var additionalData: [AnyHashable : Any]? + + /// Disables all selection logic when setting the value of isOn, reducing it to a stored property. + public var updateSelectionOnly: Bool = false public var didToggleAction: ActionBlock? { get { nil } @@ -58,14 +61,13 @@ public typealias ActionBlockConfirmation = () -> (Bool) /// The state on the toggle. Default value: false. open override var isOn: Bool { didSet { - toggleModel?.selected = isOn - _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + if !updateSelectionOnly { + viewModel.selected = isOn + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + } } } - public var toggleModel: ToggleModel? { - viewModel - } //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -141,7 +143,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) open override func toggle() { if let result = shouldToggleAction?(), result { super.toggle() - didToggleAction?() } } @@ -149,7 +150,13 @@ public typealias ActionBlockConfirmation = () -> (Bool) public func viewModelDidUpdate() { FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - isOn = viewModel.selected + if viewModel.selected { + updateSelectionOnly = false + isOn = viewModel.selected + updateSelectionOnly = true + } + + surface = viewModel.surface isAnimated = viewModel.animated isEnabled = viewModel.enabled && !viewModel.readOnly showText = viewModel.showText diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index a96bddb8..398ba42c 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -25,6 +25,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { public var alternateAction: ActionModelProtocol? public var accessibilityText: String? + public var surface: Surface { inverted ? .dark : .light } + public var inverted: Bool = false public var showText: Bool = false public var onText: String = "On" public var offText: String = "Off" @@ -53,6 +55,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { case alternateAction case accessibilityText + case inverted case showText case onText case offText @@ -121,6 +124,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false + inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) ?? false showText = try typeContainer.decodeIfPresent(Bool.self, forKey: .showText) ?? false onText = try typeContainer.decodeIfPresent(String.self, forKey: .onText) ?? "On" offText = try typeContainer.decodeIfPresent(String.self, forKey: .offText) ?? "Off" @@ -145,6 +149,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { try container.encodeIfPresent(groupName, forKey: .groupName) try container.encode(readOnly, forKey: .readOnly) + try container.encode(inverted, forKey: .inverted) try container.encode(showText, forKey: .showText) try container.encode(onText, forKey: .onText) try container.encode(offText, forKey: .offText) From fa52fa8c12948798ece5c995e42881842b25e501 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 3 Jul 2024 15:58:01 -0500 Subject: [PATCH 03/56] added protocol/refactored to deal with RadiobuttonLabel Signed-off-by: Matt Bruce --- .../Selectors/RadioButtonSelectionHelper.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift index 2b6fa01d..4ca37dce 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift @@ -6,6 +6,10 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // +public protocol RadioButtonSelectionHelperProtocol: AnyObject { + var isSelected: Bool { get set } + var radioButtonModel: RadioButtonModel { get } +} @objcMembers public class RadioButtonSelectionHelper: FormFieldProtocol { //-------------------------------------------------- @@ -14,7 +18,7 @@ public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName - private var selectedRadioButton: RadioButton? + private var selectedRadioButton: RadioButtonSelectionHelperProtocol? private var selectedRadioButtonModel: RadioButtonModel? public var baseValue: AnyHashable? public var enabled: Bool = true @@ -24,7 +28,7 @@ // MARK: - Initializer //-------------------------------------------------- - public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) { + public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButtonSelectionHelperProtocol) { self.fieldKey = radioButtonModel.fieldKey self.groupName = radioButtonModel.groupName @@ -49,7 +53,7 @@ // MARK: - Functions //-------------------------------------------------- - public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton, delegateObject: MVMCoreUIDelegateObject?) { + public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButtonSelectionHelperProtocol, delegateObject: MVMCoreUIDelegateObject?) { guard let groupName = radioButtonModel.fieldKey, let formValidator = delegateObject?.formHolderDelegate?.formValidator @@ -61,10 +65,10 @@ FormValidator.setupValidation(for: radioButtonSelectionHelper, delegate: delegateObject?.formHolderDelegate) } - public func selected(_ radioButton: RadioButton) { + public func selected(_ radioButton: RadioButtonSelectionHelperProtocol) { // Checks because the view could be reused - if selectedRadioButton?.radioModel === selectedRadioButtonModel { + if selectedRadioButton?.radioButtonModel === selectedRadioButtonModel { selectedRadioButton?.isSelected = false } else { selectedRadioButtonModel?.state = false @@ -72,7 +76,7 @@ selectedRadioButton = radioButton selectedRadioButton?.isSelected = true - selectedRadioButtonModel = selectedRadioButton?.radioModel + selectedRadioButtonModel = selectedRadioButton?.radioButtonModel } } From 10c55b12be709d291386ee24c9268308480442ea Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 3 Jul 2024 15:58:15 -0500 Subject: [PATCH 04/56] 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) From 724130a8275176e10a904034e3fd13db13c98bcb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 3 Jul 2024 15:58:22 -0500 Subject: [PATCH 05/56] converted to vds Signed-off-by: Matt Bruce --- .../RadioButtonLabel.swift | 145 +++++++++++++----- .../RadioButtonLabelModel.swift | 14 +- 2 files changed, 118 insertions(+), 41 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabel.swift index eb7266a9..39a578ba 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabel.swift @@ -7,54 +7,121 @@ // import UIKit +import VDS +@objcMembers public class RadioButtonLabel: VDS.RadioButtonItem, RadioButtonSelectionHelperProtocol, VDSMoleculeViewProtocol, MFButtonProtocol { -@objcMembers public class RadioButtonLabel: View { - - public let radioButton = RadioButton() - var delegateObject: MVMCoreUIDelegateObject? - let label = Label() - - public override func updateView(_ size: CGFloat) { - super.updateView(size) - radioButton.updateView(size) - label.updateView(size) - } + //------------------------------------------------------ + // MARK: - Properties + //------------------------------------------------------ + open var viewModel: RadioButtonLabelModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? - open override func setupView() { - super.setupView() + open var radioButtonModel: RadioButtonModel { + viewModel.radioButton + } - addSubview(radioButton) - radioButton.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor, constant: 0).isActive = true - radioButton.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor, constant: PaddingOne).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: radioButton.bottomAnchor, constant: PaddingOne).isActive = true - radioButton.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor).isActive = true + // Form Validation + var fieldKey: String? + var fieldValue: JSONValue? + var groupName: String? - if let rightView = createRightView() { - addSubview(rightView) - rightView.leftAnchor.constraint(equalTo: radioButton.rightAnchor, constant: Padding.Component.gutterForApplicationWidth).isActive = true - rightView.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor, constant: 0).isActive = true - - var constraint = rightView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor, constant: PaddingOne) - constraint.priority = .defaultHigh - constraint.isActive = true - - constraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: rightView.bottomAnchor, constant: PaddingOne) - constraint.priority = .defaultHigh - constraint.isActive = true - layoutMarginsGuide.centerYAnchor.constraint(equalTo: rightView.centerYAnchor).isActive = true + open override var isSelected: Bool { + didSet { + radioButtonModel.state = isSelected + if oldValue != isSelected { + sendActions(for: .valueChanged) + } } } - func createRightView() -> Container? { - let rightView = Container(andContain: label) - return rightView + lazy public var radioGroupName: String? = { viewModel.radioButton.fieldKey }() + + lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = { + + guard let radioGroupName = radioGroupName, + let radioButtonModel = delegateObject?.formHolderDelegate?.formValidator?.radioButtonsModelByGroup[radioGroupName] + else { return nil } + + return radioButtonModel + }() + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + @objc open func updateView(_ size: CGFloat) {} + + open override func toggle() { + guard !isSelected, isEnabled else { return } + + //removed error + if showError && isSelected == false { + showError.toggle() + } + + let wasPreviouslySelected = isSelected + if let radioButtonSelectionHelper { + radioButtonSelectionHelper.selected(self) + } else { + isSelected = !isSelected + } + + if let actionModel = viewModel.radioButton.action, isSelected, !wasPreviouslySelected { + Task(priority: .userInitiated) { + try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: viewModel) + } + } + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + } - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let radioButtonLabelModel = model as? RadioButtonLabelModel else { return } - - radioButton.set(with: radioButtonLabelModel.radioButton, delegateObject, additionalData) - label.set(with: radioButtonLabelModel.label, delegateObject, additionalData) + //-------------------------------------------------- + // MARK: - Atomic + //-------------------------------------------------- + public func viewModelDidUpdate() { + surface = viewModel.surface + + updateRadioButton() + + //primary label + labelText = viewModel.label.text + if let attributes = viewModel.label.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) { + labelTextAttributes = attributes + } + + //secondary label + if let subTitleModel = viewModel.subTitle { + childText = subTitleModel.text + if let attributes = subTitleModel.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) { + childTextAttributes = attributes + } + } } + + public func updateRadioButton() { + + if let fieldKey = viewModel.radioButton.fieldKey { + self.fieldKey = fieldKey + } + + //properties + isEnabled = viewModel.radioButton.enabled && !viewModel.radioButton.readOnly + isSelected = viewModel.radioButton.state + + //forms + RadioButtonSelectionHelper.setupForRadioButtonGroup(viewModel.radioButton, self, delegateObject: delegateObject) + + //events + viewModel.radioButton.updateUI = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + let isValid = viewModel.radioButton.isValid ?? true + showError = !isValid + errorText = viewModel.radioButton.errorMessage + isEnabled = viewModel.radioButton.enabled + }) + } + } + } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift index 571848dc..0f4a4be6 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift @@ -8,8 +8,9 @@ import Foundation import MVMCore +import VDS -@objcMembers public class RadioButtonLabelModel: MoleculeModelProtocol { +@objcMembers public class RadioButtonLabelModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -21,14 +22,23 @@ import MVMCore public var moleculeName: String = RadioButtonLabelModel.identifier public var radioButton: RadioButtonModel public var label: LabelModel + public var subTitle: LabelModel? + public var inverted: Bool? = false + public var surface: Surface { inverted ?? false ? .dark : .light } + public var children: [MoleculeModelProtocol] { + guard let subTitle else { return [radioButton, label] } + return [radioButton, label, subTitle] + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public init(radioButton: RadioButtonModel, label: LabelModel) { + public init(radioButton: RadioButtonModel, label: LabelModel, subTitle: LabelModel?) { self.radioButton = radioButton self.label = label + self.subTitle = subTitle } } From 04f228ea48982b0a85d4700a845c069b5e978e6d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 3 Jul 2024 15:58:35 -0500 Subject: [PATCH 06/56] refactored to remove old properties Signed-off-by: Matt Bruce --- .../ListLeftVariableRadioButtonAllTextAndLinks.swift | 4 +--- .../ListLeftVariableRadioButtonAndPaymentMethod.swift | 4 +--- .../LeftVariable/ListLeftVariableRadioButtonBodyText.swift | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift index cc3e57d6..df62b27d 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift @@ -81,9 +81,7 @@ func updateAccessibilityLabel() { var message = "" - - radioButton.updateAccessibilityLabel() - + if let radioButtonLabel = radioButton.accessibilityLabel { message += radioButtonLabel + ", " } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift index b749355d..cf38c043 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift @@ -98,9 +98,7 @@ import UIKit func updateAccessibilityLabel() { var message = "" - - radioButton.updateAccessibilityLabel() - + if let radioButtonLabel = radioButton.accessibilityLabel { message += radioButtonLabel + ", " } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyText.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyText.swift index dc3a582a..5fbd617b 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyText.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyText.swift @@ -85,7 +85,6 @@ open class ListLeftVariableRadioButtonBodyText: TableViewCell { var message = "" - radioButton.updateAccessibilityLabel() if let radioButtonLabel = radioButton.accessibilityLabel { message += radioButtonLabel + ", " } From 8a12bf12c2e3f25c372cd9bc8c622d8a3b6e130d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 9 Jul 2024 10:21:55 -0500 Subject: [PATCH 07/56] refactored models Signed-off-by: Matt Bruce --- .../Atoms/Selectors/RadioBoxModel.swift | 18 +++-- .../Atoms/Selectors/RadioBoxesModel.swift | 77 ++++++++----------- 2 files changed, 42 insertions(+), 53 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift index acec6e83..87326c7d 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift @@ -6,6 +6,7 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // import MVMCore +import VDS @objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol { //-------------------------------------------------- @@ -17,15 +18,17 @@ import MVMCore public var text: String public var subText: String? + public var subTextRight: String? public var backgroundColor: Color? public var accessibilityIdentifier: String? - public var selectedAccentColor: Color? public var selected: Bool = false public var enabled: Bool = true public var readOnly: Bool = false public var strikethrough: Bool = false public var fieldValue: String? public var action: ActionModelProtocol? + public var inverted: Bool = false + public var surface: Surface { inverted ? .dark : .light } //-------------------------------------------------- // MARK: - Keys @@ -36,7 +39,7 @@ import MVMCore case moleculeName case text case subText - case selectedAccentColor + case subTextRight case backgroundColor case accessibilityIdentifier case selected @@ -45,6 +48,7 @@ import MVMCore case fieldValue case action case readOnly + case inverted } //-------------------------------------------------- @@ -65,8 +69,7 @@ import MVMCore id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString text = try typeContainer.decode(String.self, forKey: .text) subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText) - selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + subTextRight = try typeContainer.decodeIfPresent(String.self, forKey: .subTextRight) accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { @@ -80,6 +83,10 @@ import MVMCore strikethrough = isStrikeTrough } + if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { + self.inverted = inverted + } + fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) action = try typeContainer.decodeModelIfPresent(codingKey: .action) } @@ -90,8 +97,7 @@ import MVMCore try container.encode(moleculeName, forKey: .moleculeName) try container.encode(text, forKey: .text) try container.encodeIfPresent(subText, forKey: .subText) - try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(subTextRight, forKey: .subTextRight) try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encode(selected, forKey: .selected) try container.encode(enabled, forKey: .enabled) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift index f1c652f7..105f9645 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift @@ -6,32 +6,38 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // import MVMCore +import VDS -@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol { +@objcMembers public class RadioBoxesModel: FormFieldModel { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public static var identifier: String = "radioBoxes" - public var id: String = UUID().uuidString + public override static var identifier: String { "radioBoxes" } public var boxes: [RadioBoxModel] - public var backgroundColor: Color? - public var accessibilityIdentifier: String? - public var selectedAccentColor: Color? - public var boxesColor: Color? - public var fieldKey: String? - public var groupName: String = FormValidator.defaultGroupName - public var baseValue: AnyHashable? - public var enabled: Bool = true - public var readOnly: Bool = false - + public var inverted: Bool = false + public var surface: Surface { inverted ? .dark : .light } + + public var selectorModels: [VDS.RadioBoxGroup.RadioBoxItemModel] { + boxes.compactMap({ item in + var radioBox = RadioBoxGroup.RadioBoxItemModel() + radioBox.text = item.text + radioBox.subText = item.subText + radioBox.subTextRight = item.subTextRight + radioBox.surface = surface + radioBox.selected = item.selected + radioBox.strikethrough = item.strikethrough + radioBox.disabled = !(item.enabled && !item.readOnly) + return radioBox + }) + } //-------------------------------------------------- // MARK: - Form Validation //-------------------------------------------------- /// Returns the fieldValue of the selected box, otherwise the text of the selected box. - public func formFieldValue() -> AnyHashable? { + public override func formFieldValue() -> AnyHashable? { guard enabled else { return nil } let selectedBox = boxes.first { (box) -> Bool in return box.selected @@ -42,7 +48,7 @@ import MVMCore //-------------------------------------------------- // MARK: - Server Value //-------------------------------------------------- - open func formFieldServerValue() -> AnyHashable? { + open override func formFieldServerValue() -> AnyHashable? { return formFieldValue() } @@ -51,17 +57,8 @@ import MVMCore //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case id - case moleculeName - case enabled - case readOnly - case selectedAccentColor - case backgroundColor - case accessibilityIdentifier - case boxesColor case boxes - case fieldKey - case groupName + case inverted } //-------------------------------------------------- @@ -69,7 +66,8 @@ import MVMCore //-------------------------------------------------- public init(with boxes: [RadioBoxModel]){ - self.boxes = boxes + self.boxes = boxes + super.init() } //-------------------------------------------------- @@ -78,32 +76,17 @@ import MVMCore required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString - selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) - boxesColor = try typeContainer.decodeIfPresent(Color.self, forKey: .boxesColor) boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) - fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { - self.groupName = groupName + if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { + self.inverted = inverted } - enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true - readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false - baseValue = formFieldValue() + try super.init(from: decoder) } - 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.encode(id, forKey: .id) - try container.encode(moleculeName, forKey: .moleculeName) try container.encode(boxes, forKey: .boxes) - try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) - try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encode(groupName, forKey: .groupName) - try container.encode(enabled, forKey: .enabled) - try container.encode(readOnly, forKey: .readOnly) + try container.encode(inverted, forKey: .inverted) } } From 8c5840fba326dcf5fbbaf7782d7bf8b3bc63b74d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 9 Jul 2024 10:23:05 -0500 Subject: [PATCH 08/56] views updated for model changes Signed-off-by: Matt Bruce --- .../Atomic/Atoms/Selectors/RadioBox.swift | 246 +++--------------- .../Atomic/Atoms/Selectors/RadioBoxes.swift | 202 ++++---------- 2 files changed, 79 insertions(+), 369 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift index 9fb6b354..ce890bc3 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -5,243 +5,57 @@ // Created by Scott Pfeil on 4/9/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import VDS - -open class RadioBox: Control, MFButtonProtocol { - //-------------------------------------------------- +open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol { + //------------------------------------------------------ // MARK: - Properties - //-------------------------------------------------- - - public let label = Label(fontStyle: .RegularBodySmall) - public let subTextLabel = Label(fontStyle: .RegularMicro) - public var isOutOfStock = false - public var accentColor = UIColor.mvmRed + //------------------------------------------------------ + open var viewModel: RadioBoxModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? - public let innerPadding: CGFloat = 12.0 + public var isOutOfStock: Bool { viewModel.strikethrough } - private var borderLayer: CALayer? - private var strikeLayer: CALayer? - private var maskLayer: CALayer? - - public var subTextLabelHeightConstraint: NSLayoutConstraint? - - private var delegateObject: MVMCoreUIDelegateObject? - var additionalData: [AnyHashable: Any]? - - public var radioBoxModel: RadioBoxModel? { - model as? RadioBoxModel - } - - public override var isSelected: Bool { - didSet { updateAccessibility() } - } - - public override var isEnabled: Bool { - didSet { updateAccessibility() } - } - //-------------------------------------------------- // MARK: - MVMCoreViewProtocol //-------------------------------------------------- - open override func updateView(_ size: CGFloat) { - super.updateView(size) - label.updateView(size) - subTextLabel.updateView(size) - layer.setNeedsDisplay() - } + open func updateView(_ size: CGFloat) {} - open override func setupView() { - super.setupView() - - layer.delegate = self - layer.borderColor = UIColor.mvmCoolGray6.cgColor - layer.borderWidth = 1 - - label.numberOfLines = 1 - addSubview(label) - NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: innerPadding, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) - - subTextLabel.textColor = .mvmCoolGray6 - subTextLabel.numberOfLines = 1 - addSubview(subTextLabel) - NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) - bottomAnchor.constraint(greaterThanOrEqualTo: subTextLabel.bottomAnchor, constant: innerPadding).isActive = true - subTextLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2).isActive = true - subTextLabelHeightConstraint = subTextLabel.heightAnchor.constraint(equalToConstant: 0) - subTextLabelHeightConstraint?.isActive = true - + open override func setup() { + super.setup() + addTarget(self, action: #selector(selectBox), for: .touchUpInside) + } + + public func viewModelDidUpdate() { + + text = viewModel.text + subText = viewModel.subText + subTextRight = viewModel.subTextRight + strikethrough = viewModel.strikethrough + isSelected = viewModel.selected + isEnabled = viewModel.enabled && !viewModel.readOnly - isAccessibilityElement = true - } - - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - guard let model = model as? RadioBoxModel else { return } - self.delegateObject = delegateObject - self.additionalData = additionalData - label.text = model.text - subTextLabel.text = model.subText - isOutOfStock = model.strikethrough - subTextLabelHeightConstraint?.isActive = (subTextLabel.text?.count ?? 0) == 0 - if let color = model.selectedAccentColor?.uiColor { - accentColor = color + onChange = { [weak self] _ in + if let radioBoxModel = self?.viewModel, let actionModel = radioBoxModel.action { + Task(priority: .userInitiated) { [weak self] in + guard let self else { return } + try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioBoxModel) + } + } } - isSelected = model.selected - isEnabled = model.enabled && !model.readOnly - } - - open override func reset() { - super.reset() - backgroundColor = .white - accentColor = .mvmRed - } - - //-------------------------------------------------- - // MARK: - State Handling - //-------------------------------------------------- - - open override func draw(_ layer: CALayer, in ctx: CGContext) { - // Draw the strikethrough - strikeLayer?.removeFromSuperlayer() - if isOutOfStock { - let line = getStrikeThrough(color: isSelected ? .black : .mvmCoolGray6, thickness: 1) - layer.addSublayer(line) - strikeLayer = line - } - - // Draw the border - borderLayer?.removeFromSuperlayer() - if isSelected { - layer.borderWidth = 0 - let border = getSelectedBorder() - layer.addSublayer(border) - borderLayer = border - } else { - layer.borderWidth = 1 - } - - // Handle Mask - maskLayer?.removeFromSuperlayer() - if !isEnabled { - let mask = getMaskLayer() - layer.mask = mask - maskLayer = mask - } - } - - open override func layoutSubviews() { - super.layoutSubviews() - // Accounts for any size changes - layer.setNeedsDisplay() } @objc open func selectBox() { guard isEnabled, !isSelected else { return } isSelected = true - radioBoxModel?.selected = isSelected - if let radioBoxModel = radioBoxModel, let actionModel = radioBoxModel.action { - Task(priority: .userInitiated) { - try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioBoxModel) - } - } - layer.setNeedsDisplay() + viewModel.selected = isSelected } @objc open func deselectBox() { - isSelected = false - radioBoxModel?.selected = isSelected - layer.setNeedsDisplay() - } - - /// Gets the selected state border - func getSelectedBorder() -> CAShapeLayer { - let layer = CAShapeLayer() - - let topLineWidth: CGFloat = 4 - let topLinePath = UIBezierPath() - topLinePath.lineWidth = topLineWidth - topLinePath.move(to: CGPoint(x: 0, y: topLineWidth / 2.0)) - topLinePath.addLine(to: CGPoint(x: bounds.width, y: topLineWidth / 2.0)) - - let topLineLayer = CAShapeLayer() - topLineLayer.fillColor = nil - topLineLayer.strokeColor = accentColor.cgColor - topLineLayer.lineWidth = 4 - topLineLayer.path = topLinePath.cgPath - layer.addSublayer(topLineLayer) - let lineWidth: CGFloat = 1 - let halfLineWidth: CGFloat = 0.5 - let linePath = UIBezierPath() - linePath.move(to: CGPoint(x: halfLineWidth, y: topLineWidth)) - linePath.addLine(to: CGPoint(x: halfLineWidth, y: bounds.height)) - linePath.move(to: CGPoint(x: 0, y: bounds.height - halfLineWidth)) - linePath.addLine(to: CGPoint(x: bounds.width, y: bounds.height - halfLineWidth)) - linePath.move(to: CGPoint(x: bounds.width - halfLineWidth, y: bounds.height)) - linePath.addLine(to: CGPoint(x: bounds.width - halfLineWidth, y: topLineWidth)) - - let borderLayer = CAShapeLayer() - borderLayer.fillColor = nil - borderLayer.strokeColor = UIColor.black.cgColor - borderLayer.lineWidth = lineWidth - borderLayer.path = linePath.cgPath - layer.addSublayer(borderLayer) - - return layer - } - - /// Adds a border to edge - func getStrikeThrough(color: UIColor, thickness: CGFloat) -> CAShapeLayer { - let border = CAShapeLayer() - border.name = "strikethrough" - border.fillColor = nil - border.opacity = 1.0 - border.lineWidth = thickness - border.strokeColor = color.cgColor - - let linePath = UIBezierPath() - linePath.move(to: CGPoint(x: 0, y: bounds.height)) - linePath.addLine(to: CGPoint(x: bounds.width, y: 0)) - border.path = linePath.cgPath - return border - } - - func getMaskLayer() -> CALayer { - let mask = CALayer() - mask.backgroundColor = UIColor.white.cgColor - mask.opacity = 0.3 - mask.frame = bounds - return mask - } - - //-------------------------------------------------- - // MARK: - Accessibility - //-------------------------------------------------- - - public func updateAccessibility() { - - var message = "" - - if let labelText = label.text, label.hasText { - message += labelText + ", " - } - - if let subLabelText = subTextLabel.text, subTextLabel.hasText { - message += subLabelText + ", " - } - - accessibilityLabel = message - accessibilityTraits = .button - - if isSelected { - accessibilityTraits.insert(.selected) - } - - if !isEnabled { - accessibilityTraits.insert(.notEnabled) - } } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift index ced00073..7ba1f755 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift @@ -7,172 +7,68 @@ // import Foundation +import VDS public protocol RadioBoxSelectionDelegate: AnyObject { func selected(radioBox: RadioBoxModel) } -open class RadioBoxes: View { - - public var collectionView: CollectionView! - public var collectionViewHeight: NSLayoutConstraint! - private let boxWidth: CGFloat = 151.0 - private let boxHeight: CGFloat = 64.0 - private var itemSpacing: CGFloat = 12.0 - private var numberOfColumns: CGFloat = 2.0 - private var radioBoxesModel: RadioBoxesModel? { - return model as? RadioBoxesModel - } - public weak var radioDelegate: RadioBoxSelectionDelegate? - private var delegateObject: MVMCoreUIDelegateObject? +open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol { + //------------------------------------------------------ + // MARK: - Properties + //------------------------------------------------------ + open var viewModel: RadioBoxesModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + + // Form Validation + var fieldKey: String? + var fieldValue: JSONValue? + var groupName: String? + /// The models for the molecules. public var boxes: [RadioBoxModel]? + public weak var radioDelegate: RadioBoxSelectionDelegate? - private var size: CGFloat? - - open override func layoutSubviews() { - super.layoutSubviews() - // Accounts for any collection size changes - DispatchQueue.main.async { - self.collectionView.collectionViewLayout.invalidateLayout() - } +// TODO: this matches the current accessibility however not what was passed by Barbara's team. +// open override var items: [RadioBoxItem] { +// didSet { +// let total = items.count +// for (index, radioBoxItem) in items.enumerated() { +// radioBoxItem.selectorView.bridge_accessibilityValueBlock = { +// guard let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"), +// let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index + 1)) else { return ""} +// return String(format: format, indexString, total) +// } +// } +// } +// } + + open override func setup() { + super.setup() + } - - open func updateAccessibilityValue(collectionView: UICollectionView, cell: RadioBoxCollectionViewCell, indexPath: IndexPath) { - guard let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"), - let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: indexPath.row + 1)) else { return } - let total = self.collectionView(collectionView, numberOfItemsInSection: indexPath.section) - cell.accessibilityValue = String(format: format, indexString, total) - } - - // MARK: - MVMCoreViewProtocol - open override func setupView() { - super.setupView() - collectionView = createCollectionView() - addSubview(collectionView) - NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) - collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) - collectionViewHeight?.isActive = true - } - + // MARK: - MoleculeViewProtocol - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - self.delegateObject = delegateObject + public func viewModelDidUpdate() { + boxes = viewModel.boxes + selectorModels = viewModel.selectorModels + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - guard let model = model as? RadioBoxesModel else { return } - boxes = model.boxes - FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) + } + + open func updateView(_ size: CGFloat) {} + + open override func didSelect(_ selectedControl: RadioBoxItem) { + super.didSelect(selectedControl) - backgroundColor = model.backgroundColor?.uiColor - - registerCells() - setHeight() - collectionView.reloadData() - } - - @objc override open func updateView(_ size: CGFloat) { - super.updateView(size) - self.size = size - itemSpacing = Padding.Component.gutterFor(size: size) - collectionView.updateView(size) - } - - // MARK: - Creation - - /// Creates the layout for the collection. - open func createCollectionViewLayout() -> UICollectionViewLayout { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .vertical - layout.minimumLineSpacing = itemSpacing - layout.minimumInteritemSpacing = itemSpacing - return layout - } - - /// Creates the collection view. - open func createCollectionView() -> CollectionView { - let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) - collection.dataSource = self - collection.delegate = self - return collection - } - - /// Registers the cells with the collection view - open func registerCells() { - collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell") - } - - // MARK: - JSON Setters - open func setHeight() { - guard let boxes = boxes, boxes.count > 0 else { - collectionViewHeight.constant = 0 - return + // since the boxes has the state being tracked, we need to update the values here. + if let index = items.firstIndex(where: {$0 === selectedControl}), let selectedBox = boxes?[index] { + boxes?.forEach {$0.selected = false } + selectedBox.selected = true + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + radioDelegate?.selected(radioBox: selectedBox) } - - // Calculate the height - let rows = ceil(CGFloat(boxes.count) / numberOfColumns) - let height = (rows * boxHeight) + ((rows - 1) * itemSpacing) - collectionViewHeight?.constant = height - } -} - -extension RadioBoxes: UICollectionViewDelegateFlowLayout { - open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth: CGFloat = (collectionView.bounds.width - itemSpacing) / numberOfColumns - return CGSize(width: itemWidth, height: boxHeight) - } -} - -extension RadioBoxes: UICollectionViewDataSource { - open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return boxes?.count ?? 0 - } - - open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let molecule = boxes?[indexPath.row], - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { - fatalError() - } - cell.reset() - cell.radioBox.isUserInteractionEnabled = false - - if let color = radioBoxesModel?.boxesColor { - cell.radioBox.backgroundColor = color.uiColor - } - if let color = radioBoxesModel?.selectedAccentColor { - cell.radioBox.accentColor = color.uiColor - } - - cell.set(with: molecule, delegateObject, nil) - cell.updateView(size ?? collectionView.bounds.width) - if molecule.selected { - collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically) - } - updateAccessibilityValue(collectionView: collectionView, cell: cell, indexPath: indexPath) - cell.layoutIfNeeded() - return cell - } -} - -extension RadioBoxes: UICollectionViewDelegate { - open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { - guard let molecule = boxes?[indexPath.row] else { return false } - return molecule.enabled - } - - open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } - cell.radioBox.selectBox() - _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) - cell.updateAccessibility() - guard let radioBox = boxes?[indexPath.row] else { return } - radioDelegate?.selected(radioBox: radioBox) - } - - open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { - guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } - cell.radioBox.deselectBox() - cell.updateAccessibility() } } From 144efea342b4b32df1b43103dd1d1b29c6366704 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 12:54:50 -0500 Subject: [PATCH 09/56] added inverted Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift index 190d3a01..ca6f1e52 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift @@ -7,6 +7,7 @@ // import Foundation +import VDS @objcMembers open class FormFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol { @@ -28,6 +29,9 @@ import Foundation public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? + + public var inverted: Bool = false + public var surface: Surface { inverted ? .dark : .light } public var dynamicErrorMessage: String? { didSet { @@ -66,6 +70,7 @@ import Foundation case required case fieldKey case groupName + case inverted } //-------------------------------------------------- @@ -103,6 +108,10 @@ import Foundation readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) ?? FormValidator.defaultGroupName + + if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { + self.inverted = inverted + } } open func encode(to encoder: Encoder) throws { @@ -116,5 +125,6 @@ import Foundation try container.encode(readOnly, forKey: .readOnly) try container.encode(enabled, forKey: .enabled) try container.encode(required, forKey: .required) + try container.encode(inverted, forKey: .inverted) } } From 8bf4a28b0fd55be2d16fea3e5597f3a0c682fad7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 14:36:47 -0500 Subject: [PATCH 10/56] added extension Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Views/TooltipModel.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/TooltipModel.swift b/MVMCoreUI/Atomic/Atoms/Views/TooltipModel.swift index 2b12cead..974cca8c 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/TooltipModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/TooltipModel.swift @@ -96,3 +96,17 @@ open class TooltipModel: MoleculeModelProtocol { } } + +extension TooltipModel { + public func toVDSTooltipModel() -> Tooltip.TooltipModel { + var moleculeView: MoleculeViewProtocol? + if let molecule, let view = ModelRegistry.createMolecule(molecule) { + moleculeView = view + } + return .init(closeButtonText: closeButtonText, + title: title, + content: content, + contentView: moleculeView + ) + } +} From 981bdac2922f2eb1505cce6b9a9db8844e1d0e30 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 14:37:04 -0500 Subject: [PATCH 11/56] refactored to use VDS Dropdown Select Signed-off-by: Matt Bruce --- .../ItemDropdownEntryField.swift | 143 ++++++++++-------- .../ItemDropdownEntryFieldModel.swift | 12 +- 2 files changed, 91 insertions(+), 64 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift index 6f69be43..734d39e8 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift @@ -7,19 +7,41 @@ // import UIKit +import VDS +open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol { + //------------------------------------------------------ + // MARK: - Properties + //------------------------------------------------------ + open var viewModel: ItemDropdownEntryFieldModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + + // Form Validation + var fieldKey: String? + var fieldValue: JSONValue? + var groupName: String? + + open var pickerData: [String] = [] { + didSet { + options = pickerData.compactMap({ DropdownOptionModel(text: $0) }) + } + } -open class ItemDropdownEntryField: BaseItemPickerEntryField { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + public var isValid: Bool = false + + /// Closure passed here will run as picker changes items. + public var observeDropdownChange: ((String?, String) -> ())? - open var pickerData: [String] = [] - - public var itemDropdownEntryFieldModel: ItemDropdownEntryFieldModel? { - model as? ItemDropdownEntryFieldModel - } + /// Closure passed here will run upon dismissing the selection picker. + public var observeDropdownSelection: ((String) -> ())? + /// When selecting for first responder, allow initial selected value to appear in empty text field. + public var setInitialValueInTextField = true + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -28,7 +50,7 @@ open class ItemDropdownEntryField: BaseItemPickerEntryField { super.init(frame: frame) } - @objc public convenience init() { + @objc public convenience required init() { self.init(frame: .zero) } @@ -40,14 +62,36 @@ open class ItemDropdownEntryField: BaseItemPickerEntryField { @objc required public init?(coder: NSCoder) { fatalError("ItemDropdownEntryField init(coder:) has not been implemented") } - - required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.init(model: model, delegateObject, additionalData) - } - + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- + open override func setup() { + super.setup() + publisher(for: .valueChanged) + .sink { [weak self] control in + guard let self, let selectedItem else { return } + viewModel.selectedIndex = control.selectId + observeDropdownSelection?(selectedItem.text) + if let valid = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) { + isValid = valid + } + }.store(in: &subscribers) + + dropdownField + .publisher(for: .editingDidBegin) + .sink { [weak self] textField in + guard let self else { return } + setInitialValueFromPicker() + }.store(in: &subscribers) + + dropdownField + .publisher(for: .editingDidEnd) + .sink { [weak self] textField in + guard let self else { return } + performDropdownAction() + }.store(in: &subscribers) + } /// Sets the textField with the first value of the available picker data. @objc private func setInitialValueFromPicker() { @@ -55,61 +99,36 @@ open class ItemDropdownEntryField: BaseItemPickerEntryField { guard !pickerData.isEmpty else { return } if setInitialValueInTextField { - let pickerIndex = pickerView.selectedRow(inComponent: 0) - itemDropdownEntryFieldModel?.selectedIndex = pickerIndex - observeDropdownChange?(text, pickerData[pickerIndex]) - text = pickerData[pickerIndex] + let pickerIndex = optionsPicker.selectedRow(inComponent: 0) + viewModel.selectedIndex = pickerIndex + selectId = pickerIndex + observeDropdownChange?(selectedItem?.text, pickerData[pickerIndex]) } } - @objc override func startEditing() { - super.startEditing() - - setInitialValueFromPicker() + public func viewModelDidUpdate() { + pickerData = viewModel.options + labelText = viewModel.title + helperText = viewModel.feedback + isEnabled = viewModel.enabled + isReadOnly = viewModel.readOnly + isRequired = viewModel.required + isSelected = viewModel.selected ?? false + tooltipModel = viewModel.tooltip?.toVDSTooltipModel() + if let index = viewModel.selectedIndex { + selectId = index + optionsPicker.selectRow(index, inComponent: 0, animated: false) + pickerView(optionsPicker, didSelectRow: index, inComponent: 0) + } + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) } - @objc override func endInputing() { - super.endInputing() - - guard !pickerData.isEmpty else { return } - - observeDropdownSelection?(pickerData[pickerView.selectedRow(inComponent: 0)]) - } - - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - - guard let model = model as? ItemDropdownEntryFieldModel else { return } - - pickerData = model.options - - if let index = model.selectedIndex { - self.pickerView.selectRow(index, inComponent: 0, animated: false) - self.pickerView(pickerView, didSelectRow: index, inComponent: 0) - } + func performDropdownAction() { + guard let actionModel = viewModel.action, + !dropdownField.isFirstResponder + else { return } + MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel, additionalData: additionalData, delegateObject: delegateObject) } - //-------------------------------------------------- - // MARK: - Picker Delegate - //-------------------------------------------------- - - @objc public override func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 } - - @objc public override func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - pickerData.count - } - - @objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - guard !pickerData.isEmpty else { return nil } - - return pickerData[row] - } - - @objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - guard !pickerData.isEmpty else { return } - - itemDropdownEntryFieldModel?.selectedIndex = row - observeDropdownChange?(text, pickerData[row]) - text = pickerData[row] - } + public func updateView(_ size: CGFloat) { } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift index 1c88ddab..131c2123 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift @@ -5,16 +5,18 @@ // Created by Kevin Christiano on 1/22/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import VDS -@objcMembers open class ItemDropdownEntryFieldModel: BaseItemPickerEntryFieldModel { +@objcMembers open class ItemDropdownEntryFieldModel: TextEntryFieldModel { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public override class var identifier: String { "dropDown" } - + public var action: ActionModelProtocol? public var options: [String] = [] public var selectedIndex: Int? + public var tooltip: TooltipModel? public init(with options: [String], selectedIndex: Int? = nil) { self.options = options @@ -42,6 +44,8 @@ private enum CodingKeys: String, CodingKey { case options case selectedIndex + case action + case tooltip } //-------------------------------------------------- @@ -58,6 +62,8 @@ self.selectedIndex = selectedIndex baseValue = options.indices.contains(selectedIndex) ? options[selectedIndex] : nil } + action = try typeContainer.decodeModelIfPresent(codingKey: .action) + tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) } public override func encode(to encoder: Encoder) throws { @@ -65,5 +71,7 @@ var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(options, forKey: .options) try container.encodeIfPresent(selectedIndex, forKey: .selectedIndex) + try container.encodeModelIfPresent(action, forKey: .action) + try container.encodeIfPresent(tooltip, forKey: .tooltip) } } From 79d552999165b26d32de0b2d1c2618b710b80197 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 14:51:10 -0500 Subject: [PATCH 12/56] added delegates Signed-off-by: Matt Bruce --- .../ItemDropdownEntryField.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift index 734d39e8..51504dc8 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift @@ -9,7 +9,7 @@ import UIKit import VDS -open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol { +open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, ObservingTextFieldDelegate { //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ @@ -42,6 +42,23 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol { /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true + //-------------------------------------------------- + // MARK: - Delegate Properties + //-------------------------------------------------- + + /// The delegate and block for validation. Validates if the text that the user has entered. + public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? + + /// If you're using a ViewController, you must set this to it + open weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { dropdownField.delegate } + set { dropdownField.delegate = newValue } + } + + @objc public func dismissFieldInput(_ sender: Any?) { + _ = resignFirstResponder() + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- From a4b550cf0394cf811931f44a90f2e7a4ed6d100f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 10:19:01 -0500 Subject: [PATCH 13/56] first cut of textEntry Signed-off-by: Matt Bruce --- .../TextFields/TextViewEntryField.swift | 316 ++++++------------ .../TextFields/TextViewEntryFieldModel.swift | 29 +- 2 files changed, 113 insertions(+), 232 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 46e4e233..415f1bb4 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -7,123 +7,63 @@ // import UIKit +import VDS - -class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDelegate { - //-------------------------------------------------- - // MARK: - Outlets - //-------------------------------------------------- - - open private(set) var textView: TextView = { - let textView = TextView() - textView.setContentCompressionResistancePriority(.required, for: .vertical) - return textView - }() - - //-------------------------------------------------- +class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFieldDelegate { + //------------------------------------------------------ // MARK: - Properties - //-------------------------------------------------- + //------------------------------------------------------ + open var viewModel: TextViewEntryFieldModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + + // Form Validation + var fieldKey: String? + var fieldValue: JSONValue? + var groupName: String? - private var observingForChange: Bool = false + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + public var isValid: Bool = true + + private var isEditting: Bool = false { + didSet { + viewModel.selected = isEditting + } + } //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- - - public var textViewEntryFieldModel: TextViewEntryFieldModel? { - model as? TextViewEntryFieldModel - } - - public override var isEnabled: Bool { - get { super.isEnabled } - set (enabled) { - super.isEnabled = enabled - - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.textView.isEnabled = enabled - if self.textView.isShowingPlaceholder { - self.textView.textColor = self.textView.placeholderTextColor - } else { - self.textView.textColor = (self.textView.isEnabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor - } - } - } - } - - public override var showError: Bool { - get { super.showError } - set (error) { - - if error { - textView.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textView_error_message") ?? "", textView.text ?? "", entryFieldModel?.errorMessage ?? "") - } else { - textView.accessibilityValue = nil - } - - super.showError = error - } - } - /// The text of this textView. open override var text: String? { - get { textViewEntryFieldModel?.text } - set { - textView.text = newValue - textViewEntryFieldModel?.text = newValue + didSet { + viewModel?.text = text } } /// Placeholder access for the textView. public var placeholder: String? { - get { textViewEntryFieldModel?.placeholder } + get { viewModel?.placeholder } set { textView.placeholder = newValue ?? "" - textViewEntryFieldModel?.placeholder = newValue - textView.setPlaceholderIfAvailable() + viewModel?.placeholder = newValue } } - //-------------------------------------------------- - // MARK: - Constraint - //-------------------------------------------------- - - public var heightConstraint: NSLayoutConstraint? - private var topConstraint: NSLayoutConstraint? - private var leadingConstraint: NSLayoutConstraint? - private var trailingConstraint: NSLayoutConstraint? - private var bottomConstraint: NSLayoutConstraint? - - private func adjustMarginConstraints(constant: CGFloat) { - - topConstraint?.constant = constant - leadingConstraint?.constant = constant - trailingConstraint?.constant = constant - bottomConstraint?.constant = constant + override var errorText: String? { + get { + viewModel.dynamicErrorMessage ?? viewModel.errorMessage + } + set {} } - //-------------------------------------------------- // MARK: - Delegate Properties //-------------------------------------------------- /// The delegate and block for validation. Validates if the text that the user has entered. - public weak var observingTextViewDelegate: ObservingTextFieldDelegate? { - didSet { - if observingTextViewDelegate != nil && !observingForChange { - observingForChange = true - NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextView.textDidChangeNotification, object: textView) - NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextView.textDidEndEditingNotification, object: textView) - NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextView.textDidBeginEditingNotification, object: textView) - - } else if observingTextViewDelegate == nil && observingForChange { - observingForChange = false - NotificationCenter.default.removeObserver(self, name: UITextView.textDidChangeNotification, object: textView) - NotificationCenter.default.removeObserver(self, name: UITextView.textDidEndEditingNotification, object: textView) - NotificationCenter.default.removeObserver(self, name: UITextView.textDidBeginEditingNotification, object: textView) - } - } - } + public weak var observingTextViewDelegate: ObservingTextFieldDelegate? /// If you're using a ViewController, you must set this to it public weak var uiTextViewDelegate: UITextViewDelegate? { @@ -136,102 +76,49 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele uiTextViewDelegate = delegate } - open func setupTextViewToolbar() { - let observingDelegate = observingTextViewDelegate ?? self - textView.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate, - action: #selector(observingDelegate.dismissFieldInput)) - } - //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- - - @objc open override func setupFieldContainerContent(_ container: UIView) { + override func setup() { + super.setup() - container.addSubview(textView) + publisher(for: .valueChanged) + .sink { [weak self] control in + guard let self else { return } + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + }.store(in: &subscribers) - topConstraint = textView.topAnchor.constraint(equalTo: container.topAnchor, constant: Padding.Three) - leadingConstraint = textView.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: Padding.Three) - trailingConstraint = container.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: Padding.Three) - bottomConstraint = container.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: Padding.Three) + textView + .publisher(for: .editingDidBegin) + .sink { [weak self] textView in + guard let self else { return } + isEditting = true + + }.store(in: &subscribers) - topConstraint?.isActive = true - leadingConstraint?.isActive = true - trailingConstraint?.isActive = true - bottomConstraint?.isActive = true - - heightConstraint = textView.heightAnchor.constraint(equalToConstant: 0) - accessibilityElements = [textView] + textView + .publisher(for: .editingDidEnd) + .sink { [weak self] textView in + guard let self else { return } + isEditting = false + if let valid = viewModel.isValid { + isValid = valid + } + showError = !isValid + + }.store(in: &subscribers) + + //String(format: MVMCoreUIUtility.hardcodedString(withKey: "textView_error_message") ?? "", textView.text ?? "", entryFieldModel?.errorMessage ?? "") } - - open override func updateView(_ size: CGFloat) { - super.updateView(size) - textView.updateView(size) - } - - open override func reset() { - super.reset() - textView.reset() - adjustMarginConstraints(constant: Padding.Three) - heightConstraint?.constant = 0 - heightConstraint?.isActive = false - } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - /// Validates the text of the entry field. - @objc public override func validateText() { - text = textView.text - super.validateText() - } - - /// Executes on UITextView.textDidBeginEditingNotification - @objc override func startEditing() { - super.startEditing() - _ = textView.becomeFirstResponder() - } - - /// Executes on UITextView.textDidChangeNotification (each character entry) - @objc override func valueChanged() { - super.valueChanged() - validateText() - } - - /// Executes on UITextView.textDidEndEditingNotification - @objc override func endInputing() { - super.endInputing() - - // Don't show error till user starts typing. - guard text?.count ?? 0 != 0 else { - showError = false - return - } - - if let isValid = textViewEntryFieldModel?.isValid { - self.isValid = isValid - } - - showError = !isValid - } - //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- + open func updateView(_ size: CGFloat) {} + + open func viewModelDidUpdate() { - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - - guard let model = model as? TextViewEntryFieldModel else { return } - - if let height = model.height { - heightConstraint?.constant = height - heightConstraint?.isActive = true - } - - text = model.text + text = viewModel.text uiTextViewDelegate = delegateObject?.uiTextViewDelegate observingTextViewDelegate = delegateObject?.observingTextFieldDelegate @@ -239,17 +126,12 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele accessibilityLabel = accessibilityText } - textView.isEditable = model.editable - textView.textAlignment = model.textAlignment + textView.isEditable = viewModel.editable + textView.textAlignment = viewModel.textAlignment textView.accessibilityIdentifier = model.accessibilityIdentifier - textView.textColor = model.enabled ? model.enabledTextColor.uiColor : model.disabledTextColor.uiColor - textView.font = model.fontStyle.getFont() - textView.placeholder = model.placeholder ?? "" - textView.placeholderFontStyle = model.placeholderFontStyle - textView.placeholderTextColor = model.placeholderTextColor.uiColor - textView.setPlaceholderIfAvailable() + textView.placeholder = viewModel.placeholder ?? "" - switch model.type { + switch viewModel.type { case .secure, .password: textView.isSecureTextEntry = true @@ -266,40 +148,60 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele default: break } + if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected { + + viewModel.wasInitiallySelected = true + isEditting = true + } + /// No point in configuring if the TextView is Read-only. if textView.isEditable { - FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) - setupTextViewToolbar() + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - if isSelected { + if isEditting { DispatchQueue.main.async { - _ = self.textView.becomeFirstResponder() + _ = self.becomeFirstResponder() } } } - if model.hideBorders { - adjustMarginConstraints(constant: 0) + viewModel.updateUI = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + + if isEditting { + updateValidation(viewModel.isValid ?? true) + + } else if viewModel.isValid ?? true && showError { + showError = false + } + isEnabled = viewModel.enabled + }) } - updateAccessibility(model: model) + + viewModel.updateUIDynamicError = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + + let validState = viewModel.isValid ?? false + if !validState && viewModel.shouldClearText { + text = "" + viewModel.shouldClearText = false + } + updateValidation(validState) + }) + } + } - func updateAccessibility(model: TextViewEntryFieldModel) { + private func updateValidation(_ isValid: Bool) { + let previousValidity = self.isValid + self.isValid = isValid - var message = "" - - if let titleText = model.accessibilityText ?? model.title { - message += "\(titleText) \( model.enabled ? String(format: (MVMCoreUIUtility.hardcodedString(withKey: "textfield_optional")) ?? "") : "" ) \(self.textView.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" + if previousValidity && !isValid { + showError = true + } else if (!previousValidity && isValid) { + showError = false } - - if let feedback = model.feedback { - message += ", " + feedback - } - - if let errorMessage = errorLabel.text { - message += ", " + errorMessage - } - - textView.accessibilityLabel = message } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift index 42da3ca2..fa941d10 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift @@ -17,12 +17,9 @@ class TextViewEntryFieldModel: TextEntryFieldModel { public override class var identifier: String { "textView" } public var accessibilityText: String? - public var fontStyle: Styler.Font = Styler.Font.RegularBodyLarge - public var height: CGFloat? - public var placeholderTextColor: Color = Color(uiColor: .mvmCoolGray3) - public var placeholderFontStyle: Styler.Font = Styler.Font.RegularMicro public var editable: Bool = true public var showsPlaceholder: Bool = false + public var tooltip: TooltipModel? //-------------------------------------------------- // MARK: - Keys @@ -30,11 +27,8 @@ class TextViewEntryFieldModel: TextEntryFieldModel { private enum CodingKeys: String, CodingKey { case accessibilityText - case fontStyle - case height - case placeholderFontStyle - case placeholderTextColor case editable + case tooltip } //-------------------------------------------------- @@ -45,34 +39,19 @@ class TextViewEntryFieldModel: TextEntryFieldModel { try super.init(from: decoder) let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - if let placeholderFontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .placeholderFontStyle) { - self.placeholderFontStyle = placeholderFontStyle - } - - if let placeholderTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .placeholderTextColor) { - self.placeholderTextColor = placeholderTextColor - } - - if let fontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .fontStyle) { - self.fontStyle = fontStyle - } - if let editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) { self.editable = editable } accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) - height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height) + tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) - try container.encodeIfPresent(height, forKey: .height) - try container.encode(fontStyle, forKey: .fontStyle) try container.encode(editable, forKey: .editable) - try container.encode(placeholderFontStyle, forKey: .placeholderFontStyle) - try container.encode(placeholderTextColor, forKey: .placeholderTextColor) + try container.encodeIfPresent(tooltip, forKey: .tooltip) } } From b1c59124a7237aa9c0fe804cca356d5426be6d45 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 11:20:17 -0500 Subject: [PATCH 14/56] fixed bugs in validation to match textViewentryfield Signed-off-by: Matt Bruce --- .../ItemDropdownEntryField.swift | 115 ++++++++++++++---- 1 file changed, 89 insertions(+), 26 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift index 51504dc8..ad38f476 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift @@ -27,11 +27,13 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, options = pickerData.compactMap({ DropdownOptionModel(text: $0) }) } } + + private var isEditting: Bool = false //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var isValid: Bool = false + public var isValid: Bool = true /// Closure passed here will run as picker changes items. public var observeDropdownChange: ((String?, String) -> ())? @@ -42,6 +44,12 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, /// When selecting for first responder, allow initial selected value to appear in empty text field. public var setInitialValueInTextField = true + open override var errorText: String? { + get { + viewModel.dynamicErrorMessage ?? viewModel.errorMessage + } + set {} + } //-------------------------------------------------- // MARK: - Delegate Properties //-------------------------------------------------- @@ -90,15 +98,14 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, guard let self, let selectedItem else { return } viewModel.selectedIndex = control.selectId observeDropdownSelection?(selectedItem.text) - if let valid = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) { - isValid = valid - } - }.store(in: &subscribers) + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + }.store(in: &subscribers) dropdownField .publisher(for: .editingDidBegin) .sink { [weak self] textField in guard let self else { return } + isEditting = true setInitialValueFromPicker() }.store(in: &subscribers) @@ -106,12 +113,76 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, .publisher(for: .editingDidEnd) .sink { [weak self] textField in guard let self else { return } + isEditting = false + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + if let valid = viewModel.isValid { + updateValidation(valid) + } performDropdownAction() }.store(in: &subscribers) } + public func viewModelDidUpdate() { + pickerData = viewModel.options + labelText = viewModel.title + helperText = viewModel.feedback + isEnabled = viewModel.enabled + isReadOnly = viewModel.readOnly + isRequired = viewModel.required + tooltipModel = viewModel.tooltip?.toVDSTooltipModel() + + if let index = viewModel.selectedIndex { + selectId = index + optionsPicker.selectRow(index, inComponent: 0, animated: false) + pickerView(optionsPicker, didSelectRow: index, inComponent: 0) + } + + if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected { + + viewModel.wasInitiallySelected = true + isEditting = true + } + + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) + if isEditting { + DispatchQueue.main.async { + _ = self.becomeFirstResponder() + } + } + + viewModel.updateUI = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + + if isEditting { + updateValidation(viewModel.isValid ?? true) + + } else if viewModel.isValid ?? true && showError { + showError = false + } + isEnabled = viewModel.enabled + }) + } + + viewModel.updateUIDynamicError = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + + let validState = viewModel.isValid ?? false + if !validState && viewModel.shouldClearText { + selectId = nil + viewModel.shouldClearText = false + } + updateValidation(validState) + }) + } + + } + + public func updateView(_ size: CGFloat) { } + /// Sets the textField with the first value of the available picker data. - @objc private func setInitialValueFromPicker() { + private func setInitialValueFromPicker() { guard !pickerData.isEmpty else { return } @@ -123,29 +194,21 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, } } - public func viewModelDidUpdate() { - pickerData = viewModel.options - labelText = viewModel.title - helperText = viewModel.feedback - isEnabled = viewModel.enabled - isReadOnly = viewModel.readOnly - isRequired = viewModel.required - isSelected = viewModel.selected ?? false - tooltipModel = viewModel.tooltip?.toVDSTooltipModel() - if let index = viewModel.selectedIndex { - selectId = index - optionsPicker.selectRow(index, inComponent: 0, animated: false) - pickerView(optionsPicker, didSelectRow: index, inComponent: 0) - } - FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - } - - func performDropdownAction() { + private func performDropdownAction() { guard let actionModel = viewModel.action, !dropdownField.isFirstResponder else { return } MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel, additionalData: additionalData, delegateObject: delegateObject) } - - public func updateView(_ size: CGFloat) { } + + private func updateValidation(_ isValid: Bool) { + let previousValidity = self.isValid + self.isValid = isValid + + if previousValidity && !isValid { + showError = true + } else if (!previousValidity && isValid) { + showError = false + } + } } From 9e9a1ab853e497a28f64856e3fdc89640fd686b3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 11:23:14 -0500 Subject: [PATCH 15/56] added properties from model Signed-off-by: Matt Bruce --- .../FormFields/TextFields/TextViewEntryField.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 415f1bb4..21eb169f 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -102,10 +102,9 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi guard let self else { return } isEditting = false if let valid = viewModel.isValid { - isValid = valid + updateValidation(valid) } - showError = !isValid - + }.store(in: &subscribers) //String(format: MVMCoreUIUtility.hardcodedString(withKey: "textView_error_message") ?? "", textView.text ?? "", entryFieldModel?.errorMessage ?? "") @@ -119,6 +118,13 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi open func viewModelDidUpdate() { text = viewModel.text + labelText = viewModel.title + helperText = viewModel.feedback + isEnabled = viewModel.enabled + isReadOnly = viewModel.readOnly + isRequired = viewModel.required + tooltipModel = viewModel.tooltip?.toVDSTooltipModel() + uiTextViewDelegate = delegateObject?.uiTextViewDelegate observingTextViewDelegate = delegateObject?.observingTextFieldDelegate From c7a4bf72c31c200f1798220148835921843a9f80 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:00:54 -0500 Subject: [PATCH 16/56] added more propeties Signed-off-by: Matt Bruce --- .../TextFields/TextViewEntryField.swift | 2 ++ .../TextFields/TextViewEntryFieldModel.swift | 15 ++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 21eb169f..382c418c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -124,6 +124,8 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi isReadOnly = viewModel.readOnly isRequired = viewModel.required tooltipModel = viewModel.tooltip?.toVDSTooltipModel() + width = viewModel.width + transparentBackground = viewModel.transparentBackground uiTextViewDelegate = delegateObject?.uiTextViewDelegate observingTextViewDelegate = delegateObject?.observingTextFieldDelegate diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift index fa941d10..cfcef654 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift @@ -20,7 +20,9 @@ class TextViewEntryFieldModel: TextEntryFieldModel { public var editable: Bool = true public var showsPlaceholder: Bool = false public var tooltip: TooltipModel? - + public var transparentBackground: Bool = false + public var width: CGFloat? + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -29,6 +31,8 @@ class TextViewEntryFieldModel: TextEntryFieldModel { case accessibilityText case editable case tooltip + case transparentBackground + case width } //-------------------------------------------------- @@ -39,12 +43,11 @@ class TextViewEntryFieldModel: TextEntryFieldModel { try super.init(from: decoder) let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - if let editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) { - self.editable = editable - } - + editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) ?? true accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) + transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false + width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) } public override func encode(to encoder: Encoder) throws { @@ -53,5 +56,7 @@ class TextViewEntryFieldModel: TextEntryFieldModel { try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encode(editable, forKey: .editable) try container.encodeIfPresent(tooltip, forKey: .tooltip) + try container.encode(transparentBackground, forKey: .transparentBackground) + try container.encodeIfPresent(width, forKey: .width) } } From 7ec685136b271e003d2362449540014fd005fa49 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:22:28 -0500 Subject: [PATCH 17/56] added more properties Signed-off-by: Matt Bruce --- .../FormFields/TextFields/TextViewEntryField.swift | 3 +++ .../TextFields/TextViewEntryFieldModel.swift | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 382c418c..9016ae5c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -118,6 +118,9 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi open func viewModelDidUpdate() { text = viewModel.text + minHeight = viewModel.minHeight + maxLength = viewModel.maxLength + labelText = viewModel.title helperText = viewModel.feedback isEnabled = viewModel.enabled diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift index cfcef654..fb1c2167 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift @@ -7,7 +7,7 @@ // import UIKit - +import VDS class TextViewEntryFieldModel: TextEntryFieldModel { //-------------------------------------------------- @@ -20,9 +20,11 @@ class TextViewEntryFieldModel: TextEntryFieldModel { public var editable: Bool = true public var showsPlaceholder: Bool = false public var tooltip: TooltipModel? + public var minHeight: VDS.TextArea.Height = .twoX + public var maxLength: Int? public var transparentBackground: Bool = false public var width: CGFloat? - + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -30,6 +32,8 @@ class TextViewEntryFieldModel: TextEntryFieldModel { private enum CodingKeys: String, CodingKey { case accessibilityText case editable + case minHeight + case maxLength case tooltip case transparentBackground case width @@ -44,6 +48,8 @@ class TextViewEntryFieldModel: TextEntryFieldModel { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) ?? true + minHeight = try typeContainer.decodeIfPresent(VDS.TextArea.Height.self, forKey: .minHeight) ?? .twoX + maxLength = try typeContainer.decodeIfPresent(Int.self, forKey: .maxLength) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false @@ -55,6 +61,8 @@ class TextViewEntryFieldModel: TextEntryFieldModel { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encode(editable, forKey: .editable) + try container.encode(minHeight, forKey: .minHeight) + try container.encodeIfPresent(maxLength, forKey: .maxLength) try container.encodeIfPresent(tooltip, forKey: .tooltip) try container.encode(transparentBackground, forKey: .transparentBackground) try container.encodeIfPresent(width, forKey: .width) From be5a69136569601652c2077c8e45377119ac36a1 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:41:45 -0500 Subject: [PATCH 18/56] added more properties Signed-off-by: Matt Bruce --- .../Item Dropdown/ItemDropdownEntryField.swift | 3 +++ .../Item Dropdown/ItemDropdownEntryFieldModel.swift | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift index ad38f476..456000d3 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift @@ -129,7 +129,10 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, isEnabled = viewModel.enabled isReadOnly = viewModel.readOnly isRequired = viewModel.required + showInlineLabel = viewModel.showInlineLabel tooltipModel = viewModel.tooltip?.toVDSTooltipModel() + width = viewModel.width + transparentBackground = viewModel.transparentBackground if let index = viewModel.selectedIndex { selectId = index diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift index 131c2123..4ff79af8 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift @@ -16,7 +16,11 @@ import VDS public var action: ActionModelProtocol? public var options: [String] = [] public var selectedIndex: Int? + public var showInlineLabel: Bool = false + public var feedbackTextPlacement: VDS.EntryFieldBase.HelperTextPlacement = .bottom public var tooltip: TooltipModel? + public var transparentBackground: Bool = false + public var width: CGFloat? public init(with options: [String], selectedIndex: Int? = nil) { self.options = options @@ -45,7 +49,10 @@ import VDS case options case selectedIndex case action + case showInlineLabel case tooltip + case transparentBackground + case width } //-------------------------------------------------- @@ -62,8 +69,11 @@ import VDS self.selectedIndex = selectedIndex baseValue = options.indices.contains(selectedIndex) ? options[selectedIndex] : nil } + showInlineLabel = try typeContainer.decodeIfPresent(Bool.self, forKey: .showInlineLabel) ?? false action = try typeContainer.decodeModelIfPresent(codingKey: .action) tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) + transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false + width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) } public override func encode(to encoder: Encoder) throws { @@ -71,7 +81,10 @@ import VDS var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(options, forKey: .options) try container.encodeIfPresent(selectedIndex, forKey: .selectedIndex) + try container.encode(showInlineLabel, forKey: .showInlineLabel) try container.encodeModelIfPresent(action, forKey: .action) try container.encodeIfPresent(tooltip, forKey: .tooltip) + try container.encode(transparentBackground, forKey: .transparentBackground) + try container.encodeIfPresent(width, forKey: .width) } } From 4c283121915cc646e7c99abd912218fbdade0885 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:47:31 -0500 Subject: [PATCH 19/56] updated helper text placement Signed-off-by: Matt Bruce --- .../Item Dropdown/ItemDropdownEntryField.swift | 5 ++++- .../Item Dropdown/ItemDropdownEntryFieldModel.swift | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift index 456000d3..0986a092 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift @@ -93,6 +93,8 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, //-------------------------------------------------- open override func setup() { super.setup() + useRequiredRule = false + publisher(for: .valueChanged) .sink { [weak self] control in guard let self, let selectedItem else { return } @@ -124,12 +126,13 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, public func viewModelDidUpdate() { pickerData = viewModel.options + showInlineLabel = viewModel.showInlineLabel + helperTextPlacement = viewModel.feedbackTextPlacement labelText = viewModel.title helperText = viewModel.feedback isEnabled = viewModel.enabled isReadOnly = viewModel.readOnly isRequired = viewModel.required - showInlineLabel = viewModel.showInlineLabel tooltipModel = viewModel.tooltip?.toVDSTooltipModel() width = viewModel.width transparentBackground = viewModel.transparentBackground diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift index 4ff79af8..eb83a48a 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift @@ -50,6 +50,7 @@ import VDS case selectedIndex case action case showInlineLabel + case feedbackTextPlacement case tooltip case transparentBackground case width @@ -70,6 +71,7 @@ import VDS baseValue = options.indices.contains(selectedIndex) ? options[selectedIndex] : nil } showInlineLabel = try typeContainer.decodeIfPresent(Bool.self, forKey: .showInlineLabel) ?? false + feedbackTextPlacement = try typeContainer.decodeIfPresent(VDS.EntryFieldBase.HelperTextPlacement.self, forKey: .feedbackTextPlacement) ?? .bottom action = try typeContainer.decodeModelIfPresent(codingKey: .action) tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false @@ -82,6 +84,7 @@ import VDS try container.encode(options, forKey: .options) try container.encodeIfPresent(selectedIndex, forKey: .selectedIndex) try container.encode(showInlineLabel, forKey: .showInlineLabel) + try container.encode(feedbackTextPlacement, forKey: .feedbackTextPlacement) try container.encodeModelIfPresent(action, forKey: .action) try container.encodeIfPresent(tooltip, forKey: .tooltip) try container.encode(transparentBackground, forKey: .transparentBackground) From 0abc903793562c411c8a9b649b51b13327d5e2d5 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:52:53 -0500 Subject: [PATCH 20/56] turn off required Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 9016ae5c..6d5a45d4 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -81,6 +81,8 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi //-------------------------------------------------- override func setup() { super.setup() + //turn off internal required rule + useRequiredRule = false publisher(for: .valueChanged) .sink { [weak self] control in From fedf1760ec9921434c2f482e1b135799f9e7b6a2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 09:33:21 -0500 Subject: [PATCH 21/56] made public/open Signed-off-by: Matt Bruce --- .../TextFields/TextViewEntryField.swift | 67 +++++++++++++------ .../TextFields/TextViewEntryFieldModel.swift | 2 +- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 6d5a45d4..58c3cbc6 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -9,7 +9,7 @@ import UIKit import VDS -class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFieldDelegate { +open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFieldDelegate, ViewMaskingProtocol { //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ @@ -18,9 +18,9 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi open var additionalData: [AnyHashable : Any]? // Form Validation - var fieldKey: String? - var fieldValue: JSONValue? - var groupName: String? + open var fieldKey: String? + open var fieldValue: JSONValue? + open var groupName: String? //-------------------------------------------------- // MARK: - Stored Properties @@ -36,15 +36,12 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- - /// The text of this textView. - open override var text: String? { - didSet { - viewModel?.text = text - } + open var shouldMaskWhileRecording: Bool { + return viewModel.shouldMaskRecordedView ?? false } - + /// Placeholder access for the textView. - public var placeholder: String? { + open var placeholder: String? { get { viewModel?.placeholder } set { textView.placeholder = newValue ?? "" @@ -52,26 +49,34 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi } } - override var errorText: String? { + /// The text of this textView. + open override var text: String? { + didSet { + viewModel?.text = text + } + } + + open override var errorText: String? { get { viewModel.dynamicErrorMessage ?? viewModel.errorMessage } set {} } + //-------------------------------------------------- // MARK: - Delegate Properties //-------------------------------------------------- /// The delegate and block for validation. Validates if the text that the user has entered. - public weak var observingTextViewDelegate: ObservingTextFieldDelegate? + open weak var observingTextViewDelegate: ObservingTextFieldDelegate? /// If you're using a ViewController, you must set this to it - public weak var uiTextViewDelegate: UITextViewDelegate? { + open weak var uiTextViewDelegate: UITextViewDelegate? { get { textView.delegate } set { textView.delegate = newValue } } - @objc public func setBothTextDelegates(to delegate: (UITextViewDelegate & ObservingTextFieldDelegate)?) { + @objc open func setBothTextDelegates(to delegate: (UITextViewDelegate & ObservingTextFieldDelegate)?) { observingTextViewDelegate = delegate uiTextViewDelegate = delegate } @@ -79,7 +84,7 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- - override func setup() { + open override func setup() { super.setup() //turn off internal required rule useRequiredRule = false @@ -135,29 +140,33 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi uiTextViewDelegate = delegateObject?.uiTextViewDelegate observingTextViewDelegate = delegateObject?.observingTextFieldDelegate - if let accessibilityText = model.accessibilityText { + if let accessibilityText = viewModel.accessibilityText { accessibilityLabel = accessibilityText } - + containerView.accessibilityIdentifier = viewModel.accessibilityIdentifier textView.isEditable = viewModel.editable textView.textAlignment = viewModel.textAlignment - textView.accessibilityIdentifier = model.accessibilityIdentifier textView.placeholder = viewModel.placeholder ?? "" switch viewModel.type { case .secure, .password: textView.isSecureTextEntry = true - + textView.shouldMaskWhileRecording = true + case .numberSecure: textView.isSecureTextEntry = true textView.keyboardType = .numberPad - + textView.shouldMaskWhileRecording = true + case .number: textView.keyboardType = .numberPad case .email: textView.keyboardType = .emailAddress +// case .securityCode, .creditCard: +// textView.shouldMaskWhileRecording = true + default: break } @@ -218,3 +227,19 @@ class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFi } } } + +internal struct ViewMasking { + static var shouldMaskWhileRecording: UInt8 = 0 +} + +extension VDS.TextView: ViewMaskingProtocol { + public var shouldMaskWhileRecording: Bool { + get { + return (objc_getAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording) as? Bool) ?? false + } + set { + objc_setAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} + diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift index fb1c2167..fc7dc46e 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift @@ -9,7 +9,7 @@ import UIKit import VDS -class TextViewEntryFieldModel: TextEntryFieldModel { +public class TextViewEntryFieldModel: TextEntryFieldModel { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- From 1b391272c51e83ac31c132e0e4f21be78a63b57c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:45:20 -0500 Subject: [PATCH 22/56] updated model Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift index ca6f1e52..c96a8266 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/FormFieldModel.swift @@ -25,6 +25,7 @@ import VDS public var readOnly: Bool = false public var showError: Bool? public var errorMessage: String? + public var initialErrorMessage: String? public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName @@ -89,9 +90,11 @@ import VDS if let ruleErrorMessage = errorMessage, fieldKey != nil { self.errorMessage = ruleErrorMessage + } else { + self.errorMessage = initialErrorMessage } - self.isValid = valid + isValid = valid updateUI?() } @@ -103,6 +106,7 @@ import VDS id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) + initialErrorMessage = errorMessage enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? true readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false From 716550bf23a6e1c7f0b1799c084272c2c5daabe7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:48:36 -0500 Subject: [PATCH 23/56] added new rule Signed-off-by: Matt Bruce --- MVMCoreUI.xcodeproj/project.pbxproj | 4 ++ .../Rules/Rules/RuleVDSModel.swift | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 39bd239c..25045a2f 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -578,6 +578,7 @@ EA17584A2BC97EF100A5C0D9 /* BadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1758492BC97EF100A5C0D9 /* BadgeIndicatorModel.swift */; }; EA17584C2BC9894800A5C0D9 /* ButtonIconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */; }; EA17584E2BC9895A00A5C0D9 /* ButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */; }; + EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */; }; EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; }; EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; }; EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; @@ -1199,6 +1200,7 @@ EA1758492BC97EF100A5C0D9 /* BadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeIndicatorModel.swift; sourceTree = ""; }; EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconModel.swift; sourceTree = ""; }; EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIcon.swift; sourceTree = ""; }; + EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleVDSModel.swift; sourceTree = ""; }; EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = ""; }; EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = ""; }; EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = ""; }; @@ -1296,6 +1298,7 @@ 011D95A0240453D0000E3791 /* RuleEqualsModel.swift */, 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */, FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */, + EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */, ); name = Rules; path = Rules/Rules; @@ -2836,6 +2839,7 @@ D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */, EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */, 011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */, + EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */, EA985C892981AB7100F2FF2E /* VDS-TextStyle.swift in Sources */, BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */, D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */, diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift new file mode 100644 index 00000000..da818e64 --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift @@ -0,0 +1,59 @@ +// +// RuleVDSModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 7/12/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// + +import Foundation +import VDS + +open class VDSRuleBase: RuleAnyModelProtocol { + open var ruleId: String? + open var errorMessage: [String : String]? + open var fields = [String]() + public init(){} + + open func isValid(_ formField: any FormFieldProtocol) -> Bool { + fatalError() + } + public static var identifier: String = "AnyVDSRule" +} + +public class RuleVDSModel: VDSRuleBase { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var rule: AnyRule + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(field: String, rule: AnyRule) { + self.rule = rule + super.init() + self.fields = [field] + self.ruleId = "\(rule.self)-\(Int.random(in: 1...1000))" + } + + required init(from decoder: any Decoder) throws { + fatalError("init(from:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Validation + //-------------------------------------------------- + + public override func isValid(_ formField: FormFieldProtocol) -> Bool { + let value = formField.formFieldValue() as? ValueType + let valid = rule.isValid(value: value) + if let field = fields.first, valid { + errorMessage = [field: rule.errorMessage] + } else { + errorMessage = nil + } + return valid + } +} From c92532334717274d36c0991ab0193d58e9ac8298 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:48:48 -0500 Subject: [PATCH 24/56] implemented new protocol Signed-off-by: Matt Bruce --- MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift index 7bd05047..8b578fb3 100644 --- a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift @@ -6,7 +6,7 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // // Form fields are items can be interacted with. They have value, and may need to be validated. - +import VDS public protocol FormFieldProtocol: FormItemProtocol { @@ -36,6 +36,21 @@ public extension FormFieldProtocol { } } +public protocol FormFieldInternalValidatableProtocol: FormFieldProtocol { + associatedtype ValueType + var rules: [AnyRule]? { get set } + var internalRules: [RuleAnyModelProtocol]? { get } +} + +extension FormFieldInternalValidatableProtocol { + public var internalRules: [RuleAnyModelProtocol]? { + guard let fieldKey else { return nil } + return rules?.compactMap{ rule in + return RuleVDSModel(field: fieldKey, rule: rule) + } + } +} + public class FormFieldValidity{ public var fieldKey: String public var valid: Bool = true From 05e296713109b8b6f3ca59744ac6d34967674c6a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:48:56 -0500 Subject: [PATCH 25/56] add to validator Signed-off-by: Matt Bruce --- MVMCoreUI/FormUIHelpers/FormValidator.swift | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 3aa50030..b01c7ad4 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -44,6 +44,29 @@ import MVMCore if let fieldKey = field.fieldKey { fields[fieldKey] = field } + // add internal validators if needed + if let field = field as? any FormFieldInternalValidatableProtocol { + addInternalRules(field) + } + } + + /// Adds additional Rules that are from another source + private func addInternalRules(_ field: any FormFieldInternalValidatableProtocol) { + if let internalRules = field.internalRules, !internalRules.isEmpty { + + //find the group + if let formGroup = formRules?.first(where: {$0.groupName == field.groupName}) { + formGroup.rules.append(contentsOf: internalRules) + } else { + //create the new group + let formGroup = FormGroupRule(field.groupName, internalRules, []) + if var formRules { + formRules.append(formGroup) + } else { + formRules = [formGroup] + } + } + } } /// Adds the form action to the validator. @@ -72,7 +95,6 @@ import MVMCore if let validator = delegate?.formValidator { validator.delegate = delegate validator.insert(item) - // TODO: Temporary hacks, rewrite architecture to support this. _ = validator.validate() } From c8d817948b76f3a32cf7d1149b4a481d2ab8fcd2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:57:56 -0500 Subject: [PATCH 26/56] bug in rule Signed-off-by: Matt Bruce --- MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift index da818e64..e4cfb2aa 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift @@ -49,7 +49,7 @@ public class RuleVDSModel: VDSRuleBase { public override func isValid(_ formField: FormFieldProtocol) -> Bool { let value = formField.formFieldValue() as? ValueType let valid = rule.isValid(value: value) - if let field = fields.first, valid { + if let field = fields.first, !valid { errorMessage = [field: rule.errorMessage] } else { errorMessage = nil From b5f61953df66ae2d698ef4b947c0a62a01900017 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:58:05 -0500 Subject: [PATCH 27/56] updated textViewEntry Signed-off-by: Matt Bruce --- .../Atoms/FormFields/TextFields/TextViewEntryField.swift | 3 +++ .../FormFields/TextFields/TextViewEntryFieldModel.swift | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 58c3cbc6..be7f3e54 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -176,6 +176,9 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT isEditting = true } + /// append any internal rules: + viewModel.rules = rules + /// No point in configuring if the TextView is Read-only. if textView.isEditable { FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift index fc7dc46e..338f276c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift @@ -9,7 +9,7 @@ import UIKit import VDS -public class TextViewEntryFieldModel: TextEntryFieldModel { +public class TextViewEntryFieldModel: TextEntryFieldModel, FormFieldInternalValidatableProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -25,6 +25,11 @@ public class TextViewEntryFieldModel: TextEntryFieldModel { public var transparentBackground: Bool = false public var width: CGFloat? + //-------------------------------------------------- + // MARK: - FormFieldInternalValidatableProtocol + //-------------------------------------------------- + open var rules: [AnyRule]? + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- From 1a23c97a735c4361c354462c28c3680c069638b3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 15:03:46 -0500 Subject: [PATCH 28/56] updated FormField Signed-off-by: Matt Bruce --- MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift index 8b578fb3..8490cd69 100644 --- a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift @@ -37,7 +37,7 @@ public extension FormFieldProtocol { } public protocol FormFieldInternalValidatableProtocol: FormFieldProtocol { - associatedtype ValueType + associatedtype ValueType = AnyHashable var rules: [AnyRule]? { get set } var internalRules: [RuleAnyModelProtocol]? { get } } From d2eae79f47a1872faf42868d6f2d52759d36daf2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:57:56 -0500 Subject: [PATCH 29/56] bug in rule Signed-off-by: Matt Bruce --- MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift index da818e64..e4cfb2aa 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift @@ -49,7 +49,7 @@ public class RuleVDSModel: VDSRuleBase { public override func isValid(_ formField: FormFieldProtocol) -> Bool { let value = formField.formFieldValue() as? ValueType let valid = rule.isValid(value: value) - if let field = fields.first, valid { + if let field = fields.first, !valid { errorMessage = [field: rule.errorMessage] } else { errorMessage = nil From c8496194f0ae98ca2cf4d8199351bfb468a93a2e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 15:03:46 -0500 Subject: [PATCH 30/56] updated FormField Signed-off-by: Matt Bruce --- MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift index 8b578fb3..8490cd69 100644 --- a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift @@ -37,7 +37,7 @@ public extension FormFieldProtocol { } public protocol FormFieldInternalValidatableProtocol: FormFieldProtocol { - associatedtype ValueType + associatedtype ValueType = AnyHashable var rules: [AnyRule]? { get set } var internalRules: [RuleAnyModelProtocol]? { get } } From 273f45def04f66a6eafb2183ca27580a7b786d65 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:14 -0500 Subject: [PATCH 31/56] updated model Signed-off-by: Matt Bruce --- .../TextFields/TextEntryFieldModel.swift | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift index 053a5ac5..cb40ae9b 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift @@ -5,9 +5,10 @@ // Created by Kevin Christiano on 1/22/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import VDS - -@objcMembers open class TextEntryFieldModel: EntryFieldModel { +@objcMembers open class TextEntryFieldModel: EntryFieldModel, FormFieldInternalValidatableProtocol { + //-------------------------------------------------- // MARK: - Types //-------------------------------------------------- @@ -20,6 +21,39 @@ case email case text case phone + + //additional + case inlineAction + case creditCard + case date + case securityCode + + public func toVDSFieldType() -> VDS.InputField.FieldType { + switch self { + case .password: + .password + case .secure: + .text + case .number: + .number + case .numberSecure: + .number + case .email: + .text + case .text: + .text + case .phone: + .telephone + case .inlineAction: + .inlineAction + case .creditCard: + .creditCard + case .date: + .date + case .securityCode: + .securityCode + } + } } //-------------------------------------------------- @@ -33,12 +67,21 @@ public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3) public var textAlignment: NSTextAlignment = .left public var keyboardOverride: String? - public var type: EntryType? + public var type: EntryType = .text public var clearTextOnTap: Bool = false public var displayFormat: String? public var displayMask: String? public var enableClipboardActions: Bool = true + public var tooltip: TooltipModel? + public var transparentBackground: Bool = false + public var width: CGFloat? + + //-------------------------------------------------- + // MARK: - FormFieldInternalValidatableProtocol + //-------------------------------------------------- + open var rules: [AnyRule]? + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -114,6 +157,9 @@ case displayFormat case displayMask case enableClipboardActions + case tooltip + case transparentBackground + case width } //-------------------------------------------------- @@ -128,7 +174,7 @@ displayFormat = try typeContainer.decodeIfPresent(String.self, forKey: .displayFormat) keyboardOverride = try typeContainer.decodeIfPresent(String.self, forKey: .keyboardOverride) displayMask = try typeContainer.decodeIfPresent(String.self, forKey: .displayMask) - type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type) + type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type) ?? .text if let clearTextOnTap = try typeContainer.decodeIfPresent(Bool.self, forKey: .clearTextOnTap) { self.clearTextOnTap = clearTextOnTap @@ -149,6 +195,10 @@ if let enableClipboardActions = try typeContainer.decodeIfPresent(Bool.self, forKey: .enableClipboardActions) { self.enableClipboardActions = enableClipboardActions } + + tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) + transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false + width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) } open override func encode(to encoder: Encoder) throws { @@ -164,5 +214,8 @@ try container.encode(disabledTextColor, forKey: .disabledTextColor) try container.encode(clearTextOnTap, forKey: .clearTextOnTap) try container.encode(enableClipboardActions, forKey: .enableClipboardActions) + try container.encodeIfPresent(tooltip, forKey: .tooltip) + try container.encode(transparentBackground, forKey: .transparentBackground) + try container.encodeIfPresent(width, forKey: .width) } } From 65be46c7678f1ff138eb07fe16b025f99e87f6c9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:27 -0500 Subject: [PATCH 32/56] added inputentryfield Signed-off-by: Matt Bruce --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../TextFields/InputEntryField.swift | 350 ++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 25045a2f..a5c48b12 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -579,6 +579,7 @@ EA17584C2BC9894800A5C0D9 /* ButtonIconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */; }; EA17584E2BC9895A00A5C0D9 /* ButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */; }; EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */; }; + EA1B02E02C470AFD00F0758B /* InputEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */; }; EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; }; EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; }; EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; @@ -1201,6 +1202,7 @@ EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconModel.swift; sourceTree = ""; }; EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIcon.swift; sourceTree = ""; }; EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleVDSModel.swift; sourceTree = ""; }; + EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputEntryField.swift; sourceTree = ""; }; EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = ""; }; EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = ""; }; EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = ""; }; @@ -2351,6 +2353,7 @@ children = ( 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */, 0A21DB7E235DECC500C160A2 /* EntryField.swift */, + EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */, 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */, 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */, @@ -3140,6 +3143,7 @@ 323AC96A24C837F000F8E4C4 /* ListThreeColumnBillChangesModel.swift in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, + EA1B02E02C470AFD00F0758B /* InputEntryField.swift in Sources */, D28BA730247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift new file mode 100644 index 00000000..198daad4 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift @@ -0,0 +1,350 @@ +// +// InputEntryField.swift +// MVMCoreUI +// +// Created by Matt Bruce on 7/16/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// + +import Foundation +import VDS + +@objcMembers open class InputEntryField: VDS.InputField, VDSMoleculeViewProtocol, ObservingTextFieldDelegate, ViewMaskingProtocol { + + //------------------------------------------------------ + // MARK: - Properties + //------------------------------------------------------ + open var viewModel: TextEntryFieldModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + + // Form Validation + var fieldKey: String? + var fieldValue: JSONValue? + var groupName: String? + + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + public var isValid: Bool = true + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + private weak var proprietorTextDelegate: UITextFieldDelegate? + + private var isEditting: Bool = false { + didSet { + viewModel.selected = isEditting + } + } + + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + + private var observingForChange: Bool = false + + /// Validate when user resigns editing. Default: true + open var validateWhenDoneEditing: Bool = true + + open var shouldMaskWhileRecording: Bool { + return viewModel.shouldMaskRecordedView ?? false + } + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + /// The text of this TextField. + open override var text: String? { + didSet { + viewModel?.text = text + } + } + + open override var errorText: String? { + get { + viewModel.dynamicErrorMessage ?? viewModel.errorMessage + } + set {} + } + + /// Placeholder access for the TextField. + public var placeholder: String? { + get { textField.placeholder } + set { textField.placeholder = newValue } + } + + //-------------------------------------------------- + // MARK: - Delegate Properties + //-------------------------------------------------- + /// The delegate and block for validation. Validates if the text that the user has entered. + public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? + + /// If you're using a ViewController, you must set this to it + open weak var uiTextFieldDelegate: UITextFieldDelegate? + { + get { textField.delegate } + set { + textField.delegate = self + proprietorTextDelegate = newValue + } + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setup() { + super.setup() + //turn off internal required rule + useRequiredRule = false + + publisher(for: .valueChanged) + .sink { [weak self] control in + guard let self else { return } + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + if (viewModel.type == .email) { + // remove spaces (either user entered Or auto-correct suggestion) for the email field + text = textField.text?.replacingOccurrences(of: " ", with: "") + } + }.store(in: &subscribers) + + textField + .publisher(for: .editingDidBegin) + .sink { [weak self] textView in + guard let self else { return } + isEditting = true + if viewModel.clearTextOnTap { + text = "" + } + }.store(in: &subscribers) + + textField + .publisher(for: .editingDidEnd) + .sink { [weak self] textView in + guard let self else { return } + isEditting = false + if validateWhenDoneEditing, let valid = viewModel.isValid { + updateValidation(valid) + } + regexTextFieldOutputIfAvailable() + + }.store(in: &subscribers) + + } + + + @objc open func updateView(_ size: CGFloat) {} + + @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + observingTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + + //-------------------------------------------------- + // MARK: - Observing for Change (TextFieldDelegate) + //-------------------------------------------------- + + func regexTextFieldOutputIfAvailable() { + + if let regex = viewModel?.displayFormat, + let mask = viewModel?.displayMask, + let finalText = text { + + let range = NSRange(finalText.startIndex..., in: finalText) + + if let regex = try? NSRegularExpression(pattern: regex) { + let maskedText = regex.stringByReplacingMatches(in: finalText, + range: range, + withTemplate: mask) + textField.text = maskedText + } + } + } + + @objc public func dismissFieldInput(_ sender: Any?) { + _ = resignFirstResponder() + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + open override func updateView() { + super.updateView() + + if let viewModel { + switch viewModel.type { + case .secure: + textField.isSecureTextEntry = true + textField.shouldMaskWhileRecording = true + + case .numberSecure: + textField.isSecureTextEntry = true + textField.shouldMaskWhileRecording = true + textField.keyboardType = .numberPad + + case .email: + textField.keyboardType = .emailAddress + + case .securityCode, .creditCard, .password: + textField.shouldMaskWhileRecording = true + + default: + break; + } + + // Override the preset keyboard set in type. + if let keyboardType = viewModel.assignKeyboardType() { + textField.keyboardType = keyboardType + } + } + + } + + open func viewModelDidUpdate() { + + fieldType = viewModel.type.toVDSFieldType() + text = viewModel.text + placeholder = viewModel.placeholder + + labelText = viewModel.title + helperText = viewModel.feedback + isEnabled = viewModel.enabled + isReadOnly = viewModel.readOnly + isRequired = viewModel.required + tooltipModel = viewModel.tooltip?.toVDSTooltipModel() + width = viewModel.width + transparentBackground = viewModel.transparentBackground + + containerView.accessibilityIdentifier = model.accessibilityIdentifier + textField.textAlignment = viewModel.textAlignment + textField.enableClipboardActions = viewModel.enableClipboardActions + textField.placeholder = viewModel.placeholder ?? "" + uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate + observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate + + if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected { + + viewModel.wasInitiallySelected = true + isEditting = true + } + + viewModel.rules = rules + + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) + + if isEditting { + DispatchQueue.main.async { + _ = self.becomeFirstResponder() + } + } + + viewModel.updateUI = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + + if isEditting { + updateValidation(viewModel.isValid ?? true) + + } else if viewModel.isValid ?? true && showError { + showError = false + } + isEnabled = viewModel.enabled + }) + } + + viewModel.updateUIDynamicError = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + + let validState = viewModel.isValid ?? false + if !validState && viewModel.shouldClearText { + text = "" + viewModel.shouldClearText = false + } + updateValidation(validState) + }) + } + + //Added to override text when view is reloaded. + if let text = viewModel.text, !text.isEmpty { + regexTextFieldOutputIfAvailable() + } + } + + private func updateValidation(_ isValid: Bool) { + let previousValidity = self.isValid + self.isValid = isValid + + if previousValidity && !isValid { + showError = true + //observingTextFieldDelegate?.isValid?(textfield: self) + } else if (!previousValidity && isValid) { + showError = false + //observingTextFieldDelegate?.isInvalid?(textfield: self) + } + } +} + +extension InputEntryField { + //-------------------------------------------------- + // MARK: - Implemented TextField Delegate + //-------------------------------------------------- + @discardableResult + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true + } + + @objc public override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) + ?? + super.textField(textField, shouldChangeCharactersIn: range, replacementString: string) + } + + @objc public override func textFieldDidBeginEditing(_ textField: UITextField) { + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) ?? super.textFieldDidBeginEditing(textField) + } + + @objc public override func textFieldDidEndEditing(_ textField: UITextField) { + proprietorTextDelegate?.textFieldDidEndEditing?(textField) ?? super.textFieldDidEndEditing(textField) + } + + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true + } + + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true + } + + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true + } +} + +// MARK: - Accessibility +extension InputEntryField { + + @objc open func pushAccessibilityNotification() { + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + UIAccessibility.post(notification: .layoutChanged, argument: containerView) + } + } +} + +internal struct ViewMasking { + static var shouldMaskWhileRecording: UInt8 = 0 +} + +extension VDS.TextField: ViewMaskingProtocol { + public var shouldMaskWhileRecording: Bool { + get { + return (objc_getAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording) as? Bool) ?? false + } + set { + objc_setAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} + From 36669b61cbd25ff3fc476290eff080dcf2b099e7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:42 -0500 Subject: [PATCH 33/56] used new input in mapping for now Signed-off-by: Matt Bruce --- MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 22f65ade..af99d1be 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -41,7 +41,7 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(handler: ButtonGroup.self, for: ButtonGroupModel.self) // MARK:- Entry Field - ModelRegistry.register(handler: TextEntryField.self, for: TextEntryFieldModel.self) + ModelRegistry.register(handler: InputEntryField.self, for: TextEntryFieldModel.self) ModelRegistry.register(handler: MdnEntryField.self, for: MdnEntryFieldModel.self) ModelRegistry.register(handler: DigitEntryField.self, for: DigitEntryFieldModel.self) ModelRegistry.register(handler: ItemDropdownEntryField.self, for: ItemDropdownEntryFieldModel.self) From 239af70710c65230e405d57e57894cc61c2d0acf Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:54 -0500 Subject: [PATCH 34/56] fix initial updates Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift | 5 ++--- .../Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift index 2e098ad9..36700a9e 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift @@ -345,9 +345,8 @@ import UIKit numberOfDigits = model.digits - if let entryType = model.type { - setAsSecureTextEntry(entryType == .secure || entryType == .password) - } + let entryType = model.type + setAsSecureTextEntry(entryType == .secure || entryType == .password) let observingDelegate = delegateObject?.observingTextFieldDelegate ?? self diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift index 910712d5..6181cfdc 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift @@ -144,8 +144,8 @@ import MVMCore picker.displayedPropertyKeys = ["phoneNumbers"] picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0") picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'") - Task(priority: .userInitiated) { - await NavigationHandler.shared().present(viewController: picker, animated: true) + if let viewContoller = UIApplication.topViewController() { + viewContoller.present(picker, animated: true) } } From 018fe9a25e23f44f258e7537252625fb817a6633 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 16:16:39 -0500 Subject: [PATCH 35/56] updated MDN Field to use new inputfield Signed-off-by: Matt Bruce --- .../FormFields/TextFields/MdnEntryField.swift | 138 ++++-------------- .../TextFields/MdnEntryFieldModel.swift | 5 + 2 files changed, 33 insertions(+), 110 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift index 6181cfdc..f80f89d3 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift @@ -14,7 +14,7 @@ import MVMCore /** This class provides the convenience of formatting the MDN entered/displayer for the user. */ -@objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { +@objcMembers open class MdnEntryField: InputEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { //-------------------------------------------------- // MARK: - Stored Properties //-------------------------------------------------- @@ -47,52 +47,17 @@ import MVMCore get { MVMCoreUIUtility.removeMdnFormat(text) } set { text = MVMCoreUIUtility.formatMdn(newValue) } } - - /// Toggles selected or original (unselected) UI. - public override var isSelected: Bool { - get { return entryFieldContainer.isSelected } - set (selected) { - if selected && showError { - showError = false - } - - super.isSelected = selected - } - } - - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - - @objc public override init(frame: CGRect) { - super.init(frame: .zero) - } - - @objc public convenience init() { - self.init(frame: .zero) - } - - @objc required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("MdnEntryField xib not supported.") - } - - required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.init(model: model, delegateObject, additionalData) - } - + //-------------------------------------------------- // MARK: - Setup //-------------------------------------------------- - @objc public override func setupFieldContainerContent(_ container: UIView) { - super.setupFieldContainerContent(container) - - textField.keyboardType = .numberPad + open override func setup() { + super.setup() + setupTextFieldToolbar() } - open override func setupTextFieldToolbar() { - + open func setupTextFieldToolbar() { let toolbar = UIToolbar.createEmptyToolbar() let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) let contacts = UIBarButtonItem(title: MVMCoreUIUtility.hardcodedString(withKey: "textfield_contacts_barbutton"), style: .plain, target: self, action: #selector(getContacts)) @@ -103,40 +68,7 @@ import MVMCore //-------------------------------------------------- // MARK: - Methods - //-------------------------------------------------- - - @objc public func hasValidMDN() -> Bool { - - guard let MDN = mdn, !MDN.isEmpty else { return false } - - if isNationalMDN { - return MVMCoreUIUtility.validateMDNString(MDN) - } - - return MVMCoreUIUtility.validateInternationalMDNString(MDN) - } - - @objc public func validateMDNTextField() -> Bool { - - guard !shouldValidateMDN, let MDN = mdn, !MDN.isEmpty else { - isValid = true - return true - } - - isValid = hasValidMDN() - - if self.isValid { - showError = false - - } else { - entryFieldModel?.errorMessage = entryFieldModel?.errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") - showError = true - UIAccessibility.post(notification: .layoutChanged, argument: textField) - } - - return isValid - } - + //-------------------------------------------------- @objc public func getContacts(_ sender: Any?) { let picker = CNContactPickerViewController() @@ -152,11 +84,12 @@ import MVMCore //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- - - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - - textField.keyboardType = .phonePad + public override func viewModelDidUpdate() { + viewModel.type = .phone + super.viewModelDidUpdate() + if let phoneNumber = viewModel.text { + text = phoneNumber.formatUSNumber() + } } //-------------------------------------------------- @@ -179,62 +112,47 @@ import MVMCore let startIndex = unformedMDN.index(unformedMDN.startIndex, offsetBy: 1) unformattedMDN = String(unformedMDN[startIndex...]) } - text = unformattedMDN textFieldShouldReturn(textField) textFieldDidEndEditing(textField) } } - + //-------------------------------------------------- // MARK: - Implemented TextField Delegate //-------------------------------------------------- @discardableResult - @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - - textField.resignFirstResponder() - - return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true + @objc public override func textFieldShouldReturn(_ textField: UITextField) -> Bool { + _ = resignFirstResponder() + let superValue = super.textFieldShouldReturn(textField) + return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? superValue } - @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - - if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { - return false - } - - return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + @objc public override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let superValue = super.textField(textField, shouldChangeCharactersIn: range, replacementString: string) + return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? superValue } - @objc public func textFieldDidBeginEditing(_ textField: UITextField) { - - textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + @objc public override func textFieldDidBeginEditing(_ textField: UITextField) { + super.textFieldDidBeginEditing(textField) proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } - @objc public func textFieldDidEndEditing(_ textField: UITextField) { - + @objc public override func textFieldDidEndEditing(_ textField: UITextField) { proprietorTextDelegate?.textFieldDidEndEditing?(textField) - - if validateMDNTextField() { - if isNationalMDN { - textField.text = MVMCoreUIUtility.formatMdn(textField.text) - } - // Validate the base input field along with triggering form field validation rules. - validateText() - } + super.textFieldDidEndEditing(textField) } - @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + @objc public override func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } - @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + @objc public override func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true } - @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + @objc public override func textFieldShouldClear(_ textField: UITextField) -> Bool { proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift index 53d0703d..f4b94922 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift @@ -12,4 +12,9 @@ //-------------------------------------------------- public override class var identifier: String { "mdnEntryField" } + + open override func formFieldServerValue() -> AnyHashable? { + guard let value = formFieldValue() as? String else { return nil } + return value.filter { $0.isNumber } + } } From 2888eebb6996a350567ba3bdd6fe5b29a3b5b419 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 14:16:55 -0500 Subject: [PATCH 36/56] updated validity Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/TextEntryField.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index 9d030d0f..368f634c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -317,9 +317,9 @@ import UIKit super.shouldShowError(showError) if showError { - observingTextFieldDelegate?.isValid?(textfield: self) - } else { observingTextFieldDelegate?.isInvalid?(textfield: self) + } else { + observingTextFieldDelegate?.isValid?(textfield: self) } } From 3689b163393f916e61e2332775ab148038c570f4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 25 Jul 2024 10:19:55 -0500 Subject: [PATCH 37/56] refactored into VDS.Label extension Signed-off-by: Matt Bruce --- .../Atomic/Atoms/Views/Label/Label.swift | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 605f2e8a..82928c0f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -34,11 +34,7 @@ public typealias ActionBlock = () -> () /// A specific text index to use as a unique marker. public var hero: Int? - - public var getRange: NSRange { - NSRange(location: 0, length: text?.count ?? 0) - } - + public var shouldMaskWhileRecording: Bool = false public var hasText: Bool { @@ -378,19 +374,24 @@ extension Label { // MARK: - Atomization extension Label { - + public func needsToBeConstrained() -> Bool { true } public func horizontalAlignment() -> UIStackView.Alignment { .leading } public func copyBackgroundColor() -> Bool { true } + } // MARK: - Multi-Link Functionality -extension Label { +extension VDS.Label { + + public var getRange: NSRange { + NSRange(location: 0, length: text?.count ?? 0) + } /// Underlines the tappable region and stores the tap logic for interation. - private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) { + internal func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) { guard let text, text.isValid(range: range) else { return } var textLink = ActionLabelAttribute(location: range.location, length: range.length) @@ -417,8 +418,16 @@ extension Label { return { [weak self] in guard let self = self else { return } - if (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + if let button = self as? MFButtonProtocol { + if (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(button, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, + additionalData: additionalData, + delegateObject: delegateObject) + } + } else { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, + additionalData: additionalData, + delegateObject: delegateObject) } } } From 226c23abae345a900dca1a810ef23af508794970 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 25 Jul 2024 10:39:40 -0500 Subject: [PATCH 38/56] removed inverted Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift index 308ede2b..5340b079 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift @@ -21,8 +21,6 @@ open class RadioButtonModel: FormFieldModel { public var fieldValue: String? public var action: ActionModelProtocol? - public var inverted: Bool = false - public var surface: Surface { inverted ? .dark : .light } //-------------------------------------------------- // MARK: - Keys @@ -32,7 +30,6 @@ open class RadioButtonModel: FormFieldModel { case state case fieldValue case action - case inverted } //-------------------------------------------------- @@ -77,9 +74,6 @@ open class RadioButtonModel: FormFieldModel { baseValue = state fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) action = try typeContainer.decodeModelIfPresent(codingKey: .action) - if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { - self.inverted = inverted - } } public override func encode(to encoder: Encoder) throws { @@ -88,6 +82,5 @@ open class RadioButtonModel: FormFieldModel { try container.encode(state, forKey: .state) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeModelIfPresent(action, forKey: .action) - try container.encodeIfPresent(inverted, forKey: .inverted) } } From 6db3a58782e129936d0099f2c634cb3a8578ff60 Mon Sep 17 00:00:00 2001 From: Nandhini Rajendran Date: Fri, 26 Jul 2024 01:55:36 +0530 Subject: [PATCH 39/56] CXTDT-590886 Fix to make placeholder text visible. --- MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift index bc3e9617..95f15e06 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift @@ -315,7 +315,9 @@ import UIKit self.showError = false } self.isEnabled = model.enabled - self.text = model.text + if let text = model.text, !text.isEmpty { + self.text = model.text + } }) } From 796215f64dfb9ce5172e8bdff6cb76c11d792620 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:07:34 -0500 Subject: [PATCH 40/56] rearranged methods and comments Signed-off-by: Matt Bruce --- .../TextFields/InputEntryField.swift | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift index 198daad4..d1e37047 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift @@ -131,42 +131,6 @@ import VDS } - - @objc open func updateView(_ size: CGFloat) {} - - @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - observingTextFieldDelegate = delegate - uiTextFieldDelegate = delegate - } - - //-------------------------------------------------- - // MARK: - Observing for Change (TextFieldDelegate) - //-------------------------------------------------- - - func regexTextFieldOutputIfAvailable() { - - if let regex = viewModel?.displayFormat, - let mask = viewModel?.displayMask, - let finalText = text { - - let range = NSRange(finalText.startIndex..., in: finalText) - - if let regex = try? NSRegularExpression(pattern: regex) { - let maskedText = regex.stringByReplacingMatches(in: finalText, - range: range, - withTemplate: mask) - textField.text = maskedText - } - } - } - - @objc public func dismissFieldInput(_ sender: Any?) { - _ = resignFirstResponder() - } - - //-------------------------------------------------- - // MARK: - MoleculeViewProtocol - //-------------------------------------------------- open override func updateView() { super.updateView() @@ -196,9 +160,8 @@ import VDS textField.keyboardType = keyboardType } } - } - + open func viewModelDidUpdate() { fieldType = viewModel.type.toVDSFieldType() @@ -269,7 +232,36 @@ import VDS regexTextFieldOutputIfAvailable() } } + + //-------------------------------------------------- + // MARK: - Observing for Change (TextFieldDelegate) + //-------------------------------------------------- + @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + observingTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + func regexTextFieldOutputIfAvailable() { + + if let regex = viewModel?.displayFormat, + let mask = viewModel?.displayMask, + let finalText = text { + + let range = NSRange(finalText.startIndex..., in: finalText) + + if let regex = try? NSRegularExpression(pattern: regex) { + let maskedText = regex.stringByReplacingMatches(in: finalText, + range: range, + withTemplate: mask) + textField.text = maskedText + } + } + } + + @objc public func dismissFieldInput(_ sender: Any?) { + _ = resignFirstResponder() + } + private func updateValidation(_ isValid: Bool) { let previousValidity = self.isValid self.isValid = isValid @@ -282,6 +274,11 @@ import VDS //observingTextFieldDelegate?.isInvalid?(textfield: self) } } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + @objc open func updateView(_ size: CGFloat) {} } extension InputEntryField { From 62a74e8c34156ff71222d726a693fe58c38c2acc Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:09:44 -0500 Subject: [PATCH 41/56] removed accessibility settings to use VDS and rearranged Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index 30bcd85a..c268158c 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -109,22 +109,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- - public func updateView(_ size: CGFloat) { } - - public override func setup() { - super.setup() - - bridge_accessibilityHintBlock = { [weak self] in - guard let self else { return nil } - return MVMCoreUIUtility.hardcodedString(withKey: isEnabled ? "AccToggleHint" : "AccDisabled") - } - - bridge_accessibilityValueBlock = { [weak self] in - guard let self else { return nil } - return isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") - } - } - public override func reset() { super.reset() accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") @@ -132,21 +116,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) shouldToggleAction = { return true } } - //-------------------------------------------------- - // MARK: - Actions - //-------------------------------------------------- - /// This will toggle the state of the Toggle and execute the actionBlock if provided. - public func toggleAndAction() { - toggle() - } - - open override func toggle() { - if let result = shouldToggleAction?(), result { - super.toggle() - } - } - - // MARK:- MoleculeViewProtocol public func viewModelDidUpdate() { FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) @@ -185,7 +154,27 @@ public typealias ActionBlockConfirmation = () -> (Bool) } } } + + //-------------------------------------------------- + // MARK: - Actions + //-------------------------------------------------- + /// This will toggle the state of the Toggle and execute the actionBlock if provided. + public func toggleAndAction() { + toggle() + } + open override func toggle() { + if let result = shouldToggleAction?(), result { + super.toggle() + } + } + + //-------------------------------------------------- + // MARK:- MoleculeViewProtocol + //-------------------------------------------------- + + public func updateView(_ size: CGFloat) {} + public class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return Self().intrinsicContentSize.height } From 5ebdbd24f1acfc6155328dec98f2e94e6aa8d053 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:15:17 -0500 Subject: [PATCH 42/56] rearranged methods for commenting Signed-off-by: Matt Bruce --- .../TextFields/TextViewEntryField.swift | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index be7f3e54..a39e1c97 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -113,15 +113,32 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT } }.store(in: &subscribers) - - //String(format: MVMCoreUIUtility.hardcodedString(withKey: "textView_error_message") ?? "", textView.text ?? "", entryFieldModel?.errorMessage ?? "") } - //-------------------------------------------------- - // MARK: - MoleculeViewProtocol - //-------------------------------------------------- - open func updateView(_ size: CGFloat) {} + open override func updateView() { + super.updateView() + switch viewModel.type { + case .secure, .password: + textView.isSecureTextEntry = true + textView.shouldMaskWhileRecording = true + case .numberSecure: + textView.isSecureTextEntry = true + textView.keyboardType = .numberPad + textView.shouldMaskWhileRecording = true + + case .number: + textView.keyboardType = .numberPad + + case .email: + textView.keyboardType = .emailAddress +//uncomment out once inputField branch is merged and update this logic +// case .securityCode, .creditCard: +// textView.shouldMaskWhileRecording = true + + default: break + } + } open func viewModelDidUpdate() { text = viewModel.text @@ -147,29 +164,7 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT textView.isEditable = viewModel.editable textView.textAlignment = viewModel.textAlignment textView.placeholder = viewModel.placeholder ?? "" - - switch viewModel.type { - case .secure, .password: - textView.isSecureTextEntry = true - textView.shouldMaskWhileRecording = true - - case .numberSecure: - textView.isSecureTextEntry = true - textView.keyboardType = .numberPad - textView.shouldMaskWhileRecording = true - - case .number: - textView.keyboardType = .numberPad - - case .email: - textView.keyboardType = .emailAddress - -// case .securityCode, .creditCard: -// textView.shouldMaskWhileRecording = true - - default: break - } - + if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected { viewModel.wasInitiallySelected = true @@ -229,6 +224,11 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT showError = false } } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + open func updateView(_ size: CGFloat) {} } internal struct ViewMasking { From 35ab4c94d202e269f53fa09d05277df62e7bf6c1 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:17:51 -0500 Subject: [PATCH 43/56] removed accessibility and rearranged methods/comments Signed-off-by: Matt Bruce --- .../Atomic/Atoms/Selectors/RadioButton.swift | 58 ++++++++----------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift index 9c0d9e46..3b4cfbc8 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift @@ -22,9 +22,9 @@ import VDS } // Form Validation - var fieldKey: String? - var fieldValue: JSONValue? - var groupName: String? + open var fieldKey: String? + open var fieldValue: JSONValue? + open var groupName: String? open override var isSelected: Bool { didSet { @@ -86,22 +86,27 @@ import VDS //-------------------------------------------------- // 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 - } + 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 + + }) } - - accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint") + + isSelected = viewModel.state + isEnabled = viewModel.enabled && !viewModel.readOnly + RadioButtonSelectionHelper.setupForRadioButtonGroup(viewModel, self, delegateObject: delegateObject) } - + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- open override func toggle() { guard !isSelected, isEnabled else { return } @@ -136,7 +141,7 @@ import VDS } //-------------------------------------------------- - // MARK: - Molecular + // MARK: - MoleculeViewProtocol //-------------------------------------------------- open func needsToBeConstrained() -> Bool { true } @@ -145,21 +150,4 @@ import VDS 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 - - }) - } - - isSelected = viewModel.state - isEnabled = viewModel.enabled && !viewModel.readOnly - RadioButtonSelectionHelper.setupForRadioButtonGroup(viewModel, self, delegateObject: delegateObject) - } } From 8a89b2b2a917d9a34ff5396f1c5ac9a45fb94a0f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:22:24 -0500 Subject: [PATCH 44/56] updated methods and comments Signed-off-by: Matt Bruce --- .../Atomic/Atoms/Selectors/RadioBox.swift | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift index ce890bc3..73b23f93 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -7,7 +7,7 @@ // import VDS -open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol { +@objcMembers open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol { //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ @@ -15,14 +15,18 @@ open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol open var delegateObject: MVMCoreUIDelegateObject? open var additionalData: [AnyHashable : Any]? - public var isOutOfStock: Bool { viewModel.strikethrough } + open var isOutOfStock: Bool { + get { strikethrough } + set { + strikethrough = newValue + viewModel?.strikethrough = newValue + } + } //-------------------------------------------------- - // MARK: - MVMCoreViewProtocol + // MARK: - Lifecycle //-------------------------------------------------- - open func updateView(_ size: CGFloat) {} - open override func setup() { super.setup() @@ -48,14 +52,23 @@ open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol } } + //-------------------------------------------------- + // MARK: - Functions + //-------------------------------------------------- + @objc open func selectBox() { - guard isEnabled, !isSelected else { return } - isSelected = true - viewModel.selected = isSelected + toggle() } @objc open func deselectBox() { - + toggle() } + + //-------------------------------------------------- + // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- + + open func updateView(_ size: CGFloat) {} + } From 192509feef0e32513926cd0d5b9621eb6f1114a9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:24:05 -0500 Subject: [PATCH 45/56] remvoed inverted since this is now in subclass Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift index 105f9645..463afde3 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift @@ -16,8 +16,6 @@ import VDS public override static var identifier: String { "radioBoxes" } public var boxes: [RadioBoxModel] - public var inverted: Bool = false - public var surface: Surface { inverted ? .dark : .light } public var selectorModels: [VDS.RadioBoxGroup.RadioBoxItemModel] { boxes.compactMap({ item in @@ -58,7 +56,6 @@ import VDS private enum CodingKeys: String, CodingKey { case boxes - case inverted } //-------------------------------------------------- @@ -77,9 +74,6 @@ import VDS required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) - if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { - self.inverted = inverted - } try super.init(from: decoder) } @@ -87,6 +81,5 @@ import VDS try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(boxes, forKey: .boxes) - try container.encode(inverted, forKey: .inverted) } } From 8215542ee76716036560156b37e4cbef818bb898 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:25:21 -0500 Subject: [PATCH 46/56] removed method not needed Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift index 73b23f93..815f2959 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -27,12 +27,6 @@ import VDS // MARK: - Lifecycle //-------------------------------------------------- - open override func setup() { - super.setup() - - addTarget(self, action: #selector(selectBox), for: .touchUpInside) - } - public func viewModelDidUpdate() { text = viewModel.text From b754b476a67f7d72a1d2dc079ca106cffee59819 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:29:07 -0500 Subject: [PATCH 47/56] reverted code Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift index f80f89d3..401dfa6b 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift @@ -76,8 +76,8 @@ import MVMCore picker.displayedPropertyKeys = ["phoneNumbers"] picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0") picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'") - if let viewContoller = UIApplication.topViewController() { - viewContoller.present(picker, animated: true) + Task(priority: .userInitiated) { + await NavigationHandler.shared().present(viewController: picker, animated: true) } } From 66464cebc3f6f5707d72f88e45835b4f2c41750b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 10:39:04 -0500 Subject: [PATCH 48/56] comment Signed-off-by: Matt Bruce --- .../TextFields/TextViewEntryField.swift | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index a39e1c97..a76e9451 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -115,30 +115,6 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT }.store(in: &subscribers) } - open override func updateView() { - super.updateView() - switch viewModel.type { - case .secure, .password: - textView.isSecureTextEntry = true - textView.shouldMaskWhileRecording = true - - case .numberSecure: - textView.isSecureTextEntry = true - textView.keyboardType = .numberPad - textView.shouldMaskWhileRecording = true - - case .number: - textView.keyboardType = .numberPad - - case .email: - textView.keyboardType = .emailAddress -//uncomment out once inputField branch is merged and update this logic -// case .securityCode, .creditCard: -// textView.shouldMaskWhileRecording = true - - default: break - } - } open func viewModelDidUpdate() { text = viewModel.text @@ -171,6 +147,28 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT isEditting = true } + switch viewModel.type { + case .secure, .password: + textView.isSecureTextEntry = true + textView.shouldMaskWhileRecording = true + + case .numberSecure: + textView.isSecureTextEntry = true + textView.keyboardType = .numberPad + textView.shouldMaskWhileRecording = true + + case .number: + textView.keyboardType = .numberPad + + case .email: + textView.keyboardType = .emailAddress +//uncomment out once inputField branch is merged and update this logic +// case .securityCode, .creditCard: +// textView.shouldMaskWhileRecording = true + + default: break + } + /// append any internal rules: viewModel.rules = rules From bdaa4b5ea716889e3fca3a68c75826780503a4f9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 11:09:40 -0500 Subject: [PATCH 49/56] added extra check to ensure you aren't duplicating rules Signed-off-by: Matt Bruce --- MVMCoreUI/FormUIHelpers/FormValidator.swift | 6 ++++++ .../FormUIHelpers/Rules/Rules/RuleVDSModel.swift | 14 +++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index b01c7ad4..e68b87a9 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -56,6 +56,12 @@ import MVMCore //find the group if let formGroup = formRules?.first(where: {$0.groupName == field.groupName}) { + var appendingRules = [RulesProtocol]() + internalRules.forEach { internalRule in + if !formGroup.rules.contains(where: { internalRule.type == $0.type && internalRule.fields == $0.fields } ) { + appendingRules.append(internalRule) + } + } formGroup.rules.append(contentsOf: internalRules) } else { //create the new group diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift index e4cfb2aa..5443cda4 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleVDSModel.swift @@ -9,12 +9,15 @@ import Foundation import VDS -open class VDSRuleBase: RuleAnyModelProtocol { +open class VDSRuleBase: RuleAnyModelProtocol { open var ruleId: String? + open var ruleType: String open var errorMessage: [String : String]? open var fields = [String]() - public init(){} - + public init(){ + ruleType = Self.identifier + } + public var type: String { ruleType } open func isValid(_ formField: any FormFieldProtocol) -> Bool { fatalError() } @@ -26,7 +29,7 @@ public class RuleVDSModel: VDSRuleBase { // MARK: - Properties //-------------------------------------------------- public var rule: AnyRule - + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -34,8 +37,8 @@ public class RuleVDSModel: VDSRuleBase { public init(field: String, rule: AnyRule) { self.rule = rule super.init() + self.ruleType = rule.ruleType self.fields = [field] - self.ruleId = "\(rule.self)-\(Int.random(in: 1...1000))" } required init(from decoder: any Decoder) throws { @@ -57,3 +60,4 @@ public class RuleVDSModel: VDSRuleBase { return valid } } + From 68083819beb33fe433ce74474de0377d3de474ac Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 13:16:16 -0500 Subject: [PATCH 50/56] change protocol to any Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/InputEntryField.swift | 4 ++-- .../Atomic/Atoms/FormFields/TextFields/TextEntryField.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift index d1e37047..51cbd26c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift @@ -268,10 +268,10 @@ import VDS if previousValidity && !isValid { showError = true - //observingTextFieldDelegate?.isValid?(textfield: self) + observingTextFieldDelegate?.isValid?(textfield: self) } else if (!previousValidity && isValid) { showError = false - //observingTextFieldDelegate?.isInvalid?(textfield: self) + observingTextFieldDelegate?.isInvalid?(textfield: self) } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index 368f634c..d4b0539d 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -11,9 +11,9 @@ import UIKit @objc public protocol ObservingTextFieldDelegate { /// Called when the entered text becomes valid based on the validation block - @objc optional func isValid(textfield: TextEntryField?) + @objc optional func isValid(textfield: Any?) /// Called when the entered text becomes invalid based on the validation block - @objc optional func isInvalid(textfield: TextEntryField?) + @objc optional func isInvalid(textfield: Any?) /// Dismisses the keyboard. @objc optional func dismissFieldInput(_ sender: Any?) } From c794add84d5679401a9a4cd116fabb538f8baab9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 13:44:03 -0500 Subject: [PATCH 51/56] removed duplicate struct for view masking protocol Signed-off-by: Matt Bruce --- .../TextFields/TextViewEntryField.swift | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index a76e9451..1cacbeae 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -148,25 +148,28 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT } switch viewModel.type { - case .secure, .password: + case .secure: textView.isSecureTextEntry = true textView.shouldMaskWhileRecording = true - + case .numberSecure: textView.isSecureTextEntry = true - textView.keyboardType = .numberPad textView.shouldMaskWhileRecording = true - - case .number: textView.keyboardType = .numberPad case .email: textView.keyboardType = .emailAddress -//uncomment out once inputField branch is merged and update this logic -// case .securityCode, .creditCard: -// textView.shouldMaskWhileRecording = true - - default: break + + case .securityCode, .creditCard, .password: + textView.shouldMaskWhileRecording = true + + default: + break; + } + + // Override the preset keyboard set in type. + if let keyboardType = viewModel.assignKeyboardType() { + textView.keyboardType = keyboardType } /// append any internal rules: @@ -229,10 +232,6 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT open func updateView(_ size: CGFloat) {} } -internal struct ViewMasking { - static var shouldMaskWhileRecording: UInt8 = 0 -} - extension VDS.TextView: ViewMaskingProtocol { public var shouldMaskWhileRecording: Bool { get { From 3e702ec0f5526695ef70b875513999d60c25c4ed Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 13:44:19 -0500 Subject: [PATCH 52/56] removed properties that were pushed down Signed-off-by: Matt Bruce --- .../ItemDropdownEntryFieldModel.swift | 12 ----------- .../TextFields/TextViewEntryFieldModel.swift | 21 ++----------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift index eb83a48a..f6f20b9f 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift @@ -18,9 +18,6 @@ import VDS public var selectedIndex: Int? public var showInlineLabel: Bool = false public var feedbackTextPlacement: VDS.EntryFieldBase.HelperTextPlacement = .bottom - public var tooltip: TooltipModel? - public var transparentBackground: Bool = false - public var width: CGFloat? public init(with options: [String], selectedIndex: Int? = nil) { self.options = options @@ -51,9 +48,6 @@ import VDS case action case showInlineLabel case feedbackTextPlacement - case tooltip - case transparentBackground - case width } //-------------------------------------------------- @@ -73,9 +67,6 @@ import VDS showInlineLabel = try typeContainer.decodeIfPresent(Bool.self, forKey: .showInlineLabel) ?? false feedbackTextPlacement = try typeContainer.decodeIfPresent(VDS.EntryFieldBase.HelperTextPlacement.self, forKey: .feedbackTextPlacement) ?? .bottom action = try typeContainer.decodeModelIfPresent(codingKey: .action) - tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) - transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false - width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) } public override func encode(to encoder: Encoder) throws { @@ -86,8 +77,5 @@ import VDS try container.encode(showInlineLabel, forKey: .showInlineLabel) try container.encode(feedbackTextPlacement, forKey: .feedbackTextPlacement) try container.encodeModelIfPresent(action, forKey: .action) - try container.encodeIfPresent(tooltip, forKey: .tooltip) - try container.encode(transparentBackground, forKey: .transparentBackground) - try container.encodeIfPresent(width, forKey: .width) } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift index 338f276c..2f47b2a6 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryFieldModel.swift @@ -9,7 +9,7 @@ import UIKit import VDS -public class TextViewEntryFieldModel: TextEntryFieldModel, FormFieldInternalValidatableProtocol { +public class TextViewEntryFieldModel: TextEntryFieldModel { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,17 +19,9 @@ public class TextViewEntryFieldModel: TextEntryFieldModel, FormFieldInternalVali public var accessibilityText: String? public var editable: Bool = true public var showsPlaceholder: Bool = false - public var tooltip: TooltipModel? public var minHeight: VDS.TextArea.Height = .twoX public var maxLength: Int? - public var transparentBackground: Bool = false - public var width: CGFloat? - - //-------------------------------------------------- - // MARK: - FormFieldInternalValidatableProtocol - //-------------------------------------------------- - open var rules: [AnyRule]? - + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -39,9 +31,6 @@ public class TextViewEntryFieldModel: TextEntryFieldModel, FormFieldInternalVali case editable case minHeight case maxLength - case tooltip - case transparentBackground - case width } //-------------------------------------------------- @@ -56,9 +45,6 @@ public class TextViewEntryFieldModel: TextEntryFieldModel, FormFieldInternalVali minHeight = try typeContainer.decodeIfPresent(VDS.TextArea.Height.self, forKey: .minHeight) ?? .twoX maxLength = try typeContainer.decodeIfPresent(Int.self, forKey: .maxLength) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) - tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip) - transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false - width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) } public override func encode(to encoder: Encoder) throws { @@ -68,8 +54,5 @@ public class TextViewEntryFieldModel: TextEntryFieldModel, FormFieldInternalVali try container.encode(editable, forKey: .editable) try container.encode(minHeight, forKey: .minHeight) try container.encodeIfPresent(maxLength, forKey: .maxLength) - try container.encodeIfPresent(tooltip, forKey: .tooltip) - try container.encode(transparentBackground, forKey: .transparentBackground) - try container.encodeIfPresent(width, forKey: .width) } } From 27718086743551597b1381d224c4a26667e40da3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 14:10:24 -0500 Subject: [PATCH 53/56] added inverted encoding Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift index 87326c7d..cd7623c2 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift @@ -105,5 +105,6 @@ import VDS try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeModelIfPresent(action, forKey: .action) + try container.encode(inverted, forKey: .inverted) } } From 4b06a5ba1f2f63f974273f6bf1514a8203429995 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 14:48:36 -0500 Subject: [PATCH 54/56] ensured inverted is set Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift index 7ba1f755..725cc893 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift @@ -53,6 +53,7 @@ open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol { // MARK: - MoleculeViewProtocol public func viewModelDidUpdate() { boxes = viewModel.boxes + surface = viewModel.surface selectorModels = viewModel.selectorModels FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) From eab6fb5f3b52509f331c448226c5bfbac6a08080 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 16:13:17 -0500 Subject: [PATCH 55/56] refactored model to remove default text Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index 398ba42c..c310e47c 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -28,8 +28,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { public var surface: Surface { inverted ? .dark : .light } public var inverted: Bool = false public var showText: Bool = false - public var onText: String = "On" - public var offText: String = "Off" + public var onText: String? + public var offText: String? public var textSize: VDS.Toggle.TextSize = .small public var textWeight: VDS.Toggle.TextWeight = .regular public var textPosition: VDS.Toggle.TextPosition = .left @@ -126,8 +126,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) ?? false showText = try typeContainer.decodeIfPresent(Bool.self, forKey: .showText) ?? false - onText = try typeContainer.decodeIfPresent(String.self, forKey: .onText) ?? "On" - offText = try typeContainer.decodeIfPresent(String.self, forKey: .offText) ?? "Off" + onText = try typeContainer.decodeIfPresent(String.self, forKey: .onText) + offText = try typeContainer.decodeIfPresent(String.self, forKey: .offText) textSize = try typeContainer.decodeIfPresent(VDS.Toggle.TextSize.self, forKey: .textSize) ?? .small textWeight = try typeContainer.decodeIfPresent(VDS.Toggle.TextWeight.self, forKey: .textWeight) ?? .regular textPosition = try typeContainer.decodeIfPresent(VDS.Toggle.TextPosition.self, forKey: .textPosition) ?? .left @@ -151,8 +151,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { try container.encode(inverted, forKey: .inverted) try container.encode(showText, forKey: .showText) - try container.encode(onText, forKey: .onText) - try container.encode(offText, forKey: .offText) + try container.encodeIfPresent(onText, forKey: .onText) + try container.encodeIfPresent(offText, forKey: .offText) try container.encode(textSize, forKey: .textSize) try container.encode(textWeight, forKey: .textWeight) try container.encode(textPosition, forKey: .textPosition) From fe66380d7212cee41bbe52cc239012aa3f0e4321 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 16:13:35 -0500 Subject: [PATCH 56/56] refactored setting of isON on viewModelDidUpdate Signed-off-by: Matt Bruce --- MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index c268158c..b229d12e 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -28,15 +28,11 @@ public typealias ActionBlockConfirmation = () -> (Bool) open var delegateObject: MVMCoreUIDelegateObject? open var additionalData: [AnyHashable : Any]? - /// Disables all selection logic when setting the value of isOn, reducing it to a stored property. - public var updateSelectionOnly: Bool = false - public var didToggleAction: ActionBlock? { - get { nil } - set { - if let action = newValue { + didSet { + if let didToggleAction { onChange = { _ in - action() + didToggleAction() } } else { onChange = nil @@ -58,16 +54,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) didSet { isUserInteractionEnabled = !isLocked } } - /// The state on the toggle. Default value: false. - open override var isOn: Bool { - didSet { - if !updateSelectionOnly { - viewModel.selected = isOn - _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) - } - } - } - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -119,18 +105,17 @@ public typealias ActionBlockConfirmation = () -> (Bool) public func viewModelDidUpdate() { FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) - if viewModel.selected { - updateSelectionOnly = false - isOn = viewModel.selected - updateSelectionOnly = true - } - + isOn = viewModel.selected surface = viewModel.surface isAnimated = viewModel.animated isEnabled = viewModel.enabled && !viewModel.readOnly showText = viewModel.showText - onText = viewModel.onText - offText = viewModel.offText + if let onText = viewModel.onText { + self.onText = onText + } + if let offText = viewModel.offText { + self.offText = offText + } textSize = viewModel.textSize textWeight = viewModel.textWeight textPosition = viewModel.textPosition @@ -166,6 +151,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) open override func toggle() { if let result = shouldToggleAction?(), result { super.toggle() + viewModel?.selected = isOn + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) } }