diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index fb0a0e32..3d70972b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -123,7 +123,9 @@ 0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB423FF18D2004C5109 /* Arrow.swift */; }; 0AE98BB723FF18E9004C5109 /* ArrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB623FF18E9004C5109 /* ArrowModel.swift */; }; 279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279B1568242BBC2F00921D6C /* ActionModelAdapter.swift */; }; - 27F973532466074500CAB5C5 /* PageBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F973522466074500CAB5C5 /* PageBehavior.swift */; }; + 27F6B08826051831008529AA /* MoleculeTreeTraversalProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F6B08726051831008529AA /* MoleculeTreeTraversalProtocol.swift */; }; + 27F6B08C26052AFF008529AA /* ParentMoleculeModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */; }; + 27F973532466074500CAB5C5 /* PageBehaviorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */; }; 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */; }; 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */; }; 31BE15CC23D8924D00452370 /* CheckboxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15CA23D8924C00452370 /* CheckboxModel.swift */; }; @@ -347,13 +349,18 @@ D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */; }; D236E5B7242007C500C38625 /* MVMControllerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */; }; D23A8FD9260CE004007E14CE /* MFStyler+PaddingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FD5260CDF01007E14CE /* MFStyler+PaddingExtension.swift */; }; + D23A8FEB26122F69007E14CE /* VisibleBehaviorForVideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FEA26122F69007E14CE /* VisibleBehaviorForVideoModel.swift */; }; + 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 */; }; + D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */; }; + D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A900826125FFB007E14CE /* GetContactBehavior.swift */; }; D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */; }; D23EA7FE247EBBB700D60C34 /* NavigationLabelButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FD247EBBB700D60C34 /* NavigationLabelButtonModel.swift */; }; D23EA800247EBD6C00D60C34 /* LabelBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */; }; D23EA802247EBED400D60C34 /* ImageBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */; }; D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; - D24918F625D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */; }; - D24918FA25D5ADBB00CAB4B1 /* PageScrolledClosureBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */; }; D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */; }; D2509ED62472EE2F001BFB9D /* NavigationImageButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */; }; D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; }; @@ -680,7 +687,9 @@ 0AE98BB423FF18D2004C5109 /* Arrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arrow.swift; sourceTree = ""; }; 0AE98BB623FF18E9004C5109 /* ArrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowModel.swift; sourceTree = ""; }; 279B1568242BBC2F00921D6C /* ActionModelAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionModelAdapter.swift; sourceTree = ""; }; - 27F973522466074500CAB5C5 /* PageBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehavior.swift; sourceTree = ""; }; + 27F6B08726051831008529AA /* MoleculeTreeTraversalProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeTreeTraversalProtocol.swift; sourceTree = ""; }; + 27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentMoleculeModelProtocol.swift; sourceTree = ""; }; + 27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocol.swift; sourceTree = ""; }; 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenBrightnessModifierBehavior.swift; sourceTree = ""; }; 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxLabelModel.swift; sourceTree = ""; }; 31BE15CA23D8924C00452370 /* CheckboxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxModel.swift; sourceTree = ""; }; @@ -904,13 +913,18 @@ D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescriptionModel.swift; sourceTree = ""; }; D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMControllerModelProtocol.swift; sourceTree = ""; }; D23A8FD5260CDF01007E14CE /* MFStyler+PaddingExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFStyler+PaddingExtension.swift"; sourceTree = ""; }; + D23A8FEA26122F69007E14CE /* VisibleBehaviorForVideoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideoModel.swift; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItemProtocol.swift; sourceTree = ""; }; D23EA7FD247EBBB700D60C34 /* NavigationLabelButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationLabelButtonModel.swift; sourceTree = ""; }; D23EA7FF247EBD6C00D60C34 /* LabelBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelBarButtonItem.swift; sourceTree = ""; }; D23EA801247EBED400D60C34 /* ImageBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBarButtonItem.swift; sourceTree = ""; }; D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; - D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageVisibilityClosureBehavior.swift; sourceTree = ""; }; - D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageScrolledClosureBehavior.swift; sourceTree = ""; }; D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; D2509ED52472EE2F001BFB9D /* NavigationImageButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationImageButtonModel.swift; sourceTree = ""; }; D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = ""; }; @@ -1149,6 +1163,7 @@ D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */, D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */, D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */, + 27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */, ); path = ModelProtocols; sourceTree = ""; @@ -1283,10 +1298,13 @@ 27F973512466071600CAB5C5 /* Behaviors */ = { isa = PBXGroup; children = ( - 27F973522466074500CAB5C5 /* PageBehavior.swift */, + D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */, + D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */, + D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */, + D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */, + 27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */, 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, - D24918F525D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift */, - D24918F925D5ADBA00CAB4B1 /* PageScrolledClosureBehavior.swift */, + D23A900826125FFB007E14CE /* GetContactBehavior.swift */, ); path = Behaviors; sourceTree = ""; @@ -1705,6 +1723,18 @@ path = TwoColumn; sourceTree = ""; }; + D23A8FE926122F41007E14CE /* Video */ = { + isa = PBXGroup; + children = ( + D29C559525C099630082E7D6 /* VideoDataManager.swift */, + D29C559225C0992D0082E7D6 /* VideoModel.swift */, + D29C558F25C095210082E7D6 /* Video.swift */, + D23A8FEA26122F69007E14CE /* VisibleBehaviorForVideoModel.swift */, + D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */, + ); + path = Video; + sourceTree = ""; + }; D23EA7FC247EBB7500D60C34 /* Buttons */ = { isa = PBXGroup; children = ( @@ -2053,6 +2083,7 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + D23A8FE926122F41007E14CE /* Video */, 0A9D09162433796500D2E6C0 /* CarouselIndicator */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */, @@ -2085,9 +2116,6 @@ AA37CBD42519072F0027344C /* Stars.swift */, AA07EA902510A442009A2AE3 /* StarModel.swift */, AA07EA922510A451009A2AE3 /* Star.swift */, - D29C559525C099630082E7D6 /* VideoDataManager.swift */, - D29C559225C0992D0082E7D6 /* VideoModel.swift */, - D29C558F25C095210082E7D6 /* Video.swift */, ); path = Views; sourceTree = ""; @@ -2278,6 +2306,7 @@ isa = PBXGroup; children = ( 012A88C7238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift */, + 27F6B08726051831008529AA /* MoleculeTreeTraversalProtocol.swift */, 017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */, D20F3B43252E00E4004B3F56 /* PageProtocol.swift */, 012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */, @@ -2576,6 +2605,7 @@ AAB9C10A243496DD00151545 /* RadioSwatch.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, 011D9602240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift in Sources */, + D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */, D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */, AA617AB22453012400910B8F /* ListDeviceComplexLinkSmallModel.swift in Sources */, D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, @@ -2587,11 +2617,13 @@ D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */, 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, D2A92884241ACB25004E01C6 /* ProgrammaticScrollViewController.swift in Sources */, + D23A90002612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift in Sources */, 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, AA1EC59724373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift in Sources */, + D23A8FEB26122F69007E14CE /* VisibleBehaviorForVideoModel.swift in Sources */, BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */, D2CAC7D3251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift in Sources */, AA07EA932510A451009A2AE3 /* Star.swift in Sources */, @@ -2609,6 +2641,7 @@ D2ED27EC254B0CE700A1C293 /* UIAlertControllerStyle+Extension.swift in Sources */, C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */, 014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */, + D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */, 0A9D091F2433796500D2E6C0 /* NumericIndicatorView.swift in Sources */, D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */, 0AE98BAF23FEF956004C5109 /* ExternalLink.swift in Sources */, @@ -2630,6 +2663,7 @@ AAC23FAF24D92A1E009208DF /* ListThreeColumnSpeedTest.swift in Sources */, 0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, + D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, D2ED27ED254B0CE700A1C293 /* ActionPopupModel.swift in Sources */, 94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */, @@ -2752,7 +2786,6 @@ 94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */, 011D958724042492000E3791 /* FormFieldProtocol.swift in Sources */, 011D95AF2407266E000E3791 /* RadioButtonModel.swift in Sources */, - D24918F625D5AD8E00CAB4B1 /* PageVisibilityClosureBehavior.swift in Sources */, D20492A624329CE200A5EED6 /* LoadImageView.swift in Sources */, 017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */, D274CA332236A78900B01B62 /* FooterView.swift in Sources */, @@ -2823,6 +2856,7 @@ 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */, AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */, + 27F6B08826051831008529AA /* MoleculeTreeTraversalProtocol.swift in Sources */, D2D3957A252FDBB300047B11 /* ModalSectionListTemplate.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, D28BA741248025A300B75CB8 /* TabBarModel.swift in Sources */, @@ -2838,7 +2872,6 @@ 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */, D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */, 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, - D24918FA25D5ADBB00CAB4B1 /* PageScrolledClosureBehavior.swift in Sources */, 0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */, D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */, 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */, @@ -2905,7 +2938,7 @@ D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, 0AB000BC24BF64A50090C5E7 /* ModalStackPageTemplateModel.swift in Sources */, - 27F973532466074500CAB5C5 /* PageBehavior.swift in Sources */, + 27F973532466074500CAB5C5 /* PageBehaviorProtocol.swift in Sources */, 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */, D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, @@ -2914,6 +2947,7 @@ 012A88F123985E0100FE3DA1 /* Color.swift in Sources */, D22D8393241C27B100D3DF69 /* TemplateModel.swift in Sources */, 012A889C23889E8400FE3DA1 /* TemplateModelProtocol.swift in Sources */, + D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */, 52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */, D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */, 0AA4D2E125CAEC72008DB32D /* AccessibilityModelProtocol.swift in Sources */, @@ -2949,6 +2983,7 @@ 8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */, D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */, 012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */, + 27F6B08C26052AFF008529AA /* ParentMoleculeModelProtocol.swift in Sources */, 0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, 94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */, @@ -2975,6 +3010,7 @@ 011D959F240453A1000E3791 /* RuleAllValueChangedModel.swift in Sources */, AA0A257A24766CA200862F64 /* ListLeftVariableIconWithRightCaretBodyText.swift in Sources */, 011D95AD2406BB57000E3791 /* FormHolderProtocol.swift in Sources */, + D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */, 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */, D2169301251E51E7002A6324 /* SectionListTemplate.swift in Sources */, 0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index 6b120d0c..fbbc9e03 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -7,12 +7,12 @@ // -@objcMembers public class LabelModel: MoleculeModelProtocol { +@objcMembers open class LabelModel: MoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public static var identifier: String = "label" + open class var identifier: String { "label" } public var backgroundColor: Color? public var text: String public var accessibilityText: String? diff --git a/MVMCoreUI/Atomic/Atoms/Views/Video.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/Video.swift rename to MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/VideoDataManager.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/VideoDataManager.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/VideoDataManager.swift rename to MVMCoreUI/Atomic/Atoms/Views/Video/VideoDataManager.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/VideoModel.swift similarity index 64% rename from MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift rename to MVMCoreUI/Atomic/Atoms/Views/Video/VideoModel.swift index 1d079599..7c348760 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/VideoModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Video/VideoModel.swift @@ -8,7 +8,7 @@ import Foundation -open class VideoModel: MoleculeModelProtocol { +open class VideoModel: MoleculeModelProtocol, PageBehaviorProtocolRequirer { public static var identifier = "video" public var backgroundColor: Color? public var video: String @@ -39,8 +39,6 @@ open class VideoModel: MoleculeModelProtocol { /// Keeps a reference to the video data. public var videoDataManager: VideoDataManager - private weak var visibleBehavior: PageVisibilityClosureBehavior? - private weak var scrollBehavior: PageScrolledClosureBehavior? private var activeListener: Any? private var resignActiveListener: Any? @@ -81,61 +79,16 @@ open class VideoModel: MoleculeModelProtocol { try container.encode(alwaysReset, forKey: .alwaysReset) } + public func getRequiredBehaviors() -> [PageBehaviorModelProtocol] { + return [VisibleBehaviorForVideoModel(with: self)] + } + open func addVisibilityHalting(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { self.view = view halted = false - addVisibleBehavior(for: view, delegateObject: delegateObject) - addScrollBehavior(for: view, delegateObject: delegateObject) addActiveListener(for: view, delegateObject: delegateObject) } - /// Adds a behavior to pause the video on page hidden behavior and unpause if necessary on page shown. - open func addVisibleBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { - - let onShow = { [weak self] in - guard let self = self, - let view = self.view, - view.isVisibleInDelegate() else { return } - self.halted = false - } - let onHide: () -> Void = { [weak self] in - self?.halted = true - } - - guard visibleBehavior == nil else { - visibleBehavior?.pageShownHandler = onShow - visibleBehavior?.pageHiddenHandler = onHide - return - } - - guard var delegate = delegateObject?.behaviorTemplateDelegate else { return } - let pauseBehavior = PageVisibilityClosureBehavior(with: onShow, onPageHiddenHandler: onHide) - delegate.add(behavior: pauseBehavior) - self.visibleBehavior = pauseBehavior - } - - /// Adds a behavior to pause the video if scrolled off of the page and unpause if necessary if scrolled on. - open func addScrollBehavior(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { - - let onScroll = { [weak self] (scrollView: UIScrollView) in - // If visible to not visible, pause video. - // If not visible to visible, unpause if needed, add visible behavior - guard let self = self, - let view = self.view else { return } - self.halted = !view.isVisible(in: scrollView) - } - - guard scrollBehavior == nil else { - scrollBehavior?.pageScrolledHandler = onScroll - return - } - - guard var delegate = delegateObject?.behaviorTemplateDelegate else { return } - let scrollBehavior = PageScrolledClosureBehavior(with: onScroll) - delegate.add(behavior: scrollBehavior) - self.scrollBehavior = scrollBehavior - } - open func addActiveListener(for view: Video, delegateObject: MVMCoreUIDelegateObject?) { removeActiveListener() diff --git a/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideo.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideo.swift new file mode 100644 index 00000000..4f01e375 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideo.swift @@ -0,0 +1,36 @@ +// +// VisibleBehaviorForVideo.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/29/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class VisibleBehaviorForVideo: PageVisibilityBehavior, PageScrolledBehavior { + var model: PageBehaviorModelProtocol + + required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + self.model = model + } + + public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + guard let model = (model as? VisibleBehaviorForVideoModel)?.videoModel, + let view = model.view, + view.isVisibleInDelegate() else { return } + model.halted = false + } + + public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) { + (model as? VisibleBehaviorForVideoModel)?.videoModel?.halted = true + } + + public func pageScrolled(scrollView: UIScrollView, _ delegateObject: MVMCoreUIDelegateObject?) { + // If visible to not visible, pause video. + // If not visible to visible, unpause if needed, add visible behavior + guard let model = (model as? VisibleBehaviorForVideoModel)?.videoModel, + let view = model.view else { return } + model.halted = !view.isVisible(in: scrollView) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideoModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideoModel.swift new file mode 100644 index 00000000..45734dac --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideoModel.swift @@ -0,0 +1,20 @@ +// +// VisibleBehaviorForVideoModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/29/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class VisibleBehaviorForVideoModel: PageBehaviorModelProtocol { + + public static var identifier: String = "visibleBehaviorForVideoModel" + public var shouldAllowMultipleInstances: Bool = true + public weak var videoModel: VideoModel? + + init(with videoModel: VideoModel) { + self.videoModel = videoModel + } +} diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index f868169d..539ed2ba 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -243,6 +243,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: NotificationView.self, viewModelClass: NotificationModel.self) MoleculeObjectMapping.shared()?.register(viewClass: CollapsableNotification.self, viewModelClass: CollapsableNotificationModel.self) + // TODO: move all of these out of here. // MARK:- Helper models try? ModelRegistry.register(RuleRequiredModel.self) try? ModelRegistry.register(RuleAnyRequiredModel.self) @@ -261,7 +262,8 @@ import Foundation try? ModelRegistry.register(ActionTopNotificationModel.self) // MARK:- Behaviors - try? ModelRegistry.register(ScreenBrightnessModifierBehavior.self) + try? ModelRegistry.register(handler: ScreenBrightnessModifierBehavior.self, for: ScreenBrightnessModifierBehaviorModel.self) + try? ModelRegistry.register(handler: PageGetContactBehavior.self, for: PageGetContactBehaviorModel.self) } /// Convenience function to get required modules for a give model diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift index 8581deb1..46de07fa 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift @@ -7,7 +7,7 @@ // -public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModelProtocol { +public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModelProtocol, ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -16,6 +16,10 @@ public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModel public var image: ImageViewModel public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel + public var children: [MoleculeModelProtocol] { + return [image, eyebrowHeadlineBodyLink] + } + //-------------------------------------------------- // MARK: - Method //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift index d4da0ba4..c767dc36 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -9,7 +9,7 @@ import UIKit -public class TwoButtonViewModel: MoleculeModelProtocol { +public class TwoButtonViewModel: ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,6 +19,10 @@ public class TwoButtonViewModel: MoleculeModelProtocol { public var primaryButton: ButtonModel? public var secondaryButton: ButtonModel? + public var children: [MoleculeModelProtocol] { + return [primaryButton, secondaryButton].compactMap { $0 } + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift index c83b8933..d0f13c1f 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift @@ -12,7 +12,7 @@ public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { public static var identifier: String = "tabsListItem" var tabs: TabsModel var molecules: [[ListItemModelProtocol & MoleculeModelProtocol]] - + private enum CodingKeys: String, CodingKey { case moleculeName case tabs diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/BGVideoImageMoleculeModel.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/BGVideoImageMoleculeModel.swift index f2ee20af..6b9a89c3 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/BGVideoImageMoleculeModel.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/BGVideoImageMoleculeModel.swift @@ -15,6 +15,10 @@ open class BGVideoImageMoleculeModel: BGImageMoleculeModel { public var video: VideoModel + public override var children: [MoleculeModelProtocol] { + return [video, molecule] + } + private enum CodingKeys: String, CodingKey { case video } diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift index 77b40716..5ddec277 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift @@ -15,6 +15,10 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco public var backgroundColor: Color? public var molecule: MoleculeModelProtocol + public var children: [MoleculeModelProtocol] { + return [molecule] + } + private enum CodingKeys: String, CodingKey { case moleculeName case molecule @@ -40,7 +44,7 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) try super.init(from: decoder) } - + open override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift index 882cf181..1e34932f 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift @@ -8,6 +8,14 @@ import Foundation -public protocol MoleculeContainerModelProtocol: ContainerModelProtocol { +public protocol MoleculeContainerModelProtocol: ContainerModelProtocol, ParentMoleculeModelProtocol { var molecule: MoleculeModelProtocol { get set } } + +public extension MoleculeContainerModelProtocol { + + var children: [MoleculeModelProtocol] { + return [molecule] + } + +} diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift index c9410775..ec646f86 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift @@ -7,7 +7,7 @@ // -public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol { +public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -20,6 +20,10 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol { public var body: LabelModel? public var link: LinkModel? + public var children: [MoleculeModelProtocol] { + [eyebrow, headline, body, link].compactMap { (molecule: MoleculeModelProtocol?) in molecule } + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -68,7 +72,7 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) - headline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .headline) + headline = try typeContainer.decodeMoleculeIfPresent(codingKey: .headline) body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body) link = try typeContainer.decodeIfPresent(LinkModel.self, forKey: .link) setDefaults() @@ -83,7 +87,7 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol { try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(eyebrow, forKey: .eyebrow) - try container.encodeIfPresent(headline, forKey: .headline) + try container.encodeModelIfPresent(headline, forKey: .headline) try container.encodeIfPresent(body, forKey: .body) try container.encodeIfPresent(link, forKey: .link) } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index ebc5614e..a7ff7280 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -9,7 +9,7 @@ import Foundation -@objcMembers open class HeadlineBodyModel: MoleculeModelProtocol { +@objcMembers open class HeadlineBodyModel: ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -21,6 +21,10 @@ import Foundation public var style: Style? public var backgroundColor: Color? + public var children: [MoleculeModelProtocol] { + return [headline, body].compactMap { $0 } + } + //-------------------------------------------------- // MARK: - Enum //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index 5bf0728c..8721db72 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -9,7 +9,7 @@ import UIKit -@objcMembers public class CarouselModel: MoleculeModelProtocol, FormFieldProtocol { +@objcMembers public class CarouselModel: ParentMoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- // MARK: - Properties @@ -144,3 +144,11 @@ import UIKit try container.encode(selectedIndex, forKey: .selectedIndex) } } + +extension CarouselModel { + + public var children: [MoleculeModelProtocol] { + return molecules + } + +} diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index dae6fad8..8201ddb9 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -7,7 +7,8 @@ // -@objcMembers public class StackModel: ContainerModel, StackModelProtocol, MoleculeModelProtocol { +@objcMembers public class StackModel: ContainerModel, StackModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -22,6 +23,10 @@ public var spacing: CGFloat = Padding.Four public var useStackSpacingBeforeFirstItem = false + public var children: [MoleculeModelProtocol] { + return molecules + } + //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- @@ -79,4 +84,5 @@ try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } + } diff --git a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift index 54f6c9d0..068418f0 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift @@ -8,9 +8,15 @@ import Foundation -public protocol StackModelProtocol { +public protocol StackModelProtocol: ParentMoleculeModelProtocol { var molecules: [StackItemModelProtocol & MoleculeModelProtocol] { get set } var axis: NSLayoutConstraint.Axis { get set } var spacing: CGFloat { get set } var useStackSpacingBeforeFirstItem: Bool { get set } } + +extension StackModelProtocol { + + public var children: [MoleculeModelProtocol] { return molecules } + +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift index a998b79d..2823e5a5 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift @@ -7,7 +7,7 @@ public enum MolecularError: Swift.Error { } -public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol { +public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol, MoleculeTreeTraversalProtocol { var moleculeName: String { get } var backgroundColor: Color? { get set } } @@ -20,3 +20,69 @@ public extension MoleculeModelProtocol { static var categoryCodingKey: String { "moleculeName" } } + +extension KeyedDecodingContainer where Key: CodingKey { + /// Decodes to a registered molecule based on the identifier + public func decodeMoleculeIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> T? { + + guard let model: MoleculeModelProtocol = try decodeModelIfPresent(codingKey: codingKey) else { return nil } + guard let modelT = model as? T else { + let message = "ModelRegistry Error wrong type: \(codingKey.stringValue)" + MVMCoreLoggingHandler.logDebugMessage(withDelegate: message) + throw ModelRegistry.Error.decoderOther(message: message) + } + return modelT + } +} + +public extension MoleculeModelProtocol { + + // Base case. No additional children to traverse. + func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int)->Result) -> Result { + return nextPartialResult(initialResult, self, depth) + } + + // Base case. No additional children to traverse. + func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol)->Void) { + onVisit(depth, self) + } + +} + +public extension Array where Element == MoleculeModelProtocol { + + func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int)->Result) -> Result { + return reduce(initialResult) { (result, molecule) -> Result in + return molecule.reduceDepthFirstTraverse(options: options, depth: depth, initialResult: result, nextPartialResult: nextPartialResult) + } + } + + func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol)->Void) { + forEach { (molecule) in + molecule.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) + } + } +} + +// Would prefer these to be defined in MoleculeTreeTraversalProtocol that MoleculeModelProtocol inherits. +public extension Array where Element == MoleculeModelProtocol { + + func countMolecules() -> Int { + return reduce(0) { (accumulator, molecule) in + return accumulator + molecule.countMolecules() + } + } + + func printMolecules() { + forEach { (molecule) in + molecule.printMolecules() + } + } + + func allMoleculesOfType() -> [T] { + return reduce([]) { (accumulator, molecule) in + return accumulator + molecule.allMoleculesOfType() + } + } + +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift new file mode 100644 index 00000000..2ec05db4 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -0,0 +1,55 @@ +// +// ParentModelProtocol.swift +// MVMCoreUI +// +// Created by Kyle on 3/19/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ParentMoleculeModelProtocol: MoleculeModelProtocol { + + var children: [MoleculeModelProtocol] { get } + +} + +public extension ParentMoleculeModelProtocol { + + func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result { + var result = initialResult + if (options == .parentFirst) { + result = nextPartialResult(result, self, depth) + } + result = children.reduce(result) { (result, molecule) -> Result in + if let additionalParent = molecule as? ParentMoleculeModelProtocol { + // Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol. + return additionalParent.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult) + } + return molecule.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult) + } + if (options == .childFirst) { + result = nextPartialResult(result, self, depth) + } + // if options == .leafOnly don't call on self. + return result + } + + func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol)->Void) { + if (options == .parentFirst) { + onVisit(depth, self) + } + children.forEach { (molecule) in + if let additionalParent = molecule 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) + } else { + molecule.depthFirstTraverse(options: options, depth: depth + 1, onVisit: onVisit) + } + } + if (options == .childFirst) { + onVisit(depth, self) + } + // if options == .leafOnly don't call on self. + } +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift index 3ed37d6b..1f9db75b 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift @@ -9,8 +9,9 @@ import Foundation -public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol { +public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol { var template: String { get } + var rootMolecules: [MoleculeModelProtocol] { get } } public extension TemplateModelProtocol { @@ -26,4 +27,12 @@ public extension TemplateModelProtocol { static var categoryName: String { return "\(TemplateModelProtocol.self)" } + + func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result { + return rootMolecules.reduceDepthFirstTraverse(options: options, depth: depth, initialResult: initialResult, nextPartialResult: nextPartialResult) + } + + func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol) -> Void) { + return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) + } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift new file mode 100644 index 00000000..4e7efabb --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift @@ -0,0 +1,54 @@ + +// +// TreeTraversalProtocol.swift +// MVMCoreUI +// +// Created by Kyle on 3/19/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +public enum TreeTraversalOptions { + case parentFirst + case childFirst + case leafNodesOnly +} + +public protocol MoleculeTreeTraversalProtocol { + + // Future options -- Parent first depth first, leaves only. + + func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int)->Result) -> Result + + func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol)->Void) + + //func breadthFirstTraverse() +} + +// +// Helper Extensions +// + +extension MoleculeTreeTraversalProtocol { + + func countMolecules(options: TreeTraversalOptions = .parentFirst) -> Int { + return reduceDepthFirstTraverse(options: options, depth: 0, initialResult: 0) { (accumulator, molecule, depth) in + return accumulator + 1 + } + } + + func printMolecules(options: TreeTraversalOptions = .parentFirst) { + depthFirstTraverse(options: options, depth: 1) { (depth, molecule) in + print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule)]") + } + } + + func allMoleculesOfType(options: TreeTraversalOptions = .parentFirst) -> [T] { + return reduceDepthFirstTraverse(options: options, depth: 0, initialResult: []) { (accumulator, molecule, depth) in + if let typedMolecule = molecule as? T { + return accumulator + [typedMolecule] + } + return accumulator + } + } + +} diff --git a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift index 04bc080b..9a7e9e33 100644 --- a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift @@ -22,12 +22,29 @@ public extension TemplateProtocol where Self: ViewController { let data = try JSONSerialization.data(withJSONObject: pageJSON) let decoder = JSONDecoder() try decoder.add(delegateObject: delegateObjectIVar) - self.templateModel = try decodeTemplate(using: decoder, from: data) - self.model = templateModel as? MVMControllerModelProtocol + templateModel = try decodeTemplate(using: decoder, from: data) + model = templateModel as? MVMControllerModelProtocol + guard let model = model else { return } + traverseAndAddRequiredBehaviors() + var behaviorHandler = self + behaviorHandler.createBehaviors(for: model, delegateObject: delegateObjectIVar) } func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel { return try decoder.decode(TemplateModel.self, from: data) } + /// Traverses all models and adds any required behavior models. + func traverseAndAddRequiredBehaviors() { + guard var model = model else { return } + let behaviorModels: [PageBehaviorModelProtocol] = model.reduceDepthFirstTraverse(options: .childFirst, depth: 0, initialResult: []) { (accumulator, molecule, depth) in + if let behaviorRequirer = molecule as? PageBehaviorProtocolRequirer { + return accumulator + behaviorRequirer.getRequiredBehaviors() + } + return accumulator + } + for behavior in behaviorModels { + model.add(behavior: behavior) + } + } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift index f9d8c0a7..b0f3ae92 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift @@ -18,6 +18,13 @@ import Foundation } public var molecules: [CollectionItemModelProtocol & MoleculeModelProtocol]? public var columns: Int? + + public override var rootMolecules: [MoleculeModelProtocol] { + if let molecules = molecules { + return super.rootMolecules + molecules + } + return super.rootMolecules + } //-------------------------------------------------- // MARK: - Initializer diff --git a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift index a7c92a08..44868e10 100644 --- a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift @@ -20,6 +20,13 @@ import Foundation public var line: LineModel? public var scrollToRowIndex: Int? + public override var rootMolecules: [MoleculeModelProtocol] { + if let molecules = molecules { + return super.rootMolecules + molecules + } + return super.rootMolecules + } + /// 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 f21a0421..6491bd8d 100644 --- a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift @@ -15,6 +15,10 @@ import Foundation } public var moleculeStack: StackModel + public override var rootMolecules: [MoleculeModelProtocol] { + return [header, moleculeStack, footer].compactMap { $0 } + } + public init(pageType: String, moleculeStack: StackModel) { self.moleculeStack = moleculeStack super.init(pageType: pageType) diff --git a/MVMCoreUI/Atomic/Templates/TemplateModel.swift b/MVMCoreUI/Atomic/Templates/TemplateModel.swift index bb8bb104..94d6ee80 100644 --- a/MVMCoreUI/Atomic/Templates/TemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/TemplateModel.swift @@ -29,7 +29,8 @@ import Foundation public var screenHeading: String? public var navigationBar: (NavigationItemModelProtocol & MoleculeModelProtocol)? public var formRules: [FormGroupRule]? - public var behaviors: [PageBehaviorProtocol]? + public var behaviors: [PageBehaviorModelProtocol]? + public var rootMolecules: [MoleculeModelProtocol] { [] } public var tabBarHidden: Bool = false public var tabBarIndex: Int? diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift index 1120b0e7..25e7d1e9 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift @@ -9,11 +9,16 @@ import Foundation @objcMembers open class ThreeLayerModelBase: TemplateModel, ThreeLayerTemplateModelProtocol { + public var anchorHeader: Bool = false public var header: MoleculeModelProtocol? public var anchorFooter: Bool = false public var footer: MoleculeModelProtocol? + public override var rootMolecules: [MoleculeModelProtocol] { + return [header, footer].compactMap { $0 } + } + public override init(pageType: String) { super.init(pageType: pageType) } diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift index c4e93f7a..ced0b9ab 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift @@ -14,6 +14,13 @@ import Foundation } public var middle: MoleculeModelProtocol? + public override var rootMolecules: [MoleculeModelProtocol] { + if let middle = middle { + return super.rootMolecules + [middle] + } + return super.rootMolecules + } + public init(pageType: String, header: MoleculeModelProtocol?, middle: MoleculeModelProtocol?, footer: MoleculeModelProtocol?) { super.init(pageType: pageType) self.header = header diff --git a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift index 64242fc3..78f6f604 100644 --- a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift +++ b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift @@ -9,6 +9,6 @@ import Foundation -public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorsTemplateProtocol { +public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorHandlerModelProtocol { } diff --git a/MVMCoreUI/BaseControllers/ScrollingViewController.swift b/MVMCoreUI/BaseControllers/ScrollingViewController.swift index 19c9d001..4fd23be2 100644 --- a/MVMCoreUI/BaseControllers/ScrollingViewController.swift +++ b/MVMCoreUI/BaseControllers/ScrollingViewController.swift @@ -74,8 +74,8 @@ open class ScrollingViewController: ViewController { } open func scrollViewDidScroll(_ scrollView: UIScrollView) { - executeBehaviors { (behavior: PageScrolledBehavior) in - behavior.pageScrolled(scrollView: scrollView) + executeBehaviors { [weak self] (behavior: PageScrolledBehavior) in + behavior.pageScrolled(scrollView: scrollView, self?.delegateObjectIVar) } } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index a2d82952..b06260a8 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -8,7 +8,7 @@ import UIKit -@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate, MVMCoreUIDetailViewProtocol, PageProtocol { +@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate, MVMCoreUIDetailViewProtocol, PageProtocol, PageBehaviorHandlerProtocol { //-------------------------------------------------- // MARK: - Properties @@ -34,6 +34,8 @@ import UIKit public var formValidator: FormValidator? + public var behaviors: [PageBehaviorProtocol]? + public var needsUpdateUI = false private var observingForResponses = false private var initialLoadFinished = false @@ -380,16 +382,16 @@ import UIKit pageShown() } - executeBehaviors { (behavior: PageVisibilityBehavior) in - behavior.onPageShown() + executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in + behavior.onPageShown(self?.delegateObjectIVar) } } open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - executeBehaviors { (behavior: PageVisibilityBehavior) in - behavior.onPageHidden() + executeBehaviors { [weak self] (behavior: PageVisibilityBehavior) in + behavior.onPageHidden(self?.delegateObjectIVar) } } @@ -620,6 +622,6 @@ import UIKit //-------------------------------------------------- func executeBehaviors(_ behaviorBlock:(_ behavior:T)->Void) { - model?.behaviors?.compactMap { $0 as? T }.forEach { behaviorBlock($0) } + behaviors?.compactMap { $0 as? T }.forEach { behaviorBlock($0) } } } diff --git a/MVMCoreUI/Behaviors/GetContactBehavior.swift b/MVMCoreUI/Behaviors/GetContactBehavior.swift new file mode 100644 index 00000000..179fb8b2 --- /dev/null +++ b/MVMCoreUI/Behaviors/GetContactBehavior.swift @@ -0,0 +1,56 @@ +// +// GetContactBehavior.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/22/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation +import Contacts + +public protocol PageGetContactBehaviorConsumerProtocol { + func getMatchParameters() -> (NSPredicate, [CNKeyDescriptor])? + func consume(contacts: [CNContact]) +} + +public class PageGetContactBehaviorModel: PageBehaviorModelProtocol { + public class var identifier: String { "pageGetContactBehavior" } + public var shouldAllowMultipleInstances: Bool { false } + + public init() {} +} + +public class PageGetContactBehavior: PageVisibilityBehavior { + var delegate: MVMCoreUIDelegateObject? + + public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + self.delegate = delegateObject + } + + public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + // Ask for permission + CNContactStore().requestAccess(for: .contacts) { [weak self] (access, error) in + guard access, + error == nil, + let model = (self?.delegate?.moleculeDelegate as? PageProtocol)?.pageModel as? TemplateModelProtocol else { return } + // Iterate models and provide contact + let page = self?.delegate?.moleculeDelegate as? PageProtocol + let store = CNContactStore() + let consumers: [PageGetContactBehaviorConsumerProtocol] = model.allMoleculesOfType() + for consumer in consumers { + guard let parameters = consumer.getMatchParameters(), + let contacts = try? store.unifiedContacts(matching: parameters.0, keysToFetch: parameters.1) else { return } + consumer.consume(contacts: contacts) + } + + // Tell template to update + MVMCoreDispatchUtility.performBlock(onMainThread: { + // TODO: move to protocol function instead + (page as? ViewController)?.handleNewData() + }) + } + } + + public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) {} +} diff --git a/MVMCoreUI/Behaviors/PageBehavior.swift b/MVMCoreUI/Behaviors/PageBehavior.swift deleted file mode 100644 index aaf915a1..00000000 --- a/MVMCoreUI/Behaviors/PageBehavior.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// PageBehaviors.swift -// MVMCoreUI -// -// Created by Kyle on 5/8/20. -// Copyright © 2020 Verizon Wireless. All rights reserved. -// - -import Foundation - -public protocol PageBehaviorProtocol: ModelProtocol { - - /// The type of rule - var behaviorName: String { get } -} - -public extension PageBehaviorProtocol { - - var behaviorName: String { - get { Self.identifier } - } - - static var categoryCodingKey: String { - "behaviorName" - } - - static var categoryName: String { - "\(PageBehaviorProtocol.self)" - } -} - -public protocol PageVisibilityBehavior: PageBehaviorProtocol { - - func onPageShown() - func onPageHidden() -} - -public protocol PageScrolledBehavior: PageBehaviorProtocol { - - func pageScrolled(scrollView: UIScrollView) -} - -public protocol PageBehaviorsTemplateProtocol { - - var behaviors: [PageBehaviorProtocol]? { get set } - -} - -public extension PageBehaviorsTemplateProtocol { - mutating func add(behavior: PageBehaviorProtocol) { - var newBehaviors = behaviors ?? [] - newBehaviors.append(behavior) - self.behaviors = newBehaviors - } -} - -public extension MVMCoreUIDelegateObject { - weak var behaviorTemplateDelegate: (PageBehaviorsTemplateProtocol & NSObjectProtocol)? { - get { - return (moleculeDelegate as? PageProtocol)?.pageModel as? (PageBehaviorsTemplateProtocol & NSObjectProtocol) - } - } -} diff --git a/MVMCoreUI/Behaviors/PageBehaviorHandlerModelProtocol.swift b/MVMCoreUI/Behaviors/PageBehaviorHandlerModelProtocol.swift new file mode 100644 index 00000000..17f1b85e --- /dev/null +++ b/MVMCoreUI/Behaviors/PageBehaviorHandlerModelProtocol.swift @@ -0,0 +1,25 @@ +// +// PageBehaviorHandlerModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/29/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +public protocol PageBehaviorHandlerModelProtocol { + var behaviors: [PageBehaviorModelProtocol]? { get set } +} + +public extension PageBehaviorHandlerModelProtocol { + + /// Adds the behavior model to the behaviors if possible. + mutating func add(behavior: PageBehaviorModelProtocol) { + var newBehaviors = behaviors ?? [] + guard !behavior.shouldAllowMultipleInstances, + !newBehaviors.contains(where: { $0.behaviorName == behavior.behaviorName + }) else { return } + newBehaviors.append(behavior) + self.behaviors = newBehaviors + } +} + diff --git a/MVMCoreUI/Behaviors/PageBehaviorHandlerProtocol.swift b/MVMCoreUI/Behaviors/PageBehaviorHandlerProtocol.swift new file mode 100644 index 00000000..b90ac1aa --- /dev/null +++ b/MVMCoreUI/Behaviors/PageBehaviorHandlerProtocol.swift @@ -0,0 +1,30 @@ +// +// PageBehaviorHandlerProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/29/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol PageBehaviorHandlerProtocol { + var behaviors: [PageBehaviorProtocol]? { get set } +} + +public extension PageBehaviorHandlerProtocol { + /// Creates the behaviors and sets the variable. + mutating func createBehaviors(for model: PageBehaviorHandlerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + guard let behaviorModels = model.behaviors else { + behaviors = nil + return + } + var behaviors: [PageBehaviorProtocol] = [] + for behaviorModel in behaviorModels { + guard let handlerType = ModelRegistry.getHandler(behaviorModel) as? PageBehaviorProtocol.Type else { continue } + let behavior = handlerType.init(model: behaviorModel, delegateObject: delegateObject) + behaviors.append(behavior) + } + self.behaviors = behaviors.count > 0 ? behaviors : nil + } +} diff --git a/MVMCoreUI/Behaviors/PageBehaviorModelProtocol.swift b/MVMCoreUI/Behaviors/PageBehaviorModelProtocol.swift new file mode 100644 index 00000000..d8502a7e --- /dev/null +++ b/MVMCoreUI/Behaviors/PageBehaviorModelProtocol.swift @@ -0,0 +1,35 @@ +// +// PageBehaviorModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/29/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +public protocol PageBehaviorModelProtocol: ModelProtocol { + + /// The type of rule + var behaviorName: String { get } + + /// If the behavior should allow multiple instances + var shouldAllowMultipleInstances: Bool { get } +} + +public extension PageBehaviorModelProtocol { + + var behaviorName: String { + get { type(of:self).identifier } + } + + static var shouldAllowMultipleInstances: Bool { + get { true } + } + + static var categoryCodingKey: String { + "behaviorName" + } + + static var categoryName: String { + "\(PageBehaviorModelProtocol.self)" + } +} diff --git a/MVMCoreUI/Behaviors/PageBehaviorProtocol.swift b/MVMCoreUI/Behaviors/PageBehaviorProtocol.swift new file mode 100644 index 00000000..6085188c --- /dev/null +++ b/MVMCoreUI/Behaviors/PageBehaviorProtocol.swift @@ -0,0 +1,34 @@ +// +// PageBehaviorProtocol.swift +// MVMCoreUI +// +// Created by Kyle on 5/8/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol PageBehaviorProtocol: ModelHandlerProtocol { + + /// Initializes the behavior with the model + init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) +} + +public protocol PageVisibilityBehavior: PageBehaviorProtocol { + + func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) + func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) +} + +public protocol PageScrolledBehavior: PageBehaviorProtocol { + + func pageScrolled(scrollView: UIScrollView,_ delegateObject: MVMCoreUIDelegateObject?) +} + +public extension MVMCoreUIDelegateObject { + weak var behaviorTemplateDelegate: (PageBehaviorHandlerProtocol & NSObjectProtocol)? { + get { + return (moleculeDelegate as? PageProtocol)?.pageModel as? (PageBehaviorHandlerProtocol & NSObjectProtocol) + } + } +} diff --git a/MVMCoreUI/Behaviors/PageBehaviorProtocolRequirer.swift b/MVMCoreUI/Behaviors/PageBehaviorProtocolRequirer.swift new file mode 100644 index 00000000..9ca72e1d --- /dev/null +++ b/MVMCoreUI/Behaviors/PageBehaviorProtocolRequirer.swift @@ -0,0 +1,11 @@ +// +// PageBehaviorProtocolRequirer.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/29/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +public protocol PageBehaviorProtocolRequirer { + func getRequiredBehaviors() -> [PageBehaviorModelProtocol] +} diff --git a/MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift b/MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift deleted file mode 100644 index c43d4ecc..00000000 --- a/MVMCoreUI/Behaviors/PageScrolledClosureBehavior.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// PageScrolledClosureBehavior.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 2/11/21. -// Copyright © 2021 Verizon Wireless. All rights reserved. -// - -import Foundation - -public class PageScrolledClosureBehavior: PageScrolledBehavior { - - public static var identifier = "pageScrolledClosureBehavior" - - public var pageScrolledHandler: (_ scrollView: UIScrollView) -> Void - - public init(with onPageScrolledHandler: @escaping (_ scrollView: UIScrollView) -> Void) { - self.pageScrolledHandler = onPageScrolledHandler - } - - // This class is not meant to be decoded and encoded really. - public required init(from decoder: Decoder) throws { - throw ModelRegistry.Error.decoderOther(message: "PageScrolledClosureBehavior does not decode.") - } - - public func encode(to encoder: Encoder) throws { - throw ModelRegistry.Error.decoderOther(message: "PageScrolledClosureBehavior does not encode.") - } - - public func pageScrolled(scrollView: UIScrollView) { - pageScrolledHandler(scrollView) - } -} diff --git a/MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift b/MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift deleted file mode 100644 index f5ecd82c..00000000 --- a/MVMCoreUI/Behaviors/PageVisibilityClosureBehavior.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// PageVisibilityClosureBehavior.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 2/11/21. -// Copyright © 2021 Verizon Wireless. All rights reserved. -// - -import Foundation - -public class PageVisibilityClosureBehavior: PageVisibilityBehavior { - - public static var identifier = "pageVisibilityClosureBehavior" - - public var pageShownHandler: () -> Void - public var pageHiddenHandler: () -> Void - - public init(with onPageShownHandler: @escaping () -> Void, onPageHiddenHandler: @escaping () -> Void) { - self.pageShownHandler = onPageShownHandler - self.pageHiddenHandler = onPageHiddenHandler - } - - // This class is not meant to be decoded and encoded really. - public required init(from decoder: Decoder) throws { - throw ModelRegistry.Error.decoderOther(message: "PageVisibilityClosureBehavior does not decode.") - } - - public func encode(to encoder: Encoder) throws { - throw ModelRegistry.Error.decoderOther(message: "PageVisibilityClosureBehavior does not encode.") - } - - //MARK:- PageVisibilityBehavior - public func onPageShown() { - pageShownHandler() - } - - public func onPageHidden() { - pageHiddenHandler() - } -} diff --git a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift index b6ea9f6d..59d80211 100644 --- a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift +++ b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift @@ -6,52 +6,14 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -public class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { - +public class ScreenBrightnessModifierBehaviorModel: PageBehaviorModelProtocol { + public var shouldAllowMultipleInstances: Bool = false public static var identifier = "screenBrightnessModifier" - @Clamping(range: 0...1) var screenBrightness: CGFloat - var originalScreenBrightness: CGFloat? - //MARK:- PageVisibilityBehavior - - public func onPageShown() { - changeScreenBrightness() - } - - public func onPageHidden() { - restoreScreenBrightness() - } - - //MARK:- Behavior - - func changeScreenBrightness() { - guard originalScreenBrightness == nil else { return } - originalScreenBrightness = UIScreen.main.brightness - UIScreen.main.brightness = screenBrightness - NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil) - } - - func restoreScreenBrightness() { - guard let originalScreenBrightness = originalScreenBrightness else { return } - UIScreen.main.brightness = originalScreenBrightness - self.originalScreenBrightness = nil - NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) - } - - @objc func willResignActive() { - restoreScreenBrightness() - NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) - } - - @objc func didBecomeActive() { - NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) - changeScreenBrightness() - } - //MARK:- Codable - + private enum CodingKeys: String, CodingKey { case screenBrightness } @@ -66,3 +28,49 @@ public class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { try container.encode(screenBrightness, forKey: .screenBrightness) } } + +public class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { + var model: PageBehaviorModelProtocol + + required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + self.model = model + } + + //MARK:- PageVisibilityBehavior + + public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) { + changeScreenBrightness() + } + + public func onPageHidden(_ delegateObject: MVMCoreUIDelegateObject?) { + restoreScreenBrightness() + } + + //MARK:- Behavior + + func changeScreenBrightness() { + guard let model = model as? ScreenBrightnessModifierBehaviorModel, + model.originalScreenBrightness == nil else { return } + model.originalScreenBrightness = UIScreen.main.brightness + UIScreen.main.brightness = model.screenBrightness + NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil) + } + + func restoreScreenBrightness() { + guard let model = model as? ScreenBrightnessModifierBehaviorModel, + let originalScreenBrightness = model.originalScreenBrightness else { return } + UIScreen.main.brightness = originalScreenBrightness + model.originalScreenBrightness = nil + NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) + } + + @objc func willResignActive() { + restoreScreenBrightness() + NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) + } + + @objc func didBecomeActive() { + NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) + changeScreenBrightness() + } +}