From b89c1e66f5d87d92b297b22281ff72b55444cc3f Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Thu, 1 Apr 2021 17:33:50 -0400 Subject: [PATCH] expand out behaviors into page data sharing, molecule updates before rendering and custom action handling. continue expanding molecular traversal reach and custom model overrides. --- .../Atomic/Atoms/Views/ImageViewModel.swift | 5 ++- ...tLeftVariableIconWithRightCaretModel.swift | 12 +++-- .../TwoButtonViewModel.swift | 4 +- .../HeadlineBodyModel.swift | 28 ++++++++++++ .../Protocols/MoleculeDelegateProtocol.swift | 2 + .../BaseControllers/ViewController.swift | 44 ++++++++++++++++--- MVMCoreUI/Behaviors/GetContactBehavior.swift | 5 +-- .../Behaviors/PageBehaviorProtocol.swift | 22 +++++++++- .../PageBehaviorProtocolRequirer.swift | 1 + 9 files changed, 105 insertions(+), 18 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift b/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift index a970e072..65bc978c 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift @@ -7,12 +7,13 @@ // -@objcMembers public class ImageViewModel: MoleculeModelProtocol { +@objcMembers open class ImageViewModel: MoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public static var identifier: String = "image" + open class var identifier: String { "image" } + public var backgroundColor: Color? public var moleculeName: String = ImageViewModel.identifier public var image: String diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift index 479ebadb..5e204eda 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift @@ -7,7 +7,7 @@ // -public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeModelProtocol { +public class ListLeftVariableIconWithRightCaretModel: ListItemModel, ParentMoleculeModelProtocol { //----------------------------------------------------- // MARK: - Properties //----------------------------------------------------- @@ -17,6 +17,10 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeMod public var leftLabel: LabelModel public var rightLabel: LabelModel + public var children: [MoleculeModelProtocol] { + return [image, leftLabel, rightLabel] + } + //----------------------------------------------------- // MARK: - Methods //----------------------------------------------------- @@ -57,9 +61,9 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeMod required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - leftLabel = try typeContainer.decode(LabelModel.self, forKey: .leftLabel) - rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) - image = try typeContainer.decode(ImageViewModel.self, forKey: .image) + leftLabel = try typeContainer.decodeMoleculeIfPresent(codingKey: .leftLabel)! + rightLabel = try typeContainer.decodeMoleculeIfPresent(codingKey: .rightLabel)! + image = try typeContainer.decodeMoleculeIfPresent(codingKey: .image)! try super.init(from: decoder) } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift index c767dc36..4d843e6a 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -50,11 +50,11 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol { required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - primaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .primaryButton) + primaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .primaryButton) if primaryButton?.style == nil { primaryButton?.style = .primary } - secondaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .secondaryButton) + secondaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .secondaryButton) if secondaryButton?.style == nil { secondaryButton?.style = .secondary } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index a7ff7280..25086093 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -48,4 +48,32 @@ import Foundation public init(body: LabelModel) { self.body = body } + + //----------------------------------------------------- + // MARK: - Codec + //----------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case headline + case body + case style + case backgroundColor + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + headline = try typeContainer.decodeMoleculeIfPresent(codingKey: .headline) + body = try typeContainer.decodeMoleculeIfPresent(codingKey: .body) + style = try typeContainer.decodeIfPresent(Style.self, forKey: .style) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(headline, forKey: .headline) + try container.encodeIfPresent(style, forKey: .style) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift index 4f152216..e61ea662 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift @@ -10,6 +10,8 @@ import Foundation public protocol MoleculeDelegateProtocol: AnyObject { + func getRootMolecules() -> [MoleculeModelProtocol] + /// returns a module for the corresponding module name. func getModuleWithName(_ name: String?) -> [AnyHashable : Any]? func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index b06260a8..efb083a5 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -143,7 +143,6 @@ import UIKit // Parse the model for the page. do { try parsePageJSON() - return true } catch let parsingError { // Log all parsing errors and fail load. handleLoggingFor(parsingError: parsingError) @@ -153,6 +152,14 @@ import UIKit } return false } + + if let pageData = loadObject.dataForPage { + executeBehaviors { (behavior: PageLocalDataShareBehavior) in + behavior.receiveLocalPageData(pageData, delegateObjectIVar) + } + } + + return true } func handleLoggingFor(parsingError: Error) { @@ -230,6 +237,10 @@ import UIKit /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, open func handleNewData() { + executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in + behavior.onPageNew(rootMolecules: getRootMolecules(), delegateObjectIVar) + } + if formValidator == nil { let rules = model?.formRules formValidator = FormValidator(rules) @@ -373,6 +384,10 @@ import UIKit // Track. MVMCoreUISession.sharedGlobal()?.currentPageType = pageType MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self) + + executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in + behavior.onPageShown(self?.delegateObjectIVar) + } } open override func viewDidAppear(_ animated: Bool) { @@ -381,10 +396,6 @@ import UIKit if manager == nil { pageShown() } - - executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in - behavior.onPageShown(self?.delegateObjectIVar) - } } open override func viewDidDisappear(_ animated: Bool) { @@ -463,17 +474,38 @@ 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, actionInformation: actionInformation, additionalData: additionalData, delegateObject: delegateObject()) + var pageForwardedData = additionalData ?? [:] + executeBehaviors { (behavior: PageLocalDataShareBehavior) in + let dataMap = behavior.compileLocalPageDataForTransfer(delegateObjectIVar) + pageForwardedData.merge(dataMap) { (current, _) in current } + } + MVMCoreActionHandler.defaultHandleOpenPage(for: requestParameters, actionInformation: actionInformation, additionalData: pageForwardedData, delegateObject: delegateObject()) } open func logAction(withActionInformation actionInformation: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?) { MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: self, actionInformation: actionInformation, additionalData: additionalData) } + open func handleUnknownActionType(_ actionType: String?, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { + var handled = false + executeBehaviors { (behavior: PageCustomActionHandlerBehavior) in + if (!handled) { + handled = behavior.handleAction(type: actionType, information: actionInformation, additionalData: additionalData) + } + } + if (!handled) { + MVMCoreUIActionHandler.defaultHandleUnknownActionType(actionType, actionInformation: actionInformation, additionalData: additionalData, delegateObject: delegateObjectIVar) + } + } + //-------------------------------------------------- // MARK: - MoleculeDelegateProtocol //-------------------------------------------------- + open func getRootMolecules() -> [MoleculeModelProtocol] { + return model?.rootMolecules ?? [] + } + open func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? { guard let name = name else { return nil } return loadObject?.modulesJSON?.optionalDictionaryForKey(name) diff --git a/MVMCoreUI/Behaviors/GetContactBehavior.swift b/MVMCoreUI/Behaviors/GetContactBehavior.swift index 91ef599d..2c6205f2 100644 --- a/MVMCoreUI/Behaviors/GetContactBehavior.swift +++ b/MVMCoreUI/Behaviors/GetContactBehavior.swift @@ -33,11 +33,10 @@ public class PageGetContactBehavior: PageVisibilityBehavior { CNContactStore().requestAccess(for: .contacts) { [weak self] (access, error) in guard access, error == nil, - // TODO: Clean up this interface - let model = (self?.delegate?.moleculeDelegate as? PageProtocol)?.pageModel as? TemplateModelProtocol else { return } + let rootMolecules = self?.delegate?.moleculeDelegate?.getRootMolecules() else { return } // Iterate models and provide contact let store = CNContactStore() - let consumers: [PageGetContactBehaviorConsumerProtocol] = model.allMoleculesOfType() + let consumers: [PageGetContactBehaviorConsumerProtocol] = rootMolecules.allMoleculesOfType() for consumer in consumers { guard let parameters = consumer.getMatchParameters(), let contacts = try? store.unifiedContacts(matching: parameters.0, keysToFetch: parameters.1) else { return } diff --git a/MVMCoreUI/Behaviors/PageBehaviorProtocol.swift b/MVMCoreUI/Behaviors/PageBehaviorProtocol.swift index 6085188c..8aa684b8 100644 --- a/MVMCoreUI/Behaviors/PageBehaviorProtocol.swift +++ b/MVMCoreUI/Behaviors/PageBehaviorProtocol.swift @@ -14,6 +14,15 @@ public protocol PageBehaviorProtocol: ModelHandlerProtocol { init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) } +/** + Behavior conforming protocols. Behaviors will conform to one or more of these protocols to receive page lifecycle events that pertain to them. + */ + +public protocol PageMoleculeTransformationBehavior { + + func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject) +} + public protocol PageVisibilityBehavior: PageBehaviorProtocol { func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) @@ -22,7 +31,18 @@ public protocol PageVisibilityBehavior: PageBehaviorProtocol { public protocol PageScrolledBehavior: PageBehaviorProtocol { - func pageScrolled(scrollView: UIScrollView,_ delegateObject: MVMCoreUIDelegateObject?) + func pageScrolled(scrollView: UIScrollView, _ delegateObject: MVMCoreUIDelegateObject?) +} + +public protocol PageLocalDataShareBehavior: PageBehaviorProtocol { + + func compileLocalPageDataForTransfer(_ delegateObject: MVMCoreUIDelegateObject) -> [AnyHashable: Any] + func receiveLocalPageData(_ data:[AnyHashable: Any], _ delegateObject: MVMCoreUIDelegateObject) +} + +public protocol PageCustomActionHandlerBehavior: PageBehaviorProtocol { + + func handleAction(type actionType: String?, information: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) -> Bool } public extension MVMCoreUIDelegateObject { diff --git a/MVMCoreUI/Behaviors/PageBehaviorProtocolRequirer.swift b/MVMCoreUI/Behaviors/PageBehaviorProtocolRequirer.swift index 9ca72e1d..6c7f897b 100644 --- a/MVMCoreUI/Behaviors/PageBehaviorProtocolRequirer.swift +++ b/MVMCoreUI/Behaviors/PageBehaviorProtocolRequirer.swift @@ -6,6 +6,7 @@ // Copyright © 2021 Verizon Wireless. All rights reserved. // +/// Protocol for molecules to implicity require behaviors. public protocol PageBehaviorProtocolRequirer { func getRequiredBehaviors() -> [PageBehaviorModelProtocol] }