From 1f895ebdbda36536887a105be73c522b2793c7ba Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Tue, 12 Sep 2023 20:10:20 -0400 Subject: [PATCH 01/36] replaceMoleculeBehavior, cleaner child replacement helper, first working sample. --- ...LeftVariableRadioButtonBodyTextModel.swift | 13 ++++++- .../HeadlineBodyModel.swift | 8 +--- MVMCoreUI/Atomic/Organisms/StackModel.swift | 3 ++ .../ParentMoleculeModelProtocol.swift | 23 +++++++---- .../MoleculeTreeTraversalProtocol.swift | 7 +++- .../BaseControllers/ViewController.swift | 4 +- .../Protocols/PageBehaviorProtocol.swift | 4 +- .../ReplacementMoleculeBehavior.swift | 39 +++++++++++++++++++ .../OtherHandlers/CoreUIModelMapping.swift | 1 + 9 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift index 6d8881e4..b4ef3495 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift @@ -7,7 +7,7 @@ // -open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, MoleculeModelProtocol { +open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, ParentMoleculeModelProtocol { //----------------------------------------------------- // MARK: - Properties //----------------------------------------------------- @@ -15,7 +15,16 @@ open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, MoleculeMode public static var identifier: String = "listLVRBBdy" public var radioButton: RadioButtonModel public var headlineBody: HeadlineBodyModel - + + public var children: [MoleculeModelProtocol] { + [radioButton, headlineBody] + } + + public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { + return replace(childMolecule: &radioButton, with: replacementMolecule) + || replace(childMolecule: &headlineBody, with: replacementMolecule) + } + //----------------------------------------------------- // MARK: - Initializer //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index a3eaf918..4371fe34 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -25,12 +25,8 @@ } public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { - return [ - \HeadlineBodyModel.headline, - \HeadlineBodyModel.body, - ].contains { - replaceChildMolecule(on: self, keyPath: $0, replacementMolecule: replacementMolecule) - } + return replace(childMolecule:&headline, with: replacementMolecule) + || replace(childMolecule:&body, with: replacementMolecule) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index da6c067e..cb8740ac 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -26,6 +26,9 @@ } public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { + // IDEALLY: + //return replace(inChildMolecules: &molecules, with: replacementMolecule) + guard let replacementMolecule = replacementMolecule as? StackItemModelProtocol & MoleculeModelProtocol else { return false } guard let matchingIndex = molecules.firstIndex(where: { molecule in molecule.id == replacementMolecule.id diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift index 78377a25..19a3a711 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -60,20 +60,29 @@ public extension ParentMoleculeModelProtocol { /// Top level test to replace child molecules. Each parent molecule should attempt to replace. func replaceChildMolecule(with molecule: MoleculeModelProtocol) -> Bool { return false } - /// Helper function for replacing molecules on a path. - func replaceChildMolecule(on target: P, keyPath: ReferenceWritableKeyPath, replacementMolecule: MoleculeModelProtocol) -> Bool { - if let currentMolecule = target[keyPath: keyPath], currentMolecule.id == replacementMolecule.id, let newHeadline = replacementMolecule as? T { - target[keyPath: keyPath] = newHeadline + /// Helper function for replacing molecules. + func replace(childMolecule: inout T?, with replacementMolecule: MoleculeModelProtocol) -> Bool { + if childMolecule != nil, childMolecule?.id == replacementMolecule.id, let newHeadline = replacementMolecule as? T { + childMolecule = newHeadline return true } return false } - func replaceChildMolecule(on target: P, keyPath: ReferenceWritableKeyPath, replacementMolecule: MoleculeModelProtocol) -> Bool { - if target[keyPath: keyPath].id == replacementMolecule.id, let newHeadline = replacementMolecule as? T { - target[keyPath: keyPath] = newHeadline + func replace(childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) -> Bool { + if childMolecule.id == replacementMolecule.id, let newHeadline = replacementMolecule as? T { + childMolecule = newHeadline return true } return false } + + func replace(inChildMolecules molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) -> Bool where T: MoleculeModelProtocol { + guard let replacementMolecule = replacementMolecule as? T else { return false } + guard let matchingIndex = molecules.firstIndex(where: { molecule in + molecule.id == replacementMolecule.id + }) else { return false } + molecules[matchingIndex] = replacementMolecule + return true + } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift index a2da7cf4..48f5166b 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift @@ -51,10 +51,13 @@ public extension MoleculeTreeTraversalProtocol { } } - func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) { + func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { + var didReplaceMolecule = false depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in guard let parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } - stop = parentMolecule.replaceChildMolecule(with: replacementMolecule) + didReplaceMolecule = parentMolecule.replaceChildMolecule(with: replacementMolecule) + stop = didReplaceMolecule } + return didReplaceMolecule } } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index d91e776e..16c1500f 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -74,7 +74,9 @@ import MVMCore } open func modulesToListenFor() -> [String]? { - loadObject?.requestParameters?.allModules() + let requestModules = loadObject?.requestParameters?.allModules() ?? [] + let behaviorModules = behaviors?.flatMap { $0.modulesToListenFor() } ?? [] + return requestModules + behaviorModules } @objc open func responseJSONUpdated(notification: Notification) { diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift index 543ee60c..c6292783 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift @@ -14,12 +14,15 @@ public protocol PageBehaviorProtocol: ModelHandlerProtocol { /// Should the behavior persist regardless of page behavior model updates. var transcendsPageUpdates: Bool { get } + func modulesToListenFor() -> [String] + /// Initializes the behavior with the model init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) } public extension PageBehaviorProtocol { var transcendsPageUpdates: Bool { return false } + func modulesToListenFor() -> [String] { return [] } } /** @@ -46,7 +49,6 @@ public extension PageMoleculeTransformationBehavior { } public protocol PageVisibilityBehavior: PageBehaviorProtocol { - func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) func willHidePage(_ delegateObject: MVMCoreUIDelegateObject?) diff --git a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift new file mode 100644 index 00000000..89a34f4a --- /dev/null +++ b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift @@ -0,0 +1,39 @@ +// +// ReplacementMoleculeBehavior.swift +// MVMCoreUI +// +// Created by Kyle Hedden on 9/12/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +public class ReplacableMoleculeBehaviorModel: PageBehaviorModelProtocol { + public class var identifier: String { "replaceMoleculeBehavior" } + public var shouldAllowMultipleInstances: Bool { true } + public var moleculeIds: [String] +} + +public class ReplacableMoleculeBehavior: PageMoleculeTransformationBehavior { + var moleculeIds: [String] + + public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + moleculeIds = (model as! ReplacableMoleculeBehaviorModel).moleculeIds + } + + public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + var shouldRefreshUI = false + for moleculeId in moleculeIds { + guard let replacementModel = delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId) else { continue } + let didReplace = rootMolecules.contains(where: { model in + return model.replaceMolecule(with: replacementModel) + }) + shouldRefreshUI = shouldRefreshUI || didReplace + } + } + + public func modulesToListenFor() -> [String] { + moleculeIds + } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index af356d93..91042b47 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -227,6 +227,7 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(handler: PageGetContactBehavior.self, for: PageGetContactBehaviorModel.self) ModelRegistry.register(handler: AddRemoveMoleculesBehavior.self, for: AddRemoveMoleculesBehaviorModel.self) ModelRegistry.register(handler: GetNotificationAuthStatusBehavior.self, for: GetNotificationAuthStatusBehaviorModel.self) + ModelRegistry.register(handler: ReplacableMoleculeBehavior.self, for: ReplacableMoleculeBehaviorModel.self) } open override class func registerActions() { From caf995c0b02de5af099e6c9f190d2a493990986a Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Wed, 13 Sep 2023 20:30:00 -0400 Subject: [PATCH 02/36] add type check error logic. expand to replacement to the rest of parent molecules. lift full tree replacement to the template. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 ++ .../Headers/H1/HeadersH1ButtonModel.swift | 5 ++ .../H1/HeadersH1LandingPageHeaderModel.swift | 9 +++ .../H1/HeadersH1NoButtonsBodyTextModel.swift | 4 ++ .../Headers/H2/HeadersH2ButtonsModel.swift | 5 ++ .../Headers/H2/HeadersH2CaretLinkModel.swift | 5 ++ .../Headers/H2/HeadersH2LinkModel.swift | 5 ++ .../H2/HeadersH2NoButtonsBodyTextModel.swift | 4 ++ .../H2/HeadersH2PricingTwoRowsModel.swift | 11 ++++ .../Headers/H2/HeadersH2TinyButtonModel.swift | 5 ++ ...istLeftVariableCheckboxBodyTextModel.swift | 5 ++ ...istLeftVariableIconAllTextLinksModel.swift | 5 ++ ...eIconWithRightCaretAllTextLinksModel.swift | 6 ++ ...iableIconWithRightCaretBodyTextModel.swift | 6 ++ ...tLeftVariableIconWithRightCaretModel.swift | 6 ++ ...LeftVariableRadioButtonBodyTextModel.swift | 6 +- ...tOneColumnFullWidthTextBodyTextModel.swift | 4 ++ ...htVariableButtonAllTextAndLinksModel.swift | 5 ++ ...riableRightCaretAlltextAndLinksModel.swift | 5 ++ .../LockUps/TitleLockupModel.swift | 6 ++ ...nFullWidthTextDividerSubsectionModel.swift | 5 ++ ...nTextWithWhitespaceDividerShortModel.swift | 5 ++ ...mnTextWithWhitespaceDividerTallModel.swift | 5 ++ .../TwoButtonViewModel.swift | 5 ++ .../LeftRightViews/CornerLabelsModel.swift | 9 +++ .../MoleculeContainerModel.swift | 4 ++ .../MoleculeContainerProtocol.swift | 4 ++ .../EyebrowHeadlineBodyLinkModel.swift | 7 ++ .../HeadlineBodyModel.swift | 6 +- .../Organisms/Carousel/CarouselModel.swift | 4 ++ MVMCoreUI/Atomic/Organisms/StackModel.swift | 16 ----- .../Atomic/Organisms/StackModelProtocol.swift | 4 ++ .../ParentMoleculeModelProtocol.swift | 66 ++++++++++--------- .../TemplateModelProtocol.swift | 30 ++++++++- .../Protocols/MoleculeDelegateProtocol.swift | 11 ++++ .../MoleculeTreeTraversalProtocol.swift | 12 +--- .../Atomic/Templates/BaseTemplateModel.swift | 6 +- .../Templates/CollectionTemplateModel.swift | 5 ++ .../Templates/ListPageTemplateModel.swift | 6 ++ .../Templates/StackPageTemplateModel.swift | 8 ++- .../Templates/ThreeLayerModelBase.swift | 6 ++ .../ThreeLayerPageTemplateModel.swift | 5 ++ .../BaseControllers/ViewController.swift | 6 +- .../ReplacementMoleculeBehavior.swift | 24 ++++--- .../OtherHandlers/CoreUIModelMapping.swift | 2 +- 45 files changed, 293 insertions(+), 79 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index acb13d9a..f02ca813 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -168,6 +168,7 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; + 5823ADF62AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823ADF52AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; }; @@ -754,6 +755,7 @@ 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = ""; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; + 5823ADF52AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplacementMoleculeBehavior.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = ""; }; @@ -1390,6 +1392,7 @@ D23A900826125FFB007E14CE /* GetContactBehavior.swift */, D270E5662642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift */, 22B678F829E7944E00CF4196 /* GetNotificationAuthStatusBehavior.swift */, + 5823ADF52AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift */, ); path = Behaviors; sourceTree = ""; @@ -2752,6 +2755,7 @@ 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */, + 5823ADF62AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift in Sources */, D20F3B44252E00E4004B3F56 /* PageProtocol.swift in Sources */, AA37CBD3251907200027344C /* StarsModel.swift in Sources */, 8D084AD22410BF7600951227 /* ListOneColumnFullWidthTextBodyText.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift index cdb61a4d..9ed584a9 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift @@ -20,6 +20,11 @@ public class HeadersH1ButtonModel: HeaderModel, MoleculeModelProtocol, ParentMol [headlineBody, buttons] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + || replaceChildMolecule(at: &buttons, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift index 79705fd9..651a7f89 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift @@ -24,6 +24,15 @@ public class HeadersH1LandingPageHeaderModel: HeaderModel, MoleculeModelProtocol [headline, headline2, subHeadline, body, link, buttons] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headline, with: molecule) + || replaceChildMolecule(at: &headline2, with: molecule) + || replaceChildMolecule(at: &subHeadline, with: molecule) + || replaceChildMolecule(at: &body, with: molecule) + || replaceChildMolecule(at: &link, with: molecule) + || replaceChildMolecule(at: &buttons, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift index 9d246d98..9f3e6133 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift @@ -20,6 +20,10 @@ public class HeadersH1NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol [headlineBody] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift index 77bc25c1..8a5a3e64 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift @@ -22,6 +22,11 @@ public class HeadersH2ButtonsModel: HeaderModel, MoleculeModelProtocol, ParentMo [headlineBody, buttons] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + || replaceChildMolecule(at: &buttons, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift index e0831a40..c50fb736 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift @@ -19,6 +19,11 @@ public class HeadersH2CaretLinkModel: HeaderModel, MoleculeModelProtocol, Parent [headlineBody, caretLink] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + || replaceChildMolecule(at: &caretLink, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift index 20d6afe2..ced6b47a 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift @@ -21,6 +21,11 @@ public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMolec [headlineBody, link] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + || replaceChildMolecule(at: &link, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift index 86d38707..774102da 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift @@ -21,6 +21,10 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol [headlineBody] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift index ebca7e25..54a62b5b 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift @@ -25,6 +25,17 @@ public class HeadersH2PricingTwoRowsModel: HeaderModel, MoleculeModelProtocol, P [headline, body, subBody, body2, subBody2, body3, subBody3].compactMap({$0}) } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headline, with: molecule) + || replaceChildMolecule(at: &body, with: molecule) + || replaceChildMolecule(at: &subBody, with: molecule) + || replaceChildMolecule(at: &body2, with: molecule) + || replaceChildMolecule(at: &body2, with: molecule) + || replaceChildMolecule(at: &subBody2, with: molecule) + || replaceChildMolecule(at: &body3, with: molecule) + || replaceChildMolecule(at: &subBody3, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift index e04ab07f..2a91c860 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift @@ -22,6 +22,11 @@ public class HeadersH2TinyButtonModel: HeaderModel, MoleculeModelProtocol, Paren [headlineBody, button] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + || replaceChildMolecule(at: &button, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift index 3ed92e02..ba7d4986 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift @@ -20,6 +20,11 @@ open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelPr [checkbox, headlineBody] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &checkbox, with: molecule) + || replaceChildMolecule(at: &headlineBody, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift index 46de07fa..c7a32e78 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift @@ -20,6 +20,11 @@ public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModel return [image, eyebrowHeadlineBodyLink] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &image, with: molecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) + } + //-------------------------------------------------- // MARK: - Method //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift index a4fed00e..9d5ce50b 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift @@ -21,6 +21,12 @@ public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel, return [image, eyebrowHeadlineBodyLink, rightLabel] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &image, with: molecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) + || replaceChildMolecule(at: &rightLabel, with: molecule) + } + //----------------------------------------------------- // MARK: - Methods //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift index 633989d5..6b95c669 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift @@ -21,6 +21,12 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Par [image, headlineBody, rightLabel] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &image, with: molecule) + || replaceChildMolecule(at: &headlineBody, with: molecule) + || replaceChildMolecule(at: &rightLabel, with: molecule) + } + //----------------------------------------------------- // MARK: - Methods //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift index 186ad988..a804b8d4 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift @@ -21,6 +21,12 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, ParentMolec return [image, leftLabel, rightLabel] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &image, with: molecule) + || replaceChildMolecule(at: &leftLabel, with: molecule) + || replaceChildMolecule(at: &rightLabel, with: molecule) + } + //----------------------------------------------------- // MARK: - Methods //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift index b4ef3495..808ee19e 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift @@ -20,9 +20,9 @@ open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, ParentMolecu [radioButton, headlineBody] } - public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { - return replace(childMolecule: &radioButton, with: replacementMolecule) - || replace(childMolecule: &headlineBody, with: replacementMolecule) + public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &radioButton, with: replacementMolecule) + || replaceChildMolecule(at: &headlineBody, with: replacementMolecule) } //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift index 94be5a53..aee85f74 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift @@ -39,6 +39,10 @@ public class ListOneColumnFullWidthTextBodyTextModel: ListItemModel, MoleculeMod return [headlineBody] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headlineBody, with: molecule) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift index 21a4b08c..121db2f4 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift @@ -40,6 +40,11 @@ public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, Molecul return [button, eyebrowHeadlineBodyLink] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &button, with: molecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift index e2bc1691..94d65c37 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift @@ -20,6 +20,11 @@ public class ListRightVariableRightCaretAllTextAndLinksModel: ListItemModel, Par [rightLabel, eyebrowHeadlineBodyLink] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &rightLabel, with: molecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) + } + //----------------------------------------------------- // MARK: - Methods //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift index 89d32ca9..bb77c26a 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift @@ -64,6 +64,12 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco [eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &eyebrow, with: molecule) + || replaceChildMolecule(at: &title, with: molecule) + || replaceChildMolecule(at: &subTitle, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift index 8a039144..af226405 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift @@ -22,6 +22,11 @@ public class ListOneColumnFullWidthTextDividerSubsectionModel: ListItemModel, Mo [headline, body].compactMap({$0}) } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headline, with: molecule) + || replaceChildMolecule(at: &body, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift index 6a40ebf7..596b9ff0 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift @@ -22,6 +22,11 @@ public class ListOneColumnTextWithWhitespaceDividerShortModel: ListItemModel, Mo [headline, body].compactMap({$0}) } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headline, with: molecule) + || replaceChildMolecule(at: &body, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift index 09b462fe..ea4c3ce2 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift @@ -22,6 +22,11 @@ public class ListOneColumnTextWithWhitespaceDividerTallModel: ListItemModel, Mol [headline, body].compactMap({$0}) } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &headline, with: molecule) + || replaceChildMolecule(at: &body, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift index b5d07c4a..fa8b51a8 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -24,6 +24,11 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol { return [primaryButton, secondaryButton].compactMap { $0 } } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &primaryButton, with: molecule) + || replaceChildMolecule(at: &secondaryButton, with: molecule) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift index dc8f2396..4925d03b 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift @@ -18,10 +18,19 @@ public class CornerLabelsModel: ParentMoleculeModelProtocol { public var bottomLeftLabel: LabelModel? public var bottomRightLabel: LabelModel? public var molecule: MoleculeModelProtocol? + public var children: [MoleculeModelProtocol] { [molecule, topLeftLabel, topRightLabel, bottomLeftLabel, bottomRightLabel].compactMap { $0 } } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &self.molecule, with: molecule) + || replaceChildMolecule(at: &topLeftLabel, with: molecule) + || replaceChildMolecule(at: &topRightLabel, with: molecule) + || replaceChildMolecule(at: &bottomLeftLabel, with: molecule) + || replaceChildMolecule(at: &bottomRightLabel, with: molecule) + } + public init(with molecule: MoleculeModelProtocol?) { self.molecule = molecule } diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift index 735e95c1..6860b40a 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift @@ -20,6 +20,10 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco return [molecule] } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &self.molecule, with: molecule) + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift index 84b1f38e..29017c96 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift @@ -16,5 +16,9 @@ public extension MoleculeContainerModelProtocol { var children: [MoleculeModelProtocol] { return [molecule] } + + func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &molecule, with: replacementMolecule) + } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift index 0d46916e..90f72be3 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift @@ -25,6 +25,13 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule [eyebrow, headline, body, link].compactMap { (molecule: MoleculeModelProtocol?) in molecule } } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &eyebrow, with: molecule) + || replaceChildMolecule(at: &headline, with: molecule) + || replaceChildMolecule(at: &body, with: molecule) + || replaceChildMolecule(at: &link, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index 4371fe34..76e26f77 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -24,9 +24,9 @@ [headline, body].compactMap { $0 } } - public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { - return replace(childMolecule:&headline, with: replacementMolecule) - || replace(childMolecule:&body, with: replacementMolecule) + public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at:&headline, with: replacementMolecule) + || replaceChildMolecule(at:&body, with: replacementMolecule) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index 5bfd8d0f..c18f0e95 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -180,4 +180,8 @@ extension CarouselModel { return molecules } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(in: &molecules, with: molecule) + } + } diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index cb8740ac..bc254727 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -21,22 +21,6 @@ public var spacing: CGFloat = Padding.Four public var useStackSpacingBeforeFirstItem = false - public var children: [MoleculeModelProtocol] { - return molecules - } - - public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { - // IDEALLY: - //return replace(inChildMolecules: &molecules, with: replacementMolecule) - - guard let replacementMolecule = replacementMolecule as? StackItemModelProtocol & MoleculeModelProtocol else { return false } - guard let matchingIndex = molecules.firstIndex(where: { molecule in - molecule.id == replacementMolecule.id - }) else { return false } - molecules[matchingIndex] = replacementMolecule - return true - } - //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift index 068418f0..3d57fe72 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift @@ -19,4 +19,8 @@ extension StackModelProtocol { public var children: [MoleculeModelProtocol] { return molecules } + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(in: &molecules, with: molecule) + } + } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift index 19a3a711..3e16069e 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -8,11 +8,44 @@ import Foundation -public protocol ParentMoleculeModelProtocol: MoleculeModelProtocol, AnyObject { +public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol, AnyObject { var children: [MoleculeModelProtocol] { get } - func replaceChildMolecule(with molecule: MoleculeModelProtocol) -> Bool + func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool +} + +public extension ParentModelProtocol { + /// Top level test to replace child molecules. Each parent molecule should attempt to replace. + func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { return false } + + /// Helper function for replacing molecules. + func replaceChildMolecule(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + guard let childIdMolecule = childMolecule as? MoleculeModelProtocol else { return false } + if childIdMolecule.id == replacementMolecule.id { + guard let replacementMolecule = replacementMolecule as? T else { + throw MolecularError.error("Molecular replacement '\(replacementMolecule.id)' does not type match \(type(of: T.self)) of \(type(of: self))") + } + childMolecule = replacementMolecule + return true + } + return false + } + + func replaceChildMolecule(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + guard let moleculeIdModels = molecules as? [MoleculeModelProtocol], let matchingIndex = moleculeIdModels.firstIndex(where: { + $0.id == replacementMolecule.id + }) else { return false } + guard let replacementMolecule = replacementMolecule as? T else { + throw MolecularError.error("Molecular replacement '\(replacementMolecule.id)' does not type match \(type(of: T.self)) of \(type(of: self))") + } + molecules[matchingIndex] = replacementMolecule + return true + } +} + +public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelProtocol { + } public extension ParentMoleculeModelProtocol { @@ -56,33 +89,4 @@ public extension ParentMoleculeModelProtocol { } // if options == .leafOnly don't call on self. } - - /// Top level test to replace child molecules. Each parent molecule should attempt to replace. - func replaceChildMolecule(with molecule: MoleculeModelProtocol) -> Bool { return false } - - /// Helper function for replacing molecules. - func replace(childMolecule: inout T?, with replacementMolecule: MoleculeModelProtocol) -> Bool { - if childMolecule != nil, childMolecule?.id == replacementMolecule.id, let newHeadline = replacementMolecule as? T { - childMolecule = newHeadline - return true - } - return false - } - - func replace(childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) -> Bool { - if childMolecule.id == replacementMolecule.id, let newHeadline = replacementMolecule as? T { - childMolecule = newHeadline - return true - } - return false - } - - func replace(inChildMolecules molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) -> Bool where T: MoleculeModelProtocol { - guard let replacementMolecule = replacementMolecule as? T else { return false } - guard let matchingIndex = molecules.firstIndex(where: { molecule in - molecule.id == replacementMolecule.id - }) else { return false } - molecules[matchingIndex] = replacementMolecule - return true - } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift index 2cd233b0..34a8e13a 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift @@ -7,13 +7,17 @@ // -public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol { +public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol, ParentModelProtocol { var template: String { get } var rootMolecules: [MoleculeModelProtocol] { get } } public extension TemplateModelProtocol { + var children: [MoleculeModelProtocol] { + return rootMolecules + } + var template: String { get { return Self.identifier } } @@ -33,4 +37,28 @@ public extension TemplateModelProtocol { func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) { return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) } + + func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + // Attempt root level replacement on the template model first. + if try self.replaceChildMolecule(with: replacementMolecule) { + return true + } + + var didReplaceMolecule = false + var possibleError: Error? + // Dive into each root thereafter. + depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in + guard let parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } + do { + didReplaceMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule) + } catch { + possibleError = error + } + stop = didReplaceMolecule || possibleError != nil + } + if let error = possibleError { + throw error + } + return didReplaceMolecule + } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift index f2f01b6c..6086849c 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift @@ -9,6 +9,8 @@ public protocol MoleculeDelegateProtocol: AnyObject { + func getTemplateModel() -> TemplateModelProtocol? + func getRootMolecules() -> [MoleculeModelProtocol] /// returns a module for the corresponding module name. @@ -22,6 +24,10 @@ public protocol MoleculeDelegateProtocol: AnyObject { extension MoleculeDelegateProtocol { + public func getRootMolecules() -> [MoleculeModelProtocol] { + getTemplateModel()?.rootMolecules ?? [] + } + public func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } public func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? { @@ -42,6 +48,11 @@ extension MoleculeDelegateProtocol { } extension MoleculeDelegateProtocol where Self: TemplateProtocol { + + public func getTemplateModel() -> TemplateModelProtocol? { + return templateModel + } + public func getRootMolecules() -> [MoleculeModelProtocol] { templateModel?.rootMolecules ?? [] } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift index 48f5166b..f595af9b 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift @@ -38,7 +38,7 @@ public extension MoleculeTreeTraversalProtocol { func printMolecules(options: TreeTraversalOptions = .parentFirst) { depthFirstTraverse(options: options, depth: 1) { depth, molecule, stop in - print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule)]") + print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule): \(molecule.id)]") } } @@ -50,14 +50,4 @@ public extension MoleculeTreeTraversalProtocol { return accumulator } } - - func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { - var didReplaceMolecule = false - depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in - guard let parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } - didReplaceMolecule = parentMolecule.replaceChildMolecule(with: replacementMolecule) - stop = didReplaceMolecule - } - return didReplaceMolecule - } } diff --git a/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift b/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift index daa3aed1..ee6a0a77 100644 --- a/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift @@ -32,7 +32,11 @@ import Foundation public var tabBarIndex: Int? public var shouldMaskScreenWhileRecording: Bool? - + + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &navigationBar, with: molecule) + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift index fc71921e..e94f8153 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift @@ -22,6 +22,11 @@ } return super.rootMolecules } + + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try super.replaceChildMolecule(with: molecule) + || (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule)) + } //-------------------------------------------------- // MARK: - Initializer diff --git a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift index 4796bed7..e1713b01 100644 --- a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift @@ -26,6 +26,12 @@ return super.rootMolecules } + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try super.replaceChildMolecule(with: molecule) + || (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule)) + || replaceChildMolecule(at: &line, with: molecule) + } + /// This template requires content. func validateModelHasContent() throws { if header == nil, diff --git a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift index 1ed663c2..d5d783d3 100644 --- a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift @@ -16,7 +16,13 @@ public var moleculeStack: StackModel public override var rootMolecules: [MoleculeModelProtocol] { - [navigationBar, header, moleculeStack, footer].compactMap { $0 } + super.rootMolecules + [moleculeStack] + } + + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try super.replaceChildMolecule(with: molecule) + || replaceChildMolecule(at: &navigationBar, with: molecule) + || replaceChildMolecule(at: &moleculeStack, with: molecule) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift index 9f130089..6ca05550 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift @@ -21,6 +21,12 @@ [navigationBar, header, footer].compactMap { $0 } } + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try super.replaceChildMolecule(with: molecule) + || replaceChildMolecule(at: &header, with: molecule) + || replaceChildMolecule(at: &footer, with: molecule) + } + //-------------------------------------------------- // MARK: - Init //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift index ab4f29d5..d3acea86 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift @@ -22,6 +22,11 @@ return super.rootMolecules } + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try super.replaceChildMolecule(with: molecule) + || replaceChildMolecule(at: &middle, with: molecule) + } + //-------------------------------------------------- // MARK: - Init //-------------------------------------------------- diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 16c1500f..afd91ec0 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -513,9 +513,9 @@ import MVMCore // MARK: - MoleculeDelegateProtocol //-------------------------------------------------- - open func getRootMolecules() -> [MoleculeModelProtocol] { - model?.rootMolecules ?? [] - } + open func getTemplateModel() -> TemplateModelProtocol? { model } + + open func getRootMolecules() -> [MoleculeModelProtocol] { model?.rootMolecules ?? [] } // Needed otherwise when subclassed, the extension gets called. open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } diff --git a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift index 89a34f4a..d5e6d86c 100644 --- a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift @@ -9,27 +9,35 @@ import Foundation import MVMCore -public class ReplacableMoleculeBehaviorModel: PageBehaviorModelProtocol { +public class ReplaceableMoleculeBehaviorModel: PageBehaviorModelProtocol { public class var identifier: String { "replaceMoleculeBehavior" } public var shouldAllowMultipleInstances: Bool { true } public var moleculeIds: [String] } -public class ReplacableMoleculeBehavior: PageMoleculeTransformationBehavior { +public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { var moleculeIds: [String] public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { - moleculeIds = (model as! ReplacableMoleculeBehaviorModel).moleculeIds + moleculeIds = (model as! ReplaceableMoleculeBehaviorModel).moleculeIds } public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { - var shouldRefreshUI = false + + guard let templateModel = delegateObject?.moleculeDelegate?.getTemplateModel() else { return } + + templateModel.printMolecules() + for moleculeId in moleculeIds { guard let replacementModel = delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId) else { continue } - let didReplace = rootMolecules.contains(where: { model in - return model.replaceMolecule(with: replacementModel) - }) - shouldRefreshUI = shouldRefreshUI || didReplace + do { + let didReplace = try templateModel.replaceMolecule(with: replacementModel) + if !didReplace { + MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Failed to find '\(moleculeId)' in the current screen.", code: ErrorCode.viewControllerProcessingJSON.rawValue, domain: ErrorDomainSystem, location: String(describing: type(of: self)))!) + } + } catch { + MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))!) + } } } diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 91042b47..26e539bc 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -227,7 +227,7 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(handler: PageGetContactBehavior.self, for: PageGetContactBehaviorModel.self) ModelRegistry.register(handler: AddRemoveMoleculesBehavior.self, for: AddRemoveMoleculesBehaviorModel.self) ModelRegistry.register(handler: GetNotificationAuthStatusBehavior.self, for: GetNotificationAuthStatusBehaviorModel.self) - ModelRegistry.register(handler: ReplacableMoleculeBehavior.self, for: ReplacableMoleculeBehaviorModel.self) + ModelRegistry.register(handler: ReplaceableMoleculeBehavior.self, for: ReplaceableMoleculeBehaviorModel.self) } open override class func registerActions() { From 6758aed034bdced6fd0d18fadea278210eabd142 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Wed, 13 Sep 2023 20:36:59 -0400 Subject: [PATCH 03/36] add comments --- .../ModelProtocols/ParentMoleculeModelProtocol.swift | 5 ++++- .../Protocols/ModelProtocols/TemplateModelProtocol.swift | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift index 3e16069e..c970bc39 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -12,14 +12,16 @@ public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol, AnyObject { var children: [MoleculeModelProtocol] { get } + /// Method for replacing surface level children. (Does not recurse.) func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool } public extension ParentModelProtocol { + /// Top level test to replace child molecules. Each parent molecule should attempt to replace. func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { return false } - /// Helper function for replacing molecules. + /// Helper function for replacing a single molecules with type and optionality checks. func replaceChildMolecule(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> Bool { guard let childIdMolecule = childMolecule as? MoleculeModelProtocol else { return false } if childIdMolecule.id == replacementMolecule.id { @@ -32,6 +34,7 @@ public extension ParentModelProtocol { return false } + /// Helper for replacing a molecule in place within an array. Note the "in". func replaceChildMolecule(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> Bool { guard let moleculeIdModels = molecules as? [MoleculeModelProtocol], let matchingIndex = moleculeIdModels.firstIndex(where: { $0.id == replacementMolecule.id diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift index 34a8e13a..f884e4aa 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift @@ -38,9 +38,10 @@ public extension TemplateModelProtocol { return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) } + /// Recursively finds and replaces the first child matching the replacement molecule id property. func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { // Attempt root level replacement on the template model first. - if try self.replaceChildMolecule(with: replacementMolecule) { + if try replaceChildMolecule(with: replacementMolecule) { return true } From 41ce041a0dbbafae273b9874d27f9e7a1177f041 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 14 Sep 2023 17:28:56 -0400 Subject: [PATCH 04/36] throw module decoding errors to link back to the source of the error --- .../OtherContainers/ModuleMolecule.swift | 8 +++---- .../Protocols/MoleculeDelegateProtocol.swift | 21 ++++++++----------- .../ReplacementMoleculeBehavior.swift | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift index 6083ac5e..82525b01 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/ModuleMolecule.swift @@ -23,7 +23,7 @@ open class ModuleMolecule: Container { super.set(with: model, delegateObject, additionalData) guard let moduleMoleculeModel = model as? ModuleMoleculeModel, - let moduleModel = delegateObject?.moleculeDelegate?.getModuleWithName(moduleMoleculeModel.moduleName) else { + let moduleModel = try? delegateObject?.moleculeDelegate?.getModuleWithName(moduleMoleculeModel.moduleName) else { // Critical error return } @@ -49,7 +49,7 @@ open class ModuleMolecule: Container { public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { guard let moduleMolecule = model as? ModuleMoleculeModel, - let moduleModel = delegateObject?.moleculeDelegate?.getModuleWithName(moduleMolecule.moduleName), + let moduleModel = try? delegateObject?.moleculeDelegate?.getModuleWithName(moduleMolecule.moduleName), let classType = ModelRegistry.getMoleculeClass(moduleModel), let height = classType.estimatedHeight(with: moduleModel, delegateObject) else { // Critical error @@ -60,7 +60,7 @@ open class ModuleMolecule: Container { public override class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { guard let moduleMolecule = model as? ModuleMoleculeModel, - let moduleModel = delegateObject?.moleculeDelegate?.getModuleWithName(moduleMolecule.moduleName), + let moduleModel = try? delegateObject?.moleculeDelegate?.getModuleWithName(moduleMolecule.moduleName), let classType = ModelRegistry.getMoleculeClass(moduleModel), let name = classType.nameForReuse(with: moduleModel, delegateObject) else { // Critical error @@ -72,7 +72,7 @@ open class ModuleMolecule: Container { public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { guard let moduleName = (model as? ModuleMoleculeModel)?.moduleName, - let _ = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { + let _ = try? delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) { error?.pointee = errorObject MVMCoreUILoggingHandler.addError(toLog: errorObject) diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift index 6086849c..e78c0b1b 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift @@ -13,10 +13,11 @@ public protocol MoleculeDelegateProtocol: AnyObject { func getRootMolecules() -> [MoleculeModelProtocol] - /// returns a module for the corresponding module name. + /// Returns a raw module map for the corresponding module name if the key name exists. func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? - func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? + /// Returns the decoded module for the corresponding module name if the key name exists. Throws if there is a decoding error. + func getModuleWithName(_ moleculeName: String) throws -> MoleculeModelProtocol? /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. Mainly used for list or collections. func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) //optional @@ -30,20 +31,16 @@ extension MoleculeDelegateProtocol { public func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } - public func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? { + public func getModuleWithName(_ moleculeName: String) throws -> MoleculeModelProtocol? { let moduleJSON: [AnyHashable: Any]? = getModuleWithName(moleculeName) - guard let moduleJSON = moduleJSON as? [String: Any], - let moleculeName = moduleJSON.optionalStringForKey("moleculeName"), - let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) - else { return nil } + guard let moduleJSON = moduleJSON as? [String: Any] else { return nil } - do { - return try modelType.decode(jsonDict: moduleJSON as [String : Any]) as? MoleculeModelProtocol - } catch { - MVMCoreUILoggingHandler.logDebugMessage(withDelegate: "error: \(error)") + guard let moleculeName = moduleJSON.optionalStringForKey(KeyMoleculeName), + let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) else { + throw ModelRegistry.Error.decoderErrorModelNotMapped(identifer: moduleJSON.optionalStringForKey(KeyMoleculeName)) } - return nil + return try modelType.decode(jsonDict: moduleJSON as [String : Any]) as? MoleculeModelProtocol } } diff --git a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift index d5e6d86c..8daf3c8f 100644 --- a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift @@ -29,8 +29,8 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { templateModel.printMolecules() for moleculeId in moleculeIds { - guard let replacementModel = delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId) else { continue } do { + guard let replacementModel = try delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId) else { continue } let didReplace = try templateModel.replaceMolecule(with: replacementModel) if !didReplace { MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Failed to find '\(moleculeId)' in the current screen.", code: ErrorCode.viewControllerProcessingJSON.rawValue, domain: ErrorDomainSystem, location: String(describing: type(of: self)))!) From cd35990a954bc856549e77a525eb69002e57caab Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Fri, 15 Sep 2023 17:39:36 -0400 Subject: [PATCH 05/36] split handlenewdata from updateUI. refactor behavior constuction. molecule iteration bug fixes. removing AnyObject requirement from ParentMoleculeProtocol. --- MVMCoreUI.xcodeproj/project.pbxproj | 10 ++--- .../MoleculeContainerProtocol.swift | 8 ++-- .../Atomic/Organisms/StackModelProtocol.swift | 7 ++-- .../MoleculeModelProtocol.swift | 9 ++++- .../ParentMoleculeModelProtocol.swift | 6 +-- .../TemplateModelProtocol.swift | 7 +++- .../Atomic/Protocols/TemplateProtocol.swift | 27 +++++++------ .../Atomic/Templates/CollectionTemplate.swift | 4 +- .../Templates/ModalSectionListTemplate.swift | 4 +- .../Templates/MoleculeListTemplate.swift | 10 ++--- .../ThreeLayerFillMiddleTemplate.swift | 4 +- .../Atomic/Templates/ThreeLayerTemplate.swift | 4 +- .../MVMControllerModelProtocol.swift | 2 +- .../ScrollingViewController.swift | 4 +- .../ThreeLayerCollectionViewController.swift | 4 +- .../ThreeLayerTableViewController.swift | 4 +- .../ThreeLayerViewController.swift | 4 +- .../BaseControllers/ViewController.swift | 25 +++++++----- MVMCoreUI/Behaviors/GetContactBehavior.swift | 2 +- .../GetNotificationAuthStatusBehavior.swift | 9 +++-- ... PageBehaviorConatinerModelProtocol.swift} | 9 +++-- .../PageBehaviorHandlerProtocol.swift | 40 +++++++++++++------ .../Protocols/PageBehaviorProtocol.swift | 4 +- .../ReplacementMoleculeBehavior.swift | 2 +- 24 files changed, 121 insertions(+), 88 deletions(-) rename MVMCoreUI/Behaviors/Protocols/{PageBehaviorHandlerModelProtocol.swift => PageBehaviorConatinerModelProtocol.swift} (79%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index f02ca813..e661e013 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -382,7 +382,7 @@ D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */; }; D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */; }; D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */; }; - D23A90002612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */; }; + D23A90002612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */; }; D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */; }; D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A900826125FFB007E14CE /* GetContactBehavior.swift */; }; D23A90682614B0B4007E14CE /* CoreUIModelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */; }; @@ -969,7 +969,7 @@ D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideo.swift; sourceTree = ""; }; D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocolRequirer.swift; sourceTree = ""; }; D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorModelProtocol.swift; sourceTree = ""; }; - D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerModelProtocol.swift; sourceTree = ""; }; + D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorConatinerModelProtocol.swift; sourceTree = ""; }; D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerProtocol.swift; sourceTree = ""; }; D23A900826125FFB007E14CE /* GetContactBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContactBehavior.swift; sourceTree = ""; }; D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUIModelMapping.swift; sourceTree = ""; }; @@ -1202,10 +1202,10 @@ D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */, 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */, D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */, + 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */, 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */, 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */, EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */, - 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */, 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */, D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */, @@ -1321,7 +1321,7 @@ children = ( D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */, D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */, - D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */, + D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */, D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */, 27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */, ); @@ -2720,7 +2720,7 @@ 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, D2A92884241ACB25004E01C6 /* ProgrammaticScrollViewController.swift in Sources */, EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */, - D23A90002612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift in Sources */, + D23A90002612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift in Sources */, EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */, 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift index 29017c96..44b040aa 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift @@ -12,13 +12,15 @@ public protocol MoleculeContainerModelProtocol: ContainerModelProtocol, ParentMo } public extension MoleculeContainerModelProtocol { - + var children: [MoleculeModelProtocol] { return [molecule] } +} + +public extension MoleculeContainerModelProtocol where Self: AnyObject { - func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { return try replaceChildMolecule(at: &molecule, with: replacementMolecule) } - } diff --git a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift index 3d57fe72..1456f6e5 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift @@ -16,11 +16,12 @@ public protocol StackModelProtocol: ParentMoleculeModelProtocol { } extension StackModelProtocol { - public var children: [MoleculeModelProtocol] { return molecules } +} + +extension StackModelProtocol where Self: AnyObject { - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { return try replaceChildMolecule(in: &molecules, with: molecule) } - } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift index 31adc6ef..0fcab2d0 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift @@ -68,8 +68,13 @@ public extension Array where Element == MoleculeModelProtocol { } func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) { - forEach { (molecule) in - molecule.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) + var shouldStop = false + for molecule in self { + molecule.depthFirstTraverse(options: options, depth: depth) { depth, molecule, stop in + onVisit(depth, molecule, &shouldStop) + stop = shouldStop + } + if shouldStop { break } } } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift index c970bc39..a9f4f8ac 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -8,15 +8,15 @@ import Foundation -public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol, AnyObject { +public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol { var children: [MoleculeModelProtocol] { get } /// Method for replacing surface level children. (Does not recurse.) - func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool + mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool } -public extension ParentModelProtocol { +public extension ParentModelProtocol where Self: AnyObject { /// Top level test to replace child molecules. Each parent molecule should attempt to replace. func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { return false } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift index f884e4aa..296f73f5 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift @@ -37,9 +37,12 @@ public extension TemplateModelProtocol { func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) { return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) } +} + +extension TemplateModelProtocol { /// Recursively finds and replaces the first child matching the replacement molecule id property. - func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { // Attempt root level replacement on the template model first. if try replaceChildMolecule(with: replacementMolecule) { return true @@ -49,7 +52,7 @@ public extension TemplateModelProtocol { var possibleError: Error? // Dive into each root thereafter. depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in - guard let parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } + guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } do { didReplaceMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule) } catch { diff --git a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift index 144001bc..80fa29a0 100644 --- a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift @@ -8,7 +8,7 @@ import Foundation -public protocol TemplateProtocol: AnyObject, PageProtocol { +public protocol TemplateProtocol: AnyObject, PageProtocol{ associatedtype TemplateModel: TemplateModelProtocol var templateModel: TemplateModel? { get set } @@ -28,25 +28,26 @@ public extension TemplateProtocol { } } + func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel { + try decoder.decode(TemplateModel.self, from: data) + } +} + +public extension TemplateProtocol where Self: PageBehaviorHandlerProtocol, Self: MVMCoreViewControllerProtocol { + /// Helper function to do common parsing logic. func parseTemplate(json: [AnyHashable: Any]?) throws { guard let pageJSON = json else { return } - let delegateObject = (self as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject + let delegateObject = delegateObject?() as? MVMCoreUIDelegateObject let data = try JSONSerialization.data(withJSONObject: pageJSON) let decoder = JSONDecoder.create(with: delegateObject) templateModel = try decodeTemplate(using: decoder, from: data) // Add additional required behaviors if applicable. - guard var behaviorHandlerModel = templateModel as? TemplateModelProtocol & PageBehaviorHandlerModelProtocol, - var behaviorHandler = self as? PageBehaviorHandlerProtocol else { return } - behaviorHandlerModel.traverseAndAddRequiredBehaviors() - behaviorHandler.createBehaviors(for: behaviorHandlerModel, delegateObject: delegateObject) - if let viewController = self as? UIViewController { - MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController) - } - } - - func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel { - try decoder.decode(TemplateModel.self, from: data) + guard var pageBehaviorsModel = templateModel as? TemplateModelProtocol & PageBehaviorConatinerModelProtocol else { return } + + pageBehaviorsModel.traverseAndAddRequiredBehaviors() + var behaviorHandler = self + behaviorHandler.applyBehaviors(pageBehaviorModel: pageBehaviorsModel, delegateObject: delegateObject) } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index b84792a6..e4e50d89 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -80,12 +80,12 @@ } - open override func handleNewData() { + open override func updateUI() { topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false setup() registerCells() - super.handleNewData() + super.updateUI() } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift b/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift index baf8d78a..071b78c2 100644 --- a/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift @@ -21,8 +21,8 @@ open class ModalSectionListTemplate: SectionListTemplate { // MARK: - Lifecycle //-------------------------------------------------- - override open func handleNewData() { - super.handleNewData() + override open func updateUI() { + super.updateUI() _ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: { [weak self] _ in guard let self = self else { return } let closeAction = (self.templateModel as? ModalSectionListTemplateModel)?.closeAction ?? diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 950b6785..0605e4e6 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -81,12 +81,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol return molecule } - open override func handleNewData() { + open override func updateUI() { topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false setup() registerWithTable() - super.handleNewData() + super.updateUI() } open override func viewDidAppear(_ animated: Bool) { @@ -300,7 +300,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } extension MoleculeListTemplate: MoleculeListProtocol { - open func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) { + public func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) { for (index, indexPath) in indexPaths.sorted().enumerated() { let removeIndex = indexPath.row - index moleculesInfo?.remove(at: removeIndex) @@ -313,7 +313,7 @@ extension MoleculeListTemplate: MoleculeListProtocol { view.layoutIfNeeded() } - open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) { + public func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) { var indexPaths: [IndexPath] = [] for molecule in molecules { @@ -332,7 +332,7 @@ extension MoleculeListTemplate: MoleculeListProtocol { self.view.layoutIfNeeded() } - open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { + public func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in return equal(moleculeA: molecule, moleculeB: moleculeInfo.molecule) }) else { return nil } diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift index eb8a674f..bb6c5bdb 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift @@ -26,8 +26,8 @@ } } - open override func handleNewData() { - super.handleNewData() + open override func updateUI() { + super.updateUI() heightConstraint?.isActive = true } } diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift index 933d6043..d0203e70 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift @@ -19,10 +19,10 @@ import UIKit try super.parsePageJSON() } - open override func handleNewData() { + open override func updateUI() { topViewOutsideOfScroll = templateModel?.anchorHeader ?? false bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false - super.handleNewData() + super.updateUI() } open override func viewForTop() -> UIView? { diff --git a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift index 78f6f604..b4aabd55 100644 --- a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift +++ b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift @@ -9,6 +9,6 @@ import Foundation -public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorHandlerModelProtocol { +public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorConatinerModelProtocol { } diff --git a/MVMCoreUI/BaseControllers/ScrollingViewController.swift b/MVMCoreUI/BaseControllers/ScrollingViewController.swift index 4fd23be2..e7698d49 100644 --- a/MVMCoreUI/BaseControllers/ScrollingViewController.swift +++ b/MVMCoreUI/BaseControllers/ScrollingViewController.swift @@ -63,8 +63,8 @@ open class ScrollingViewController: ViewController { registerForKeyboardNotifications() } - open override func handleNewData() { - super.handleNewData() + open override func updateUI() { + super.updateUI() // will change scrollView indicatorStyle automatically on the basis of backgroundColor var greyScale: CGFloat = 0 if view.backgroundColor?.getWhite(&greyScale, alpha: nil) ?? false { diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index 4011f4f8..13b62f2b 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -108,8 +108,8 @@ import Foundation } } - open override func handleNewData() { - super.handleNewData() + open override func updateUI() { + super.updateUI() topView?.removeFromSuperview() bottomView?.removeFromSuperview() topView = viewForTop() diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 4faff136..f452fa96 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -50,8 +50,8 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { tableView.reloadData() } - open override func handleNewData() { - super.handleNewData() + open override func updateUI() { + super.updateUI() createViewForTableHeader() createViewForTableFooter() tableView?.reloadData() diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index ec8aaaa3..0ec3cac9 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -49,8 +49,8 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { } } - open override func handleNewData() { - super.handleNewData() + open override func updateUI() { + super.updateUI() // Removes the views topView?.removeFromSuperview() diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index afd91ec0..1b4e5728 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -111,9 +111,12 @@ import MVMCore guard newData else { return } do { + // TODO: Parse parsePageJSON modifies the page model on a different thread than + // the UI update which could cause discrepancies. Parse should return the resulting + // object and assignment should be synchronized on handleNewData(model: ). try parsePageJSON() MVMCoreDispatchUtility.performBlock(onMainThread: { - self.handleNewDataAndUpdateUI() + self.handleNewData() }) } catch { if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") { @@ -160,7 +163,7 @@ import MVMCore } return false } - + return true } @@ -229,9 +232,12 @@ import MVMCore return true } - /// Calls processNewData and then sets the ui to update with updateView - open func handleNewDataAndUpdateUI() { - handleNewData() + @MainActor + open func updateUI() { + if let backgroundColor = model?.backgroundColor { + view.backgroundColor = backgroundColor.uiColor + } + needsUpdateUI = true view.setNeedsLayout() } @@ -244,6 +250,7 @@ import MVMCore } /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, + @MainActor open func handleNewData() { if model?.navigationBar == nil { let navigationItem = createDefaultLegacyNavigationModel() @@ -259,12 +266,10 @@ import MVMCore formValidator = FormValidator(rules) } - if let backgroundColor = model?.backgroundColor { - view.backgroundColor = backgroundColor.uiColor - } - // Notify the manager of new data manager?.newDataReceived?(in: self) + + updateUI() } public func generateMoleculeView(from model: MoleculeModelProtocol) -> MoleculeViewProtocol? { @@ -333,7 +338,7 @@ import MVMCore initialLoad() } - handleNewDataAndUpdateUI() + handleNewData() } open override func viewDidLayoutSubviews() { diff --git a/MVMCoreUI/Behaviors/GetContactBehavior.swift b/MVMCoreUI/Behaviors/GetContactBehavior.swift index 77c0d7f4..6f9a3cd4 100644 --- a/MVMCoreUI/Behaviors/GetContactBehavior.swift +++ b/MVMCoreUI/Behaviors/GetContactBehavior.swift @@ -43,7 +43,7 @@ public class PageGetContactBehavior: PageVisibilityBehavior { MVMCoreDispatchUtility.performBlock(onMainThread: { // TODO: move to protocol function instead guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return } - controller.handleNewDataAndUpdateUI() + controller.handleNewData() }) } } diff --git a/MVMCoreUI/Behaviors/GetNotificationAuthStatusBehavior.swift b/MVMCoreUI/Behaviors/GetNotificationAuthStatusBehavior.swift index 78bb1129..980f0dbf 100644 --- a/MVMCoreUI/Behaviors/GetNotificationAuthStatusBehavior.swift +++ b/MVMCoreUI/Behaviors/GetNotificationAuthStatusBehavior.swift @@ -45,11 +45,12 @@ public class GetNotificationAuthStatusBehavior: PageVisibilityBehavior { for consumer in consumers { consumer.consume(notificationStatus: settings.authorizationStatus) } - // Tell template to update - MVMCoreDispatchUtility.performBlock(onMainThread: { + + Task { + // Tell template to update guard let controller = self.delegate?.moleculeDelegate as? ViewController else { return } - controller.handleNewDataAndUpdateUI() - }) + await controller.handleNewData() + } } } diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerModelProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorConatinerModelProtocol.swift similarity index 79% rename from MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerModelProtocol.swift rename to MVMCoreUI/Behaviors/Protocols/PageBehaviorConatinerModelProtocol.swift index bb752694..ddc1e646 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerModelProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorConatinerModelProtocol.swift @@ -1,16 +1,17 @@ // -// PageBehaviorHandlerModelProtocol.swift +// PageBehaviorConatinerModelProtocol.swift // MVMCoreUI // // Created by Scott Pfeil on 3/29/21. // Copyright © 2021 Verizon Wireless. All rights reserved. // -public protocol PageBehaviorHandlerModelProtocol { +/// Protocol applied to a model that contains a list of behavior models. +public protocol PageBehaviorConatinerModelProtocol { var behaviors: [PageBehaviorModelProtocol]? { get set } } -public extension PageBehaviorHandlerModelProtocol { +public extension PageBehaviorConatinerModelProtocol { /// Adds the behavior model to the behaviors if possible. mutating func add(behavior: PageBehaviorModelProtocol) { @@ -24,7 +25,7 @@ public extension PageBehaviorHandlerModelProtocol { } } -public extension PageBehaviorHandlerModelProtocol where Self: MoleculeTreeTraversalProtocol { +public extension PageBehaviorConatinerModelProtocol where Self: MoleculeTreeTraversalProtocol { /// Traverses all models and adds any required behavior models. mutating func traverseAndAddRequiredBehaviors() { diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift index 9e5c6a6d..7607f5e2 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift @@ -12,20 +12,10 @@ public protocol PageBehaviorHandlerProtocol { } public extension PageBehaviorHandlerProtocol { + /// Creates the behaviors and sets the variable. - mutating func createBehaviors(for model: PageBehaviorHandlerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { - - behaviors = behaviors?.filter { $0.transcendsPageUpdates } - if behaviors?.isEmpty ?? false { - behaviors = nil - } - - guard let behaviorModels = model.behaviors else { - return - } - - var behaviors: [PageBehaviorProtocol] = behaviors ?? [] - + func createBehaviors(for behaviorModels: [PageBehaviorModelProtocol], delegateObject: MVMCoreUIDelegateObject?) -> [PageBehaviorProtocol] { + var behaviors = [PageBehaviorProtocol]() for behaviorModel in behaviorModels { do { let handlerType = try ModelRegistry.getHandler(behaviorModel) as! PageBehaviorProtocol.Type @@ -37,7 +27,23 @@ public extension PageBehaviorHandlerProtocol { } } } + return behaviors + } + + mutating func applyBehaviors(pageBehaviorModel: PageBehaviorConatinerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + // Pull the existing behaviors. + var behaviors = (behaviors ?? []).filter { $0.transcendsPageUpdates } + // Create and append any new behaviors based on the incoming models. + let newBehaviors = createBehaviors(for: pageBehaviorModel.behaviors ?? [], delegateObject: delegateObject) + behaviors.append(contentsOf: newBehaviors) + + // Apply them to the page. self.behaviors = behaviors.count > 0 ? behaviors : nil + + // Ask the session to apply any more. (Curently inverted contol due to Swift <--> Obj-C conflict. + if let viewController = self as? UIViewController { + MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController) + } } /// Executes all behaviors of type. @@ -49,3 +55,11 @@ public extension PageBehaviorHandlerProtocol { return try behaviors?.compactMap({$0 as? T}).allSatisfy({ return try behaviourBlock($0) }) ?? true } } + +public extension PageBehaviorHandlerProtocol where Self: MVMCoreViewControllerProtocol { + + mutating func applyBehaviors(pageBehaviorModel: PageBehaviorConatinerModelProtocol) { + applyBehaviors(pageBehaviorModel: pageBehaviorModel, delegateObject: delegateObject?() as? MVMCoreUIDelegateObject) + } + +} diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift index c6292783..6d6fda04 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift @@ -86,8 +86,8 @@ public protocol PageCustomActionHandlerBehavior: PageBehaviorProtocol { } public extension MVMCoreUIDelegateObject { - var behaviorModelDelegate: PageBehaviorHandlerModelProtocol? { - (moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorHandlerModelProtocol + var behaviorModelDelegate: PageBehaviorConatinerModelProtocol? { + (moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorConatinerModelProtocol } weak var behaviorTemplateDelegate: (PageBehaviorHandlerProtocol & NSObjectProtocol)? { diff --git a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift index 8daf3c8f..3773e717 100644 --- a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift @@ -24,7 +24,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { - guard let templateModel = delegateObject?.moleculeDelegate?.getTemplateModel() else { return } + guard var templateModel = delegateObject?.moleculeDelegate?.getTemplateModel() else { return } templateModel.printMolecules() From 21c1747f5580fc45188454d25e84c85a6ae37126 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Fri, 15 Sep 2023 17:42:43 -0400 Subject: [PATCH 06/36] format reset --- MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift index 80fa29a0..81b08c73 100644 --- a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift @@ -8,7 +8,7 @@ import Foundation -public protocol TemplateProtocol: AnyObject, PageProtocol{ +public protocol TemplateProtocol: AnyObject, PageProtocol { associatedtype TemplateModel: TemplateModelProtocol var templateModel: TemplateModel? { get set } From 33a05b79a62116cd3ff7043f2a000ac23deae2e2 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 18 Sep 2023 15:04:57 -0400 Subject: [PATCH 07/36] List template behavior fix. --- .../Atomic/Templates/CollectionTemplate.swift | 8 +++-- .../Templates/MoleculeListTemplate.swift | 8 +++-- .../BaseControllers/ViewController.swift | 31 ++++++++++++------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index e4e50d89..896aa5bf 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -80,11 +80,15 @@ } + open override func handleNewData() { + setup() + registerCells() + super.handleNewData() + } + open override func updateUI() { topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false - setup() - registerCells() super.updateUI() } diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 0605e4e6..406f72a9 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -81,11 +81,15 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol return molecule } + open override func handleNewData() { + setup() + registerWithTable() + super.handleNewData() // Currently stuck as MoleculeListProtocol being called from AddRemoveMoleculesBehaviorModel. + } + open override func updateUI() { topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false - setup() - registerWithTable() super.updateUI() } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 1b4e5728..37051176 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -232,16 +232,6 @@ import MVMCore return true } - @MainActor - open func updateUI() { - if let backgroundColor = model?.backgroundColor { - view.backgroundColor = backgroundColor.uiColor - } - - needsUpdateUI = true - view.setNeedsLayout() - } - /// Creates a legacy navigation model. open func createDefaultLegacyNavigationModel() -> NavigationItemModel { let navigationModel = NavigationItemModel() @@ -249,7 +239,7 @@ import MVMCore return navigationModel } - /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, + /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, Triggers a render refresh. @MainActor open func handleNewData() { if model?.navigationBar == nil { @@ -269,7 +259,24 @@ import MVMCore // Notify the manager of new data manager?.newDataReceived?(in: self) - updateUI() + Task { @MainActor in + updateUI() + } + } + + /// Applies the latest model to the UI. + @MainActor + open func updateUI() { + executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in + behavior.willRender(rootMolecules: getRootMolecules(), delegateObjectIVar) + } + + if let backgroundColor = model?.backgroundColor { + view.backgroundColor = backgroundColor.uiColor + } + + needsUpdateUI = true + view.setNeedsLayout() } public func generateMoleculeView(from model: MoleculeModelProtocol) -> MoleculeViewProtocol? { From 1cb490f822ae36e6b920cddce2824ae6400e453d Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 18 Sep 2023 19:20:26 -0400 Subject: [PATCH 08/36] Add will render event to avoid circumvent addRemoveListItemBehavior conflict. Page concurrent update sequentialization. --- MVMCoreUI/BaseControllers/ViewController.swift | 17 ++++++++++------- .../Behaviors/AddRemoveMoleculeBehavior.swift | 2 +- .../Protocols/PageBehaviorProtocol.swift | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 37051176..73bba7b9 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -38,7 +38,7 @@ import MVMCore public var behaviors: [PageBehaviorProtocol]? public var needsUpdateUI = false - private var observingForResponses = false + private var observingForResponses: NSObjectProtocol? private var initialLoadFinished = false public var previousScreenSize = CGSize.zero @@ -54,18 +54,21 @@ import MVMCore //-------------------------------------------------- open func observeForResponseJSONUpdates() { - guard !observingForResponses, + guard observingForResponses == nil, (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) + let pageUpdateQueue = OperationQueue() + pageUpdateQueue.maxConcurrentOperationCount = 1 + pageUpdateQueue.qualityOfService = .userInteractive + + observingForResponses = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil, queue: pageUpdateQueue, using: responseJSONUpdated(notification:)) } open func stopObservingForResponseJSONUpdates() { - guard observingForResponses else { return } - NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) - observingForResponses = false + guard let observingForResponses = observingForResponses else { return } + NotificationCenter.default.removeObserver(observingForResponses) + self.observingForResponses = nil } open func pagesToListenFor() -> [String]? { diff --git a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift index ed9b8196..6e11e074 100644 --- a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift @@ -76,7 +76,7 @@ public class AddRemoveMoleculesBehavior: PageCustomActionHandlerBehavior, PageMo self.delegate = delegateObject } - public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + public func willRender(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { guard let list = delegate?.moleculeListDelegate else { return } for case let model as (MoleculeModelProtocol & ListItemModelProtocol & AddMolecules) in rootMolecules { if let moleculesToAdd = model.getRecursiveMoleculesToAdd(), diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift index 6d6fda04..7fb9dc1f 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift @@ -35,6 +35,7 @@ public protocol PageMoleculeTransformationBehavior: PageBehaviorProtocol { func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) func didSetupNavigationBar(view: UINavigationBar, with model: NavigationItemModelProtocol) + func willRender(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject) throws -> Bool } @@ -45,6 +46,7 @@ public extension PageMoleculeTransformationBehavior { func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) {} func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) {} func didSetupNavigationBar(view: UINavigationBar, with model: NavigationItemModelProtocol) {} + func willRender(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {} func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject) throws -> Bool { return true } } From 91081cf6a6c226149e7100a7c0410092092d7731 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 18 Sep 2023 19:20:48 -0400 Subject: [PATCH 09/36] tabsListItem parent protocol conformance --- .../Molecules/Items/TabsListItemModel.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift index becb0106..82f65e0b 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift @@ -9,7 +9,7 @@ import UIKit -public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { +public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,6 +19,23 @@ public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { var molecules: [[ListItemModelProtocol & MoleculeModelProtocol]] private var addedMolecules: [ListItemModelProtocol & MoleculeModelProtocol]? + public var children: [MoleculeModelProtocol] { + return molecules.flatMap { $0 } + } + + public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + guard let replacementMolecule = replacementMolecule as? ListItemModelProtocol & MoleculeModelProtocol else { return false } + for (tabIndex, _) in molecules.enumerated() { + for (elementIndex, _) in molecules[tabIndex].enumerated() { + if molecules[tabIndex][elementIndex].id == replacementMolecule.id { + molecules[tabIndex][elementIndex] = replacementMolecule + return true + } + } + } + return false + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- From 9ff07fdcce4f260603e94ceeb63a5153218d4d28 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 18 Sep 2023 19:37:54 -0400 Subject: [PATCH 10/36] fix mismatched signature --- .../Molecules/OtherContainers/MoleculeContainerProtocol.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift index 44b040aa..cbda8dde 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift @@ -19,8 +19,7 @@ public extension MoleculeContainerModelProtocol { } public extension MoleculeContainerModelProtocol where Self: AnyObject { - - mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + mutating func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { return try replaceChildMolecule(at: &molecule, with: replacementMolecule) } } From 6957f18753fed95619f120229118f051fb3ab16c Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 5 Oct 2023 13:47:55 -0400 Subject: [PATCH 11/36] decoding error clarity --- MVMCoreUI.xcodeproj/project.pbxproj | 14 +++-- .../Extensions/ReadableDecodingErrors.swift | 63 +++++++++++++++++++ .../BaseControllers/ViewController.swift | 31 +-------- ...=> ReplaceableMoleculeBehaviorModel.swift} | 10 ++- 4 files changed, 83 insertions(+), 35 deletions(-) create mode 100644 MVMCoreUI/Atomic/Extensions/ReadableDecodingErrors.swift rename MVMCoreUI/Behaviors/{ReplacementMoleculeBehavior.swift => ReplaceableMoleculeBehaviorModel.swift} (71%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index e661e013..4b4b561e 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -168,7 +168,8 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; - 5823ADF62AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823ADF52AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift */; }; + 5870636F2ACF238E00CA18D5 /* ReadableDecodingErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */; }; + 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; }; @@ -755,7 +756,8 @@ 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = ""; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; - 5823ADF52AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplacementMoleculeBehavior.swift; sourceTree = ""; }; + 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = ""; }; + 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = ""; }; @@ -1387,12 +1389,12 @@ 27F973512466071600CAB5C5 /* Behaviors */ = { isa = PBXGroup; children = ( + 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */, 0A1C30972620F61A00B47F3B /* Protocols */, 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, D23A900826125FFB007E14CE /* GetContactBehavior.swift */, D270E5662642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift */, 22B678F829E7944E00CF4196 /* GetNotificationAuthStatusBehavior.swift */, - 5823ADF52AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift */, ); path = Behaviors; sourceTree = ""; @@ -1566,6 +1568,7 @@ D202AFE2242A5F1400E5BEDF /* Extensions */ = { isa = PBXGroup; children = ( + 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */, D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */, 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */, D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */, @@ -2755,7 +2758,6 @@ 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */, - 5823ADF62AB0F7BA006045A7 /* ReplacementMoleculeBehavior.swift in Sources */, D20F3B44252E00E4004B3F56 /* PageProtocol.swift in Sources */, AA37CBD3251907200027344C /* StarsModel.swift in Sources */, 8D084AD22410BF7600951227 /* ListOneColumnFullWidthTextBodyText.swift in Sources */, @@ -2766,6 +2768,7 @@ 0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */, + 5870636F2ACF238E00CA18D5 /* ReadableDecodingErrors.swift in Sources */, 94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */, 943820842432382400B43AF3 /* WebView.swift in Sources */, 0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */, @@ -2915,6 +2918,7 @@ AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */, + 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Extensions/ReadableDecodingErrors.swift b/MVMCoreUI/Atomic/Extensions/ReadableDecodingErrors.swift new file mode 100644 index 00000000..4b48b0a4 --- /dev/null +++ b/MVMCoreUI/Atomic/Extensions/ReadableDecodingErrors.swift @@ -0,0 +1,63 @@ +// +// ReadableDecodingErrors.swift +// MVMCore +// +// Created by Kyle Hedden on 10/5/23. +// Copyright © 2023 myverizon. All rights reserved. +// + +import Foundation + +protocol HumanReadableDecodingErrorProtocol { + var readableDescription: String { get } +} + +extension JSONError: HumanReadableDecodingErrorProtocol { + var readableDescription: String { + switch (self) { + case .other(let other): + if let other = other as? HumanReadableDecodingErrorProtocol { + return other.readableDescription + } + return description + default: + return description + } + } +} + +extension ModelRegistry.Error: HumanReadableDecodingErrorProtocol { + var readableDescription: String { + switch (self) { + case .decoderErrorModelNotMapped(let identifier, let codingKey, let codingPath) where identifier != nil && codingKey != nil && codingPath != nil: + return "Model identifier \"\(identifier!)\" is not mapped for \"\(codingKey!.stringValue)\" @ \(codingPath!.map { return $0.stringValue })" + + case .decoderErrorObjectNotPresent(let codingKey, let codingPath): + return "Required model \"\(codingKey.stringValue)\" was not found @ \(codingPath.map { return $0.stringValue })" + + default: + return "Registry error: \((self as NSError).localizedFailureReason ?? self.localizedDescription)" + } + } +} + +extension DecodingError: HumanReadableDecodingErrorProtocol { + var readableDescription: String { + switch (self) { + case .keyNotFound(let codingKey, let context): + return "Required key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })" + + case .valueNotFound(_, let context): + return "Value not found @ \(context.codingPath.map { return $0.stringValue })" + + case .typeMismatch(_, let context): + return "Value type mismatch @ \(context.codingPath.map { return $0.stringValue })" + + case .dataCorrupted(let context): + return "Data corrupted @ \(context.codingPath.map { return $0.stringValue })" + + @unknown default: + return (self as NSError).localizedFailureReason ?? self.localizedDescription + } + } +} diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 73bba7b9..fdeeeed9 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -171,35 +171,8 @@ import MVMCore } func describe(parsingError: Error) -> String { - if let registryError = parsingError as? ModelRegistry.Error { - switch (registryError) { - case .decoderErrorModelNotMapped(let identifier, let codingKey, let codingPath) where identifier != nil && codingKey != nil && codingPath != nil: - return "Error parsing template. Model identifier \"\(identifier!)\" is not mapped for \"\(codingKey!.stringValue)\" @ \(codingPath!.map { return $0.stringValue })" - - case .decoderErrorObjectNotPresent(let codingKey, let codingPath): - return "Error parsing template. Required model \"\(codingKey.stringValue)\" was not found @ \(codingPath.map { return $0.stringValue })" - - default: - return "Error parsing template. Registry error: \((registryError as NSError).localizedFailureReason ?? registryError.localizedDescription)" - } - } - if let decodingError = parsingError as? DecodingError { - switch (decodingError) { - case .keyNotFound(let codingKey, let context): - return "Error parsing template. Required key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })" - - case .valueNotFound(_, let context): - return "Error parsing template. Value not found @ \(context.codingPath.map { return $0.stringValue })" - - case .typeMismatch(_, let context): - return "Error parsing template. Value type mismatch @ \(context.codingPath.map { return $0.stringValue })" - - case .dataCorrupted(let context): - return "Error parsing template. Data corrupted @ \(context.codingPath.map { return $0.stringValue })" - - @unknown default: - return "Error parsing template. \((parsingError as NSError).localizedFailureReason ?? parsingError.localizedDescription)" - } + if let error = parsingError as? HumanReadableDecodingErrorProtocol { + return "Error parsing template. \(error.readableDescription)" } return "Error parsing template. \((parsingError as NSError).localizedFailureReason ?? parsingError.localizedDescription)" } diff --git a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift similarity index 71% rename from MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift rename to MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift index 3773e717..d4d17c0d 100644 --- a/MVMCoreUI/Behaviors/ReplacementMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift @@ -18,8 +18,12 @@ public class ReplaceableMoleculeBehaviorModel: PageBehaviorModelProtocol { public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { var moleculeIds: [String] + public var transcendsPageUpdates: Bool { true } + public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { moleculeIds = (model as! ReplaceableMoleculeBehaviorModel).moleculeIds + guard let pageType = delegateObject?.moleculeDelegate?.getTemplateModel()?.pageType else { return } + MVMCoreViewControllerMappingObject.shared()?.addOptionalModules(toMapping: moleculeIds, forPageType: pageType) } public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { @@ -36,7 +40,11 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Failed to find '\(moleculeId)' in the current screen.", code: ErrorCode.viewControllerProcessingJSON.rawValue, domain: ErrorDomainSystem, location: String(describing: type(of: self)))!) } } catch { - MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))!) + let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))! + if let error = error as? HumanReadableDecodingErrorProtocol { + coreError.messageToLog = "Error decoding replacement \"\(moleculeId)\": \(error.readableDescription)" + } + MVMCoreLoggingHandler.addError(toLog: coreError) } } } From 50646851bae2f986c91ba8f6146e71de229e8618 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 26 Oct 2023 12:28:56 -0400 Subject: [PATCH 12/36] move model replacement to the viewcontroller for model state synchronization & stability. shift replacement molecule handling to the behavior for targeted updates. --- .../Protocols/MoleculeDelegateProtocol.swift | 2 + .../MoleculeTreeTraversalProtocol.swift | 20 +++++ .../Atomic/Templates/CollectionTemplate.swift | 4 +- .../Templates/ModalSectionListTemplate.swift | 4 +- .../Templates/MoleculeListTemplate.swift | 52 ++++++------- .../ThreeLayerFillMiddleTemplate.swift | 4 +- .../Atomic/Templates/ThreeLayerTemplate.swift | 4 +- .../ScrollingViewController.swift | 4 +- .../ThreeLayerCollectionViewController.swift | 4 +- .../ThreeLayerTableViewController.swift | 7 +- .../ThreeLayerViewController.swift | 4 +- .../BaseControllers/ViewController.swift | 50 +++++++++++-- .../ReplaceableMoleculeBehaviorModel.swift | 74 ++++++++++++++++--- 13 files changed, 168 insertions(+), 65 deletions(-) diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift index e78c0b1b..e7a4bdef 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift @@ -21,6 +21,8 @@ public protocol MoleculeDelegateProtocol: AnyObject { /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. Mainly used for list or collections. func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) //optional + + func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol]) } extension MoleculeDelegateProtocol { diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift index f595af9b..b030d92a 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift @@ -50,4 +50,24 @@ public extension MoleculeTreeTraversalProtocol { return accumulator } } + + func filterMoleculeTree(options: TreeTraversalOptions = .parentFirst, by condition: (MoleculeModelProtocol)->Bool) -> [MoleculeModelProtocol] { + return reduceDepthFirstTraverse(options: options, depth: 0, initialResult: []) { (accumulator, molecule, depth) in + if condition(molecule) { + return accumulator + [molecule] + } + return accumulator + } + } + + func findFirstMolecule(options: TreeTraversalOptions = .parentFirst, by condition: (MoleculeModelProtocol)->Bool) -> MoleculeModelProtocol? { + var foundMolecule: MoleculeModelProtocol? + depthFirstTraverse(options: options, depth: 0) { depth, molecule, isDone in + isDone = condition(molecule) + if isDone { + foundMolecule = molecule + } + } + return foundMolecule + } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index 896aa5bf..1f8cc49e 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -86,10 +86,10 @@ super.handleNewData() } - open override func updateUI() { + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false - super.updateUI() + super.updateUI(for: molecules) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift b/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift index 071b78c2..a328dac4 100644 --- a/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ModalSectionListTemplate.swift @@ -21,8 +21,8 @@ open class ModalSectionListTemplate: SectionListTemplate { // MARK: - Lifecycle //-------------------------------------------------- - override open func updateUI() { - super.updateUI() + override open func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { + super.updateUI(for: molecules) _ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: { [weak self] _ in guard let self = self else { return } let closeAction = (self.templateModel as? ModalSectionListTemplateModel)?.closeAction ?? diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 406f72a9..1af1a85a 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -87,10 +87,17 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol super.handleNewData() // Currently stuck as MoleculeListProtocol being called from AddRemoveMoleculesBehaviorModel. } - open override func updateUI() { + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false - super.updateUI() + super.updateUI(for: molecules) + + molecules?.forEach({ molecule in + if let index = moleculesInfo?.firstIndex(where: { $0.molecule.id == molecule.id }) { + moleculesInfo?[index].molecule = molecule + } + newData(for: molecule) + }) } open override func viewDidAppear(_ animated: Bool) { @@ -196,30 +203,20 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open func newData(for molecule: MoleculeModelProtocol) { //TODO: expand for header, navigation, etc - guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in - if equal(moleculeA: molecule, moleculeB: moleculeInfo.molecule) { - return true - } else if let parent = moleculeInfo.molecule as? ParentMoleculeModelProtocol { - // Get all molecules of the same type for faster check. - let molecules: [MoleculeModelProtocol] = parent.reduceDepthFirstTraverse(options: .childFirst, depth: 0, initialResult: []) { (accumulator, currentMolecule, depth) in - if currentMolecule.moleculeName == molecule.moleculeName { - return accumulator + [currentMolecule] - } - return accumulator - } - for moleculeB in molecules { - if equal(moleculeA: molecule, moleculeB: moleculeB) { - return true - } - } - } - return false - }) else { return } + guard let moleculesInfo = moleculesInfo else { return } + + let indicies = moleculesInfo.indices.filter({ index -> Bool in + return moleculesInfo[index].molecule.findFirstMolecule(by: { + $0.moleculeName == molecule.moleculeName && equal(moleculeA: molecule, moleculeB: $0) + }) != nil + }) // Refresh the cell. (reload loses cell selection) let selectedIndex = tableView.indexPathForSelectedRow - let indexPath = IndexPath(row: index, section: 0) - tableView.reloadRows(at: [indexPath], with: .automatic) + let indexPaths = indicies.map { + return IndexPath(row: $0, section: 0) + } + tableView.reloadRows(at: indexPaths, with: .automatic) if let selectedIndex = selectedIndex { tableView.selectRow(at: selectedIndex, animated: false, scrollPosition: .none) } @@ -292,14 +289,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol /// Checks if the two molecules are equal private func equal(moleculeA: MoleculeModelProtocol, moleculeB: MoleculeModelProtocol) -> Bool { - // TODO: move this to a better approach, maybe a UUID for each model. - // Do instance check - if let classMoleculeA = moleculeA as? NSObjectProtocol, - let classMoleculeB = moleculeB as? NSObjectProtocol { - return classMoleculeA === classMoleculeB - } - // Do json check - return moleculeA.toJSON() == moleculeB.toJSON() + return moleculeA.id == moleculeB.id } } diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift index bb6c5bdb..993cd9f0 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerFillMiddleTemplate.swift @@ -26,8 +26,8 @@ } } - open override func updateUI() { - super.updateUI() + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { + super.updateUI(for: molecules) heightConstraint?.isActive = true } } diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift index d0203e70..b0e36239 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift @@ -19,10 +19,10 @@ import UIKit try super.parsePageJSON() } - open override func updateUI() { + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { topViewOutsideOfScroll = templateModel?.anchorHeader ?? false bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false - super.updateUI() + super.updateUI(for: molecules) } open override func viewForTop() -> UIView? { diff --git a/MVMCoreUI/BaseControllers/ScrollingViewController.swift b/MVMCoreUI/BaseControllers/ScrollingViewController.swift index e7698d49..49bda46b 100644 --- a/MVMCoreUI/BaseControllers/ScrollingViewController.swift +++ b/MVMCoreUI/BaseControllers/ScrollingViewController.swift @@ -63,8 +63,8 @@ open class ScrollingViewController: ViewController { registerForKeyboardNotifications() } - open override func updateUI() { - super.updateUI() + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { + super.updateUI(for: molecules) // will change scrollView indicatorStyle automatically on the basis of backgroundColor var greyScale: CGFloat = 0 if view.backgroundColor?.getWhite(&greyScale, alpha: nil) ?? false { diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index 13b62f2b..22fd7778 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -108,8 +108,8 @@ import Foundation } } - open override func updateUI() { - super.updateUI() + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { + super.updateUI(for: molecules) topView?.removeFromSuperview() bottomView?.removeFromSuperview() topView = viewForTop() diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index f452fa96..95448c10 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -50,8 +50,11 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { tableView.reloadData() } - open override func updateUI() { - super.updateUI() + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { + super.updateUI(for: molecules) + + guard molecules == nil else { return } + createViewForTableHeader() createViewForTableFooter() tableView?.reloadData() diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index 0ec3cac9..378dc0bc 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -49,8 +49,8 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { } } - open override func updateUI() { - super.updateUI() + open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { + super.updateUI(for: molecules) // Removes the views topView?.removeFromSuperview() diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index fdeeeed9..a91b37df 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -44,6 +44,13 @@ import MVMCore public var selectedField: UIView? + public var pageUpdateQueue: OperationQueue = { + let pageUpdateQueue = OperationQueue() + pageUpdateQueue.maxConcurrentOperationCount = 1 + pageUpdateQueue.qualityOfService = .userInteractive + return pageUpdateQueue + }() + /// Checks if the screen width has changed open func screenSizeChanged() -> Bool { !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1) @@ -58,10 +65,6 @@ import MVMCore (pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0) else { return } - let pageUpdateQueue = OperationQueue() - pageUpdateQueue.maxConcurrentOperationCount = 1 - pageUpdateQueue.qualityOfService = .userInteractive - observingForResponses = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil, queue: pageUpdateQueue, using: responseJSONUpdated(notification:)) } @@ -241,8 +244,9 @@ import MVMCore } /// Applies the latest model to the UI. - @MainActor - open func updateUI() { + open func updateUI(for molecules: [MoleculeModelProtocol]? = nil) { + guard molecules == nil else { return } + executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in behavior.willRender(rootMolecules: getRootMolecules(), delegateObjectIVar) } @@ -508,6 +512,40 @@ import MVMCore // Needed otherwise when subclassed, the extension gets called. open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } + public func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol]) { + pageUpdateQueue.addOperation { + let replacedModels:[MoleculeModelProtocol] = moleculeModels.compactMap { model in + guard self.attemptToReplace(with: model) else { + return nil + } + return model + } + if replacedModels.count > 0 { + Task { @MainActor in + self.updateUI(for: replacedModels) + } + } + } + } + + open func attemptToReplace(with replacementModel: MoleculeModelProtocol) -> Bool { + guard var templateModel = getTemplateModel() else { return false } + var didReplace = false + do { + didReplace = try templateModel.replaceMolecule(with: replacementModel) + if !didReplace { + MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Failed to find '\(replacementModel.id)' in the current screen.", code: ErrorCode.viewControllerProcessingJSON.rawValue, domain: ErrorDomainSystem, location: String(describing: type(of: self)))!) + } + } catch { + let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))! + if let error = error as? HumanReadableDecodingErrorProtocol { + coreError.messageToLog = "Error replacing molecule \"\(replacementModel.id)\": \(error.readableDescription)" + } + MVMCoreLoggingHandler.addError(toLog: coreError) + } + return didReplace + } + //-------------------------------------------------- // MARK: - MVMCoreUIDetailViewProtocol //-------------------------------------------------- diff --git a/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift index d4d17c0d..0b4c0516 100644 --- a/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift +++ b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift @@ -17,39 +17,89 @@ public class ReplaceableMoleculeBehaviorModel: PageBehaviorModelProtocol { public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { var moleculeIds: [String] + var modulesToListenFor: [String] + private var observingForResponses: NSObjectProtocol? + private var delegateObject: MVMCoreUIDelegateObject? public var transcendsPageUpdates: Bool { true } public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { moleculeIds = (model as! ReplaceableMoleculeBehaviorModel).moleculeIds + let shouldListenForListUpdates = delegateObject?.moleculeListDelegate != nil + if shouldListenForListUpdates { + modulesToListenFor = [] + listenForModuleUpdates() + } else { + modulesToListenFor = moleculeIds + stopListeningForModuleUpdates() + } + self.delegateObject = delegateObject guard let pageType = delegateObject?.moleculeDelegate?.getTemplateModel()?.pageType else { return } MVMCoreViewControllerMappingObject.shared()?.addOptionalModules(toMapping: moleculeIds, forPageType: pageType) } public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { + self.delegateObject = delegateObject + let shouldListenForListUpdates = delegateObject?.moleculeListDelegate != nil + if shouldListenForListUpdates { + modulesToListenFor = [] + listenForModuleUpdates() + } else { + modulesToListenFor = moleculeIds + stopListeningForModuleUpdates() + } - guard var templateModel = delegateObject?.moleculeDelegate?.getTemplateModel() else { return } - - templateModel.printMolecules() - - for moleculeId in moleculeIds { + let moleculeModels = moleculeIds.compactMap { moleculeId in do { - guard let replacementModel = try delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId) else { continue } - let didReplace = try templateModel.replaceMolecule(with: replacementModel) - if !didReplace { - MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Failed to find '\(moleculeId)' in the current screen.", code: ErrorCode.viewControllerProcessingJSON.rawValue, domain: ErrorDomainSystem, location: String(describing: type(of: self)))!) - } + return try delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId) } catch { let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))! if let error = error as? HumanReadableDecodingErrorProtocol { coreError.messageToLog = "Error decoding replacement \"\(moleculeId)\": \(error.readableDescription)" } MVMCoreLoggingHandler.addError(toLog: coreError) + return nil } } + delegateObject?.moleculeDelegate?.replaceMoleculeData(moleculeModels) } - public func modulesToListenFor() -> [String] { - moleculeIds + private func listenForModuleUpdates() { + guard observingForResponses == nil else { return } + let pageUpdateQueue = OperationQueue() + pageUpdateQueue.maxConcurrentOperationCount = 1 + pageUpdateQueue.qualityOfService = .userInteractive + observingForResponses = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil, queue: pageUpdateQueue, using: responseJSONUpdated(notification:)) + } + + private func stopListeningForModuleUpdates() { + guard let observingForResponses = observingForResponses else { return } + NotificationCenter.default.removeObserver(observingForResponses) + } + + @objc func responseJSONUpdated(notification: Notification) { + guard let modulesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyModuleMap) else { return } + let modules: [MoleculeModelProtocol] = moleculeIds.compactMap { moleculeId in + guard let json = modulesLoaded.optionalDictionaryForKey(moleculeId) else { return nil } + do { + return try convertToModel(moduleJSON: json) + } catch { + let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))! + if let error = error as? HumanReadableDecodingErrorProtocol { + coreError.messageToLog = "Error decoding replacement \"\(moleculeId)\": \(error.readableDescription)" + } + MVMCoreLoggingHandler.addError(toLog: coreError) + return nil + } + } + delegateObject?.moleculeDelegate?.replaceMoleculeData(modules) + } + + private func convertToModel(moduleJSON: [String: Any]) throws -> MoleculeModelProtocol { + guard let moleculeName = moduleJSON.optionalStringForKey(KeyMoleculeName), + let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) else { + throw ModelRegistry.Error.decoderErrorModelNotMapped(identifer: moduleJSON.optionalStringForKey(KeyMoleculeName)) + } + return try modelType.decode(jsonDict: moduleJSON as [String : Any]) as! MoleculeModelProtocol } } From 339232f9fab3508652e2ab72c2ca061816782f34 Mon Sep 17 00:00:00 2001 From: vimal Date: Tue, 31 Oct 2023 16:06:20 +0530 Subject: [PATCH 13/36] give better estimate height --- MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 6b360120..f32d1308 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -181,6 +181,10 @@ open class Carousel: View { } } + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 80 + } + //-------------------------------------------------- // MARK: - JSON Setters //-------------------------------------------------- From b8cefeb7e5bd21974b5ca8f603c03a87055a83b2 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Tue, 5 Dec 2023 15:56:03 -0500 Subject: [PATCH 14/36] fix post merge file reference --- MVMCoreUI.xcodeproj/project.pbxproj | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index be89d12b..e012d228 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -168,6 +168,8 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; + 5822720B2B1FC55F00F75BAE /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */; }; + 5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; }; 5870636F2ACF238E00CA18D5 /* ReadableDecodingErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */; }; 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; @@ -298,8 +300,8 @@ AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; }; AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; - B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBE29DF34730005D28B /* BadgeModel.swift */; }; B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBC29DF34680005D28B /* Badge.swift */; }; + B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBE29DF34730005D28B /* BadgeModel.swift */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; }; BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; }; @@ -576,8 +578,8 @@ EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */; }; EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */; }; - EA985C402970939A00F2FF2E /* TileletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3F2970939A00F2FF2E /* TileletModel.swift */; }; EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3D2970938F00F2FF2E /* Tilelet.swift */; }; + EA985C402970939A00F2FF2E /* TileletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C3F2970939A00F2FF2E /* TileletModel.swift */; }; EA985C602970A3F000F2FF2E /* VDS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C5F2970A3F000F2FF2E /* VDS.framework */; }; EA985C642970A40E00F2FF2E /* VDSTypographyTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA985C632970A40E00F2FF2E /* VDSTypographyTokens.xcframework */; }; EA985C852981AA9C00F2FF2E /* VDS-Enums+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985C842981AA9C00F2FF2E /* VDS-Enums+Codable.swift */; }; @@ -760,6 +762,8 @@ 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = ""; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = ""; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; + 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; + 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = ""; }; 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = ""; }; 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; @@ -890,8 +894,8 @@ AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = ""; }; AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; - B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = ""; }; B4CC8FBC29DF34680005D28B /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = ""; }; + B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = ""; }; BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = ""; }; @@ -1464,6 +1468,15 @@ path = OneColumn; sourceTree = ""; }; + 582272082B1FC53E00F75BAE /* Accessibility */ = { + isa = PBXGroup; + children = ( + 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */, + 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */, + ); + path = Accessibility; + sourceTree = ""; + }; 8DD1E36C243B3CD900D8F2DF /* ThreeColumn */ = { isa = PBXGroup; children = ( @@ -1989,6 +2002,7 @@ D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = { isa = PBXGroup; children = ( + 582272082B1FC53E00F75BAE /* Accessibility */, 01F2C1FC27C81F9700DC3D36 /* Managers */, D2ED27D8254B0C1F00A1C293 /* Alerts */, 27F973512466071600CAB5C5 /* Behaviors */, @@ -2715,6 +2729,7 @@ 011D95A3240453F8000E3791 /* RuleRegexModel.swift in Sources */, D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, + 5822720B2B1FC55F00F75BAE /* AccessibilityHandler.swift in Sources */, BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */, 32D2609724C19E2100B56344 /* LockupsPlanSMLXLModel.swift in Sources */, EA985C872981AB0F00F2FF2E /* VDS-Tilelet+Codable.swift in Sources */, @@ -2979,6 +2994,7 @@ 8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */, AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */, BB2FB3BD247E7EF200DF73CD /* Tags.swift in Sources */, + 5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */, AA104ADC244734EA004D2810 /* HeadersH1LandingPageHeaderModel.swift in Sources */, BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */, 323AC96A24C837F000F8E4C4 /* ListThreeColumnBillChangesModel.swift in Sources */, From 0ecfef39cd619a78faee3f9aa4643eef8b48578d Mon Sep 17 00:00:00 2001 From: vimal Date: Mon, 11 Dec 2023 13:46:41 +0530 Subject: [PATCH 15/36] review comment updated. setting the height from model if not exist then hard coded value --- MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index f32d1308..ff9da6d5 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -182,7 +182,7 @@ open class Carousel: View { } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - return 80 + return (model as? CarouselModel)?.height ?? 80 } //-------------------------------------------------- From df32a513958135b07dc00ec76925d82103fa576b Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 18 Dec 2023 17:19:52 -0500 Subject: [PATCH 16/36] Fix first time carousel replacement. --- .../ParentMoleculeModelProtocol.swift | 19 ++++++++++++------- .../ReplaceableMoleculeBehaviorModel.swift | 9 +++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift index a9f4f8ac..ef53b177 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -10,6 +10,7 @@ import Foundation public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol { + /// Returns the direct children of this component. (Does not recurse.) var children: [MoleculeModelProtocol] { get } /// Method for replacing surface level children. (Does not recurse.) @@ -73,22 +74,26 @@ public extension ParentMoleculeModelProtocol { } func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool)->Void) { - var stop = false + var shouldStop = false if (options == .parentFirst) { - onVisit(depth, self, &stop) - guard !stop else { return } + onVisit(depth, self, &shouldStop) + guard !shouldStop else { return } + } + let stopIntercept: (Int, MoleculeModelProtocol, inout Bool)->Void = { depth, model, stop in + onVisit(depth, model, &shouldStop) + stop = shouldStop } for child in children { if let additionalParent = child as? ParentMoleculeModelProtocol { // Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol. - additionalParent.depthFirstTraverse(options: options, depth: depth + 1, onVisit: onVisit) + additionalParent.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept) } else { - child.depthFirstTraverse(options: options, depth: depth + 1, onVisit: onVisit) + child.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept) } - guard !stop else { return } + guard !shouldStop else { return } } if (options == .childFirst) { - onVisit(depth, self, &stop) + onVisit(depth, self, &shouldStop) } // if options == .leafOnly don't call on self. } diff --git a/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift index 0b4c0516..24e3b532 100644 --- a/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift +++ b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift @@ -61,7 +61,9 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { return nil } } - delegateObject?.moleculeDelegate?.replaceMoleculeData(moleculeModels) + if moleculeModels.count > 0 { + delegateObject?.moleculeDelegate?.replaceMoleculeData(moleculeModels) + } } private func listenForModuleUpdates() { @@ -75,6 +77,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { private func stopListeningForModuleUpdates() { guard let observingForResponses = observingForResponses else { return } NotificationCenter.default.removeObserver(observingForResponses) + self.observingForResponses = nil } @objc func responseJSONUpdated(notification: Notification) { @@ -92,7 +95,9 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior { return nil } } - delegateObject?.moleculeDelegate?.replaceMoleculeData(modules) + if modules.count > 0 { + delegateObject?.moleculeDelegate?.replaceMoleculeData(modules) + } } private func convertToModel(moduleJSON: [String: Any]) throws -> MoleculeModelProtocol { From 2857b1cd559d13f63814b68688cc451e6abbe212 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 18 Dec 2023 17:22:40 -0500 Subject: [PATCH 17/36] Stop closure efficiency. --- .../Protocols/ModelProtocols/MoleculeModelProtocol.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift index 0fcab2d0..e56840e8 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift @@ -69,11 +69,12 @@ public extension Array where Element == MoleculeModelProtocol { func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) { var shouldStop = false + let stopIntercept = { depth, molecule, stop in + onVisit(depth, molecule, &shouldStop) + stop = shouldStop + } for molecule in self { - molecule.depthFirstTraverse(options: options, depth: depth) { depth, molecule, stop in - onVisit(depth, molecule, &shouldStop) - stop = shouldStop - } + molecule.depthFirstTraverse(options: options, depth: depth, onVisit: stopIntercept) if shouldStop { break } } } From b681f331baf156837a1634a7bc91a3cadc101fc2 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Tue, 19 Dec 2023 12:42:11 -0500 Subject: [PATCH 18/36] Fix data desync on update. --- MVMCoreUI/BaseControllers/ViewController.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index a91b37df..c8914790 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -235,12 +235,11 @@ import MVMCore formValidator = FormValidator(rules) } - // Notify the manager of new data - manager?.newDataReceived?(in: self) + updateUI() - Task { @MainActor in - updateUI() - } + // Notify the manager of new data. + // Warning: Some flows cause table reloads. Until the UI update is decoupled, should be after the updateUI. + manager?.newDataReceived?(in: self) } /// Applies the latest model to the UI. @@ -521,7 +520,7 @@ import MVMCore return model } if replacedModels.count > 0 { - Task { @MainActor in + DispatchQueue.main.sync { self.updateUI(for: replacedModels) } } From 4f586ced8906152bcb6b1e5e9e429865270fec63 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Wed, 20 Dec 2023 19:16:22 -0500 Subject: [PATCH 19/36] Animate GIFs. --- MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift index 780f8829..315c739f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift @@ -327,6 +327,9 @@ let loadingImageName = self.currentImageName, loadingImageName == imageName else { return } self.isFallbackImage = isFallbackImage self.loadingSpinner.pause() + if let data = data { + self.imageView.loadGifWithData(data) + } let layoutWillChange = self.shouldNotifyDelegateOnUpdate ? self.layoutWillChange(width: self.currentImageWidth, height: self.currentImageHeight, size: image?.size) : false self.addConstraints(width: width, height: height, size: image?.size) self.loadingSpinnerHeightConstraint?.constant = 0 From ca10c0b8497fdadee3fbaa43ffa93a1c8f8a6eae Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Tue, 2 Jan 2024 17:24:42 -0500 Subject: [PATCH 20/36] indexing safety --- MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift | 2 +- MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index ff9da6d5..421c2e95 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -87,7 +87,7 @@ open class Carousel: View { showPeaking(false) // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. - guard let model = model as? CarouselModel, + guard let model = model as? CarouselModel, !model.molecules.isEmpty, (model.paging == true || loop == true) else { return } DispatchQueue.main.async { self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index c18f0e95..976018dd 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -8,7 +8,6 @@ import UIKit - @objcMembers public class CarouselModel: ParentMoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- @@ -57,14 +56,14 @@ import UIKit guard selectable else { // Use visible item value, else index - if let fieldValue = molecules[index].formFieldValue() { + if let fieldValue = molecules[safe: 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 } + guard let fieldValue = molecules[safe: selectedIndex]?.formFieldValue() else { return selectedIndex } return fieldValue } From 047d9b7014a8b9e2b5e2684e7925c8df1047e952 Mon Sep 17 00:00:00 2001 From: vimal Date: Wed, 3 Jan 2024 15:23:29 +0530 Subject: [PATCH 21/36] enhancement on the header update based on the molecules --- .../Atomic/Templates/MoleculeListTemplate.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 1af1a85a..fe30288a 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -202,7 +202,21 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } open func newData(for molecule: MoleculeModelProtocol) { - //TODO: expand for header, navigation, etc + + if let headerMolecule = molecule as? MoleculeHeaderModel { + //If complete header replaced then it makes sense to add this + /*createViewForTableHeader() + let width = view.bounds.width + if let topView = topView as? MVMCoreViewProtocol { + topView.updateView(width) + showHeader(width) + }*/ + //Just update is happening only on the existing molecules. ex. changing the title, action etc.. + (topView as? MoleculeViewProtocol)?.set(with: headerMolecule, delegateObject() as? MVMCoreUIDelegateObject, nil) + return + } + + //TODO: expand for navigation, etc guard let moleculesInfo = moleculesInfo else { return } let indicies = moleculesInfo.indices.filter({ index -> Bool in From db7e59191a8eecddf85decfa08f0ed42c3aa64eb Mon Sep 17 00:00:00 2001 From: vimal Date: Wed, 3 Jan 2024 23:25:48 +0530 Subject: [PATCH 22/36] fix for button model review comment updated --- .../Atoms/Buttons/ImageButtonModel.swift | 10 ++++++- .../Templates/MoleculeListTemplate.swift | 27 ++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift index a61ec150..5a53e637 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift @@ -8,7 +8,7 @@ import Foundation -open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol { +open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -29,6 +29,14 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro public var updateUI: ActionBlock? + public var children: [MoleculeModelProtocol] { + [image].compactMap({$0}) + } + + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + return try replaceChildMolecule(at: &image, with: molecule) + } + public init(image: ImageViewModel?, action: ActionModelProtocol) { self.image = image self.action = action diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index fe30288a..cc76ef34 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -203,16 +203,13 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open func newData(for molecule: MoleculeModelProtocol) { - if let headerMolecule = molecule as? MoleculeHeaderModel { - //If complete header replaced then it makes sense to add this - /*createViewForTableHeader() - let width = view.bounds.width - if let topView = topView as? MVMCoreViewProtocol { - topView.updateView(width) - showHeader(width) - }*/ - //Just update is happening only on the existing molecules. ex. changing the title, action etc.. - (topView as? MoleculeViewProtocol)?.set(with: headerMolecule, delegateObject() as? MVMCoreUIDelegateObject, nil) + //Update the header + if updateHeaderFooter(modelView: topView, molecule: molecule) { + return + } + + //Update the footer + if updateHeaderFooter(modelView: bottomView, molecule: molecule) { return } @@ -235,6 +232,16 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol tableView.selectRow(at: selectedIndex, animated: false, scrollPosition: .none) } } + + ///Helper functions to update the header/footer view + private func updateHeaderFooter(modelView: UIView?, molecule: MoleculeModelProtocol) -> Bool { + if let updateView = modelView, + let moleculeView = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [updateView]).first(where: { $0.model?.moleculeName == molecule.moleculeName && $0.model?.id == molecule.id }) { + updateMoleculeView(moleculeView, from: molecule) + return true + } + return false + } //-------------------------------------------------- // MARK: - Convenience From 03ea6c528c4eb0b538e50e6387e2b6bb86d30d8c Mon Sep 17 00:00:00 2001 From: vimal Date: Thu, 4 Jan 2024 11:07:34 +0530 Subject: [PATCH 23/36] minor cleanup --- .../Templates/MoleculeListTemplate.swift | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index cc76ef34..a0f6743c 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -203,17 +203,10 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open func newData(for molecule: MoleculeModelProtocol) { - //Update the header - if updateHeaderFooter(modelView: topView, molecule: molecule) { + if updateMoleculeViewByModelId(molecule: molecule) { return } - //Update the footer - if updateHeaderFooter(modelView: bottomView, molecule: molecule) { - return - } - - //TODO: expand for navigation, etc guard let moleculesInfo = moleculesInfo else { return } let indicies = moleculesInfo.indices.filter({ index -> Bool in @@ -234,11 +227,14 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } ///Helper functions to update the header/footer view - private func updateHeaderFooter(modelView: UIView?, molecule: MoleculeModelProtocol) -> Bool { - if let updateView = modelView, - let moleculeView = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [updateView]).first(where: { $0.model?.moleculeName == molecule.moleculeName && $0.model?.id == molecule.id }) { - updateMoleculeView(moleculeView, from: molecule) - return true + //TODO: expand for navigation, etc + private func updateMoleculeViewByModelId(molecule: MoleculeModelProtocol) -> Bool { + let headerFooterViews = [topView, bottomView].compactMap({$0 as? MoleculeViewProtocol}) + for headerFooterView in headerFooterViews { + if let moleculeView = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [headerFooterView]).first(where: { $0.model?.moleculeName == molecule.moleculeName && $0.model?.id == molecule.id }) { + updateMoleculeView(moleculeView, from: molecule) + return true + } } return false } From 929cb33b5fa55a07557011d98e503033a2a9bb8c Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 4 Jan 2024 14:19:28 -0500 Subject: [PATCH 24/36] set the intiial tint state for ImageButton --- MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift index 323236a7..7dd08e88 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift @@ -33,7 +33,7 @@ import Foundation super.set(with: model, delegateObject, additionalData) FormValidator.setupValidation(for: castModel, delegate: delegateObject?.formHolderDelegate) - + setState() } public func setState() { From 3bd53784f01285b748407b43edce2cb2d6f6d237 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Fri, 5 Jan 2024 22:27:47 -0500 Subject: [PATCH 25/36] PollingBehavior initial implementation. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../Behaviors/PollingBehaviorModel.swift | 89 +++++++++++++++++++ .../OtherHandlers/CoreUIModelMapping.swift | 1 + 3 files changed, 94 insertions(+) create mode 100644 MVMCoreUI/Behaviors/PollingBehaviorModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 4ef7106c..450a5c86 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -170,6 +170,7 @@ 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; 5822720B2B1FC55F00F75BAE /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */; }; 5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; }; + 5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; }; 5870636F2ACF238E00CA18D5 /* ReadableDecodingErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */; }; 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; }; 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; }; @@ -763,6 +764,7 @@ 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = ""; }; 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = ""; }; 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = ""; }; + 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PollingBehaviorModel.swift; sourceTree = ""; }; 5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = ""; }; 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = ""; }; 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = ""; }; @@ -1399,6 +1401,7 @@ 27F973512466071600CAB5C5 /* Behaviors */ = { isa = PBXGroup; children = ( + 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */, 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */, 0A1C30972620F61A00B47F3B /* Protocols */, 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, @@ -2777,6 +2780,7 @@ D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */, AF8118302AB39B0900FAD1BA /* RawRepresentableCodable.swift in Sources */, 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */, + 5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */, D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */, 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */, 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, diff --git a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift new file mode 100644 index 00000000..4e0f407c --- /dev/null +++ b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift @@ -0,0 +1,89 @@ +// +// RefreshableMoleculeBehavior.swift +// MVMCoreUI +// +// Created by Kyle Hedden on 9/12/23. +// Copyright © 2023 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +public class PollingBehaviorModel: PageBehaviorModelProtocol { + public class var identifier: String { "pollingBehavior" } + public var shouldAllowMultipleInstances: Bool { true } + + public let refreshInterval: TimeInterval + public let refreshAction: ActionOpenPageModel + + @DecodableDefault.False public var runWhileHidden: Bool + @DecodableDefault.False public var refreshOnFirstLoad: Bool + @DecodableDefault.False public var refreshOnShown: Bool + + @DecodableDefault.False public var alwaysUseFallbackResponse: Bool + public let fallbackResponse: JSONValueDictionary? +} + +public class PollingBehavior: NSObject, PageVisibilityBehavior { + + var model: PollingBehaviorModel + var delegateObject: MVMCoreUIDelegateObject? + var lastRefresh = Date.distantPast + var pollTimer: DispatchSourceTimer? + + var remainingTimeToRefresh: TimeInterval { + lastRefresh.timeIntervalSinceNow - model.refreshInterval + } + + var firstTimeLoad = true + + var refreshOnShown: Bool { + if model.refreshOnFirstLoad && firstTimeLoad { + return true + } + firstTimeLoad = false + return model.refreshOnShown + } + + public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + self.model = model as! PollingBehaviorModel + self.delegateObject = delegateObject + } + + public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + resumePollingTimer(withRemainingTime: refreshOnShown ? 0 : remainingTimeToRefresh, refreshAction: model.refreshAction, interval: model.refreshInterval) + } + + public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) { + pollTimer?.cancel() + } + + func resumePollingTimer(withRemainingTime timeRemaining: TimeInterval, refreshAction: ActionOpenPageModel, interval: TimeInterval) { + let delegateObject = DelegateObject.create(withDelegateForAll: self) + pollTimer?.cancel() + pollTimer = DispatchSource.makeTimerSource() + pollTimer?.schedule(deadline: .now() + timeRemaining, repeating: interval) + pollTimer?.setEventHandler(qos:.utility) { + Task { + try? await MVMCoreActionHandler.shared()?.handleAction(with: refreshAction, additionalData: nil, delegateObject: delegateObject) + } + } + pollTimer?.resume() + } + + deinit { + pollTimer?.cancel() + } + +} + +extension PollingBehavior: MVMCoreLoadDelegateProtocol { + + public func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) { + + if error != nil { + // Apply the default response. + } + } + +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 483ecd93..c9f057d8 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -229,6 +229,7 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(handler: AddRemoveMoleculesBehavior.self, for: AddRemoveMoleculesBehaviorModel.self) ModelRegistry.register(handler: GetNotificationAuthStatusBehavior.self, for: GetNotificationAuthStatusBehaviorModel.self) ModelRegistry.register(handler: ReplaceableMoleculeBehavior.self, for: ReplaceableMoleculeBehaviorModel.self) + ModelRegistry.register(handler: PollingBehavior.self, for: PollingBehaviorModel.self) } open override class func registerActions() { From 46a3af5e6b6723221b3cfb3c307e2b3a749dd686 Mon Sep 17 00:00:00 2001 From: vimal Date: Mon, 8 Jan 2024 09:30:09 +0530 Subject: [PATCH 26/36] review comment updated --- .../Templates/MoleculeListTemplate.swift | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index a0f6743c..61669200 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -203,7 +203,9 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open func newData(for molecule: MoleculeModelProtocol) { - if updateMoleculeViewByModelId(molecule: molecule) { + //Check header and footer if replace happens then return. + if updateMoleculeViewById(topView, with: molecule) || + updateMoleculeViewById(bottomView, with: molecule) { return } @@ -226,15 +228,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } } - ///Helper functions to update the header/footer view - //TODO: expand for navigation, etc - private func updateMoleculeViewByModelId(molecule: MoleculeModelProtocol) -> Bool { - let headerFooterViews = [topView, bottomView].compactMap({$0 as? MoleculeViewProtocol}) - for headerFooterView in headerFooterViews { - if let moleculeView = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [headerFooterView]).first(where: { $0.model?.moleculeName == molecule.moleculeName && $0.model?.id == molecule.id }) { - updateMoleculeView(moleculeView, from: molecule) - return true - } + ///Helper functions to update view with molecule + private func updateMoleculeViewById(_ view: UIView?, with molecule: MoleculeModelProtocol) -> Bool { + if let updateView = view, + let moleculeView = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [updateView]).first(where: { $0.model?.moleculeName == molecule.moleculeName && $0.model?.id == molecule.id }) { + updateMoleculeView(moleculeView, from: molecule) + return true } return false } From 7a1ef28b1c1c34217429afb9a5ea57f9d5c5fd96 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 8 Feb 2024 14:14:40 -0500 Subject: [PATCH 27/36] PageBehaviorContainerModelProtocol spelling fix. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++++---- MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift | 2 +- .../BaseControllers/MVMControllerModelProtocol.swift | 2 +- ...col.swift => PageBehaviorContainerModelProtocol.swift} | 6 +++--- .../Behaviors/Protocols/PageBehaviorHandlerProtocol.swift | 4 ++-- MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) rename MVMCoreUI/Behaviors/Protocols/{PageBehaviorConatinerModelProtocol.swift => PageBehaviorContainerModelProtocol.swift} (88%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 450a5c86..020b11f4 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -390,7 +390,7 @@ D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */; }; D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */; }; D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */; }; - D23A90002612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */; }; + D23A90002612347A007E14CE /* PageBehaviorContainerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorContainerModelProtocol.swift */; }; D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */; }; D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A900826125FFB007E14CE /* GetContactBehavior.swift */; }; D23A90682614B0B4007E14CE /* CoreUIModelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */; }; @@ -984,7 +984,7 @@ D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideo.swift; sourceTree = ""; }; D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocolRequirer.swift; sourceTree = ""; }; D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorModelProtocol.swift; sourceTree = ""; }; - D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorConatinerModelProtocol.swift; sourceTree = ""; }; + D23A8FFF2612347A007E14CE /* PageBehaviorContainerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorContainerModelProtocol.swift; sourceTree = ""; }; D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerProtocol.swift; sourceTree = ""; }; D23A900826125FFB007E14CE /* GetContactBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContactBehavior.swift; sourceTree = ""; }; D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUIModelMapping.swift; sourceTree = ""; }; @@ -1335,7 +1335,7 @@ children = ( D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */, D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */, - D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */, + D23A8FFF2612347A007E14CE /* PageBehaviorContainerModelProtocol.swift */, D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */, 27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */, ); @@ -2749,7 +2749,7 @@ 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, D2A92884241ACB25004E01C6 /* ProgrammaticScrollViewController.swift in Sources */, EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */, - D23A90002612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift in Sources */, + D23A90002612347A007E14CE /* PageBehaviorContainerModelProtocol.swift in Sources */, EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */, 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift index 81b08c73..41c8f56a 100644 --- a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift @@ -44,7 +44,7 @@ public extension TemplateProtocol where Self: PageBehaviorHandlerProtocol, Self: templateModel = try decodeTemplate(using: decoder, from: data) // Add additional required behaviors if applicable. - guard var pageBehaviorsModel = templateModel as? TemplateModelProtocol & PageBehaviorConatinerModelProtocol else { return } + guard var pageBehaviorsModel = templateModel as? TemplateModelProtocol & PageBehaviorContainerModelProtocol else { return } pageBehaviorsModel.traverseAndAddRequiredBehaviors() var behaviorHandler = self diff --git a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift index b4aabd55..ce9c132c 100644 --- a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift +++ b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift @@ -9,6 +9,6 @@ import Foundation -public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorConatinerModelProtocol { +public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorContainerModelProtocol { } diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorConatinerModelProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorContainerModelProtocol.swift similarity index 88% rename from MVMCoreUI/Behaviors/Protocols/PageBehaviorConatinerModelProtocol.swift rename to MVMCoreUI/Behaviors/Protocols/PageBehaviorContainerModelProtocol.swift index ddc1e646..1c2525f5 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorConatinerModelProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorContainerModelProtocol.swift @@ -7,11 +7,11 @@ // /// Protocol applied to a model that contains a list of behavior models. -public protocol PageBehaviorConatinerModelProtocol { +public protocol PageBehaviorContainerModelProtocol { var behaviors: [PageBehaviorModelProtocol]? { get set } } -public extension PageBehaviorConatinerModelProtocol { +public extension PageBehaviorContainerModelProtocol { /// Adds the behavior model to the behaviors if possible. mutating func add(behavior: PageBehaviorModelProtocol) { @@ -25,7 +25,7 @@ public extension PageBehaviorConatinerModelProtocol { } } -public extension PageBehaviorConatinerModelProtocol where Self: MoleculeTreeTraversalProtocol { +public extension PageBehaviorContainerModelProtocol where Self: MoleculeTreeTraversalProtocol { /// Traverses all models and adds any required behavior models. mutating func traverseAndAddRequiredBehaviors() { diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift index 29b09937..5c6a78bb 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift @@ -30,7 +30,7 @@ public extension PageBehaviorHandlerProtocol { return behaviors } - mutating func applyBehaviors(pageBehaviorModel: PageBehaviorConatinerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + mutating func applyBehaviors(pageBehaviorModel: PageBehaviorContainerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { // Pull the existing behaviors. var behaviors = (behaviors ?? []).filter { $0.transcendsPageUpdates } // Create and append any new behaviors based on the incoming models. @@ -58,7 +58,7 @@ public extension PageBehaviorHandlerProtocol { public extension PageBehaviorHandlerProtocol where Self: MVMCoreViewControllerProtocol { - mutating func applyBehaviors(pageBehaviorModel: PageBehaviorConatinerModelProtocol) { + mutating func applyBehaviors(pageBehaviorModel: PageBehaviorContainerModelProtocol) { applyBehaviors(pageBehaviorModel: pageBehaviorModel, delegateObject: delegateObject?() as? MVMCoreUIDelegateObject) } diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift index 7fb9dc1f..ae09e0ce 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift @@ -88,8 +88,8 @@ public protocol PageCustomActionHandlerBehavior: PageBehaviorProtocol { } public extension MVMCoreUIDelegateObject { - var behaviorModelDelegate: PageBehaviorConatinerModelProtocol? { - (moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorConatinerModelProtocol + var behaviorModelDelegate: PageBehaviorContainerModelProtocol? { + (moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorContainerModelProtocol } weak var behaviorTemplateDelegate: (PageBehaviorHandlerProtocol & NSObjectProtocol)? { From f4cb350a95fd38cc4fa79020bc13941dc17710af Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 8 Feb 2024 19:58:55 -0500 Subject: [PATCH 28/36] Hold onto selected tab index in the view model. --- MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift index e070b8e6..f0d93237 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift @@ -30,6 +30,7 @@ import VDS if let delegate { onTabDidSelect = { [weak self] index in guard let self else { return } + viewModel.selectedIndex = index delegate.didSelectItem(.init(row: index, section: 0), tabs: self) } From 1d4294803acbc614a6aa39c0c259330ef5a3b337 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 8 Feb 2024 21:03:19 -0500 Subject: [PATCH 29/36] Remove the duplicate load gif. Rely on the defaultCompletionBlock. --- MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift index 315c739f..780f8829 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift @@ -327,9 +327,6 @@ let loadingImageName = self.currentImageName, loadingImageName == imageName else { return } self.isFallbackImage = isFallbackImage self.loadingSpinner.pause() - if let data = data { - self.imageView.loadGifWithData(data) - } let layoutWillChange = self.shouldNotifyDelegateOnUpdate ? self.layoutWillChange(width: self.currentImageWidth, height: self.currentImageHeight, size: image?.size) : false self.addConstraints(width: width, height: height, size: image?.size) self.loadingSpinnerHeightConstraint?.constant = 0 From 3057226a751ef3aabe7bf6d6e4cac6dd7a15beca Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 12 Feb 2024 11:08:22 -0500 Subject: [PATCH 30/36] Code Review. Embracing the implicit nature of Swift (?) and preventing a change highlight. --- MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 61669200..b72cfd57 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -305,7 +305,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol /// Checks if the two molecules are equal private func equal(moleculeA: MoleculeModelProtocol, moleculeB: MoleculeModelProtocol) -> Bool { - return moleculeA.id == moleculeB.id + moleculeA.id == moleculeB.id } } From adc15bd6d8423d262c5e82366f1584f50d52ab40 Mon Sep 17 00:00:00 2001 From: vimal Date: Thu, 15 Feb 2024 19:30:15 +0530 Subject: [PATCH 31/36] review comment updated --- MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index b72cfd57..8f06ca7f 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -205,7 +205,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol //Check header and footer if replace happens then return. if updateMoleculeViewById(topView, with: molecule) || - updateMoleculeViewById(bottomView, with: molecule) { + updateMoleculeViewById(bottomView, with: molecule, isHeader: false) { return } @@ -229,10 +229,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } ///Helper functions to update view with molecule - private func updateMoleculeViewById(_ view: UIView?, with molecule: MoleculeModelProtocol) -> Bool { + private func updateMoleculeViewById(_ view: UIView?, with molecule: MoleculeModelProtocol, isHeader: Bool = true) -> Bool { if let updateView = view, let moleculeView = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [updateView]).first(where: { $0.model?.moleculeName == molecule.moleculeName && $0.model?.id == molecule.id }) { updateMoleculeView(moleculeView, from: molecule) + //Redraw the header and footer for content update + isHeader ? showHeader(nil) : showFooter(nil) return true } return false From 22ad11840a587926c0adf94f10c19055287bb3f7 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Thu, 15 Feb 2024 21:14:09 -0500 Subject: [PATCH 32/36] Open poller to any action. Drop the fallback response as it will be taken care of by openPage handler. --- .../Behaviors/PollingBehaviorModel.swift | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift index 4e0f407c..4bf04feb 100644 --- a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift +++ b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift @@ -14,14 +14,37 @@ public class PollingBehaviorModel: PageBehaviorModelProtocol { public var shouldAllowMultipleInstances: Bool { true } public let refreshInterval: TimeInterval - public let refreshAction: ActionOpenPageModel + public let refreshAction: ActionModelProtocol - @DecodableDefault.False public var runWhileHidden: Bool - @DecodableDefault.False public var refreshOnFirstLoad: Bool - @DecodableDefault.False public var refreshOnShown: Bool - - @DecodableDefault.False public var alwaysUseFallbackResponse: Bool - public let fallbackResponse: JSONValueDictionary? + public var runWhileHidden: Bool + public var refreshOnFirstLoad: Bool + public var refreshOnShown: Bool + + private enum CodingKeys: String, CodingKey { + case refreshInterval + case refreshAction + case runWhileHidden + case refreshOnFirstLoad + case refreshOnShown + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + refreshInterval = try typeContainer.decode(TimeInterval.self, forKey: .refreshInterval) + refreshAction = try typeContainer.decodeModel(codingKey: .refreshAction) + runWhileHidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .runWhileHidden) ?? false + refreshOnFirstLoad = try typeContainer.decodeIfPresent(Bool.self, forKey: .refreshOnFirstLoad) ?? false + refreshOnShown = try typeContainer.decodeIfPresent(Bool.self, forKey: .refreshOnShown) ?? false + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(refreshInterval, forKey: .refreshInterval) + try container.encode(refreshAction, forKey: .refreshAction) + try container.encode(runWhileHidden, forKey: .runWhileHidden) + try container.encode(refreshOnFirstLoad, forKey: .refreshOnFirstLoad) + try container.encode(refreshOnShown, forKey: .refreshOnShown) + } } public class PollingBehavior: NSObject, PageVisibilityBehavior { @@ -58,8 +81,8 @@ public class PollingBehavior: NSObject, PageVisibilityBehavior { pollTimer?.cancel() } - func resumePollingTimer(withRemainingTime timeRemaining: TimeInterval, refreshAction: ActionOpenPageModel, interval: TimeInterval) { - let delegateObject = DelegateObject.create(withDelegateForAll: self) + func resumePollingTimer(withRemainingTime timeRemaining: TimeInterval, refreshAction: ActionModelProtocol, interval: TimeInterval) { + let delegateObject = delegateObject pollTimer?.cancel() pollTimer = DispatchSource.makeTimerSource() pollTimer?.schedule(deadline: .now() + timeRemaining, repeating: interval) @@ -74,16 +97,4 @@ public class PollingBehavior: NSObject, PageVisibilityBehavior { deinit { pollTimer?.cancel() } - -} - -extension PollingBehavior: MVMCoreLoadDelegateProtocol { - - public func loadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: (UIViewController & MVMCoreViewControllerProtocol)?, error: MVMCoreErrorObject?) { - - if error != nil { - // Apply the default response. - } - } - } From f853275005aca1ac87d8f024709f459d05626f79 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Mon, 19 Feb 2024 21:22:57 -0500 Subject: [PATCH 33/36] Add missing Label image attachment color tinting. --- MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 59611756..d69c501d 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -368,6 +368,10 @@ public typealias ActionBlock = () -> () imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize) } + if let tintColor = imageAtt.tintColor { + imageAttachment.image = imageAttachment.image?.withTintColor(tintColor.uiColor, renderingMode: .alwaysTemplate) + } + // Confirm that the intended image location is within range. if 0...labelText.count ~= imageAtt.location { let mutableString = NSMutableAttributedString() From 0f56803a5dadaea15c4778a1bfabf90087fbc990 Mon Sep 17 00:00:00 2001 From: vimal Date: Wed, 21 Feb 2024 19:37:44 +0530 Subject: [PATCH 34/36] review comment update, updated the function name --- MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 8f06ca7f..9c9f4896 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -204,8 +204,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open func newData(for molecule: MoleculeModelProtocol) { //Check header and footer if replace happens then return. - if updateMoleculeViewById(topView, with: molecule) || - updateMoleculeViewById(bottomView, with: molecule, isHeader: false) { + if updateHeaderFooterView(topView, with: molecule) || + updateHeaderFooterView(bottomView, with: molecule, isHeader: false) { return } @@ -228,12 +228,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } } - ///Helper functions to update view with molecule - private func updateMoleculeViewById(_ view: UIView?, with molecule: MoleculeModelProtocol, isHeader: Bool = true) -> Bool { + ///Helper functions to update header/footer view + private func updateHeaderFooterView(_ view: UIView?, with molecule: MoleculeModelProtocol, isHeader: Bool = true) -> Bool { if let updateView = view, let moleculeView = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [updateView]).first(where: { $0.model?.moleculeName == molecule.moleculeName && $0.model?.id == molecule.id }) { updateMoleculeView(moleculeView, from: molecule) - //Redraw the header and footer for content update + //Redraw the header/footer for content update isHeader ? showHeader(nil) : showFooter(nil) return true } From 7c9d257a52ba0d6e006133526cdf0bb70a18a188 Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Wed, 21 Feb 2024 18:04:39 -0500 Subject: [PATCH 35/36] Code review comment to pipe polling action through the delegate (controller) first. --- MVMCoreUI/Behaviors/PollingBehaviorModel.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift index 4bf04feb..7a92941f 100644 --- a/MVMCoreUI/Behaviors/PollingBehaviorModel.swift +++ b/MVMCoreUI/Behaviors/PollingBehaviorModel.swift @@ -88,7 +88,11 @@ public class PollingBehavior: NSObject, PageVisibilityBehavior { pollTimer?.schedule(deadline: .now() + timeRemaining, repeating: interval) pollTimer?.setEventHandler(qos:.utility) { Task { - try? await MVMCoreActionHandler.shared()?.handleAction(with: refreshAction, additionalData: nil, delegateObject: delegateObject) + if let delegateActionHandler = delegateObject?.actionDelegate as? ActionDelegateProtocol { + try? await delegateActionHandler.performAction(with: refreshAction, additionalData: nil, delegateObject: delegateObject) + } else { + try? await MVMCoreActionHandler.shared()?.handleAction(with: refreshAction, additionalData: nil, delegateObject: delegateObject) + } } } pollTimer?.resume() From 3d612417dcdf7a3328bcb9714fc6e4efb3c93f38 Mon Sep 17 00:00:00 2001 From: "Bruce, Matt R" Date: Thu, 22 Feb 2024 19:07:05 +0000 Subject: [PATCH 36/36] enum codable fix --- .../Atomic/Extensions/VDS-Enums+Codable.swift | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 6a9cc598..8f184d5a 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -25,8 +25,6 @@ extension VDS.Tabs.Overflow: Codable {} extension VDS.Tabs.Size: Codable {} extension VDS.TextLink.Size: Codable {} extension VDS.TextLinkCaret.IconPosition: Codable {} -extension VDS.TileContainer.BackgroundColor: Codable {} -extension VDS.TileContainer.Padding: Codable {} extension VDS.TileContainer.AspectRatio: Codable {} extension VDS.Tooltip.FillColor: Codable {} extension VDS.Tooltip.Size: Codable {} @@ -61,3 +59,75 @@ extension DecodableDefault { public typealias BlackColor = Wrapper public typealias Surface = Wrapper } + +extension VDS.TileContainer.BackgroundColor: Codable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .custom(let value): + try container.encode(value) + default: + try container.encode(String(reflecting: self)) + } + } + + // Init from decoder to handle the decoding based on the type + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let type = try container.decode(String.self) + switch type { + case "primary": + self = .primary + case "secondary": + self = .secondary + case "white": + self = .white + case "black": + self = .black + default: + self = .custom(type) + } + } +} + +extension VDS.TileContainer.Padding: Codable { + enum PaddingError: Error { + case valueNotFound(type: String) + } + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .custom(let value): + try container.encode(value) + default: + try container.encode(String(reflecting: self)) + } + } + + // Init from decoder to handle the decoding based on the type + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + do { + let type = try container.decode(String.self) + switch type { + case "padding2X": + self = .padding2X + case "padding4X": + self = .padding4X + case "padding6X": + self = .padding6X + case "padding8X": + self = .padding8X + case "padding12X": + self = .padding12X + default: + throw PaddingError.valueNotFound(type: type) + } + } catch PaddingError.valueNotFound(let type) { + throw PaddingError.valueNotFound(type: type) + } catch { + let type = try container.decode(CGFloat.self) + self = .custom(type) + } + } +}