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() } }