diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 52ade755..641d995c 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -11,12 +11,14 @@ import UIKit public typealias FacadeElements = (fill: UIColor?, text: UIColor?, border: UIColor?) -public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, EnableableModelProtocol { +open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, EnableableModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - public static var identifier: String = "button" + //Making static property as class property so that subclasses can override getter function of the property + open class var identifier: String { + "button" + } public var backgroundColor: Color? public var accessibilityIdentifier: String? public var title: String @@ -247,7 +249,7 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupW } } - public func encode(to encoder: Encoder) throws { + open func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(title, forKey: .title) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift index cc410404..04eeed02 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift @@ -319,12 +319,14 @@ import UIKit model.updateUIDynamicError = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { guard let self = self else { return } + let validState = model.isValid ?? false - self.updateValidation(validState) if !validState && model.shouldClearText { self.text = "" model.shouldClearText = false } + _ = FormValidator.validate(delegate: self.delegateObject?.formHolderDelegate) + self.updateValidation(validState) }) } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift index fe2c483c..db3c28c4 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift @@ -147,6 +147,16 @@ import MVMCore MVMCoreNavigationHandler.shared()?.present(picker, animated: true) } + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + + textField.keyboardType = .phonePad + } + //-------------------------------------------------- // MARK: - Contact Picker Delegate //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index 6ff196c7..3d1b1534 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -200,6 +200,7 @@ import UIKit open override func reset() { super.reset() + textField.isSecureTextEntry = false textField.font = Styler.Font.RegularBodyLarge.getFont() } @@ -350,7 +351,8 @@ import UIKit case .phone: textField.keyboardType = .phonePad - default: break + default: + textField.keyboardType = .default } // Override the preset keyboard set in type. diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift index 3b6a5b76..3a342b6a 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinks.swift @@ -22,7 +22,7 @@ public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { // Fill for left vertical alignment because bottom constraint was breaking with leading. CXTDT-145456 - stack = Stack.createStack(with: [(view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading, verticalAlignment: .fill)), (view: rightLabel, model: StackItemModel(horizontalAlignment:.fill, verticalAlignment: .leading))], axis: .horizontal) + stack = Stack.createStack(with: [(view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading, verticalAlignment: .fill)), (view: rightLabel, model: StackItemModel(horizontalAlignment: .trailing, verticalAlignment: .leading))], axis: .horizontal) super.init(style: style, reuseIdentifier: reuseIdentifier) } @@ -37,6 +37,8 @@ open override func setupView() { super.setupView() rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.numberOfLines = 1 addMolecule(stack) stack.restack() } diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index be67bfca..19348070 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -21,6 +21,9 @@ import Foundation public var peakingUI: Bool? public var peakingArrowColor: Color? public var analyticsData: JSONValueDictionary? + public var fieldValue: String? + + public func formFieldValue() -> AnyHashable? { return fieldValue } //-------------------------------------------------- // MARK: - Keys @@ -30,6 +33,7 @@ import Foundation case peakingUI case peakingArrowColor case analyticsData + case fieldValue } //-------------------------------------------------- @@ -41,6 +45,7 @@ import Foundation peakingUI = try typeContainer.decodeIfPresent(Bool.self, forKey: .peakingUI) peakingArrowColor = try typeContainer.decodeIfPresent(Color.self, forKey: .peakingArrowColor) analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) + fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) try super.init(from: decoder) } @@ -50,5 +55,6 @@ import Foundation try container.encodeIfPresent(peakingUI, forKey: .peakingUI) try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) try container.encodeIfPresent(analyticsData, forKey: .analyticsData) + try container.encodeIfPresent(fieldValue, forKey: .fieldValue) } } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 31222828..a671726c 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -167,12 +167,17 @@ open class Carousel: View { registerCells(with: carouselModel, delegateObject: delegateObject) prepareMolecules(with: carouselModel) + FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate) setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject) pageIndex = carouselModel.index pagingView?.currentIndex = carouselModel.index collectionView.reloadData() + if let selectedIndex = carouselModel.selectedIndex { + let adjustedIndex = loop ? selectedIndex + 2 : selectedIndex + collectionView.selectItem(at: IndexPath(row: adjustedIndex, section: 0), animated: false, scrollPosition: []) + } } //-------------------------------------------------- @@ -391,8 +396,25 @@ extension Carousel: UICollectionViewDataSource { } extension Carousel: UICollectionViewDelegate { + public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + guard let cell = collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol else { return false } + return cell.shouldSelect(at: indexPath, delegateObject: delegateObject, additionalData: nil) + } + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - (collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObject, additionalData: nil) + // Set the selection in the model + if let model = model as? CarouselModel { + // Adjust for looping + var adjustedIndex = loop ? indexPath.row - 2 : indexPath.row + if adjustedIndex < 0 { + adjustedIndex = adjustedIndex + numberOfPages + } + model.selectedIndex = adjustedIndex + } + if let cell = collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol { + cell.didSelectCell(at: indexPath, delegateObject: delegateObject, additionalData: nil) + } + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) } } @@ -414,6 +436,7 @@ extension Carousel: UIScrollViewDelegate { if !animated { scrollViewDidEndScrollingAnimation(collectionView) } + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) } /// Adjusts the current contentOffset if we are going onto buffer cells while looping to help with the endless scrolling appearance. diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index ca0a4a10..5bf0728c 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -9,7 +9,8 @@ import UIKit -@objcMembers public class CarouselModel: MoleculeModelProtocol { +@objcMembers public class CarouselModel: MoleculeModelProtocol, FormFieldProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -33,11 +34,31 @@ import UIKit public var leftPadding: CGFloat? public var rightPadding: CGFloat? public var accessibilityText: String? - + public var baseValue: AnyHashable? + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + + public var selectable = false + public var selectedIndex: Int? + public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) { self.molecules = molecules } + public func formFieldValue() -> AnyHashable? { + guard selectable else { + // Use visible item value, else index + if let fieldValue = molecules[index].formFieldValue() { + return fieldValue + } + return index + } + // Use selected item value, else index + guard let selectedIndex = selectedIndex else { return nil } + guard let fieldValue = molecules[selectedIndex].formFieldValue() else { return selectedIndex } + return fieldValue + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -59,6 +80,10 @@ import UIKit case leftPadding case rightPadding case accessibilityText + case groupName + case fieldKey + case selectable + case selectedIndex } //-------------------------------------------------- @@ -69,6 +94,8 @@ import UIKit let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeModels(codingKey: .molecules) index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0 + selectable = try typeContainer.decodeIfPresent(Bool.self, forKey: .selectable) ?? false + selectedIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border) @@ -86,6 +113,11 @@ import UIKit leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding) rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + self.groupName = groupName + } + baseValue = formFieldValue() } public func encode(to encoder: Encoder) throws { @@ -105,5 +137,10 @@ import UIKit try container.encodeIfPresent(leftPadding, forKey: .leftPadding) try container.encodeIfPresent(rightPadding, forKey: .rightPadding) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encode(groupName, forKey: .groupName) + try container.encode(index, forKey: .index) + try container.encode(selectable, forKey: .selectable) + try container.encode(selectedIndex, forKey: .selectedIndex) } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift index c2ade02d..2ed2ca5a 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift @@ -11,11 +11,14 @@ import Foundation public protocol CarouselItemModelProtocol: ContainerModelProtocol { var analyticsData: JSONValueDictionary? { get set } + func formFieldValue() -> AnyHashable? } + public extension CarouselItemModelProtocol { var analyticsData: JSONValueDictionary? { get { return nil } set { analyticsData = newValue } } + func formFieldValue() -> AnyHashable? { return nil } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift index 55db2c6d..4e9ae26d 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift @@ -19,6 +19,9 @@ public protocol CollectionTemplateItemProtocol: UICollectionViewCell { /// Called when the cell will display. func willDisplay() + + /// Handle the selection of cell + func shouldSelect(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Bool } // Default implementation does nothing @@ -26,4 +29,8 @@ extension CollectionTemplateItemProtocol { public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {} public func willDisplay() {} + + public func shouldSelect(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Bool { + return false + } } diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index 4ac48dfc..cfbe4ac7 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -116,11 +116,15 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo // MARK: - Override - open func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - guard let action = model?.action else { return } - Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData) + open func shouldSelect(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Bool { + if let action = model?.action { + Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData) + } + return false } + open func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {} + // Column logic, set width. override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index ecb4089a..a2d82952 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -461,7 +461,7 @@ import UIKit open func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) { addFormParams(requestParameters) requestParameters.parentPageType = loadObject?.pageJSON?.optionalStringForKey("parentPageType") - MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, additionalData: additionalData, delegateObject: delegateObject()) + MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, actionInformation: actionInformation, additionalData: additionalData, delegateObject: delegateObject()) } open func logAction(withActionInformation actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Styles/Padding.swift b/MVMCoreUI/Styles/Padding.swift index 0dd9ff6e..480896a0 100644 --- a/MVMCoreUI/Styles/Padding.swift +++ b/MVMCoreUI/Styles/Padding.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - /// Padding is a multiple based on the number 4. public struct Padding { @@ -30,19 +28,19 @@ public struct Padding { public static let VerticalMarginSpacing: CGFloat = 24 public static var horizontalPaddingForApplicationWidth: CGFloat { - return MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? HorizontalMarginSpacing + MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? HorizontalMarginSpacing } public static var verticalPaddingForApplicationWidth: CGFloat { - return MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? VerticalMarginSpacing + MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? VerticalMarginSpacing } public static func horizontalPaddingForSize(_ size: CGFloat) -> CGFloat { - return MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBased(onSize: size) ?? HorizontalMarginSpacing + MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBased(onSize: size) ?? HorizontalMarginSpacing } public static func verticalPaddingForSize(_ size: CGFloat) -> CGFloat { - return MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBased(onSize: size) ?? VerticalMarginSpacing + MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBased(onSize: size) ?? VerticalMarginSpacing } } } diff --git a/MVMCoreUI/Styles/Styler.swift b/MVMCoreUI/Styles/Styler.swift index c0dd6792..77402295 100644 --- a/MVMCoreUI/Styles/Styler.swift +++ b/MVMCoreUI/Styles/Styler.swift @@ -209,7 +209,14 @@ open class Styler { } open class func sizeFontGeneric(forCurrentDevice size: CGFloat) -> CGFloat { - return sizeObjectGeneric(forCurrentDevice: size)?.getValueBasedOnApplicationWidth() ?? size + sizeObjectGeneric(forCurrentDevice: size)?.getValueBasedOnApplicationWidth() ?? size + } + + /// Provide additional size information to help calculate its intrinsic height. + /// - Returns: The available spacing that can be used for intrinsic content width. + open class func maxAvailableLayoutWidth(size: CGFloat) -> CGFloat { + // The 2 is the product of both sides of padding. + size - (Padding.Component.horizontalPaddingForSize(size) * 2) } //--------------------------------------------------