diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index ed3f746f..f277003d 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -290,6 +290,7 @@ BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC0C4FE24811DCA0087C44F /* TagModel.swift */; }; C003506123AA94CD00B6AC29 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C003506023AA94CD00B6AC29 /* Button.swift */; }; C07065C42395677300FBF997 /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07065C32395677300FBF997 /* Link.swift */; }; + C6687441259D92D400F32D13 /* ActionTopNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6687440259D92D400F32D13 /* ActionTopNotificationModel.swift */; }; C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */; }; C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A68023C9830D00BFB94E /* NumberedListModel.swift */; }; C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A69323C9909000BFB94E /* DoughnutChartModel.swift */; }; @@ -833,6 +834,7 @@ BBC0C4FE24811DCA0087C44F /* TagModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagModel.swift; sourceTree = ""; }; C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; C07065C32395677300FBF997 /* Link.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Link.swift; sourceTree = ""; }; + C6687440259D92D400F32D13 /* ActionTopNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionTopNotificationModel.swift; sourceTree = ""; }; C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnOrderedListModel.swift; sourceTree = ""; }; C695A68023C9830D00BFB94E /* NumberedListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberedListModel.swift; sourceTree = ""; }; C695A69323C9909000BFB94E /* DoughnutChartModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutChartModel.swift; sourceTree = ""; }; @@ -1308,6 +1310,7 @@ D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */, D2ED27EA254B0CE700A1C293 /* AlertModel.swift */, D2ED27E8254B0CE600A1C293 /* ActionPopupModel.swift */, + C6687440259D92D400F32D13 /* ActionTopNotificationModel.swift */, ); path = Actions; sourceTree = ""; @@ -2432,6 +2435,7 @@ 32F8804824765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift in Sources */, D2CAC7CF2511052300C75681 /* CollapsableNotificationModel.swift in Sources */, DBC4391822442197001AB423 /* CaretView.swift in Sources */, + C6687441259D92D400F32D13 /* ActionTopNotificationModel.swift in Sources */, C07065C42395677300FBF997 /* Link.swift in Sources */, 0A69F611241BDEA700F7231B /* RuleAnyRequiredModel.swift in Sources */, D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Actions/ActionTopNotificationModel.swift b/MVMCoreUI/Atomic/Actions/ActionTopNotificationModel.swift new file mode 100644 index 00000000..7ca1d6aa --- /dev/null +++ b/MVMCoreUI/Atomic/Actions/ActionTopNotificationModel.swift @@ -0,0 +1,24 @@ +// +// ActionTopNotificationModel.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 31/12/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class ActionTopNotificationModel: ActionModelProtocol { + + public static var identifier: String = "topNotification" + public var actionType: String = ActionTopNotificationModel.identifier + public var topNotification: TopNotificationModel + public var extraParameters: JSONValueDictionary? + public var analyticsData: JSONValueDictionary? + + public init(topNotification: TopNotificationModel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.topNotification = topNotification + self.extraParameters = extraParameters + self.analyticsData = analyticsData + } +} diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift index dc4e15de..ecb89f32 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift @@ -71,13 +71,17 @@ import UIKit public var showError: Bool { get { return entryFieldContainer.showError } set (error) { - self.feedback = error ? entryFieldModel?.errorMessage : entryFieldModel?.feedback + self.feedback = error ? errorMessage : entryFieldModel?.feedback self.feedbackLabel.textColor = error ? entryFieldModel?.errorTextColor?.uiColor ?? .mvmBlack : .mvmBlack self.entryFieldContainer.showError = error self.entryFieldModel?.showError = error } } + var errorMessage: String? { + entryFieldModel?.dynamicErrorMessage ?? entryFieldModel?.errorMessage + } + /// Toggles original or locked UI. public var isLocked: Bool { get { return entryFieldContainer.isLocked } @@ -306,16 +310,30 @@ import UIKit if self.isSelected { self.updateValidation(model.isValid ?? true) + } else if model.isValid ?? true && self.showError { self.showError = false } }) } + 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 + } + }) + } + title = model.title feedback = model.feedback isEnabled = model.enabled entryFieldContainer.disableAllBorders = model.hideBorders + accessibilityIdentifier = model.accessibilityIdentifier ?? model.fieldKey if let isLocked = model.locked { self.isLocked = isLocked diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift index 0cccd0fa..20f88028 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift @@ -20,6 +20,13 @@ import Foundation public var accessibilityIdentifier: String? public var title: String? public var feedback: String? + public var shouldClearText: Bool = false + public var dynamicErrorMessage: String? { + didSet { + isValid = dynamicErrorMessage?.isEmpty ?? true + updateUIDynamicError?() + } + } public var errorMessage: String? public var errorTextColor: Color? public var enabled: Bool = true @@ -39,6 +46,9 @@ import Foundation /// Temporary binding mechanism for the view to update on enable changes. public var updateUI: ActionBlock? + + // TODO: Remove once updateUI is fixed with isSelected + public var updateUIDynamicError: ActionBlock? //-------------------------------------------------- // MARK: - Keys @@ -66,7 +76,12 @@ import Foundation // MARK: - Validation Methods //-------------------------------------------------- - public func formFieldValue() -> AnyHashable? { text } + public func formFieldValue() -> AnyHashable? { + if dynamicErrorMessage != nil { + dynamicErrorMessage = nil + } + return text + } public func setValidity(_ valid: Bool, rule: RulesProtocol) { diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index ff262876..559b611b 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -259,6 +259,7 @@ import Foundation try? ModelRegistry.register(ActionTopAlertModel.self) try? ModelRegistry.register(ActionCollapseNotificationModel.self) try? ModelRegistry.register(ActionOpenPanelModel.self) + try? ModelRegistry.register(ActionTopNotificationModel.self) // MARK:- Behaviors try? ModelRegistry.register(ScreenBrightnessModifierBehavior.self) diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index 1b9ce0dc..be67bfca 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -9,12 +9,12 @@ import Foundation -@objcMembers public class CarouselItemModel: MoleculeCollectionItemModel, CarouselItemModelProtocol { +@objcMembers open class CarouselItemModel: MoleculeCollectionItemModel, CarouselItemModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public override class var identifier: String { + open override class var identifier: String { return "carouselItem" } @@ -44,7 +44,7 @@ import Foundation try super.init(from: decoder) } - public override func encode(to encoder: Encoder) throws { + open override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(peakingUI, forKey: .peakingUI) diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index 645d16b1..f9df766f 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -9,7 +9,7 @@ import Foundation /// A model for a collection item that is a container for any molecule. -@objcMembers public class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol { +@objcMembers open class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol { open override class var identifier: String { return "collectionItem" } diff --git a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift index 65c6e18c..d2696722 100644 --- a/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift +++ b/MVMCoreUI/Atomic/Molecules/TopNotification/NotificationXButton.swift @@ -26,6 +26,8 @@ import Foundation adjustsImageWhenHighlighted = false accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "AccCloseButton") setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + heightAnchor.constraint(equalToConstant: 16.0).isActive = true + widthAnchor.constraint(equalToConstant: 16.0).isActive = true } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 4b6b565e..ba057444 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -19,7 +19,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol var observer: NSKeyValueObservation? public var templateModel: ListPageTemplateModel? - + //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- @@ -41,7 +41,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - + open override func parsePageJSON() throws { try parseTemplate(json: loadObject?.pageJSON) try super.parsePageJSON() @@ -54,8 +54,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open override func viewForTop() -> UIView { guard let headerModel = templateModel?.header, - let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar) - else { return super.viewForTop() } + let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar) + else { return super.viewForTop() } // Temporary, Default the horizontal padding if var container = templateModel?.header as? ContainerModelProtocol, container.useHorizontalMargins == nil { @@ -67,8 +67,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol override open func viewForBottom() -> UIView { guard let footerModel = templateModel?.footer, - let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar) - else { return super.viewForBottom() } + let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar) + else { return super.viewForBottom() } return molecule } @@ -86,7 +86,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol //Handle scroll handleScrollToSpecificRow() } - + //-------------------------------------------------- // MARK: - Handle scroll to spefic row @@ -117,12 +117,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { guard let moleculeInfo = getMoleculeInfo(for: indexPath), - let estimatedHeight = (moleculeInfo.class as? MoleculeViewProtocol.Type)?.estimatedHeight(with: moleculeInfo.molecule, delegateObject() as? MVMCoreUIDelegateObject) - else { return 0 } + let estimatedHeight = (moleculeInfo.class as? MoleculeViewProtocol.Type)?.estimatedHeight(with: moleculeInfo.molecule, delegateObject() as? MVMCoreUIDelegateObject) + else { return 0 } return estimatedHeight } - + open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return moleculesInfo?.count ?? 0 } @@ -130,8 +130,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let moleculeInfo = getMoleculeInfo(for: indexPath), - let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier) - else { return UITableViewCell() } + let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier) + else { return UITableViewCell() } (cell as? MoleculeViewProtocol)?.reset() (cell as? MoleculeListCellProtocol)?.setLines(with: templateModel?.line, delegateObject: delegateObjectIVar, additionalData: nil, indexPath: indexPath) @@ -222,8 +222,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol func createMoleculeInfo(with listItem: MoleculeModelProtocol?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? { guard let listItem = listItem, - let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(listItem) - else { return nil } + let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(listItem) + else { return nil } let moleculeName = moleculeClass.nameForReuse(with: listItem, delegateObject() as? MVMCoreUIDelegateObject) ?? listItem.moleculeName diff --git a/MVMCoreUI/BaseClasses/UICollectionViewLeftAlignedLayout.swift b/MVMCoreUI/BaseClasses/UICollectionViewLeftAlignedLayout.swift index 717bbc55..080b4e88 100644 --- a/MVMCoreUI/BaseClasses/UICollectionViewLeftAlignedLayout.swift +++ b/MVMCoreUI/BaseClasses/UICollectionViewLeftAlignedLayout.swift @@ -9,8 +9,8 @@ import Foundation -class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout { - override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { +public class UICollectionViewLeftAlignedLayout: UICollectionViewFlowLayout { + public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil } var newAttributesForElementsInRect = [UICollectionViewLayoutAttributes]() for attribute in attributes { diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 33c47344..f0ed1ba4 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -18,12 +18,8 @@ import UIKit @objc public var loadObject: MVMCoreLoadObject? public var model: MVMControllerModelProtocol? public var pageModel: PageModelProtocol? { - get { - return model - } - set { - model = newValue as? MVMControllerModelProtocol - } + get { model } + set { model = newValue as? MVMControllerModelProtocol } } /// Set if this page is containted in a manager. @@ -31,19 +27,17 @@ import UIKit /// A temporary iVar backer for delegateObject() until we change the protocol public lazy var delegateObjectIVar: MVMCoreUIDelegateObject = { - return MVMCoreUIDelegateObject.create(withDelegateForAll: self) + MVMCoreUIDelegateObject.create(withDelegateForAll: self) }() - public func delegateObject() -> DelegateObject? { - return delegateObjectIVar - } + public func delegateObject() -> DelegateObject? { delegateObjectIVar } public var formValidator: FormValidator? public var needsUpdateUI = false private var observingForResponses = false private var initialLoadFinished = false - private var previousScreenSize = CGSize.zero + public var previousScreenSize = CGSize.zero public var selectedField: UIView? @@ -52,7 +46,7 @@ import UIKit /// Checks if the screen width has changed open func screenSizeChanged() -> Bool { - return !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1) + !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1) } //-------------------------------------------------- @@ -61,8 +55,8 @@ import UIKit open func observeForResponseJSONUpdates() { guard !observingForResponses, - (pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0) - else { return } + (pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0) + else { return } observingForResponses = true NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) @@ -80,28 +74,28 @@ import UIKit } open func modulesToListenFor() -> [String]? { - return loadObject?.requestParameters?.modules as? [String] + loadObject?.requestParameters?.modules as? [String] } @objc open func responseJSONUpdated(notification: Notification) { // Checks for a page we are listening for. var newData = false if let pagesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyPageMap), - let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in - guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened), - let pageType = page.optionalStringForKey(KeyPageType), - pageType == pageTypeListened - else { return false } - - return true - }) { + let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in + guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened), + let pageType = page.optionalStringForKey(KeyPageType), + pageType == pageTypeListened + else { return false } + + return true + }) { newData = true loadObject?.pageJSON = pagesLoaded.optionalDictionaryForKey(pageType) } // Checks for modules we are listening for. if let modulesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyModuleMap), - let modulesListened = modulesToListenFor() { + let modulesListened = modulesToListenFor() { for moduleName in modulesListened { if let module = modulesLoaded.optionalDictionaryForKey(moduleName) { newData = true @@ -196,9 +190,9 @@ import UIKit open class func verifyRequiredModulesLoaded(for loadObject: MVMCoreLoadObject?, error: AutoreleasingUnsafeMutablePointer) -> Bool { guard let pageType = loadObject?.pageType, - var modulesRequired = MVMCoreUIViewControllerMappingObject.shared()?.modulesRequired(forPageType: pageType), - !modulesRequired.isEmpty - else { return true } + var modulesRequired = MVMCoreUIViewControllerMappingObject.shared()?.modulesRequired(forPageType: pageType), + !modulesRequired.isEmpty + else { return true } guard let loadedModules = loadObject?.modulesJSON else { return false } @@ -263,8 +257,8 @@ import UIKit /// Sets the navigation item for this view controller. open func setNavigationItem() { guard let navigationItemModel = getNavigationModel(), - let navigationController = navigationController - else { return } + let navigationController = navigationController + else { return } // Utilize helper function to set the navigation item state. NavigationController.setNavigationItem(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: self) @@ -273,9 +267,9 @@ import UIKit /// Sets the appearance of the navigation bar based on the model. open func setNavigationBar() { guard let navigationItemModel = getNavigationModel(), - let navigationController = navigationController else { - MVMCoreUISession.sharedGlobal()?.splitViewController?.parent?.setNeedsStatusBarAppearanceUpdate() - return + let navigationController = navigationController else { + MVMCoreUISession.sharedGlobal()?.splitViewController?.parent?.setNeedsStatusBarAppearanceUpdate() + return } // Utilize helper function to set the split view and navigation item state. @@ -405,7 +399,7 @@ import UIKit } open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return MVMCoreGetterUtility.isOnIPad() ? UIInterfaceOrientationMask.all : UIInterfaceOrientationMask.portrait + MVMCoreGetterUtility.isOnIPad() ? UIInterfaceOrientationMask.all : UIInterfaceOrientationMask.portrait } open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -424,6 +418,7 @@ import UIKit open func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) { pageShown() } + //-------------------------------------------------- // MARK: - MVMCoreLoadDelegateProtocol //-------------------------------------------------- @@ -435,15 +430,30 @@ import UIKit // Open the support panel if error == nil, - loadObject?.requestParameters?.openSupportPanel ?? (loadObject?.systemParametersJSON?.boolForKey(KeyOpenSupport) ?? false) == true { + loadObject?.requestParameters?.openSupportPanel ?? (loadObject?.systemParametersJSON?.boolForKey(KeyOpenSupport) ?? false) == true { MVMCoreUISession.sharedGlobal()?.splitViewController?.showRightPanel(animated: true) } } - + /// Override this method to avoid adding form params. open func addFormParams(_ requestParameters: MVMCoreRequestParameters) { formValidator?.addFormParams(requestParameters: requestParameters) } + + public func handleFieldErrors(_ fieldErrors: [Any]?, loadObject: MVMCoreLoadObject) { + + for case let fieldError as [AnyHashable: Any] in fieldErrors ?? [] { + + guard let fieldName = fieldError["fieldName"] as? String, + let userError = fieldError["userMessage"] as? String, + let entryFieldModel = formValidator?.fields[fieldName] as? EntryFieldModel + else { continue } + + entryFieldModel.shouldClearText = fieldError["clearText"] as? Bool ?? true + entryFieldModel.dynamicErrorMessage = userError + } + } + //-------------------------------------------------- // MARK: - MVMCoreActionDelegateProtocol //-------------------------------------------------- @@ -469,9 +479,9 @@ import UIKit open func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? { guard let moduleJSON = loadObject?.modulesJSON?.optionalDictionaryForKey(moleculeName), - let moleculeName = moduleJSON.optionalStringForKey("moleculeName"), - let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) - else { return nil } + let moleculeName = moduleJSON.optionalStringForKey("moleculeName"), + let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) + else { return nil } do { return try modelType.decode(jsonDict: moduleJSON) as? MoleculeModelProtocol @@ -483,8 +493,8 @@ import UIKit } // Needed otherwise when subclassed, the extension gets called. - open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) {} - open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { return nil } + open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } + open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { nil } open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) { } open func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) { } @@ -514,13 +524,13 @@ import UIKit } open func showRightPanelForScreenBeforeLaunchApp() -> Bool { - return loadObject?.pageJSON?.lenientBoolForKey("showRightPanel") ?? false + loadObject?.pageJSON?.lenientBoolForKey("showRightPanel") ?? false } // TODO: make molecular open func isOverridingRightButton() -> Bool { guard let rightPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("rightPanelButtonLink") - else { return false } + else { return false } MVMCoreActionHandler.shared()?.handleAction(with: rightPanelLink, additionalData: nil, delegateObject: delegateObject()) return true } @@ -528,7 +538,7 @@ import UIKit // TODO: make molecular open func isOverridingLeftButton() -> Bool { guard let leftPanelLink = loadObject?.pageJSON?.optionalDictionaryForKey("leftPanelButtonLink") - else { return false } + else { return false } MVMCoreActionHandler.shared()?.handleAction(with: leftPanelLink, additionalData: nil, delegateObject: delegateObject()) return true } @@ -536,8 +546,8 @@ import UIKit // Eventually will be moved to Model open func bottomProgress() -> Float? { guard let progressString = loadObject?.pageJSON?.optionalStringForKey(KeyProgressPercent), - let progress = Float(progressString) - else { return nil } + let progress = Float(progressString) + else { return nil } return progress / Float(100) } @@ -558,8 +568,8 @@ import UIKit // TODO: Make this into a protocol if UIAccessibility.isVoiceOverRunning { if let toolBar = textField.inputAccessoryView as? UIToolbar, - let _ = toolBar.items?.last, - let pickerView = textField.inputView as? UIPickerView { + let _ = toolBar.items?.last, + let pickerView = textField.inputView as? UIPickerView { view.accessibilityElements = [pickerView, toolBar] } @@ -610,6 +620,6 @@ import UIKit //-------------------------------------------------- func executeBehaviors(_ behaviorBlock:(_ behavior:T)->Void) { - model?.behaviors?.compactMap({ $0 as? T }).forEach { behaviorBlock($0) } + model?.behaviors?.compactMap { $0 as? T }.forEach { behaviorBlock($0) } } } diff --git a/MVMCoreUI/Containers/Views/Container.swift b/MVMCoreUI/Containers/Views/Container.swift index 7ad02f9d..159535d7 100644 --- a/MVMCoreUI/Containers/Views/Container.swift +++ b/MVMCoreUI/Containers/Views/Container.swift @@ -15,9 +15,9 @@ open class Container: View, ContainerProtocol { //-------------------------------------------------- public var view: UIView? - let containerHelper = ContainerHelper() + public let containerHelper = ContainerHelper() - var containerModel: ContainerModelProtocol? { + public var containerModel: ContainerModelProtocol? { get { return model as? ContainerModelProtocol } } diff --git a/MVMCoreUI/Containers/Views/ContainerHelper.swift b/MVMCoreUI/Containers/Views/ContainerHelper.swift index f60d1ee6..b60bcc1e 100644 --- a/MVMCoreUI/Containers/Views/ContainerHelper.swift +++ b/MVMCoreUI/Containers/Views/ContainerHelper.swift @@ -15,23 +15,23 @@ open class ContainerHelper: NSObject { // MARK: - Constraints //-------------------------------------------------- - var leftConstraint: NSLayoutConstraint? - var topConstraint: NSLayoutConstraint? - var bottomConstraint: NSLayoutConstraint? - var rightConstraint: NSLayoutConstraint? + open var leftConstraint: NSLayoutConstraint? + open var topConstraint: NSLayoutConstraint? + open var bottomConstraint: NSLayoutConstraint? + open var rightConstraint: NSLayoutConstraint? - var alignCenterHorizontalConstraint: NSLayoutConstraint? - var alignCenterLeftConstraint: NSLayoutConstraint? - var alignCenterRightConstraint: NSLayoutConstraint? + open var alignCenterHorizontalConstraint: NSLayoutConstraint? + open var alignCenterLeftConstraint: NSLayoutConstraint? + open var alignCenterRightConstraint: NSLayoutConstraint? - var alignCenterVerticalConstraint: NSLayoutConstraint? - var alignCenterTopConstraint: NSLayoutConstraint? - var alignCenterBottomConstraint: NSLayoutConstraint? + open var alignCenterVerticalConstraint: NSLayoutConstraint? + open var alignCenterTopConstraint: NSLayoutConstraint? + open var alignCenterBottomConstraint: NSLayoutConstraint? - var leftLowConstraint: NSLayoutConstraint? - var topLowConstraint: NSLayoutConstraint? - var bottomLowConstraint: NSLayoutConstraint? - var rightLowConstraint: NSLayoutConstraint? + open var leftLowConstraint: NSLayoutConstraint? + open var topLowConstraint: NSLayoutConstraint? + open var bottomLowConstraint: NSLayoutConstraint? + open var rightLowConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Methods diff --git a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift index b9d2defe..f769e040 100644 --- a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift @@ -22,7 +22,6 @@ public protocol FormFieldProtocol: FormItemProtocol { } extension FormFieldProtocol { - var baseValue: AnyHashable? { - return nil - } + + var baseValue: AnyHashable? { nil } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h index 3835207b..f6b89806 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h @@ -24,6 +24,9 @@ NS_ASSUME_NONNULL_BEGIN // Collapses the current top notification - (void)collapseNotificationAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +// Shows a topnotification new molecular +- (void)topNotificationAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; + #pragma mark - Deprecated // Shows a popup @@ -32,6 +35,9 @@ NS_ASSUME_NONNULL_BEGIN // Shows a top alert - (void)topAlertAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate __deprecated; +// Shows a molecular top alert +- (void)topNotificationAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate __deprecated; + // Collapses the current top notification - (void)collapseNotificationAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate __deprecated; diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m index 14888ed1..79f92990 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m @@ -31,6 +31,9 @@ } else if ([actionType isEqualToString:KeyActionTypeAlert]) { [self showAlert:actionInformation additionalData:additionalData delegateObject:delegateObject]; return YES; + } else if ([actionType isEqualToString:KeyActionTypeTopNotification]) { + [self topNotificationAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; + return YES; } return NO; } @@ -102,6 +105,11 @@ } } +- (void)topNotificationAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject { + //Handle molecular topnotification + [[MVMCoreUITopAlertView sharedGlobal] showTopAlertWith:[actionInformation dict:@"topNotification"] ?: @{}]; +} + - (void)defaultHandleActionError:(nonnull MVMCoreErrorObject *)error additionalData:(nullable NSDictionary *)additionalData { [super defaultHandleActionError:error additionalData:additionalData]; if (!error.silentError) { @@ -126,6 +134,9 @@ } else if ([actionType isEqualToString:KeyActionTypeCollapseNotification]) { [self collapseNotificationAction:actionInformation additionalData:additionalData delegate:delegate]; return YES; + } else if ([actionType isEqualToString:KeyActionTypeTopNotification]) { + [self topNotificationAction:actionInformation additionalData:additionalData delegate:delegate]; + return YES; } return NO; } @@ -172,4 +183,9 @@ } } +- (void)topNotificationAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate { + //Handle molecular topnotification + [[MVMCoreUITopAlertView sharedGlobal] showTopAlertWith:[actionInformation dict:@"topNotification"] ?: @{}]; +} + @end diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertBaseView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertBaseView.m index 11d10ff0..892a6e94 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertBaseView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertBaseView.m @@ -77,6 +77,8 @@ [[MVMCoreUISession sharedGlobal].topAlertView hideAlertView:YES completionHandler:nil]; } } centeredVertically:YES]; + [closeButton.heightAnchor constraintEqualToConstant:16.0].active = YES; + [closeButton.widthAnchor constraintEqualToConstant:16.0].active = YES; [MVMCoreUITopAlertBaseView amendAccesibilityLabelForView:closeButton]; return closeButton; } diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift index f179fb11..107a3596 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift @@ -61,6 +61,12 @@ public extension MVMCoreUITopAlertView { MVMCoreAlertHandler.shared()?.add(operation) } + /// Shows the top alert with the json. + @objc func showTopAlert(with json: [AnyHashable: Any]) { + guard let model = decodeTopNotification(with: json, delegateObject: getDelegateObject()) else { return } + showTopAlert(with: model) + } + /// Checks for existing top alert object of same type and updates it. Only happens for molecular top alerts. Returns true if we updated. private func checkAndUpdateExisting(with topAlertObject: MVMCoreTopAlertObject) -> Bool { guard let queue = MVMCoreAlertHandler.shared()?.topAlertQueue.operations else { return false } diff --git a/MVMCoreUI/Utility/MVMCoreUIConstants.h b/MVMCoreUI/Utility/MVMCoreUIConstants.h index b96d50ed..ab77986b 100644 --- a/MVMCoreUI/Utility/MVMCoreUIConstants.h +++ b/MVMCoreUI/Utility/MVMCoreUIConstants.h @@ -48,6 +48,7 @@ extern NSString * const KeyActionTypePopup; extern NSString * const KeyActionTypeAlert; extern NSString * const KeyActionTypeTopAlert; extern NSString * const KeyActionTypeCollapseNotification; +extern NSString * const KeyActionTypeTopNotification; /// Key for molecular top notification architecture. extern NSString * const KeyTopAlert; diff --git a/MVMCoreUI/Utility/MVMCoreUIConstants.m b/MVMCoreUI/Utility/MVMCoreUIConstants.m index 092851cd..2483826f 100644 --- a/MVMCoreUI/Utility/MVMCoreUIConstants.m +++ b/MVMCoreUI/Utility/MVMCoreUIConstants.m @@ -47,6 +47,7 @@ NSString * const KeyActionTypeAlert = @"alert"; NSString * const KeyActionTypeTopAlert = @"topAlert"; NSString * const KeyActionTypeCollapseNotification = @"collapseNotification"; +NSString * const KeyActionTypeTopNotification = @"topNotification"; NSString * const KeyTopAlert = @"TopNotification"; #pragma mark - Values