Merge branch 'develop' into bugfix/PRODDEF-24073

This commit is contained in:
Keerthy 2024-03-06 12:05:55 +05:30
commit 5f15d630e1
87 changed files with 1346 additions and 1180 deletions

View File

@ -168,6 +168,11 @@
526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; }; 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; };
52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; }; 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; };
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.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 */; };
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 */; }; 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; };
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; }; 7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; };
71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; }; 71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; };
@ -387,7 +392,7 @@
D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */; }; D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */; };
D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */; }; D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */; };
D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */; }; D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */; };
D23A90002612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */; }; D23A90002612347A007E14CE /* PageBehaviorContainerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorContainerModelProtocol.swift */; };
D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */; }; D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */; };
D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A900826125FFB007E14CE /* GetContactBehavior.swift */; }; D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A900826125FFB007E14CE /* GetContactBehavior.swift */; };
D23A90682614B0B4007E14CE /* CoreUIModelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */; }; D23A90682614B0B4007E14CE /* CoreUIModelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */; };
@ -766,6 +771,11 @@
526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = "<group>"; }; 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = "<group>"; };
52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = "<group>"; }; 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = "<group>"; };
52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = "<group>"; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = "<group>"; };
582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; };
5822720A2B1FC55F00F75BAE /* RotorHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = "<group>"; };
5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PollingBehaviorModel.swift; sourceTree = "<group>"; };
5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = "<group>"; };
58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = "<group>"; };
608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = "<group>"; }; 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = "<group>"; };
7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; }; 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; };
71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = "<group>"; }; 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = "<group>"; };
@ -985,7 +995,7 @@
D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideo.swift; sourceTree = "<group>"; }; D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideo.swift; sourceTree = "<group>"; };
D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocolRequirer.swift; sourceTree = "<group>"; }; D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocolRequirer.swift; sourceTree = "<group>"; };
D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorModelProtocol.swift; sourceTree = "<group>"; }; D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorModelProtocol.swift; sourceTree = "<group>"; };
D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerModelProtocol.swift; sourceTree = "<group>"; }; D23A8FFF2612347A007E14CE /* PageBehaviorContainerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorContainerModelProtocol.swift; sourceTree = "<group>"; };
D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerProtocol.swift; sourceTree = "<group>"; }; D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerProtocol.swift; sourceTree = "<group>"; };
D23A900826125FFB007E14CE /* GetContactBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContactBehavior.swift; sourceTree = "<group>"; }; D23A900826125FFB007E14CE /* GetContactBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContactBehavior.swift; sourceTree = "<group>"; };
D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUIModelMapping.swift; sourceTree = "<group>"; }; D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUIModelMapping.swift; sourceTree = "<group>"; };
@ -1224,10 +1234,10 @@
D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */, D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */,
014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */, 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */,
D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */, D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */,
01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */,
012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */, 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */,
012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */, 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */,
EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */, EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */,
01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */,
012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */, 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */,
D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */,
011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */, 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */,
@ -1343,7 +1353,7 @@
children = ( children = (
D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */, D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */,
D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */, D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */,
D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */, D23A8FFF2612347A007E14CE /* PageBehaviorContainerModelProtocol.swift */,
D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */, D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */,
27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */, 27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */,
); );
@ -1409,6 +1419,8 @@
27F973512466071600CAB5C5 /* Behaviors */ = { 27F973512466071600CAB5C5 /* Behaviors */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */,
58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */,
0A1C30972620F61A00B47F3B /* Protocols */, 0A1C30972620F61A00B47F3B /* Protocols */,
27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */,
D23A900826125FFB007E14CE /* GetContactBehavior.swift */, D23A900826125FFB007E14CE /* GetContactBehavior.swift */,
@ -1475,11 +1487,11 @@
path = OneColumn; path = OneColumn;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7199C8142A4F3A40001568B7 /* Accessibility */ = { 582272082B1FC53E00F75BAE /* Accessibility */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */, 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */,
71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */, 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */,
); );
path = Accessibility; path = Accessibility;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1596,6 +1608,7 @@
D202AFE2242A5F1400E5BEDF /* Extensions */ = { D202AFE2242A5F1400E5BEDF /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5870636E2ACF238E00CA18D5 /* ReadableDecodingErrors.swift */,
D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */, D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */,
0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */, 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */,
D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */, D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */,
@ -2009,7 +2022,7 @@
D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = { D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7199C8142A4F3A40001568B7 /* Accessibility */, 582272082B1FC53E00F75BAE /* Accessibility */,
01F2C1FC27C81F9700DC3D36 /* Managers */, 01F2C1FC27C81F9700DC3D36 /* Managers */,
D2ED27D8254B0C1F00A1C293 /* Alerts */, D2ED27D8254B0C1F00A1C293 /* Alerts */,
27F973512466071600CAB5C5 /* Behaviors */, 27F973512466071600CAB5C5 /* Behaviors */,
@ -2742,6 +2755,7 @@
011D95A3240453F8000E3791 /* RuleRegexModel.swift in Sources */, 011D95A3240453F8000E3791 /* RuleRegexModel.swift in Sources */,
D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */,
012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */,
5822720B2B1FC55F00F75BAE /* AccessibilityHandler.swift in Sources */,
BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */, BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */,
32D2609724C19E2100B56344 /* LockupsPlanSMLXLModel.swift in Sources */, 32D2609724C19E2100B56344 /* LockupsPlanSMLXLModel.swift in Sources */,
EA985C872981AB0F00F2FF2E /* VDS-Tilelet+Codable.swift in Sources */, EA985C872981AB0F00F2FF2E /* VDS-Tilelet+Codable.swift in Sources */,
@ -2762,7 +2776,7 @@
01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */,
D2A92884241ACB25004E01C6 /* ProgrammaticScrollViewController.swift in Sources */, D2A92884241ACB25004E01C6 /* ProgrammaticScrollViewController.swift in Sources */,
EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */, EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */,
D23A90002612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift in Sources */, D23A90002612347A007E14CE /* PageBehaviorContainerModelProtocol.swift in Sources */,
EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */, EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */,
0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */,
D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */, D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */,
@ -2793,6 +2807,7 @@
D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */, D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */,
AF8118302AB39B0900FAD1BA /* RawRepresentableCodable.swift in Sources */, AF8118302AB39B0900FAD1BA /* RawRepresentableCodable.swift in Sources */,
94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */, 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */,
5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */,
D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */, D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */,
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */, 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */,
525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */,
@ -2808,6 +2823,7 @@
0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */, 0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */,
D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */,
D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */, D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */,
5870636F2ACF238E00CA18D5 /* ReadableDecodingErrors.swift in Sources */,
94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */, 94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */,
943820842432382400B43AF3 /* WebView.swift in Sources */, 943820842432382400B43AF3 /* WebView.swift in Sources */,
0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */, 0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */,
@ -2876,7 +2892,6 @@
0A7ECC702441001C00C828E8 /* UIToolbar+Extension.swift in Sources */, 0A7ECC702441001C00C828E8 /* UIToolbar+Extension.swift in Sources */,
D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */,
AA3561AC24C9684400452EB1 /* ListRightVariableRightCaretAllTextAndLinksModel.swift in Sources */, AA3561AC24C9684400452EB1 /* ListRightVariableRightCaretAllTextAndLinksModel.swift in Sources */,
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */,
D209234F244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift in Sources */, D209234F244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift in Sources */,
525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */, 525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */,
32D2609624C19E2100B56344 /* LockupsPlanSMLXL.swift in Sources */, 32D2609624C19E2100B56344 /* LockupsPlanSMLXL.swift in Sources */,
@ -2960,6 +2975,7 @@
EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */, EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */,
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */, AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */,
D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */,
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */,
27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */, 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */,
@ -3009,6 +3025,7 @@
8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */, 8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */,
AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */, AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */,
BB2FB3BD247E7EF200DF73CD /* Tags.swift in Sources */, BB2FB3BD247E7EF200DF73CD /* Tags.swift in Sources */,
5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */,
AA104ADC244734EA004D2810 /* HeadersH1LandingPageHeaderModel.swift in Sources */, AA104ADC244734EA004D2810 /* HeadersH1LandingPageHeaderModel.swift in Sources */,
BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */, BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */,
323AC96A24C837F000F8E4C4 /* ListThreeColumnBillChangesModel.swift in Sources */, 323AC96A24C837F000F8E4C4 /* ListThreeColumnBillChangesModel.swift in Sources */,
@ -3053,7 +3070,6 @@
D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */, D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */,
D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */, D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */,
D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */,
71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */,
AA71AD3E24A32FCE00ACA76F /* HeadersH2LinkModel.swift in Sources */, AA71AD3E24A32FCE00ACA76F /* HeadersH2LinkModel.swift in Sources */,
8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */, 8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */,
D243859923A16B1800332775 /* Container.swift in Sources */, D243859923A16B1800332775 /* Container.swift in Sources */,

View File

@ -33,6 +33,7 @@ import Foundation
super.set(with: model, delegateObject, additionalData) super.set(with: model, delegateObject, additionalData)
FormValidator.setupValidation(for: castModel, delegate: delegateObject?.formHolderDelegate) FormValidator.setupValidation(for: castModel, delegate: delegateObject?.formHolderDelegate)
setState()
} }
public func setState() { public func setState() {

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol { open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, ParentMoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
@ -29,6 +29,14 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
public var updateUI: ActionBlock? 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) { public init(image: ImageViewModel?, action: ActionModelProtocol) {
self.image = image self.image = image
self.action = action self.action = action

View File

@ -19,6 +19,7 @@ import UIKit
//-------------------------------------------------- //--------------------------------------------------
public private(set) var titleLabel: FormLabel = { public private(set) var titleLabel: FormLabel = {
let label = FormLabel() let label = FormLabel()
label.setFontStyle(.RegularMicro)
label.setContentCompressionResistancePriority(.required, for: .vertical) label.setContentCompressionResistancePriority(.required, for: .vertical)
return label return label
}() }()
@ -28,6 +29,7 @@ import UIKit
/// Provides contextual information on the TextField. /// Provides contextual information on the TextField.
public private(set) var feedbackLabel: FormLabel = { public private(set) var feedbackLabel: FormLabel = {
let label = FormLabel() let label = FormLabel()
label.setFontStyle(.RegularMicro)
label.setContentCompressionResistancePriority(.required, for: .vertical) label.setContentCompressionResistancePriority(.required, for: .vertical)
return label return label
}() }()
@ -276,15 +278,13 @@ import UIKit
backgroundColor = .clear backgroundColor = .clear
isAccessibilityElement = false isAccessibilityElement = false
titleLabel.font = Styler.Font.RegularMicro.getFont() titleLabel.setFontStyle(.RegularMicro)
titleLabel.textColor = .mvmBlack feedbackLabel.setFontStyle(.RegularMicro)
feedbackLabel.font = Styler.Font.RegularMicro.getFont() errorLabel.setFontStyle(.RegularMicro)
feedbackLabel.textColor = .mvmBlack titleLabel.text = nil
errorLabel.font = Styler.Font.RegularMicro.getFont() feedbackLabel.text = nil
errorLabel.textColor = .mvmBlack
errorLabel.text = nil errorLabel.text = nil
entryFieldContainer.disableAllBorders = false entryFieldContainer.disableAllBorders = false
feedbackLabel.text = nil
entryFieldContainer.reset() entryFieldContainer.reset()
entryFieldModel?.updateUI = nil entryFieldModel?.updateUI = nil
} }

View File

@ -9,11 +9,7 @@
import Foundation import Foundation
/// Subclass of label that helps with different states /// Subclass of label that helps with different states
public class FormLabel: Label { public class FormLabel: Label {
//properties used in setting label
private var delegateObject: MVMCoreUIDelegateObject?
private var additionalData: [AnyHashable: Any]?
//models that drive the label UI //models that drive the label UI
private var formModel: FormLabelModel! private var formModel: FormLabelModel!

View File

@ -8,15 +8,18 @@
// //
import MVMCore import MVMCore
import VDS
public typealias ActionBlock = () -> () public typealias ActionBlock = () -> ()
@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol, ViewMaskingProtocol { @objcMembers open class Label: VDS.Label, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol, ViewMaskingProtocol {
//------------------------------------------------------ //------------------------------------------------------
// MARK: - Properties // MARK: - Properties
//------------------------------------------------------ //------------------------------------------------------
open var viewModel: LabelModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
public var makeWholeViewClickable = false public var makeWholeViewClickable = false
@ -24,58 +27,23 @@ public typealias ActionBlock = () -> ()
public var standardFontSize: CGFloat = 0.0 public var standardFontSize: CGFloat = 0.0
/// Set this to use a custom sizing object during updateView instead of the standard. /// Set this to use a custom sizing object during updateView instead of the standard.
@available(*, deprecated, message: "VDS is maintaining scaleSize")
public var sizeObject: MFSizeObject? public var sizeObject: MFSizeObject?
@available(*, deprecated, message: "VDS is maintaining scaleSize")
public var scaleSize: NSNumber? public var scaleSize: NSNumber?
/// A specific text index to use as a unique marker. /// A specific text index to use as a unique marker.
public var hero: Int? public var hero: Int?
// Used for scaling the font in updateView.
private var originalAttributedString: NSAttributedString?
public var hasText: Bool {
guard let text = text, let attributedText = attributedText else { return false }
return !text.isEmpty || !attributedText.string.isEmpty
}
public var getRange: NSRange { public var getRange: NSRange {
NSRange(location: 0, length: text?.count ?? 0) NSRange(location: 0, length: text?.count ?? 0)
} }
public var shouldMaskWhileRecording: Bool = false public var shouldMaskWhileRecording: Bool = false
public var model: MoleculeModelProtocol? public var hasText: Bool {
//------------------------------------------------------ guard let text = text, let attributedText = attributedText else { return false }
// MARK: - Multi-Action Text return !text.isEmpty || !attributedText.string.isEmpty
//------------------------------------------------------
/// Data store of the tappable ranges of the text.
public var clauses: [ActionableClause] = [] {
didSet {
isUserInteractionEnabled = !clauses.isEmpty
if clauses.count > 1 {
clauses.sort { first, second in
return first.range.location < second.range.location
}
}
}
}
/// Used for tappable links in the text.
public struct ActionableClause {
public var range: NSRange
public var actionBlock: ActionBlock
public var accessibilityID: Int = 0
public func performAction() {
actionBlock()
}
public init(range: NSRange, actionBlock: @escaping ActionBlock, accessibilityID: Int = 0) {
self.range = range
self.actionBlock = actionBlock
self.accessibilityID = accessibilityID
}
} }
//------------------------------------------------------ //------------------------------------------------------
@ -84,49 +52,27 @@ public typealias ActionBlock = () -> ()
/// Sets the clauses array to empty. /// Sets the clauses array to empty.
@objc public func setEmptyClauses() { @objc public func setEmptyClauses() {
clauses = []
} }
//------------------------------------------------------ //------------------------------------------------------
// MARK: - Initialization // MARK: - Initialization
//------------------------------------------------------ //------------------------------------------------------
@objc public func setupView() { @objc public required init() {
backgroundColor = .clear super.init()
numberOfLines = 0
lineBreakMode = .byWordWrapping
translatesAutoresizingMaskIntoConstraints = false
clauses = []
accessibilityCustomActions = []
accessibilityTraits = .staticText
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(textLinkTapped))
tapGesture.numberOfTapsRequired = 1
addGestureRecognizer(tapGesture)
}
@objc public init() {
super.init(frame: .zero)
setupView()
} }
@objc required public init?(coder: NSCoder) { @objc required public init?(coder: NSCoder) {
super.init(coder: coder) super.init(coder: coder)
setupView()
} }
@objc override public init(frame: CGRect) { @objc override public init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
setupView()
} }
public init(fontStyle: Styler.Font, _ scale: Bool = true) { public init(fontStyle: Styler.Font, _ scale: Bool = true) {
super.init(frame: .zero) super.init(frame: .zero)
setupView() setFontStyle(fontStyle, scale)
font = fontStyle.getFont(false)
textColor = fontStyle.color()
setScale(scale)
} }
@objc convenience public init(standardFontSize size: CGFloat) { @objc convenience public init(standardFontSize size: CGFloat) {
@ -145,7 +91,6 @@ public typealias ActionBlock = () -> ()
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(frame: .zero) super.init(frame: .zero)
setupView()
styleB2(true) styleB2(true)
set(with: model, delegateObject, additionalData) set(with: model, delegateObject, additionalData)
} }
@ -236,12 +181,6 @@ public typealias ActionBlock = () -> ()
} }
} }
enum LabelAlignment: String {
case center
case right
case left
}
@objc public func resetAttributeStyle() { @objc public func resetAttributeStyle() {
/* /*
* This is to address a reuse issue with iOS 13 and up. * This is to address a reuse issue with iOS 13 and up.
@ -250,294 +189,121 @@ public typealias ActionBlock = () -> ()
* appropriately called. * appropriately called.
* Only other reference found of issue: https://www.thetopsites.net/article/58142205.shtml * Only other reference found of issue: https://www.thetopsites.net/article/58142205.shtml
*/ */
if let attributedText = attributedText, let text = text, !text.isEmpty { if let text = text, !text.isEmpty {
let attributedString = NSMutableAttributedString(string: text)
let range = NSRange(location: 0, length: text.count) //create the primary string
for attribute in attributedText.attributes(at: 0, effectiveRange: nil) { let mutableText = NSMutableAttributedString.mutableText(for: text,
if attribute.key == .underlineStyle { textStyle: textStyle,
attributedString.addAttribute(.underlineStyle, value: 0, range: range) useScaledFont: useScaledFont,
} textColor: textColorConfiguration.getColor(self),
if attribute.key == .strikethroughStyle { alignment: textAlignment,
attributedString.addAttribute(.strikethroughStyle, value: 0, range: range) lineBreakMode: lineBreakMode)
}
if let attributes = attributes {
mutableText.apply(attributes: attributes)
} }
self.attributedText = attributedString self.attributedText = mutableText
} }
} }
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject? = nil, _ additionalData: [AnyHashable: Any]? = nil) { public func viewModelDidUpdate() {
shouldMaskWhileRecording = viewModel.shouldMaskRecordedView ?? false
text = viewModel.text
hero = viewModel.hero
Label.setLabel(self, withHTML: viewModel.html)
textAlignment = viewModel.textAlignment ?? .left
surface = viewModel.surface
clauses = [] makeWholeViewClickable = viewModel.makeWholeViewClickable ?? false
text = nil if let backgroundColor = viewModel.backgroundColor {
attributedText = nil
originalAttributedString = nil
shouldMaskWhileRecording = model.shouldMaskRecordedView ?? false
guard let labelModel = model as? LabelModel else { return }
text = labelModel.text
if let accessibilityTraits = labelModel.accessibilityTraits {
self.accessibilityTraits = accessibilityTraits
}
resetAttributeStyle()
hero = labelModel.hero
Label.setLabel(self, withHTML: labelModel.html)
isAccessibilityElement = hasText
switch labelModel.textAlignment {
case .center:
textAlignment = .center
case .right:
textAlignment = .right
default:
textAlignment = .left
}
makeWholeViewClickable = labelModel.makeWholeViewClickable ?? false
if let backgroundColor = labelModel.backgroundColor {
self.backgroundColor = backgroundColor.uiColor self.backgroundColor = backgroundColor.uiColor
} }
if let accessibilityText = labelModel.accessibilityText { if let style = viewModel.fontStyle?.vdsTextStyle() {
accessibilityLabel = accessibilityText font = style.font
} textStyle = style
} else if let fontName = viewModel.fontName {
if let fontStyle = labelModel.fontStyle { // there is a TextStyle.defaultStyle
fontStyle.styleLabel(self, genericScaling: false) let fontSize = viewModel.fontSize
standardFontSize = font.pointSize if let fontSize {
} else {
let fontSize = labelModel.fontSize
if let fontSize = fontSize {
standardFontSize = fontSize standardFontSize = fontSize
} }
if let fontName = labelModel.fontName { if let customStyle = style(for: fontName, pointSize: fontSize ?? standardFontSize), customStyle != textStyle {
font = MFFonts.mfFont(withName: fontName, size: fontSize ?? standardFontSize) font = customStyle.font
} else if let fontSize = fontSize { textStyle = customStyle
font = font.updateSize(fontSize)
} }
} }
if let color = labelModel.textColor { if let color = viewModel.textColor {
textColor = color.uiColor textColorConfiguration = SurfaceColorConfiguration(color.uiColor, color.uiColor).eraseToAnyColorable()
} }
if let lines = labelModel.numberOfLines { if let lines = viewModel.numberOfLines {
numberOfLines = lines numberOfLines = lines
} }
if let attributes = labelModel.attributes, let labelText = text { if let attributeModels = viewModel.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) {
let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.updateSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor]) attributes = attributeModels
}
for attribute in attributes {
guard let range = validateAttribute(range: NSRange(location: attribute.location, length: attribute.length) , in: attributedString, type: attribute.type) }
else { continue }
/// See if the font that is currently set matches a VDS Font and if so grab the matching TextStyle or create custom TextStyle that
switch attribute { /// that the Label will use moving forward.
case let underlineAtt as LabelAttributeUnderlineModel: private func checkforFontChange() {
attributedString.addAttribute(.underlineStyle, value: underlineAtt.underlineValue.rawValue, range: range) guard let customStyle = style(for: font.fontName, pointSize: font.pointSize), customStyle != textStyle
if let underlineColor = underlineAtt.color?.uiColor { else { return }
attributedString.addAttribute(.underlineColor, value: underlineColor, range: range) textStyle = customStyle
} }
case _ as LabelAttributeStrikeThroughModel: private func style(for fontName: String, pointSize: CGFloat) -> TextStyle? {
attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) guard let vdsFont = Font.from(fontName: fontName),
attributedString.addAttribute(.baselineOffset, value: 0, range: range) let customStyle = TextStyle.style(from: vdsFont, pointSize: pointSize)
else { return nil }
case let colorAtt as LabelAttributeColorModel: return customStyle
if let colorHex = colorAtt.textColor { }
attributedString.removeAttribute(.foregroundColor, range: range)
attributedString.addAttribute(.foregroundColor, value: colorHex.uiColor, range: range) open override func updateView() {
} checkforFontChange()
super.updateView()
case let imageAtt as LabelAttributeImageModel: }
var fontSize = font.pointSize
if let attributeSize = imageAtt.size { open override func updateAccessibility() {
fontSize = attributeSize super.updateAccessibility()
} if let accessibilityTraits = viewModel?.accessibilityTraits {
let imageName = imageAtt.name ?? "externalLink" self.accessibilityTraits = accessibilityTraits
let imageAttachment: NSTextAttachment }
if let url = imageAtt.URL { if let accessibilityText = viewModel?.accessibilityText {
imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: self) accessibilityLabel = accessibilityText
} else { }
imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize) }
}
@objc open override func reset() {
// Confirm that the intended image location is within range. super.reset()
if 0...labelText.count ~= imageAtt.location { }
let mutableString = NSMutableAttributedString()
mutableString.append(NSAttributedString(attachment: imageAttachment)) @objc open func updateView(_ size: CGFloat) { }
attributedString.insert(mutableString, at: imageAtt.location)
} @objc open func setFont(_ font: UIFont, scale: Bool) {
self.font = font
case let fontAtt as LabelAttributeFontModel: setScale(scale)
if let fontStyle = fontAtt.style { }
attributedString.removeAttribute(.font, range: range)
attributedString.removeAttribute(.foregroundColor, range: range) @objc open func setScale(_ scale: Bool) {
attributedString.addAttribute(.font, value: fontStyle.getFont(), range: range) if scale {
attributedString.addAttribute(.foregroundColor, value: fontStyle.color(), range: range) standardFontSize = font.pointSize
} else { } else {
let fontSize = fontAtt.size standardFontSize = 0
var font: UIFont?
if let fontName = fontAtt.name {
font = MFFonts.mfFont(withName: fontName, size: fontSize ?? self.font.pointSize)
} else if let fontSize = fontSize {
font = self.font.updateSize(fontSize)
}
if let font = font {
attributedString.removeAttribute(.font, range: range)
attributedString.addAttribute(.font, value: font, range: range)
}
}
case let actionAtt as LabelAttributeActionModel:
addTappableLinkAttribute(range: NSRange(location: range.location, length: range.length)) {
MVMCoreUIActionHandler.performActionUnstructured(with: actionAtt.action, sourceModel: model, additionalData: additionalData, delegateObject: delegateObject)
}
addActionAttributes(range: range, string: attributedString)
default:
continue
}
}
attributedText = attributedString
originalAttributedString = attributedText
} }
self.model = labelModel
} }
@objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) { @objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) {
guard let label = label else { return } guard let label = label as? Label,
let json = json as? [String: Any],
// Some properties can only be set on Label. let labelModel = try? LabelModel.decode(jsonDict: json) else { return }
// Label fonts should not be scaled because it will be scaled in updateView. label.set(with: labelModel, delegate as? MVMCoreUIDelegateObject, additionalData)
let mvmLabel = label as? Label
label.text = json?.optionalStringForKey(KeyText)
setLabel(label, withHTML: json?.optionalStringForKey("html"))
if let alignment = json?.optionalStringForKey("textAlignment") {
switch alignment {
case "center":
label.textAlignment = .center
case "right":
label.textAlignment = .right
default:
label.textAlignment = .left
}
}
mvmLabel?.makeWholeViewClickable = json?.boolForKey("makeWholeViewClickable") ?? false
if let backgroundColorHex = json?.optionalStringForKey(KeyBackgroundColor), !backgroundColorHex.isEmpty {
label.backgroundColor = UIColor.mfGet(forHex: backgroundColorHex)
}
label.accessibilityLabel = json?.optionalStringForKey("accessibilityText")
if let fontStyle = json?.optionalStringForKey("fontStyle") {
MFStyler.style(label: label, styleString: fontStyle, genericScaling: mvmLabel == nil)
mvmLabel?.standardFontSize = label.font.pointSize
} else {
let fontSize = json?["fontSize"] as? CGFloat
if let fontSize = fontSize {
mvmLabel?.standardFontSize = fontSize
}
if let fontName = json?.optionalStringForKey("fontName") {
label.font = MFFonts.mfFont(withName: fontName, size: fontSize ?? mvmLabel?.standardFontSize ?? label.font.pointSize)
} else if let fontSize = fontSize {
label.font = label.font.updateSize(fontSize)
}
}
if let textColorHex = json?.optionalStringForKey(KeyTextColor), !textColorHex.isEmpty {
label.textColor = UIColor.mfGet(forHex: textColorHex)
}
if let attributes = json?.optionalArrayForKey("attributes"), let labelText = label.text {
let attributedString = NSMutableAttributedString(string: labelText,
attributes: [NSAttributedString.Key.font: mvmLabel?.font.updateSize(mvmLabel!.standardFontSize) ?? label.font as UIFont,
NSAttributedString.Key.foregroundColor: label.textColor as UIColor])
for case let attribute as [String: Any] in attributes {
guard let attributeType = attribute.optionalStringForKey(KeyType),
let location = attribute["location"] as? Int,
let length = attribute["length"] as? Int,
let range = validateAttribute(range: NSRange(location: location, length: length), in: attributedString, type: attributeType)
else { continue }
switch attributeType {
case "underline":
attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
case "strikethrough":
attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range)
attributedString.addAttribute(.baselineOffset, value: 0, range: range)
case "color":
if let colorHex = attribute.optionalStringForKey(KeyTextColor), !colorHex.isEmpty {
attributedString.removeAttribute(.foregroundColor, range: range)
attributedString.addAttribute(.foregroundColor, value: UIColor.mfGet(forHex: colorHex), range: range)
}
case "image":
let fontSize = attribute["size"] as? CGFloat ?? label.font.pointSize
let imageName = attribute["name"] as? String ?? "externalLink"
let imageURL = attribute["URL"] as? String
let imageAttachment: NSTextAttachment
if let url = imageURL, let label = label as? Label {
imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: label)
} else {
imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize)
}
let mutableString = NSMutableAttributedString()
mutableString.append(NSAttributedString(attachment: imageAttachment))
attributedString.insert(mutableString, at: location)
case "font":
if let fontStyle = attribute.optionalStringForKey("style") {
let styles = MFStyler.getAttributedString(for: "0", styleString: fontStyle, genericScaling: mvmLabel == nil)
attributedString.removeAttribute(.font, range: range)
attributedString.removeAttribute(.foregroundColor, range: range)
attributedString.addAttributes(styles.attributes(at: 0, effectiveRange: nil), range: range)
} else {
let fontSize = attribute["size"] as? CGFloat
var font: UIFont?
if let fontName = attribute.optionalStringForKey("name") {
font = MFFonts.mfFont(withName: fontName, size: fontSize ?? mvmLabel?.standardFontSize ?? label.font.pointSize)
} else if let fontSize = fontSize {
font = label.font.updateSize(fontSize)
}
if let font = font {
attributedString.removeAttribute(.font, range: range)
attributedString.addAttribute(.font, value: font, range: range)
}
}
case "action":
guard let actionLabel = label as? Label else { continue }
actionLabel.addActionAttributes(range: range, string: attributedString)
if let actionBlock = actionLabel.createActionBlockFor(actionMap: attribute, additionalData: additionalData, delegateObject: delegate) {
actionLabel.appendActionableClause(range: range, actionBlock: actionBlock)
}
default:
continue
}
}
label.attributedText = attributedString
mvmLabel?.originalAttributedString = attributedString
}
} }
//------------------------------------------------------ //------------------------------------------------------
@ -545,119 +311,50 @@ public typealias ActionBlock = () -> ()
//------------------------------------------------------ //------------------------------------------------------
public func setFontStyle(_ fontStyle: Styler.Font, _ scale: Bool = true) { public func setFontStyle(_ fontStyle: Styler.Font, _ scale: Bool = true) {
fontStyle.styleLabel(self, genericScaling: false) guard let style = fontStyle.vdsTextStyle() else { return }
textStyle = style
setScale(scale) setScale(scale)
} }
//------------------------------------------------------ //------------------------------------------------------
// MARK: - 2.0 Styling Methods // MARK: - 2.0 Styling Methods
//------------------------------------------------------ //------------------------------------------------------
@objc public func styleH1(_ scale: Bool) { @objc public func styleH1(_ scale: Bool) {
MFStyler.styleLabelH1(self, genericScaling: false) setFontStyle(.H1, scale)
setScale(scale)
} }
@objc public func styleH2(_ scale: Bool) { @objc public func styleH2(_ scale: Bool) {
MFStyler.styleLabelH2(self, genericScaling: false) setFontStyle(.H2, scale)
setScale(scale)
} }
@objc public func styleH3(_ scale: Bool) { @objc public func styleH3(_ scale: Bool) {
MFStyler.styleLabelH3(self, genericScaling: false) setFontStyle(.H3, scale)
setScale(scale)
} }
@objc public func styleH32(_ scale: Bool) { @objc public func styleH32(_ scale: Bool) {
MFStyler.styleLabelH32(self, genericScaling: false) setFontStyle(.H32, scale)
setScale(scale)
} }
@objc public func styleB1(_ scale: Bool) { @objc public func styleB1(_ scale: Bool) {
MFStyler.styleLabelB1(self, genericScaling: false) setFontStyle(.B1, scale)
setScale(scale)
} }
@objc public func styleB2(_ scale: Bool) { @objc public func styleB2(_ scale: Bool) {
MFStyler.styleLabelB2(self, genericScaling: false) setFontStyle(.B2, scale)
setScale(scale)
} }
@objc public func styleB3(_ scale: Bool) { @objc public func styleB3(_ scale: Bool) {
MFStyler.styleLabelB3(self, genericScaling: false) setFontStyle(.B3, scale)
setScale(scale)
} }
@objc public func styleB20(_ scale: Bool) { @objc public func styleB20(_ scale: Bool) {
MFStyler.styleLabelB20(self, genericScaling: false) setFontStyle(.B20, scale)
setScale(scale)
} }
/// Will remove the values contained in attributedText. }
func clearAttributes() {
guard let labelText = text, !labelText.isEmpty else { return }
guard let attributes = attributedText?.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: labelText.count))
else { return }
let attributedString = NSMutableAttributedString(string: labelText)
for attribute in attributes {
attributedString.removeAttribute(attribute.key, range: NSRange(location: 0, length: labelText.count))
}
attributedText = attributedString
}
// Mark: - Old Helpers
@objc public func updateView(_ size: CGFloat) { extension Label {
scaleSize = size as NSNumber
if let originalAttributedString = originalAttributedString {
let attributedString = NSMutableAttributedString(attributedString: originalAttributedString)
attributedString.removeAttribute(.font, range: NSRange(location: 0, length: attributedString.length))
// Loop the original attributed string, resize the fonts.
originalAttributedString.enumerateAttribute(.font, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in
if let fontObj = value as? UIFont, let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: fontObj.pointSize)?.getValueBased(onSize: size) {
attributedString.addAttribute(.font, value: fontObj.updateSize(stylerSize) as Any, range: range)
}
}
// Loop the original attributed string, resize the image attachments.
originalAttributedString.enumerateAttribute(.attachment, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in
if let attachment = value as? NSTextAttachment,
let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: attachment.bounds.width)?.getValueBased(onSize: size) {
let dimension = round(stylerSize)
attachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
}
}
attributedText = attributedString
} else if !MVMCoreGetterUtility.fequal(a: Float(standardFontSize), b: 0.0), let sizeObject = sizeObject ?? MFStyler.sizeObjectGeneric(forCurrentDevice: standardFontSize) {
font = font.updateSize(sizeObject.getValueBased(onSize: size))
}
}
@objc public func setFont(_ font: UIFont, scale: Bool) {
self.font = font
setScale(scale)
}
@objc public func setScale(_ scale: Bool) {
if scale {
standardFontSize = font.pointSize
if let floatScale = scaleSize?.floatValue {
updateView(CGFloat(floatScale))
} else {
updateView(MVMCoreUIUtility.getWidth())
}
} else {
standardFontSize = 0
}
}
/** /**
Appends an external link image to the end of the attributed string. Appends an external link image to the end of the attributed string.
Will provide one whitespace to the left of the icon; adds 2 chars to the end of the string. Will provide one whitespace to the left of the icon; adds 2 chars to the end of the string.
@ -689,7 +386,7 @@ public typealias ActionBlock = () -> ()
self.attributedText = mutableString self.attributedText = mutableString
} }
/* /*
Retrieves an NSTextAttachment for NSAttributedString that is prepped to be inserted with the text. Retrieves an NSTextAttachment for NSAttributedString that is prepped to be inserted with the text.
@ -705,47 +402,6 @@ public typealias ActionBlock = () -> ()
return imageAttachment return imageAttachment
} }
static func getTextAttachmentFrom(url: String, dimension: CGFloat, label: Label) -> NSTextAttachment {
let imageAttachment = NSTextAttachment()
imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
DispatchQueue.global(qos: .default).async {
MVMCoreCache.shared()?.getImage(url, useWidth: false, widthForS7: 0, useHeight: false, heightForS7: 0, localFallbackImageName: nil) { image, data, _ in
DispatchQueue.main.sync {
imageAttachment.image = image
label.setNeedsDisplay()
}
}
}
return imageAttachment
}
/// Call to detect in the attributedText contains an NSTextAttachment.
func containsTextAttachment() -> Bool {
guard let attributedText = attributedText else { return false }
var containsAttachment = false
attributedText.enumerateAttribute(.attachment, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, stop in
if value is NSTextAttachment {
containsAttachment = true
return
}
}
return containsAttachment
}
func appendActionableClause(range: NSRange, actionBlock: @escaping ActionBlock) {
accessibilityTraits = .button
let accessibleAction = customAccessibilityAction(range: range)
clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1))
}
public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect {
guard let abstractContainer = label.abstractTextContainer() else { return CGRect() } guard let abstractContainer = label.abstractTextContainer() else { return CGRect() }
@ -789,25 +445,11 @@ public typealias ActionBlock = () -> ()
return (textContainer, layoutManager, textStorage) return (textContainer, layoutManager, textStorage)
} }
} }
// MARK: - Atomization // MARK: - Atomization
extension Label { extension Label {
public func reset() {
text = nil
attributedText = nil
hero = nil
textAlignment = .left
originalAttributedString = nil
styleB2(true)
accessibilityCustomActions = []
clauses = []
accessibilityTraits = .staticText
numberOfLines = 0
}
public func needsToBeConstrained() -> Bool { true } public func needsToBeConstrained() -> Bool { true }
public func horizontalAlignment() -> UIStackView.Alignment { .leading } public func horizontalAlignment() -> UIStackView.Alignment { .leading }
@ -817,19 +459,25 @@ extension Label {
// MARK: - Multi-Link Functionality // MARK: - Multi-Link Functionality
extension Label { extension Label {
/// Applied to existing text. Removes underlines of tappable links and assoated actionable clauses. /// Underlines the tappable region and stores the tap logic for interation.
@objc public func clearActionableClauses() { private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
guard let attributedText = attributedText else { return } var textLink = ActionLabelAttribute(location: range.location, length: range.length)
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) textLink.subscriber = textLink
.action
clauses.forEach { clause in .sink { _ in
mutableAttributedString.removeAttribute(NSAttributedString.Key.underlineStyle, range: clause.range) actionBlock()
}
if var attributes {
attributes.append(textLink)
setNeedsUpdate()
} else {
attributes = [textLink]
} }
}
self.attributedText = mutableAttributedString
accessibilityElements = [] @objc public func clearActionableClauses() {
clauses = [] attributes = attributes?.filter { !($0 is (any ActionLabelAttributeModel)) }
} }
public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? { public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? {
@ -842,24 +490,6 @@ extension Label {
} }
} }
private func addActionAttributes(range: NSRange, string: NSMutableAttributedString?) {
guard let string = string,
let range = validateAttribute(range: range, in: string)
else { return }
string.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range)
}
fileprivate func setActionAttributes(range: NSRange) {
guard let attributedText = attributedText else { return }
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText)
addActionAttributes(range: range, string: mutableAttributedString)
self.attributedText = mutableAttributedString
}
/** /**
Provides an actionable range of text. Provides an actionable range of text.
@ -898,113 +528,6 @@ extension Label {
setTextLinkState(range: getRange, actionBlock: actionBlock) setTextLinkState(range: getRange, actionBlock: actionBlock)
} }
/// Underlines the tappable region and stores the tap logic for interation.
private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
setActionAttributes(range: range)
appendActionableClause(range: range, actionBlock: actionBlock)
}
@objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) {
for clause in clauses {
// This determines if we tapped on the desired range of text.
if gesture.didTapAttributedTextInLabel(self, inRange: clause.range) {
clause.performAction()
return
}
}
}
}
// MARK: -
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool {
// There would only ever be one clause to act on.
if label.makeWholeViewClickable {
return true
}
guard let abstractContainer = label.abstractTextContainer() else { return false }
let textContainer = abstractContainer.0
let layoutManager = abstractContainer.1
let tapLocation = location(in: label)
let indexOfGlyph = layoutManager.glyphIndex(for: tapLocation, in: textContainer)
let intrinsicWidth = label.intrinsicContentSize.width
// Assert that tapped occured within acceptable bounds based on alignment.
switch label.textAlignment {
case .right:
if tapLocation.x < label.bounds.width - intrinsicWidth {
return false
}
case .center:
let halfBounds = label.bounds.width / 2
let halfIntrinsicWidth = intrinsicWidth / 2
if tapLocation.x > halfBounds + halfIntrinsicWidth {
return false
} else if tapLocation.x < halfBounds - halfIntrinsicWidth {
return false
}
default: // Left align
if tapLocation.x > intrinsicWidth {
return false
}
}
// Affirms that the tap occured in the desired rect of provided by the target range.
return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(tapLocation) && NSLocationInRange(indexOfGlyph, targetRange)
}
}
// MARK: - Accessibility
extension Label {
func customAccessibilityAction(range: NSRange) -> UIAccessibilityCustomAction? {
guard let text = text else { return nil }
if accessibilityHint == nil {
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "swipe_to_select_with_action_hint")
}
let actionText = NSString(string: text).substring(with: range)
let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:)))
accessibilityCustomActions?.append(accessibleAction)
return accessibleAction
}
@objc public func accessibilityCustomAction(_ action: UIAccessibilityCustomAction) {
for clause in clauses {
if action.hash == clause.accessibilityID {
clause.performAction()
return
}
}
}
open override func accessibilityActivate() -> Bool {
guard let accessibleActions = accessibilityCustomActions else { return false }
for clause in clauses {
for action in accessibleActions {
if action.hash == clause.accessibilityID {
clause.performAction()
return true
}
}
}
return false
}
} }
//------------------------------------------------------ //------------------------------------------------------
@ -1025,3 +548,4 @@ func validateAttribute(range: NSRange, in string: NSAttributedString, type: Stri
return range return range
} }

View File

@ -5,7 +5,7 @@
// Created by Suresh, Kamlesh on 10/3/19. // Created by Suresh, Kamlesh on 10/3/19.
// Copyright © 2019 Suresh, Kamlesh. All rights reserved. // Copyright © 2019 Suresh, Kamlesh. All rights reserved.
// //
import VDS
@objcMembers open class LabelModel: MoleculeModelProtocol { @objcMembers open class LabelModel: MoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
@ -30,6 +30,7 @@
public var numberOfLines: Int? public var numberOfLines: Int?
public var shouldMaskRecordedView: Bool? = false public var shouldMaskRecordedView: Bool? = false
public var accessibilityTraits: UIAccessibilityTraits? public var accessibilityTraits: UIAccessibilityTraits?
public var inverted: Bool = false
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Keys // MARK: - Keys
@ -49,6 +50,7 @@
case attributes case attributes
case html case html
case hero case hero
case inverted
case makeWholeViewClickable case makeWholeViewClickable
case numberOfLines case numberOfLines
case shouldMaskRecordedView case shouldMaskRecordedView
@ -97,6 +99,7 @@
attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes) attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes)
html = try typeContainer.decodeIfPresent(String.self, forKey: .html) html = try typeContainer.decodeIfPresent(String.self, forKey: .html)
hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero) hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero)
inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable) makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable)
numberOfLines = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfLines) numberOfLines = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfLines)
shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? false shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? false
@ -123,9 +126,14 @@
try container.encodeModelsIfPresent(attributes, forKey: .attributes) try container.encodeModelsIfPresent(attributes, forKey: .attributes)
try container.encodeIfPresent(html, forKey: .html) try container.encodeIfPresent(html, forKey: .html)
try container.encodeIfPresent(hero, forKey: .hero) try container.encodeIfPresent(hero, forKey: .hero)
try container.encodeIfPresent(inverted, forKey: .inverted)
try container.encodeIfPresent(makeWholeViewClickable, forKey: .makeWholeViewClickable) try container.encodeIfPresent(makeWholeViewClickable, forKey: .makeWholeViewClickable)
try container.encodeIfPresent(numberOfLines, forKey: .numberOfLines) try container.encodeIfPresent(numberOfLines, forKey: .numberOfLines)
try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView)
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
} }
} }
extension LabelModel {
public var surface: Surface { inverted ? .dark : .light }
}

View File

@ -68,23 +68,35 @@ open class TileletModel: MoleculeModelProtocol {
public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? { public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? {
guard let title else { return nil } guard let title else { return nil }
let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
let style: TextStyle? = title.fontStyle?.vdsTextStyle()
if let style, let standardStyle = Tilelet.TitleModel.StandardStyle(rawValue: style.toStandardStyle().rawValue) { do {
return .init(text: title.text, textAttributes: attrs, standardStyle: standardStyle) if let style = title.fontStyle {
} else { return .init(text: title.text,
return .init(text: title.text, textAttributes: attrs) textAttributes: attrs,
} standardStyle: try style.vdsSubsetStyle())
} }
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: title.text, textAttributes: attrs)
}
public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? { public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? {
guard let subTitle else { return nil } guard let subTitle else { return nil }
let attrs = subTitle.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) let attrs = subTitle.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
let style: TextStyle? = subTitle.fontStyle?.vdsTextStyle() do {
if let style, let standardStyle = Tilelet.SubTitleModel.StandardStyle(rawValue: style.toStandardStyle().rawValue) { if let style = subTitle.fontStyle {
return .init(text: subTitle.text, textAttributes: attrs, standardStyle: standardStyle) return .init(text: subTitle.text,
} else { otherStandardStyle: try style.vdsSubsetStyle(),
return .init(text: subTitle.text, textAttributes: attrs) textAttributes: attrs)
} }
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: subTitle.text, textAttributes: attrs)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {

View File

@ -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
}
}
}

View File

@ -25,9 +25,8 @@ extension VDS.Tabs.Overflow: Codable {}
extension VDS.Tabs.Size: Codable {} extension VDS.Tabs.Size: Codable {}
extension VDS.TextLink.Size: Codable {} extension VDS.TextLink.Size: Codable {}
extension VDS.TextLinkCaret.IconPosition: Codable {} extension VDS.TextLinkCaret.IconPosition: Codable {}
extension VDS.TileContainer.BackgroundColor: Codable {}
extension VDS.TileContainer.Padding: Codable {}
extension VDS.TileContainer.AspectRatio: Codable {} extension VDS.TileContainer.AspectRatio: Codable {}
extension VDS.TitleLockup.TextAlignment: Codable {}
extension VDS.Tooltip.FillColor: Codable {} extension VDS.Tooltip.FillColor: Codable {}
extension VDS.Tooltip.Size: Codable {} extension VDS.Tooltip.Size: Codable {}
extension VDS.Line.Style: Codable {} extension VDS.Line.Style: Codable {}
@ -61,3 +60,75 @@ extension DecodableDefault {
public typealias BlackColor = Wrapper<Sources.BlackColor> public typealias BlackColor = Wrapper<Sources.BlackColor>
public typealias Surface = Wrapper<Sources.Surface> public typealias Surface = Wrapper<Sources.Surface>
} }
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)
}
}
}

View File

@ -12,20 +12,54 @@ import VDS
extension Styler.Font { extension Styler.Font {
//Converts Legacy Font to a VDS.TextStyle //Converts Legacy Font to a VDS.TextStyle
public func vdsTextStyle() -> VDS.TextStyle? { public func vdsTextStyle() -> VDS.TextStyle? {
let updatedRaw = rawValue.replacingOccurrences(of: "Regular", with: "")
//ensure that the current Styler.Font isn't Legacy Version
//if it is, here is the conversion to the updated version
var actualFont: Styler.Font = self
switch self {
case .Title2XLarge: actualFont = .RegularTitle2XLarge
case .TitleXLarge: actualFont = .RegularTitleXLarge
case .H1: actualFont = .RegularTitle2XLarge
case .H32: actualFont = .RegularTitleXLarge
case .H2: actualFont = .RegularTitleLarge
case .B20: actualFont = .RegularBodyLarge
case .H3: actualFont = .BoldTitleMedium
case .B1: actualFont = .BoldBodySmall
case .B2: actualFont = .RegularBodySmall
case .B3: actualFont = .RegularMicro
default: break
}
let updatedRaw = actualFont.rawValue.replacingOccurrences(of: "Regular", with: "")
let newRaw = updatedRaw.prefix(1).lowercased() + updatedRaw.dropFirst() let newRaw = updatedRaw.prefix(1).lowercased() + updatedRaw.dropFirst()
guard let style = VDS.TextStyle(rawValue: newRaw) else { return nil } guard let style = VDS.TextStyle(rawValue: newRaw) else { return nil }
return style return style
} }
public func vdsSubsetStyle<T: EnumSubset>() -> T? { public func vdsSubsetStyle<T: EnumSubset>() throws -> T {
guard let style = vdsTextStyle() else { return nil } guard let style = vdsTextStyle(), let rawValue = style.toStandardStyle().rawValue as? T.RawValue, let standardStyle = T(rawValue: rawValue) else {
guard let rawValue = style.rawValue as? T.RawValue, let err = "\(rawValue) was not found in the \(T.self), only these cases exist:\r\(T.allCases)"
let found = T(rawValue: rawValue) else { throw MVMCoreError.errorObject(MVMCoreErrorObject(title: "\(T.self) conversion Issue",
print("Style: \(style.rawValue) is not in enum \(T.self)\ronly these cases exist:\r\(T.allCases)") messageToLog: err,
return nil code: 999,
domain: ErrorDomainNative,
location: #file)!)
} }
return found return standardStyle
} }
} }
extension VDS.Font {
internal static func from(fontName: String) -> Self? {
Self.allCases.filter({$0.fontName == fontName }).first
}
}
extension VDS.TextStyle {
internal static func style(from font: VDS.Font, pointSize: CGFloat) -> TextStyle? {
guard let first = allCases.filter({$0.fontFace == font && $0.pointSize == pointSize}).first else {
return TextStyle(rawValue: "Custom-TextStyle", fontFace: font, pointSize: pointSize)
}
return first
}
}

View File

@ -20,6 +20,11 @@ public class HeadersH1ButtonModel: HeaderModel, MoleculeModelProtocol, ParentMol
[headlineBody, buttons] [headlineBody, buttons]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
|| replaceChildMolecule(at: &buttons, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -24,6 +24,15 @@ public class HeadersH1LandingPageHeaderModel: HeaderModel, MoleculeModelProtocol
[headline, headline2, subHeadline, body, link, buttons] [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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,6 +20,10 @@ public class HeadersH1NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
[headlineBody] [headlineBody]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,6 +22,11 @@ public class HeadersH2ButtonsModel: HeaderModel, MoleculeModelProtocol, ParentMo
[headlineBody, buttons] [headlineBody, buttons]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
|| replaceChildMolecule(at: &buttons, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -19,6 +19,11 @@ public class HeadersH2CaretLinkModel: HeaderModel, MoleculeModelProtocol, Parent
[headlineBody, caretLink] [headlineBody, caretLink]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
|| replaceChildMolecule(at: &caretLink, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -21,6 +21,11 @@ public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMolec
[headlineBody, link] [headlineBody, link]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
|| replaceChildMolecule(at: &link, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -21,6 +21,10 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
[headlineBody] [headlineBody]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -25,6 +25,17 @@ public class HeadersH2PricingTwoRowsModel: HeaderModel, MoleculeModelProtocol, P
[headline, body, subBody, body2, subBody2, body3, subBody3].compactMap({$0}) [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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,6 +22,11 @@ public class HeadersH2TinyButtonModel: HeaderModel, MoleculeModelProtocol, Paren
[headlineBody, button] [headlineBody, button]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
|| replaceChildMolecule(at: &button, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,6 +20,11 @@ open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelPr
[checkbox, headlineBody] [checkbox, headlineBody]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &checkbox, with: molecule)
|| replaceChildMolecule(at: &headlineBody, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,6 +20,11 @@ public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModel
return [image, eyebrowHeadlineBodyLink] 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 // MARK: - Method
//-------------------------------------------------- //--------------------------------------------------

View File

@ -21,6 +21,12 @@ public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel,
return [image, eyebrowHeadlineBodyLink, rightLabel] 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 // MARK: - Methods
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -21,6 +21,12 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Par
[image, headlineBody, rightLabel] [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 // MARK: - Methods
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -21,6 +21,12 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, ParentMolec
return [image, leftLabel, rightLabel] 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 // MARK: - Methods
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -7,7 +7,7 @@
// //
open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, MoleculeModelProtocol { open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, ParentMoleculeModelProtocol {
//----------------------------------------------------- //-----------------------------------------------------
// MARK: - Properties // MARK: - Properties
//----------------------------------------------------- //-----------------------------------------------------
@ -15,7 +15,16 @@ open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, MoleculeMode
public static var identifier: String = "listLVRBBdy" public static var identifier: String = "listLVRBBdy"
public var radioButton: RadioButtonModel public var radioButton: RadioButtonModel
public var headlineBody: HeadlineBodyModel public var headlineBody: HeadlineBodyModel
public var children: [MoleculeModelProtocol] {
[radioButton, headlineBody]
}
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &radioButton, with: replacementMolecule)
|| replaceChildMolecule(at: &headlineBody, with: replacementMolecule)
}
//----------------------------------------------------- //-----------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -39,6 +39,10 @@ public class ListOneColumnFullWidthTextBodyTextModel: ListItemModel, MoleculeMod
return [headlineBody] return [headlineBody]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &headlineBody, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Keys // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------

View File

@ -40,6 +40,11 @@ public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, Molecul
return [button, eyebrowHeadlineBodyLink] 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 // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,6 +20,11 @@ public class ListRightVariableRightCaretAllTextAndLinksModel: ListItemModel, Par
[rightLabel, eyebrowHeadlineBodyLink] [rightLabel, eyebrowHeadlineBodyLink]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &rightLabel, with: molecule)
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule)
}
//----------------------------------------------------- //-----------------------------------------------------
// MARK: - Methods // MARK: - Methods
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -12,7 +12,7 @@ import Foundation
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Outlets // MARK: - Outlets
//-------------------------------------------------- //--------------------------------------------------
public let planLabel = Label() public let planLabel = Label(fontStyle: .BoldFeatureXLarge)
public let headline = Label(fontStyle: .BoldTitleLarge) public let headline = Label(fontStyle: .BoldTitleLarge)
public let subHeadline = Label(fontStyle: .RegularTitleLarge) public let subHeadline = Label(fontStyle: .RegularTitleLarge)
public let body = Label(fontStyle: .RegularBodySmall) public let body = Label(fontStyle: .RegularBodySmall)
@ -33,8 +33,6 @@ import Foundation
//------------------------------------------------------- //-------------------------------------------------------
open override func setupView() { open override func setupView() {
super.setupView() super.setupView()
planLabel.font = MFStyler.getMVA3FontSize(96, bold: true)
planLabel.standardFontSize = 96
addSubview(stack) addSubview(stack)
planLabel.setContentCompressionResistancePriority(.required, for: .horizontal) planLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
planLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) planLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
@ -94,8 +92,7 @@ import Foundation
open override func reset() { open override func reset() {
super.reset() super.reset()
stack.reset() stack.reset()
planLabel.font = MFStyler.getMVA3FontSize(96, bold: true) planLabel.setFontStyle(.BoldFeatureXLarge)
planLabel.standardFontSize = 96
headline.setFontStyle(.BoldTitleLarge) headline.setFontStyle(.BoldTitleLarge)
subHeadline.setFontStyle(.RegularTitleLarge) subHeadline.setFontStyle(.RegularTitleLarge)
body.setFontStyle(.RegularBodySmall) body.setFontStyle(.RegularBodySmall)

View File

@ -5,119 +5,43 @@
// Created by Nadigadda, Sumanth on 04/05/22. // Created by Nadigadda, Sumanth on 04/05/22.
// Copyright © 2022 Verizon Wireless. All rights reserved. // Copyright © 2022 Verizon Wireless. All rights reserved.
// //
import VDS
@objcMembers open class TitleLockup: VDS.TitleLockup, VDSMoleculeViewProtocol {
@objcMembers open class TitleLockup: View {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Outlets // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
open var viewModel: TitleLockupModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
public let eyebrow = Label(fontStyle: .RegularBodySmall) //--------------------------------------------------
public let title = Label(fontStyle: .RegularBodySmall) // MARK: - Public Functions
public let subTitle = Label(fontStyle: .RegularBodySmall) //--------------------------------------------------
public lazy var stack: UIStackView = { open func viewModelDidUpdate() {
let stack = UIStackView(arrangedSubviews: [eyebrow, title, subTitle]) surface = viewModel.surface
stack.translatesAutoresizingMaskIntoConstraints = false textAlignment = viewModel.textAlignment
stack.axis = .vertical eyebrowModel = viewModel.eyebrowModel(delegateObject: delegateObject, additionalData: additionalData)
return stack titleModel = viewModel.titleModel(delegateObject: delegateObject, additionalData: additionalData)
}() subTitleModel = viewModel.subTitleModel(delegateObject: delegateObject, additionalData: additionalData)
var castModel: TitleLockupModel? {
get { return model as? TitleLockupModel }
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initialization // MARK: - Initialization
//-------------------------------------------------- //--------------------------------------------------
public convenience init() { public convenience required init() {
self.init(frame: .zero) self.init(frame: .zero)
} }
//--------------------------------------------------
// MARK: - MFViewProtocol
//--------------------------------------------------
open override func setupView() {
super.setupView()
addSubview(stack)
NSLayoutConstraint.constraintPinSubview(toSuperview: stack)
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
stack.updateView(size)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - MoleculeViewProtocol // MARK: - MoleculeViewProtocol
//-------------------------------------------------- //--------------------------------------------------
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
open override func reset() {
super.reset()
stack.reset()
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? TitleLockupModel else { return }
stack.setCustomSpacing(model.defaultEyebrowTitleSpacing(), after: eyebrow)
stack.setCustomSpacing(model.defaultTitleSubTitleSpacing(), after: title)
stack.updateContainedMolecules(with: [model.eyebrow,
model.title,
model.subTitle],
delegateObject, additionalData)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 65 return 65
} }
//-------------------------------------------------- open func updateView(_ size: CGFloat) {}
// MARK: - Accessibility Helpers
//--------------------------------------------------
/// Returns the labels text in one message.
func getAccessibilityMessage() -> String? {
var message = ""
if let eyebrowLabel = eyebrow.text {
message += eyebrowLabel + ", "
}
if let headlineLabel = title.text {
message += headlineLabel + ", "
}
if let bodyLabel = subTitle.text {
message += bodyLabel
}
return message.count > 0 ? message : nil
}
/// Returns an array of the appropriate accessibility elements.
func getAccessibilityElements() -> [Any]? {
var elements: [UIView] = []
if eyebrow.hasText {
elements.append(eyebrow)
}
if title.hasText {
elements.append(title)
}
if subTitle.hasText {
elements.append(subTitle)
}
return elements.count > 0 ? elements : nil
}
} }

View File

@ -7,6 +7,7 @@
// //
import VDSColorTokens import VDSColorTokens
import VDS
public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
@ -18,52 +19,27 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
public var moleculeName: String = TitleLockupModel.identifier public var moleculeName: String = TitleLockupModel.identifier
public var id: String = UUID().uuidString public var id: String = UUID().uuidString
public var textAlignment: TitleLockup.TextAlignment = .left
public var eyebrow: LabelModel? public var eyebrow: LabelModel?
public var title: LabelModel public var title: LabelModel
public var subTitle: LabelModel? public var subTitle: LabelModel?
public var subTitleColor: Use = .primary
public var alignment: Alignment = .left { public var alignment: VDS.TitleLockup.TextAlignment = .left
didSet { public var inverted: Bool = false
///Updating the text alignment for all labels
if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) {
eyebrow?.textAlignment = textAlignment
title.textAlignment = textAlignment
subTitle?.textAlignment = textAlignment
}
}
}
public var inverted: Bool = false { public var backgroundColor: Color?
didSet {
///Updating the text color
eyebrow?.textColor = titleColor
title.textColor = titleColor
subTitle?.textColor = subTitleColor
}
}
private var _backgroundColor: Color?
public var backgroundColor: Color? {
get {
return inverted ? Color(uiColor: VDSColor.backgroundPrimaryDark) : Color(uiColor: VDSColor.backgroundPrimaryLight)
}
set {
_backgroundColor = newValue
}
}
public var titleColor: Color? {
return inverted ? Color(uiColor: VDSColor.elementsPrimaryOndark) : Color(uiColor: VDSColor.elementsPrimaryOnlight)
}
public var subTitleColor: Color? {
return inverted ? Color(uiColor: VDSColor.elementsSecondaryOndark) : Color(uiColor: VDSColor.elementsSecondaryOnlight)
}
public var children: [MoleculeModelProtocol] { public var children: [MoleculeModelProtocol] {
[eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } [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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------
@ -72,58 +48,6 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
self.eyebrow = eyebrow self.eyebrow = eyebrow
self.title = title self.title = title
self.subTitle = subTitle self.subTitle = subTitle
updateLabelAttributes()
}
//--------------------------------------------------
// MARK: - Enum
//--------------------------------------------------
public enum Alignment: String, Codable {
case left
case center
}
//--------------------------------------------------
// MARK: - Styling
//--------------------------------------------------
/// Returns the default fontStyle for the subtitle, based on the title fontStyle.
func defaultSubtitleFontStyle() -> Styler.Font {
switch title.fontStyle {
case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall:
return .RegularBodyLarge
case .RegularFeatureSmall, .RegularFeatureMedium:
return .RegularTitleLarge
default:
return .RegularBodySmall
}
}
/// Returns the default spacing between the eyebrow and title, based on the title fontStyle.
func defaultEyebrowTitleSpacing() -> CGFloat {
switch title.fontStyle {
case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall:
return Padding.Three
case .RegularFeatureMedium:
return subTitle?.fontStyle == .RegularBodyLarge ? Padding.Three : Padding.Four
default:
return Padding.Two
}
}
/// Returns the default spacing between the title and subTitle, based on the title fontStyle.
func defaultTitleSubTitleSpacing() -> CGFloat {
switch title.fontStyle {
case .RegularTitleXLarge:
return Padding.Three
case .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall:
return Padding.Four
case .RegularFeatureMedium:
return Padding.Five
default:
return Padding.Two
}
} }
//-------------------------------------------------- //--------------------------------------------------
@ -133,10 +57,11 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case id case id
case moleculeName case moleculeName
case backgroundColor case textAlignment
case eyebrow case eyebrow
case title case title
case subTitle case subTitle
case subTitleColor
case inverted case inverted
case alignment case alignment
} }
@ -148,72 +73,122 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
textAlignment = try typeContainer.decodeIfPresent(TitleLockup.TextAlignment.self, forKey: .textAlignment) ?? .left
title = try typeContainer.decodeMolecule(codingKey: .title) title = try typeContainer.decodeMolecule(codingKey: .title)
eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle) subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle)
/// look for color hex code
if let color = try? typeContainer.decodeIfPresent(Color.self, forKey: .subTitleColor) {
self.subTitleColor = color.uiColor.isDark() ? .primary : .secondary
if let newAlignment = try typeContainer.decodeIfPresent(Alignment.self, forKey: .alignment) { } else if let subTitleColor = try? typeContainer.decodeIfPresent(Use.self, forKey: .subTitleColor) {
self.subTitleColor = subTitleColor
} else {
subTitleColor = .primary
}
if let newAlignment = try typeContainer.decodeIfPresent(VDS.TitleLockup.TextAlignment.self, forKey: .alignment) {
alignment = newAlignment alignment = newAlignment
} }
if let invertedStatus = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
inverted = invertedStatus self.inverted = inverted
} else {
try setInverted(deprecatedFrom: decoder)
} }
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
updateLabelAttributes()
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id) try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(textAlignment, forKey: .textAlignment)
try container.encodeIfPresent(eyebrow, forKey: .eyebrow) try container.encodeIfPresent(eyebrow, forKey: .eyebrow)
try container.encodeModel(title, forKey: .title) try container.encodeModel(title, forKey: .title)
try container.encodeIfPresent(subTitle, forKey: .subTitle) try container.encodeIfPresent(subTitle, forKey: .subTitle)
try container.encode(subTitleColor, forKey: .subTitleColor)
try container.encode(alignment, forKey: .alignment) try container.encode(alignment, forKey: .alignment)
try container.encode(inverted, forKey: .inverted) try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor)
} }
//-------------------------------------------------- public func eyebrowModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.EyebrowModel? {
// MARK: - Model updates guard let eyebrow else { return nil }
//-------------------------------------------------- let attrs = eyebrow.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
do {
if let style = eyebrow.fontStyle {
return .init(text: eyebrow.text,
isBold: style.isBold(),
standardStyle: try style.vdsSubsetStyle(),
textAttributes: attrs,
numberOfLines: eyebrow.numberOfLines ?? 0)
}
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: eyebrow.text, textAttributes: attrs, numberOfLines: eyebrow.numberOfLines ?? 0)
}
public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.TitleModel {
let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
do {
if let style = title.fontStyle {
return .init(text: title.text,
textAttributes: attrs,
isBold: style.isBold(),
standardStyle: try style.vdsSubsetStyle(),
numberOfLines: title.numberOfLines ?? 0)
}
private func updateLabelAttributes() { } catch MVMCoreError.errorObject(let object) {
// If subtitle style is not available, will set font style based on the component MVMCoreLoggingHandler.shared()?.addError(toLog: object)
if subTitle?.fontStyle == nil { } catch { }
subTitle?.fontStyle = defaultSubtitleFontStyle()
}
// If eyebrow style is not available, will set font style based on the component. Eyebrow and subtitle share the same font size return .init(text: title.text, textAttributes: attrs, numberOfLines: title.numberOfLines ?? 0)
if eyebrow?.fontStyle == nil { }
eyebrow?.fontStyle = subTitle?.fontStyle
} public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.SubTitleModel? {
guard let subTitle else { return nil }
let attrs = subTitle.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
// Updating the text color do {
if eyebrow?.textColor == nil { if let style = subTitle.fontStyle {
eyebrow?.textColor = subTitleColor return .init(text: subTitle.text,
} otherStandardStyle: try style.vdsSubsetStyle(),
if title.textColor == nil { textColor: subTitleColor,
title.textColor = titleColor textAttributes: attrs,
} numberOfLines: subTitle.numberOfLines ?? 0)
if subTitle?.textColor == nil {
subTitle?.textColor = subTitleColor
}
// Updating the text alignment for all labels
if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) {
if eyebrow?.textAlignment == nil {
eyebrow?.textAlignment = textAlignment
}
if title.textAlignment == nil {
title.textAlignment = textAlignment
}
if subTitle?.textAlignment == nil {
subTitle?.textAlignment = textAlignment
} }
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: subTitle.text, textColor: subTitleColor, textAttributes: attrs, numberOfLines: subTitle.numberOfLines ?? 0)
}
private enum DeprecatedCodingKeys: String, CodingKey {
case titleColor
case backgroundColor
}
private func setInverted(deprecatedFrom decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: DeprecatedCodingKeys.self)
if let titleColor = try typeContainer.decodeIfPresent(Color.self, forKey: .titleColor) {
inverted = !titleColor.uiColor.isDark()
}
if let backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) {
inverted = !backgroundColor.uiColor.isDark()
} }
} }
}
extension TitleLockupModel {
public var surface: Surface { inverted ? .dark : .light }
} }

View File

@ -22,6 +22,11 @@ public class ListOneColumnFullWidthTextDividerSubsectionModel: ListItemModel, Mo
[headline, body].compactMap({$0}) [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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,6 +22,11 @@ public class ListOneColumnTextWithWhitespaceDividerShortModel: ListItemModel, Mo
[headline, body].compactMap({$0}) [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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,6 +22,11 @@ public class ListOneColumnTextWithWhitespaceDividerTallModel: ListItemModel, Mol
[headline, body].compactMap({$0}) [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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -30,6 +30,7 @@ import VDS
if let delegate { if let delegate {
onTabDidSelect = { [weak self] index in onTabDidSelect = { [weak self] index in
guard let self else { return } guard let self else { return }
viewModel.selectedIndex = index
delegate.didSelectItem(.init(row: index, section: 0), tabs: self) delegate.didSelectItem(.init(row: index, section: 0), tabs: self)
} }

View File

@ -23,6 +23,11 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
public var children: [MoleculeModelProtocol] { [primaryButton, secondaryButton].compactMap { $0 } } public var children: [MoleculeModelProtocol] { [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 // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------

View File

@ -6,7 +6,7 @@
// Copyright © 2020 Verizon Wireless. All rights reserved. // Copyright © 2020 Verizon Wireless. All rights reserved.
// //
// A base class that has common list item boilerplate model stuffs. // A base class that has common list item boilerplate model stuffs.
import MVMCore
@objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol { @objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
@ -18,6 +18,7 @@
public var hideArrow: Bool? public var hideArrow: Bool?
public var line: LineModel? public var line: LineModel?
public var style: ListItemStyle? public var style: ListItemStyle?
public var gone: Bool = false
public var accessibilityTraits: UIAccessibilityTraits? public var accessibilityTraits: UIAccessibilityTraits?
public var accessibilityValue: String? public var accessibilityValue: String?
public var accessibilityText: String? public var accessibilityText: String?
@ -31,6 +32,7 @@
case hideArrow case hideArrow
case line case line
case style case style
case gone
case accessibilityTraits case accessibilityTraits
case accessibilityValue case accessibilityValue
case accessibilityText case accessibilityText
@ -107,6 +109,7 @@
hideArrow = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideArrow) hideArrow = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideArrow)
line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line)
style = try typeContainer.decodeIfPresent(ListItemStyle.self, forKey: .style) style = try typeContainer.decodeIfPresent(ListItemStyle.self, forKey: .style)
gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) ?? false
accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits) accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits)
accessibilityValue = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityValue) accessibilityValue = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityValue)
try super.init(from: decoder) try super.init(from: decoder)
@ -120,6 +123,7 @@
try container.encodeIfPresent(hideArrow, forKey: .hideArrow) try container.encodeIfPresent(hideArrow, forKey: .hideArrow)
try container.encodeIfPresent(line, forKey: .line) try container.encodeIfPresent(line, forKey: .line)
try container.encodeIfPresent(style, forKey: .style) try container.encodeIfPresent(style, forKey: .style)
try container.encode(gone, forKey: .gone)
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
try container.encodeIfPresent(accessibilityValue, forKey: .accessibilityValue) try container.encodeIfPresent(accessibilityValue, forKey: .accessibilityValue)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)

View File

@ -9,7 +9,7 @@
import UIKit import UIKit
public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
@ -19,6 +19,23 @@ public class TabsListItemModel: ListItemModel, MoleculeModelProtocol {
var molecules: [[ListItemModelProtocol & MoleculeModelProtocol]] var molecules: [[ListItemModelProtocol & MoleculeModelProtocol]]
private var addedMolecules: [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 // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------

View File

@ -76,8 +76,8 @@ import UIKit
private func setDefaultState() { private func setDefaultState() {
headlineBodyButton.headlineBody.headlineLabel.font = MFStyler.fontBoldTitleMedium() headlineBodyButton.headlineBody.headlineLabel.setFontStyle(.BoldTitleMedium)
headlineBodyButton.headlineBody.messageLabel.font = MFStyler.fontRegularMicro() headlineBodyButton.headlineBody.messageLabel.setFontStyle(.RegularMicro)
imageLoader.imageView.contentMode = .scaleAspectFit imageLoader.imageView.contentMode = .scaleAspectFit
imageLoader.addSizeConstraintsForAspectRatio = true imageLoader.addSizeConstraintsForAspectRatio = true
buttonHeaderPadding = PaddingTwo buttonHeaderPadding = PaddingTwo

View File

@ -18,10 +18,19 @@ public class CornerLabelsModel: ParentMoleculeModelProtocol {
public var bottomLeftLabel: LabelModel? public var bottomLeftLabel: LabelModel?
public var bottomRightLabel: LabelModel? public var bottomRightLabel: LabelModel?
public var molecule: MoleculeModelProtocol? public var molecule: MoleculeModelProtocol?
public var children: [MoleculeModelProtocol] { public var children: [MoleculeModelProtocol] {
[molecule, topLeftLabel, topRightLabel, bottomLeftLabel, bottomRightLabel].compactMap { $0 } [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?) { public init(with molecule: MoleculeModelProtocol?) {
self.molecule = molecule self.molecule = molecule
} }

View File

@ -14,7 +14,7 @@ import UIKit
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public let label = Label(fontStyle: .BoldBodySmall) public let label = Label().with { $0.font = Styler.Font.BoldFeatureXLarge.getFont() }
public let toggle = Toggle() public let toggle = Toggle()
//-------------------------------------------------- //--------------------------------------------------

View File

@ -23,7 +23,7 @@ open class ModuleMolecule: Container {
super.set(with: model, delegateObject, additionalData) super.set(with: model, delegateObject, additionalData)
guard let moduleMoleculeModel = model as? ModuleMoleculeModel, 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 // Critical error
return return
} }
@ -49,7 +49,7 @@ open class ModuleMolecule: Container {
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
guard let moduleMolecule = model as? ModuleMoleculeModel, 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 classType = ModelRegistry.getMoleculeClass(moduleModel),
let height = classType.estimatedHeight(with: moduleModel, delegateObject) else { let height = classType.estimatedHeight(with: moduleModel, delegateObject) else {
// Critical error // Critical error
@ -60,7 +60,7 @@ open class ModuleMolecule: Container {
public override class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { public override class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
guard let moduleMolecule = model as? ModuleMoleculeModel, 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 classType = ModelRegistry.getMoleculeClass(moduleModel),
let name = classType.nameForReuse(with: moduleModel, delegateObject) else { let name = classType.nameForReuse(with: moduleModel, delegateObject) else {
// Critical error // Critical error
@ -72,7 +72,7 @@ open class ModuleMolecule: Container {
public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
guard let moduleName = (model as? ModuleMoleculeModel)?.moduleName, 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)) { if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) {
error?.pointee = errorObject error?.pointee = errorObject
MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject) MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject)

View File

@ -20,6 +20,10 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco
return [molecule] return [molecule]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &self.molecule, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Codec // MARK: - Codec
//-------------------------------------------------- //--------------------------------------------------

View File

@ -12,9 +12,14 @@ public protocol MoleculeContainerModelProtocol: ContainerModelProtocol, ParentMo
} }
public extension MoleculeContainerModelProtocol { public extension MoleculeContainerModelProtocol {
var children: [MoleculeModelProtocol] { var children: [MoleculeModelProtocol] {
return [molecule] return [molecule]
} }
}
public extension MoleculeContainerModelProtocol where Self: AnyObject {
mutating func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &molecule, with: replacementMolecule)
}
} }

View File

@ -25,6 +25,13 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule
[eyebrow, headline, body, link].compactMap { (molecule: MoleculeModelProtocol?) in molecule } [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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -62,8 +62,8 @@
private func defaultState() { private func defaultState() {
headlineBody.headlineLabel.font = Styler.Font.BoldTitleMedium.getFont() headlineBody.headlineLabel.setFontStyle(.BoldTitleMedium)
headlineBody.messageLabel.font = Styler.Font.RegularMicro.getFont() headlineBody.messageLabel.setFontStyle(.RegularMicro)
button.use = .secondary button.use = .secondary
button.isHidden = false button.isHidden = false
buttonHeadlinePadding = PaddingTwo buttonHeadlinePadding = PaddingTwo

View File

@ -24,13 +24,9 @@
[headline, body].compactMap { $0 } [headline, body].compactMap { $0 }
} }
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool { public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
return [ return try replaceChildMolecule(at:&headline, with: replacementMolecule)
\HeadlineBodyModel.headline, || replaceChildMolecule(at:&body, with: replacementMolecule)
\HeadlineBodyModel.body,
].contains {
replaceChildMolecule(on: self, keyPath: $0, replacementMolecule: replacementMolecule)
}
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -181,6 +181,10 @@ open class Carousel: View {
} }
} }
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return (model as? CarouselModel)?.height ?? 80
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - JSON Setters // MARK: - JSON Setters
//-------------------------------------------------- //--------------------------------------------------

View File

@ -179,4 +179,8 @@ extension CarouselModel {
return molecules return molecules
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(in: &molecules, with: molecule)
}
} }

View File

@ -21,19 +21,6 @@
public var spacing: CGFloat = Padding.Four public var spacing: CGFloat = Padding.Four
public var useStackSpacingBeforeFirstItem = false public var useStackSpacingBeforeFirstItem = false
public var children: [MoleculeModelProtocol] {
return molecules
}
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) -> Bool {
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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------

View File

@ -16,7 +16,12 @@ public protocol StackModelProtocol: ParentMoleculeModelProtocol {
} }
extension StackModelProtocol { extension StackModelProtocol {
public var children: [MoleculeModelProtocol] { return molecules } public var children: [MoleculeModelProtocol] { return molecules }
}
extension StackModelProtocol where Self: AnyObject {
public mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(in: &molecules, with: molecule)
}
} }

View File

@ -20,6 +20,7 @@ public protocol ListItemModelProtocol: ContainerModelProtocol, AccessibilityMode
var action: ActionModelProtocol? { get set } var action: ActionModelProtocol? { get set }
var hideArrow: Bool? { get set } var hideArrow: Bool? { get set }
var style: ListItemStyle? { get set } var style: ListItemStyle? { get set }
var gone: Bool { get set }
} }
// Not a strict requirement. // Not a strict requirement.

View File

@ -68,8 +68,14 @@ public extension Array where Element == MoleculeModelProtocol {
} }
func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) { func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) {
forEach { (molecule) in var shouldStop = false
molecule.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) let stopIntercept = { depth, molecule, stop in
onVisit(depth, molecule, &shouldStop)
stop = shouldStop
}
for molecule in self {
molecule.depthFirstTraverse(options: options, depth: depth, onVisit: stopIntercept)
if shouldStop { break }
} }
} }
} }

View File

@ -14,4 +14,6 @@ public protocol PageModelProtocol {
var backgroundColor: Color? { get set } var backgroundColor: Color? { get set }
var navigationBar: (NavigationItemModelProtocol & MoleculeModelProtocol)? { get set } var navigationBar: (NavigationItemModelProtocol & MoleculeModelProtocol)? { get set }
var shouldMaskScreenWhileRecording: Bool? { get } var shouldMaskScreenWhileRecording: Bool? { get }
var hideLeftPanel: Bool? { get }
var hideRightPanel: Bool? { get }
} }

View File

@ -8,11 +8,48 @@
import Foundation import Foundation
public protocol ParentMoleculeModelProtocol: MoleculeModelProtocol, AnyObject { public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol {
/// Returns the direct children of this component. (Does not recurse.)
var children: [MoleculeModelProtocol] { get } var children: [MoleculeModelProtocol] { get }
func replaceChildMolecule(with molecule: MoleculeModelProtocol) -> Bool /// Method for replacing surface level children. (Does not recurse.)
mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool
}
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 }
/// Helper function for replacing a single molecules with type and optionality checks.
func replaceChildMolecule<T>(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
}
/// Helper for replacing a molecule in place within an array. Note the "in".
func replaceChildMolecule<T>(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 { public extension ParentMoleculeModelProtocol {
@ -37,43 +74,27 @@ public extension ParentMoleculeModelProtocol {
} }
func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool)->Void) { func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool)->Void) {
var stop = false var shouldStop = false
if (options == .parentFirst) { if (options == .parentFirst) {
onVisit(depth, self, &stop) onVisit(depth, self, &shouldStop)
guard !stop else { return } 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 { for child in children {
if let additionalParent = child as? ParentMoleculeModelProtocol { if let additionalParent = child as? ParentMoleculeModelProtocol {
// Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol. // 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 { } 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) { if (options == .childFirst) {
onVisit(depth, self, &stop) onVisit(depth, self, &shouldStop)
} }
// if options == .leafOnly don't call on self. // 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 on a path.
func replaceChildMolecule<P: ParentMoleculeModelProtocol, T: MoleculeModelProtocol>(on target: P, keyPath: ReferenceWritableKeyPath<P, T?>, replacementMolecule: MoleculeModelProtocol) -> Bool {
if let currentMolecule = target[keyPath: keyPath], currentMolecule.id == replacementMolecule.id, let newHeadline = replacementMolecule as? T {
target[keyPath: keyPath] = newHeadline
return true
}
return false
}
func replaceChildMolecule<P: ParentMoleculeModelProtocol, T: MoleculeModelProtocol>(on target: P, keyPath: ReferenceWritableKeyPath<P, T>, replacementMolecule: MoleculeModelProtocol) -> Bool {
if target[keyPath: keyPath].id == replacementMolecule.id, let newHeadline = replacementMolecule as? T {
target[keyPath: keyPath] = newHeadline
return true
}
return false
}
} }

View File

@ -7,13 +7,17 @@
// //
public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol { public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol, ParentModelProtocol {
var template: String { get } var template: String { get }
var rootMolecules: [MoleculeModelProtocol] { get } var rootMolecules: [MoleculeModelProtocol] { get }
} }
public extension TemplateModelProtocol { public extension TemplateModelProtocol {
var children: [MoleculeModelProtocol] {
return rootMolecules
}
var template: String { var template: String {
get { return Self.identifier } get { return Self.identifier }
} }
@ -34,3 +38,31 @@ public extension TemplateModelProtocol {
return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit)
} }
} }
extension TemplateModelProtocol {
/// Recursively finds and replaces the first child matching the replacement molecule id property.
mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
// Attempt root level replacement on the template model first.
if try 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 var 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
}
}

View File

@ -9,39 +9,49 @@
public protocol MoleculeDelegateProtocol: AnyObject { public protocol MoleculeDelegateProtocol: AnyObject {
func getTemplateModel() -> TemplateModelProtocol?
func getRootMolecules() -> [MoleculeModelProtocol] 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(_ 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. /// 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 moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) //optional
func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol])
} }
extension MoleculeDelegateProtocol { extension MoleculeDelegateProtocol {
public func getRootMolecules() -> [MoleculeModelProtocol] {
getTemplateModel()?.rootMolecules ?? []
}
public func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } public func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { }
public func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? { public func getModuleWithName(_ moleculeName: String) throws -> MoleculeModelProtocol? {
let moduleJSON: [AnyHashable: Any]? = getModuleWithName(moleculeName) let moduleJSON: [AnyHashable: Any]? = getModuleWithName(moleculeName)
guard let moduleJSON = moduleJSON as? [String: Any], guard let moduleJSON = moduleJSON as? [String: Any] else { return nil }
let moleculeName = moduleJSON.optionalStringForKey("moleculeName"),
let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self)
else { return nil }
do { guard let moleculeName = moduleJSON.optionalStringForKey(KeyMoleculeName),
return try modelType.decode(jsonDict: moduleJSON as [String : Any]) as? MoleculeModelProtocol let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) else {
} catch { throw ModelRegistry.Error.decoderErrorModelNotMapped(identifer: moduleJSON.optionalStringForKey(KeyMoleculeName))
MVMCoreUILoggingHandler.logDebugMessage(withDelegate: "error: \(error)")
} }
return nil return try modelType.decode(jsonDict: moduleJSON as [String : Any]) as? MoleculeModelProtocol
} }
} }
extension MoleculeDelegateProtocol where Self: TemplateProtocol { extension MoleculeDelegateProtocol where Self: TemplateProtocol {
public func getTemplateModel() -> TemplateModelProtocol? {
return templateModel
}
public func getRootMolecules() -> [MoleculeModelProtocol] { public func getRootMolecules() -> [MoleculeModelProtocol] {
templateModel?.rootMolecules ?? [] templateModel?.rootMolecules ?? []
} }

View File

@ -38,7 +38,7 @@ public extension MoleculeTreeTraversalProtocol {
func printMolecules(options: TreeTraversalOptions = .parentFirst) { func printMolecules(options: TreeTraversalOptions = .parentFirst) {
depthFirstTraverse(options: options, depth: 1) { depth, molecule, stop in 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)]")
} }
} }
@ -51,10 +51,23 @@ public extension MoleculeTreeTraversalProtocol {
} }
} }
func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) { func filterMoleculeTree(options: TreeTraversalOptions = .parentFirst, by condition: (MoleculeModelProtocol)->Bool) -> [MoleculeModelProtocol] {
depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in return reduceDepthFirstTraverse(options: options, depth: 0, initialResult: []) { (accumulator, molecule, depth) in
guard let parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } if condition(molecule) {
stop = parentMolecule.replaceChildMolecule(with: replacementMolecule) 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
}
} }

View File

@ -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. /// Helper function to do common parsing logic.
func parseTemplate(json: [AnyHashable: Any]?) throws { func parseTemplate(json: [AnyHashable: Any]?) throws {
guard let pageJSON = json else { return } 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 data = try JSONSerialization.data(withJSONObject: pageJSON)
let decoder = JSONDecoder.create(with: delegateObject) let decoder = JSONDecoder.create(with: delegateObject)
templateModel = try decodeTemplate(using: decoder, from: data) templateModel = try decodeTemplate(using: decoder, from: data)
// Add additional required behaviors if applicable. // Add additional required behaviors if applicable.
guard var behaviorHandlerModel = templateModel as? TemplateModelProtocol & PageBehaviorHandlerModelProtocol, guard var pageBehaviorsModel = templateModel as? TemplateModelProtocol & PageBehaviorContainerModelProtocol else { return }
var behaviorHandler = self as? PageBehaviorHandlerProtocol else { return }
behaviorHandlerModel.traverseAndAddRequiredBehaviors() pageBehaviorsModel.traverseAndAddRequiredBehaviors()
behaviorHandler.createBehaviors(for: behaviorHandlerModel, delegateObject: delegateObject) var behaviorHandler = self
if let viewController = self as? UIViewController { behaviorHandler.applyBehaviors(pageBehaviorModel: pageBehaviorsModel, delegateObject: delegateObject)
MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController)
}
}
func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel {
try decoder.decode(TemplateModel.self, from: data)
} }
} }

View File

@ -32,7 +32,13 @@ import Foundation
public var tabBarIndex: Int? public var tabBarIndex: Int?
public var shouldMaskScreenWhileRecording: Bool? public var shouldMaskScreenWhileRecording: Bool?
public var hideLeftPanel: Bool?
public var hideRightPanel: Bool?
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &navigationBar, with: molecule)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------
@ -56,6 +62,8 @@ import Foundation
case tabBarHidden case tabBarHidden
case tabBarIndex case tabBarIndex
case shouldMaskScreenWhileRecording case shouldMaskScreenWhileRecording
case hideLeftPanel
case hideRightPanel
} }
//-------------------------------------------------- //--------------------------------------------------
@ -79,6 +87,12 @@ import Foundation
if let tabBarHidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .tabBarHidden) { if let tabBarHidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .tabBarHidden) {
self.tabBarHidden = tabBarHidden self.tabBarHidden = tabBarHidden
} }
if let hideLeftPanel = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideLeftPanel) {
self.hideLeftPanel = hideLeftPanel
}
if let hideRightPanel = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideRightPanel) {
self.hideRightPanel = hideRightPanel
}
tabBarIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .tabBarIndex) tabBarIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .tabBarIndex)
shouldMaskScreenWhileRecording = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskScreenWhileRecording) shouldMaskScreenWhileRecording = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskScreenWhileRecording)
} }
@ -92,6 +106,8 @@ import Foundation
try container.encodeIfPresent(formRules, forKey: .formRules) try container.encodeIfPresent(formRules, forKey: .formRules)
try container.encodeModelIfPresent(navigationBar, forKey: .navigationBar) try container.encodeModelIfPresent(navigationBar, forKey: .navigationBar)
try container.encode(tabBarHidden, forKey: .tabBarHidden) try container.encode(tabBarHidden, forKey: .tabBarHidden)
try container.encode(hideLeftPanel, forKey: .hideLeftPanel)
try container.encode(hideRightPanel, forKey: .hideRightPanel)
try container.encodeIfPresent(tabBarIndex, forKey: .tabBarIndex) try container.encodeIfPresent(tabBarIndex, forKey: .tabBarIndex)
try container.encode(shouldMaskScreenWhileRecording, forKey: .shouldMaskScreenWhileRecording) try container.encode(shouldMaskScreenWhileRecording, forKey: .shouldMaskScreenWhileRecording)
} }

View File

@ -81,11 +81,15 @@
open override func handleNewData() { open override func handleNewData() {
setup()
registerCells()
super.handleNewData()
}
open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
setup() super.updateUI(for: molecules)
registerCells()
super.handleNewData()
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,6 +22,11 @@
} }
return super.rootMolecules 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 // MARK: - Initializer

View File

@ -26,6 +26,12 @@
return super.rootMolecules 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. /// This template requires content.
func validateModelHasContent() throws { func validateModelHasContent() throws {
if header == nil, if header == nil,

View File

@ -21,8 +21,8 @@ open class ModalSectionListTemplate: SectionListTemplate {
// MARK: - Lifecycle // MARK: - Lifecycle
//-------------------------------------------------- //--------------------------------------------------
override open func handleNewData() { override open func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
super.handleNewData() super.updateUI(for: molecules)
_ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: { [weak self] _ in _ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
let closeAction = (self.templateModel as? ModalSectionListTemplateModel)?.closeAction ?? let closeAction = (self.templateModel as? ModalSectionListTemplateModel)?.closeAction ??

View File

@ -82,11 +82,22 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
} }
open override func handleNewData() { open override func handleNewData() {
topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
setup() setup()
registerWithTable() registerWithTable()
super.handleNewData() super.handleNewData() // Currently stuck as MoleculeListProtocol being called from AddRemoveMoleculesBehaviorModel.
}
open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
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) { open override func viewDidAppear(_ animated: Bool) {
@ -124,6 +135,10 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
} }
} }
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return (getMoleculeInfo(for: indexPath)?.molecule as? ListItemModelProtocol)?.gone == true ? 0 : UITableView.automaticDimension
}
open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
guard let moleculeInfo = getMoleculeInfo(for: indexPath), guard let moleculeInfo = getMoleculeInfo(for: indexPath),
let estimatedHeight = (moleculeInfo.class as? MoleculeViewProtocol.Type)?.estimatedHeight(with: moleculeInfo.molecule, delegateObject() as? MVMCoreUIDelegateObject) let estimatedHeight = (moleculeInfo.class as? MoleculeViewProtocol.Type)?.estimatedHeight(with: moleculeInfo.molecule, delegateObject() as? MVMCoreUIDelegateObject)
@ -141,7 +156,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
guard let moleculeInfo = getMoleculeInfo(for: indexPath), guard let moleculeInfo = getMoleculeInfo(for: indexPath),
let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier) let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier)
else { return UITableViewCell() } else { return UITableViewCell() }
cell.isHidden = (getMoleculeInfo(for: indexPath)?.molecule as? ListItemModelProtocol)?.gone == true
(cell as? MoleculeViewProtocol)?.reset() (cell as? MoleculeViewProtocol)?.reset()
(cell as? MoleculeListCellProtocol)?.setLines(with: templateModel?.line, delegateObject: delegateObjectIVar, additionalData: nil, indexPath: indexPath) (cell as? MoleculeListCellProtocol)?.setLines(with: templateModel?.line, delegateObject: delegateObjectIVar, additionalData: nil, indexPath: indexPath)
if let moleculeView = cell as? MoleculeViewProtocol { if let moleculeView = cell as? MoleculeViewProtocol {
@ -191,35 +206,43 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
} }
open func newData(for molecule: MoleculeModelProtocol) { open func newData(for molecule: MoleculeModelProtocol) {
//TODO: expand for header, navigation, etc
guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in //Check header and footer if replace happens then return.
if equal(moleculeA: molecule, moleculeB: moleculeInfo.molecule) { if updateHeaderFooterView(topView, with: molecule) ||
return true updateHeaderFooterView(bottomView, with: molecule, isHeader: false) {
} else if let parent = moleculeInfo.molecule as? ParentMoleculeModelProtocol { return
// 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 { guard let moleculesInfo = moleculesInfo else { return }
return accumulator + [currentMolecule]
} let indicies = moleculesInfo.indices.filter({ index -> Bool in
return accumulator return moleculesInfo[index].molecule.findFirstMolecule(by: {
} $0.moleculeName == molecule.moleculeName && equal(moleculeA: molecule, moleculeB: $0)
for moleculeB in molecules { }) != nil
if equal(moleculeA: molecule, moleculeB: moleculeB) { })
return true
}
}
}
return false
}) else { return }
// Refresh the cell. (reload loses cell selection) // Refresh the cell. (reload loses cell selection)
let selectedIndex = tableView.indexPathForSelectedRow let selectedIndex = tableView.indexPathForSelectedRow
let indexPath = IndexPath(row: index, section: 0) let indexPaths = indicies.map {
tableView.reloadRows(at: [indexPath], with: .automatic) return IndexPath(row: $0, section: 0)
}
tableView.reloadRows(at: indexPaths, with: .automatic)
if let selectedIndex = selectedIndex { if let selectedIndex = selectedIndex {
tableView.selectRow(at: selectedIndex, animated: false, scrollPosition: .none) tableView.selectRow(at: selectedIndex, animated: false, scrollPosition: .none)
} }
} }
///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/footer for content update
isHeader ? showHeader(nil) : showFooter(nil)
return true
}
return false
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Convenience // MARK: - Convenience
@ -293,7 +316,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
} }
extension MoleculeListTemplate: MoleculeListProtocol { 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() { for (index, indexPath) in indexPaths.sorted().enumerated() {
let removeIndex = indexPath.row - index let removeIndex = indexPath.row - index
moleculesInfo?.remove(at: removeIndex) moleculesInfo?.remove(at: removeIndex)
@ -306,7 +329,7 @@ extension MoleculeListTemplate: MoleculeListProtocol {
view.layoutIfNeeded() 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] = [] var indexPaths: [IndexPath] = []
for molecule in molecules { for molecule in molecules {
@ -325,7 +348,7 @@ extension MoleculeListTemplate: MoleculeListProtocol {
self.view.layoutIfNeeded() 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 guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in
return equal(moleculeA: molecule, moleculeB: moleculeInfo.molecule) return equal(moleculeA: molecule, moleculeB: moleculeInfo.molecule)
}) else { return nil } }) else { return nil }

View File

@ -16,7 +16,13 @@
public var moleculeStack: StackModel public var moleculeStack: StackModel
public override var rootMolecules: [MoleculeModelProtocol] { 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)
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -26,8 +26,8 @@
} }
} }
open override func handleNewData() { open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
super.handleNewData() super.updateUI(for: molecules)
heightConstraint?.isActive = true heightConstraint?.isActive = true
} }
} }

View File

@ -21,6 +21,12 @@
[navigationBar, header, footer].compactMap { $0 } [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 // MARK: - Init
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,6 +22,11 @@
return super.rootMolecules 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 // MARK: - Init
//-------------------------------------------------- //--------------------------------------------------

View File

@ -19,10 +19,10 @@ import UIKit
try super.parsePageJSON() try super.parsePageJSON()
} }
open override func handleNewData() { open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
topViewOutsideOfScroll = templateModel?.anchorHeader ?? false topViewOutsideOfScroll = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false
super.handleNewData() super.updateUI(for: molecules)
} }
open override func viewForTop() -> UIView? { open override func viewForTop() -> UIView? {

View File

@ -9,6 +9,6 @@
import Foundation import Foundation
public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorHandlerModelProtocol { public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorContainerModelProtocol {
} }

View File

@ -63,8 +63,8 @@ open class ScrollingViewController: ViewController {
registerForKeyboardNotifications() registerForKeyboardNotifications()
} }
open override func handleNewData() { open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
super.handleNewData() super.updateUI(for: molecules)
// will change scrollView indicatorStyle automatically on the basis of backgroundColor // will change scrollView indicatorStyle automatically on the basis of backgroundColor
var greyScale: CGFloat = 0 var greyScale: CGFloat = 0
if view.backgroundColor?.getWhite(&greyScale, alpha: nil) ?? false { if view.backgroundColor?.getWhite(&greyScale, alpha: nil) ?? false {

View File

@ -112,8 +112,8 @@ import Foundation
} }
} }
open override func handleNewData() { open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
super.handleNewData() super.updateUI(for: molecules)
topView?.removeFromSuperview() topView?.removeFromSuperview()
bottomView?.removeFromSuperview() bottomView?.removeFromSuperview()
topView = viewForTop() topView = viewForTop()

View File

@ -55,8 +55,11 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController, Rotor
tableView.reloadData() tableView.reloadData()
} }
open override func handleNewData() { open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
super.handleNewData() super.updateUI(for: molecules)
guard molecules == nil else { return }
createViewForTableHeader() createViewForTableHeader()
createViewForTableFooter() createViewForTableFooter()
tableView?.reloadData() tableView?.reloadData()

View File

@ -49,8 +49,8 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController, RotorView
} }
} }
open override func handleNewData() { open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
super.handleNewData() super.updateUI(for: molecules)
// Removes the views // Removes the views
topView?.removeFromSuperview() topView?.removeFromSuperview()

View File

@ -38,12 +38,19 @@ import MVMCore
public var behaviors: [PageBehaviorProtocol]? public var behaviors: [PageBehaviorProtocol]?
public var needsUpdateUI = false public var needsUpdateUI = false
private var observingForResponses = false private var observingForResponses: NSObjectProtocol?
private var initialLoadFinished = false private var initialLoadFinished = false
public var previousScreenSize = CGSize.zero public var previousScreenSize = CGSize.zero
public var selectedField: UIView? 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 /// Checks if the screen width has changed
open func screenSizeChanged() -> Bool { open func screenSizeChanged() -> Bool {
!MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1) !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1)
@ -54,18 +61,17 @@ import MVMCore
//-------------------------------------------------- //--------------------------------------------------
open func observeForResponseJSONUpdates() { open func observeForResponseJSONUpdates() {
guard !observingForResponses, guard observingForResponses == nil,
(pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0) (pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0)
else { return } else { return }
observingForResponses = true observingForResponses = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil, queue: pageUpdateQueue, using: responseJSONUpdated(notification:))
NotificationCenter.default.addObserver(self, selector: #selector(responseJSONUpdated(notification:)), name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil)
} }
open func stopObservingForResponseJSONUpdates() { open func stopObservingForResponseJSONUpdates() {
guard observingForResponses else { return } guard let observingForResponses = observingForResponses else { return }
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil) NotificationCenter.default.removeObserver(observingForResponses)
observingForResponses = false self.observingForResponses = nil
} }
open func pagesToListenFor() -> [String]? { open func pagesToListenFor() -> [String]? {
@ -74,7 +80,9 @@ import MVMCore
} }
open func modulesToListenFor() -> [String]? { 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) { @objc open func responseJSONUpdated(notification: Notification) {
@ -109,9 +117,12 @@ import MVMCore
guard newData else { return } guard newData else { return }
do { 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() try parsePageJSON()
MVMCoreDispatchUtility.performBlock(onMainThread: { MVMCoreDispatchUtility.performBlock(onMainThread: {
self.handleNewDataAndUpdateUI() self.handleNewData()
}) })
} catch { } catch {
if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") { if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") {
@ -158,40 +169,13 @@ import MVMCore
} }
return false return false
} }
return true return true
} }
func describe(parsingError: Error) -> String { func describe(parsingError: Error) -> String {
if let registryError = parsingError as? ModelRegistry.Error { if let error = parsingError as? HumanReadableDecodingErrorProtocol {
switch (registryError) { return "Error parsing template. \(error.readableDescription)"
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)"
}
} }
return "Error parsing template. \((parsingError as NSError).localizedFailureReason ?? parsingError.localizedDescription)" return "Error parsing template. \((parsingError as NSError).localizedFailureReason ?? parsingError.localizedDescription)"
} }
@ -227,13 +211,6 @@ import MVMCore
return true return true
} }
/// Calls processNewData and then sets the ui to update with updateView
open func handleNewDataAndUpdateUI() {
handleNewData()
needsUpdateUI = true
view.setNeedsLayout()
}
/// Creates a legacy navigation model. /// Creates a legacy navigation model.
open func createDefaultLegacyNavigationModel() -> NavigationItemModel { open func createDefaultLegacyNavigationModel() -> NavigationItemModel {
let navigationModel = NavigationItemModel() let navigationModel = NavigationItemModel()
@ -241,7 +218,8 @@ import MVMCore
return navigationModel 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() { open func handleNewData() {
if model?.navigationBar == nil { if model?.navigationBar == nil {
let navigationItem = createDefaultLegacyNavigationModel() let navigationItem = createDefaultLegacyNavigationModel()
@ -257,12 +235,27 @@ import MVMCore
formValidator = FormValidator(rules) formValidator = FormValidator(rules)
} }
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.
open func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
guard molecules == nil else { return }
executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
behavior.willRender(rootMolecules: getRootMolecules(), delegateObjectIVar)
}
if let backgroundColor = model?.backgroundColor { if let backgroundColor = model?.backgroundColor {
view.backgroundColor = backgroundColor.uiColor view.backgroundColor = backgroundColor.uiColor
} }
// Notify the manager of new data needsUpdateUI = true
manager?.newDataReceived?(in: self) view.setNeedsLayout()
} }
public func generateMoleculeView(from model: MoleculeModelProtocol) -> MoleculeViewProtocol? { public func generateMoleculeView(from model: MoleculeModelProtocol) -> MoleculeViewProtocol? {
@ -331,7 +324,7 @@ import MVMCore
initialLoad() initialLoad()
} }
handleNewDataAndUpdateUI() handleNewData()
} }
open override func viewDidLayoutSubviews() { open override func viewDidLayoutSubviews() {
@ -511,13 +504,47 @@ import MVMCore
// MARK: - MoleculeDelegateProtocol // MARK: - MoleculeDelegateProtocol
//-------------------------------------------------- //--------------------------------------------------
open func getRootMolecules() -> [MoleculeModelProtocol] { open func getTemplateModel() -> TemplateModelProtocol? { model }
model?.rootMolecules ?? []
} open func getRootMolecules() -> [MoleculeModelProtocol] { model?.rootMolecules ?? [] }
// Needed otherwise when subclassed, the extension gets called. // Needed otherwise when subclassed, the extension gets called.
open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } 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 {
DispatchQueue.main.sync {
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.shared()?.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.shared()?.addError(toLog: coreError)
}
return didReplace
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - MVMCoreUIDetailViewProtocol // MARK: - MVMCoreUIDetailViewProtocol
//-------------------------------------------------- //--------------------------------------------------
@ -532,7 +559,7 @@ import MVMCore
public func isRightPanelAccessible() -> Bool { public func isRightPanelAccessible() -> Bool {
// TODO: Remove when FAB is 100%. // TODO: Remove when FAB is 100%.
if loadObject?.pageJSON?.boolForKey(KeyHideMainMenu) ?? false { if let hideRightPanel = model?.hideRightPanel, hideRightPanel == true {
return false return false
} }
return (MVMCoreUISession.sharedGlobal()?.launchAppLoadedSuccessfully ?? false) || showRightPanelForScreenBeforeLaunchApp() return (MVMCoreUISession.sharedGlobal()?.launchAppLoadedSuccessfully ?? false) || showRightPanelForScreenBeforeLaunchApp()

View File

@ -76,7 +76,7 @@ public class AddRemoveMoleculesBehavior: PageCustomActionHandlerBehavior, PageMo
self.delegate = delegateObject self.delegate = delegateObject
} }
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { public func willRender(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {
guard let list = delegate?.moleculeListDelegate else { return } guard let list = delegate?.moleculeListDelegate else { return }
for case let model as (MoleculeModelProtocol & ListItemModelProtocol & AddMolecules) in rootMolecules { for case let model as (MoleculeModelProtocol & ListItemModelProtocol & AddMolecules) in rootMolecules {
if let moleculesToAdd = model.getRecursiveMoleculesToAdd(), if let moleculesToAdd = model.getRecursiveMoleculesToAdd(),

View File

@ -43,7 +43,7 @@ public class PageGetContactBehavior: PageVisibilityBehavior {
MVMCoreDispatchUtility.performBlock(onMainThread: { MVMCoreDispatchUtility.performBlock(onMainThread: {
// TODO: move to protocol function instead // TODO: move to protocol function instead
guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return } guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return }
controller.handleNewDataAndUpdateUI() controller.handleNewData()
}) })
} }
} }

View File

@ -45,11 +45,12 @@ public class GetNotificationAuthStatusBehavior: PageVisibilityBehavior {
for consumer in consumers { for consumer in consumers {
consumer.consume(notificationStatus: settings.authorizationStatus) 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 } guard let controller = self.delegate?.moleculeDelegate as? ViewController else { return }
controller.handleNewDataAndUpdateUI() await controller.handleNewData()
}) }
} }
} }

View File

@ -0,0 +1,104 @@
//
// 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: ActionModelProtocol
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 {
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: ActionModelProtocol, interval: TimeInterval) {
let delegateObject = delegateObject
pollTimer?.cancel()
pollTimer = DispatchSource.makeTimerSource()
pollTimer?.schedule(deadline: .now() + timeRemaining, repeating: interval)
pollTimer?.setEventHandler(qos:.utility) {
Task {
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()
}
deinit {
pollTimer?.cancel()
}
}

View File

@ -1,16 +1,17 @@
// //
// PageBehaviorHandlerModelProtocol.swift // PageBehaviorConatinerModelProtocol.swift
// MVMCoreUI // MVMCoreUI
// //
// Created by Scott Pfeil on 3/29/21. // Created by Scott Pfeil on 3/29/21.
// Copyright © 2021 Verizon Wireless. All rights reserved. // Copyright © 2021 Verizon Wireless. All rights reserved.
// //
public protocol PageBehaviorHandlerModelProtocol { /// Protocol applied to a model that contains a list of behavior models.
public protocol PageBehaviorContainerModelProtocol {
var behaviors: [PageBehaviorModelProtocol]? { get set } var behaviors: [PageBehaviorModelProtocol]? { get set }
} }
public extension PageBehaviorHandlerModelProtocol { public extension PageBehaviorContainerModelProtocol {
/// Adds the behavior model to the behaviors if possible. /// Adds the behavior model to the behaviors if possible.
mutating func add(behavior: PageBehaviorModelProtocol) { mutating func add(behavior: PageBehaviorModelProtocol) {
@ -24,7 +25,7 @@ public extension PageBehaviorHandlerModelProtocol {
} }
} }
public extension PageBehaviorHandlerModelProtocol where Self: MoleculeTreeTraversalProtocol { public extension PageBehaviorContainerModelProtocol where Self: MoleculeTreeTraversalProtocol {
/// Traverses all models and adds any required behavior models. /// Traverses all models and adds any required behavior models.
mutating func traverseAndAddRequiredBehaviors() { mutating func traverseAndAddRequiredBehaviors() {

View File

@ -12,20 +12,10 @@ public protocol PageBehaviorHandlerProtocol {
} }
public extension PageBehaviorHandlerProtocol { public extension PageBehaviorHandlerProtocol {
/// Creates the behaviors and sets the variable. /// Creates the behaviors and sets the variable.
mutating func createBehaviors(for model: PageBehaviorHandlerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { func createBehaviors(for behaviorModels: [PageBehaviorModelProtocol], delegateObject: MVMCoreUIDelegateObject?) -> [PageBehaviorProtocol] {
var behaviors = [PageBehaviorProtocol]()
behaviors = behaviors?.filter { $0.transcendsPageUpdates }
if behaviors?.isEmpty ?? false {
behaviors = nil
}
guard let behaviorModels = model.behaviors else {
return
}
var behaviors: [PageBehaviorProtocol] = behaviors ?? []
for behaviorModel in behaviorModels { for behaviorModel in behaviorModels {
do { do {
let handlerType = try ModelRegistry.getHandler(behaviorModel) as! PageBehaviorProtocol.Type let handlerType = try ModelRegistry.getHandler(behaviorModel) as! PageBehaviorProtocol.Type
@ -37,7 +27,23 @@ public extension PageBehaviorHandlerProtocol {
} }
} }
} }
return behaviors
}
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.
let newBehaviors = createBehaviors(for: pageBehaviorModel.behaviors ?? [], delegateObject: delegateObject)
behaviors.append(contentsOf: newBehaviors)
// Apply them to the page.
self.behaviors = behaviors.count > 0 ? behaviors : nil 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. /// 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 return try behaviors?.compactMap({$0 as? T}).allSatisfy({ return try behaviourBlock($0) }) ?? true
} }
} }
public extension PageBehaviorHandlerProtocol where Self: MVMCoreViewControllerProtocol {
mutating func applyBehaviors(pageBehaviorModel: PageBehaviorContainerModelProtocol) {
applyBehaviors(pageBehaviorModel: pageBehaviorModel, delegateObject: delegateObject?() as? MVMCoreUIDelegateObject)
}
}

View File

@ -14,12 +14,15 @@ public protocol PageBehaviorProtocol: ModelHandlerProtocol {
/// Should the behavior persist regardless of page behavior model updates. /// Should the behavior persist regardless of page behavior model updates.
var transcendsPageUpdates: Bool { get } var transcendsPageUpdates: Bool { get }
func modulesToListenFor() -> [String]
/// Initializes the behavior with the model /// Initializes the behavior with the model
init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?)
} }
public extension PageBehaviorProtocol { public extension PageBehaviorProtocol {
var transcendsPageUpdates: Bool { return false } var transcendsPageUpdates: Bool { return false }
func modulesToListenFor() -> [String] { return [] }
} }
/** /**
@ -32,6 +35,7 @@ public protocol PageMoleculeTransformationBehavior: PageBehaviorProtocol {
func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol)
func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar)
func didSetupNavigationBar(view: UINavigationBar, with model: NavigationItemModelProtocol) func didSetupNavigationBar(view: UINavigationBar, with model: NavigationItemModelProtocol)
func willRender(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?)
func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject) throws -> Bool func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject) throws -> Bool
} }
@ -42,11 +46,11 @@ public extension PageMoleculeTransformationBehavior {
func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) {} func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) {}
func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) {} func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) {}
func didSetupNavigationBar(view: UINavigationBar, with model: NavigationItemModelProtocol) {} func didSetupNavigationBar(view: UINavigationBar, with model: NavigationItemModelProtocol) {}
func willRender(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {}
func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject) throws -> Bool { return true } func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject) throws -> Bool { return true }
} }
public protocol PageVisibilityBehavior: PageBehaviorProtocol { public protocol PageVisibilityBehavior: PageBehaviorProtocol {
func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?)
func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?)
func willHidePage(_ delegateObject: MVMCoreUIDelegateObject?) func willHidePage(_ delegateObject: MVMCoreUIDelegateObject?)
@ -84,8 +88,8 @@ public protocol PageCustomActionHandlerBehavior: PageBehaviorProtocol {
} }
public extension MVMCoreUIDelegateObject { public extension MVMCoreUIDelegateObject {
var behaviorModelDelegate: PageBehaviorHandlerModelProtocol? { var behaviorModelDelegate: PageBehaviorContainerModelProtocol? {
(moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorHandlerModelProtocol (moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorContainerModelProtocol
} }
weak var behaviorTemplateDelegate: (PageBehaviorHandlerProtocol & NSObjectProtocol)? { weak var behaviorTemplateDelegate: (PageBehaviorHandlerProtocol & NSObjectProtocol)? {

View File

@ -0,0 +1,110 @@
//
// ReplacementMoleculeBehavior.swift
// MVMCoreUI
//
// Created by Kyle Hedden on 9/12/23.
// Copyright © 2023 Verizon Wireless. All rights reserved.
//
import Foundation
import MVMCore
public class ReplaceableMoleculeBehaviorModel: PageBehaviorModelProtocol {
public class var identifier: String { "replaceMoleculeBehavior" }
public var shouldAllowMultipleInstances: Bool { true }
public var moleculeIds: [String]
}
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()
}
let moleculeModels = moleculeIds.compactMap { moleculeId in
do {
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.shared()?.addError(toLog: coreError)
return nil
}
}
if moleculeModels.count > 0 {
delegateObject?.moleculeDelegate?.replaceMoleculeData(moleculeModels)
}
}
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)
self.observingForResponses = nil
}
@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.shared()?.addError(toLog: coreError)
return nil
}
}
if modules.count > 0 {
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
}
}

View File

@ -231,6 +231,8 @@ open class CoreUIModelMapping: ModelMapping {
ModelRegistry.register(handler: PageGetContactBehavior.self, for: PageGetContactBehaviorModel.self) ModelRegistry.register(handler: PageGetContactBehavior.self, for: PageGetContactBehaviorModel.self)
ModelRegistry.register(handler: AddRemoveMoleculesBehavior.self, for: AddRemoveMoleculesBehaviorModel.self) ModelRegistry.register(handler: AddRemoveMoleculesBehavior.self, for: AddRemoveMoleculesBehaviorModel.self)
ModelRegistry.register(handler: GetNotificationAuthStatusBehavior.self, for: GetNotificationAuthStatusBehaviorModel.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() { open override class func registerActions() {

View File

@ -57,59 +57,6 @@ open class Styler {
case B2 // Maps to RegularBodySmall case B2 // Maps to RegularBodySmall
case B3 // Maps to RegularMicro case B3 // Maps to RegularMicro
/// Returns the font size of the current enum case.
public func pointSize() -> CGFloat {
switch self {
case .RegularFeatureXLarge,
.BoldFeatureXLarge:
return 96
case .RegularFeatureLarge,
.BoldFeatureLarge:
return 80
case .RegularFeatureMedium,
.BoldFeatureMedium:
return 64
case .RegularFeatureSmall,
.BoldFeatureSmall:
return 48
case .RegularFeatureXSmall,
.BoldFeatureXSmall,
.RegularTitle2XLarge,
.BoldTitle2XLarge,
.Title2XLarge,
.H1:
return 40
case .RegularTitleXLarge,
.BoldTitleXLarge,
.TitleXLarge,
.H32:
return 32
case .BoldTitleLarge,
.RegularTitleLarge,
.H2:
return 24
case .BoldTitleMedium,
.RegularTitleMedium,
.H3:
return 20
case .RegularTitleSmall,
.BoldTitleSmall,
.BoldBodyLarge,
.RegularBodyLarge,
.B20:
return 16
case .RegularBodyMedium,
.BoldBodyMedium:
return 14
case .BoldBodySmall, .B1,
.RegularBodySmall, .B2:
return 12
case .BoldMicro,
.RegularMicro, .B3:
return 11
}
}
public func color() -> UIColor { public func color() -> UIColor {
switch self { switch self {
case .B3: case .B3:
@ -148,8 +95,8 @@ open class Styler {
/// Returns the font based on the declared enum case. /// Returns the font based on the declared enum case.
public func getFont(_ genericScaling: Bool = true) -> UIFont { public func getFont(_ genericScaling: Bool = true) -> UIFont {
let size = genericScaling ? sizeFontGeneric(forCurrentDevice: pointSize()) : pointSize() let vdsStyle = vdsTextStyle() ?? .defaultStyle
return MFStyler.getFontFor(size: size, isBold: isBold()) return vdsStyle.font
} }
/// Styles the provided label to the declared enum Font case. /// Styles the provided label to the declared enum Font case.
@ -237,7 +184,7 @@ open class Styler {
/// Creates the appropriate VZW font for a VDS style, scaling based on the scaleValue threshold passed in. /// Creates the appropriate VZW font for a VDS style, scaling based on the scaleValue threshold passed in.
@objc static func getFontFor(styleString: String, scaleValue: CGFloat) -> UIFont? { @objc static func getFontFor(styleString: String, scaleValue: CGFloat) -> UIFont? {
guard let font = Styler.Font(rawValue: styleString), guard let font = Styler.Font(rawValue: styleString),
let size = Styler.Font(rawValue: styleString)?.pointSize(), let size = font.vdsTextStyle()?.pointSize,
let newSize = Styler.sizeObjectGeneric(forCurrentDevice: size)?.getValueBased(onSize: scaleValue) else { return nil } let newSize = Styler.sizeObjectGeneric(forCurrentDevice: size)?.getValueBased(onSize: scaleValue) else { return nil }
return getFontFor(size: newSize, isBold: font.isBold()) return getFontFor(size: newSize, isBold: font.isBold())
} }