Merge branch 'develop' into feature/ACT-192-Year-In-Review
# Conflicts: # MVMCoreUI/Atomic/Templates/ModalMoleculeStackTemplate.swift
This commit is contained in:
commit
291633e8fe
@ -12,6 +12,7 @@ stages:
|
||||
# - xcode_12_2
|
||||
|
||||
download_artifacts:
|
||||
when: manual
|
||||
stage: download
|
||||
script:
|
||||
- ./Scripts/download_dependencies.sh
|
||||
@ -27,6 +28,7 @@ download_artifacts:
|
||||
ARTIFACTORY_URL: https://oneartifactoryci.verizon.com/artifactory
|
||||
|
||||
build_project:
|
||||
when: manual
|
||||
stage: build
|
||||
script:
|
||||
- ./Scripts/build_aggregate.sh
|
||||
@ -37,6 +39,7 @@ build_project:
|
||||
- xcode_12_2
|
||||
|
||||
deploy_snapshot:
|
||||
when: manual
|
||||
stage: deploy
|
||||
script:
|
||||
- cd Scripts && ./upload_core_ui_frameworks.sh
|
||||
|
||||
@ -170,8 +170,15 @@
|
||||
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 */; };
|
||||
583335592BF64E77001D90D7 /* MVMCoreUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583335582BF64E77001D90D7 /* MVMCoreUITests.swift */; };
|
||||
5833355A2BF64E77001D90D7 /* MVMCoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */; };
|
||||
583335632BF6509C001D90D7 /* UAD_page_model.json in Resources */ = {isa = PBXBuildFile; fileRef = 583335622BF6509C001D90D7 /* UAD_page_model.json */; };
|
||||
583335652BF6A5C3001D90D7 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583335642BF6A5C3001D90D7 /* TestUtils.swift */; };
|
||||
583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */ = {isa = PBXBuildFile; fileRef = 583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */; };
|
||||
5833356D2BFBF51C001D90D7 /* UAD_page_model_3.json in Resources */ = {isa = PBXBuildFile; fileRef = 5833356C2BFBF51C001D90D7 /* UAD_page_model_3.json */; };
|
||||
5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; };
|
||||
58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; };
|
||||
58E7561D2BE04C320088BB5D /* MoleculeComparisonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */; };
|
||||
608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; };
|
||||
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
|
||||
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; };
|
||||
@ -219,9 +226,6 @@
|
||||
94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */; };
|
||||
94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */; };
|
||||
94C661DA23CCF4FB00D9FE5B /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */; };
|
||||
94CA227C24058534002D6750 /* VerizonNHGeTX-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 94CA227824058533002D6750 /* VerizonNHGeTX-Bold.otf */; };
|
||||
94CA227D24058534002D6750 /* VerizonNHGeDS-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 94CA227924058533002D6750 /* VerizonNHGeDS-Regular.otf */; };
|
||||
94CA227E24058534002D6750 /* VerizonNHGeDS-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 94CA227A24058533002D6750 /* VerizonNHGeDS-Bold.otf */; };
|
||||
94F6516D2437954100631BF9 /* Tabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F6516C2437954100631BF9 /* Tabs.swift */; };
|
||||
AA07EA912510A442009A2AE3 /* StarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA07EA902510A442009A2AE3 /* StarModel.swift */; };
|
||||
AA07EA932510A451009A2AE3 /* Star.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA07EA922510A451009A2AE3 /* Star.swift */; };
|
||||
@ -434,7 +438,6 @@
|
||||
D28764AC245898A400CB882D /* ThreeLayerFillMiddleTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28764AB245898A400CB882D /* ThreeLayerFillMiddleTemplateModel.swift */; };
|
||||
D28764F9245A327200CB882D /* TwoLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28764F8245A327200CB882D /* TwoLinkView.swift */; };
|
||||
D28764FB245A33A500CB882D /* TwoLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28764FA245A33A500CB882D /* TwoLinkViewModel.swift */; };
|
||||
D287651A245B338E00CB882D /* VerizonNHGeTX-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 94CA227B24058533002D6750 /* VerizonNHGeTX-Regular.otf */; };
|
||||
D28A837923C7D5BC00DFE4FC /* PageModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */; };
|
||||
D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837A23C928DA00DFE4FC /* MoleculeListCellProtocol.swift */; };
|
||||
D28A837D23CCA86A00DFE4FC /* TabsListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */; };
|
||||
@ -558,7 +561,6 @@
|
||||
D2ED27EE254B0CE700A1C293 /* ActionAlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */; };
|
||||
D2ED27EF254B0CE700A1C293 /* AlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27EA254B0CE700A1C293 /* AlertModel.swift */; };
|
||||
D2ED27FC254B0E0300A1C293 /* AlertObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2ED27F3254B0E0200A1C293 /* AlertObject.swift */; };
|
||||
D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D12513EA6900564112 /* NotificationXButton.swift */; };
|
||||
D2FA83D42514F80C00564112 /* CollapsableNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D32514F80C00564112 /* CollapsableNotification.swift */; };
|
||||
D2FA83D62515021F00564112 /* CollapsableNotificationTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */; };
|
||||
D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */; };
|
||||
@ -609,6 +611,16 @@
|
||||
FD99130028E21E4900542CC3 /* RuleNotEqualsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
5833355B2BF64E77001D90D7 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D29DF0C321E404D4003B2FB9 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D29DF0CB21E404D4003B2FB9;
|
||||
remoteInfo = MVMCoreUI;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
01004F2F22721C3800991ECC /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
|
||||
0103B84D23D7E33A009C315C /* HeadlineBodyToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyToggleModel.swift; sourceTree = "<group>"; };
|
||||
@ -731,7 +743,6 @@
|
||||
0AE98BB423FF18D2004C5109 /* Arrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arrow.swift; sourceTree = "<group>"; };
|
||||
0AE98BB623FF18E9004C5109 /* ArrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowModel.swift; sourceTree = "<group>"; };
|
||||
0AF60F0826B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIUtility+Extension.swift"; sourceTree = "<group>"; };
|
||||
187FEB292844D2A600BF29C2 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = "<group>"; };
|
||||
1D6D258626899B0B00DEBB08 /* ImageButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageButtonModel.swift; path = MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift; sourceTree = SOURCE_ROOT; };
|
||||
1D6D258726899B0B00DEBB08 /* ImageButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageButton.swift; path = MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift; sourceTree = SOURCE_ROOT; };
|
||||
22B678F829E7944E00CF4196 /* GetNotificationAuthStatusBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetNotificationAuthStatusBehavior.swift; sourceTree = "<group>"; };
|
||||
@ -776,10 +787,17 @@
|
||||
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>"; };
|
||||
583335562BF64E77001D90D7 /* MVMCoreUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MVMCoreUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
583335582BF64E77001D90D7 /* MVMCoreUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUITests.swift; sourceTree = "<group>"; };
|
||||
583335622BF6509C001D90D7 /* UAD_page_model.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = UAD_page_model.json; sourceTree = "<group>"; };
|
||||
583335642BF6A5C3001D90D7 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; };
|
||||
583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = UAD_page_model_2.json; sourceTree = "<group>"; };
|
||||
5833356C2BFBF51C001D90D7 /* UAD_page_model_3.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = UAD_page_model_3.json; sourceTree = "<group>"; };
|
||||
5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PollingBehaviorModel.swift; sourceTree = "<group>"; };
|
||||
5878F0A42BD7E68800ADE23D /* mvmcoreui.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui.xcconfig; sourceTree = "<group>"; };
|
||||
5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui_dev.xcconfig; sourceTree = "<group>"; };
|
||||
58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = "<group>"; };
|
||||
58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeComparisonProtocol.swift; sourceTree = "<group>"; };
|
||||
608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = "<group>"; };
|
||||
8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; };
|
||||
8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = "<group>"; };
|
||||
@ -826,10 +844,6 @@
|
||||
94C2D9A623872DA90006CF46 /* LabelAttributeColorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeColorModel.swift; sourceTree = "<group>"; };
|
||||
94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeImageModel.swift; sourceTree = "<group>"; };
|
||||
94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeActionModel.swift; sourceTree = "<group>"; };
|
||||
94CA227824058533002D6750 /* VerizonNHGeTX-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeTX-Bold.otf"; sourceTree = "<group>"; };
|
||||
94CA227924058533002D6750 /* VerizonNHGeDS-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeDS-Regular.otf"; sourceTree = "<group>"; };
|
||||
94CA227A24058533002D6750 /* VerizonNHGeDS-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeDS-Bold.otf"; sourceTree = "<group>"; };
|
||||
94CA227B24058533002D6750 /* VerizonNHGeTX-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeTX-Regular.otf"; sourceTree = "<group>"; };
|
||||
94F6516C2437954100631BF9 /* Tabs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tabs.swift; sourceTree = "<group>"; };
|
||||
AA07EA902510A442009A2AE3 /* StarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarModel.swift; sourceTree = "<group>"; };
|
||||
AA07EA922510A451009A2AE3 /* Star.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Star.swift; sourceTree = "<group>"; };
|
||||
@ -907,7 +921,6 @@
|
||||
AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = "<group>"; };
|
||||
AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingDelegateProtocol.swift; sourceTree = "<group>"; };
|
||||
AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = "<group>"; };
|
||||
AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = "<group>"; };
|
||||
AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = "<group>"; };
|
||||
B4CC8FBC29DF34680005D28B /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = "<group>"; };
|
||||
B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = "<group>"; };
|
||||
@ -1168,7 +1181,6 @@
|
||||
D2ED27E9254B0CE600A1C293 /* ActionAlertModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionAlertModel.swift; sourceTree = "<group>"; };
|
||||
D2ED27EA254B0CE700A1C293 /* AlertModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertModel.swift; sourceTree = "<group>"; };
|
||||
D2ED27F3254B0E0200A1C293 /* AlertObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertObject.swift; sourceTree = "<group>"; };
|
||||
D2FA83D12513EA6900564112 /* NotificationXButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationXButton.swift; sourceTree = "<group>"; };
|
||||
D2FA83D32514F80C00564112 /* CollapsableNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotification.swift; sourceTree = "<group>"; };
|
||||
D2FA83D52515021F00564112 /* CollapsableNotificationTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsableNotificationTopView.swift; sourceTree = "<group>"; };
|
||||
D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainer.swift; sourceTree = "<group>"; };
|
||||
@ -1201,7 +1213,6 @@
|
||||
EA985C3D2970938F00F2FF2E /* Tilelet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tilelet.swift; sourceTree = "<group>"; };
|
||||
EA985C3F2970939A00F2FF2E /* TileletModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileletModel.swift; sourceTree = "<group>"; };
|
||||
EA985C5F2970A3F000F2FF2E /* VDS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VDS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EA985C632970A40E00F2FF2E /* VDSTypographyTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTypographyTokens.xcframework; path = ../SharedFrameworks/VDSTypographyTokens.xcframework; sourceTree = "<group>"; };
|
||||
EA985C842981AA9C00F2FF2E /* VDS-Enums+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VDS-Enums+Codable.swift"; sourceTree = "<group>"; };
|
||||
EA985C862981AB0F00F2FF2E /* VDS-Tilelet+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VDS-Tilelet+Codable.swift"; sourceTree = "<group>"; };
|
||||
EA985C882981AB7100F2FF2E /* VDS-TextStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VDS-TextStyle.swift"; sourceTree = "<group>"; };
|
||||
@ -1221,6 +1232,14 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
583335532BF64E77001D90D7 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5833355A2BF64E77001D90D7 /* MVMCoreUI.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D29DF0C921E404D4003B2FB9 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -1254,6 +1273,7 @@
|
||||
D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */,
|
||||
27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */,
|
||||
27577DCC286CA959001EC47E /* MoleculeMaskingProtocol.swift */,
|
||||
58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */,
|
||||
);
|
||||
path = ModelProtocols;
|
||||
sourceTree = "<group>";
|
||||
@ -1504,6 +1524,34 @@
|
||||
path = Accessibility;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
583335572BF64E77001D90D7 /* MVMCoreUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
583335602BF65063001D90D7 /* JSON */,
|
||||
583335582BF64E77001D90D7 /* MVMCoreUITests.swift */,
|
||||
583335642BF6A5C3001D90D7 /* TestUtils.swift */,
|
||||
);
|
||||
path = MVMCoreUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
583335602BF65063001D90D7 /* JSON */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
583335612BF6506C001D90D7 /* Modelling */,
|
||||
);
|
||||
path = JSON;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
583335612BF6506C001D90D7 /* Modelling */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */,
|
||||
583335622BF6509C001D90D7 /* UAD_page_model.json */,
|
||||
5833356C2BFBF51C001D90D7 /* UAD_page_model_3.json */,
|
||||
);
|
||||
path = Modelling;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8DD1E36C243B3CD900D8F2DF /* ThreeColumn */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2013,6 +2061,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */,
|
||||
583335572BF64E77001D90D7 /* MVMCoreUITests */,
|
||||
D29DF0CD21E404D4003B2FB9 /* Products */,
|
||||
D29DF0E421E4F3C7003B2FB9 /* Frameworks */,
|
||||
);
|
||||
@ -2022,6 +2071,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */,
|
||||
583335562BF64E77001D90D7 /* MVMCoreUITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -2089,10 +2139,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAD715A92BBC8FAF00DEDA6A /* VDSTokens.xcframework */,
|
||||
EA985C632970A40E00F2FF2E /* VDSTypographyTokens.xcframework */,
|
||||
EA985C5F2970A3F000F2FF2E /* VDS.framework */,
|
||||
187FEB292844D2A600BF29C2 /* VDSFormControlsTokens.xcframework */,
|
||||
AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */,
|
||||
D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@ -2379,10 +2426,6 @@
|
||||
D29DF31521ECECC0003B2FB9 /* Fonts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
94CA227A24058533002D6750 /* VerizonNHGeDS-Bold.otf */,
|
||||
94CA227924058533002D6750 /* VerizonNHGeDS-Regular.otf */,
|
||||
94CA227824058533002D6750 /* VerizonNHGeTX-Bold.otf */,
|
||||
94CA227B24058533002D6750 /* VerizonNHGeTX-Regular.otf */,
|
||||
D29DF31721ECECC0003B2FB9 /* OCRAExtended.ttf */,
|
||||
);
|
||||
path = Fonts;
|
||||
@ -2496,7 +2539,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D2CAC7CA251104E100C75681 /* NotificationXButtonModel.swift */,
|
||||
D2FA83D12513EA6900564112 /* NotificationXButton.swift */,
|
||||
D2CAC7CC251104FE00C75681 /* NotificationMoleculeModel.swift */,
|
||||
D23118B225124E18001C8440 /* NotificationMoleculeView.swift */,
|
||||
D2CAC7CE2511052300C75681 /* CollapsableNotificationModel.swift */,
|
||||
@ -2598,6 +2640,24 @@
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
583335552BF64E77001D90D7 /* MVMCoreUITests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5833355F2BF64E77001D90D7 /* Build configuration list for PBXNativeTarget "MVMCoreUITests" */;
|
||||
buildPhases = (
|
||||
583335522BF64E77001D90D7 /* Sources */,
|
||||
583335532BF64E77001D90D7 /* Frameworks */,
|
||||
583335542BF64E77001D90D7 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
5833355C2BF64E77001D90D7 /* PBXTargetDependency */,
|
||||
);
|
||||
name = MVMCoreUITests;
|
||||
productName = MVMCoreUITests;
|
||||
productReference = 583335562BF64E77001D90D7 /* MVMCoreUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D29DF0D421E404D4003B2FB9 /* Build configuration list for PBXNativeTarget "MVMCoreUI" */;
|
||||
@ -2622,9 +2682,13 @@
|
||||
D29DF0C321E404D4003B2FB9 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1540;
|
||||
LastUpgradeCheck = 1320;
|
||||
ORGANIZATIONNAME = "Verizon Wireless";
|
||||
TargetAttributes = {
|
||||
583335552BF64E77001D90D7 = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
D29DF0CB21E404D4003B2FB9 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
LastSwiftMigration = 1010;
|
||||
@ -2647,22 +2711,29 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */,
|
||||
583335552BF64E77001D90D7 /* MVMCoreUITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
583335542BF64E77001D90D7 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */,
|
||||
5833356D2BFBF51C001D90D7 /* UAD_page_model_3.json in Resources */,
|
||||
583335632BF6509C001D90D7 /* UAD_page_model.json in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D29DF0CA21E404D4003B2FB9 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
94CA227C24058534002D6750 /* VerizonNHGeTX-Bold.otf in Resources */,
|
||||
D29DF32C21EE8736003B2FB9 /* Localizable.strings in Resources */,
|
||||
0A6C1FC324927E2E00E64B52 /* colors.xcassets in Resources */,
|
||||
94CA227D24058534002D6750 /* VerizonNHGeDS-Regular.otf in Resources */,
|
||||
D29DF32E21EE8C3D003B2FB9 /* Media.xcassets in Resources */,
|
||||
94CA227E24058534002D6750 /* VerizonNHGeDS-Bold.otf in Resources */,
|
||||
D287651A245B338E00CB882D /* VerizonNHGeTX-Regular.otf in Resources */,
|
||||
D29DF31B21ECECC0003B2FB9 /* OCRAExtended.ttf in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -2670,6 +2741,15 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
583335522BF64E77001D90D7 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
583335592BF64E77001D90D7 /* MVMCoreUITests.swift in Sources */,
|
||||
583335652BF6A5C3001D90D7 /* TestUtils.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D29DF0C821E404D4003B2FB9 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -3010,6 +3090,7 @@
|
||||
EA1758482BC97ED800A5C0D9 /* BadgeIndicator.swift in Sources */,
|
||||
012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */,
|
||||
0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */,
|
||||
58E7561D2BE04C320088BB5D /* MoleculeComparisonProtocol.swift in Sources */,
|
||||
D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */,
|
||||
AF1C336928859778006B1001 /* ActionAlertHandler.swift in Sources */,
|
||||
9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */,
|
||||
@ -3063,7 +3144,6 @@
|
||||
EACCF38C2ABB346700E0F104 /* VDS-Interpreters.swift in Sources */,
|
||||
C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */,
|
||||
0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */,
|
||||
D2FA83D22513EA6900564112 /* NotificationXButton.swift in Sources */,
|
||||
D2D90B442404789000DD6EC9 /* MoleculeContainerProtocol.swift in Sources */,
|
||||
0A7ECC5F243CEB1200C828E8 /* ColorViewWithLabel.swift in Sources */,
|
||||
BB3BC12F2550094500297977 /* ListLeftVariableIconWithRightCaretAllTextLinks.swift in Sources */,
|
||||
@ -3242,6 +3322,14 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
5833355C2BF64E77001D90D7 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */;
|
||||
targetProxy = 5833355B2BF64E77001D90D7 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
D29DF32821EE8736003B2FB9 /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@ -3256,6 +3344,49 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
5833355D2BF64E77001D90D7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5833355E2BF64E77001D90D7 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
D29DF0D221E404D4003B2FB9 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */;
|
||||
@ -3448,6 +3579,15 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
5833355F2BF64E77001D90D7 /* Build configuration list for PBXNativeTarget "MVMCoreUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5833355D2BF64E77001D90D7 /* Debug */,
|
||||
5833355E2BF64E77001D90D7 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D29DF0C621E404D4003B2FB9 /* Build configuration list for PBXProject "MVMCoreUI" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1540"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D29DF0CB21E404D4003B2FB9"
|
||||
BuildableName = "MVMCoreUI.framework"
|
||||
BlueprintName = "MVMCoreUI"
|
||||
ReferencedContainer = "container:MVMCoreUI.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D29DF0CB21E404D4003B2FB9"
|
||||
BuildableName = "MVMCoreUI.framework"
|
||||
BlueprintName = "MVMCoreUI"
|
||||
ReferencedContainer = "container:MVMCoreUI.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -25,4 +25,11 @@ public struct ActionAlertModel: ActionModelProtocol {
|
||||
public init(alert: AlertModel) {
|
||||
self.alert = alert
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
&& model.alert == alert
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,4 +20,12 @@ public struct ActionCollapseNotificationModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
//public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
// guard let model = model as? Self else { return false }
|
||||
// return model.actionType == actionType
|
||||
// && model.extraParameters == extraParameters
|
||||
// && model.analyticsData == analyticsData
|
||||
//}
|
||||
}
|
||||
|
||||
@ -20,4 +20,12 @@ public struct ActionDismissNotificationModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
// public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
// guard let model = model as? Self else { return false }
|
||||
// return model.actionType == actionType
|
||||
// && model.extraParameters == extraParameters
|
||||
// && model.analyticsData == analyticsData
|
||||
//}
|
||||
}
|
||||
|
||||
@ -29,4 +29,12 @@ public struct ActionOpenPanelModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
&& model.panel == panel
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,4 +22,11 @@ public struct ActionTopNotificationModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
&& model.topNotification == topNotification
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
import UIKit
|
||||
import MVMCore
|
||||
|
||||
public struct AlertButtonModel: Codable {
|
||||
public struct AlertButtonModel: Codable, Equatable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -62,14 +63,21 @@ public struct AlertButtonModel: Codable {
|
||||
try container.encodeModel(action, forKey: .action)
|
||||
try container.encodeIfPresent(preferred, forKey: .preferred)
|
||||
}
|
||||
|
||||
public static func == (lhs: AlertButtonModel, rhs: AlertButtonModel) -> Bool {
|
||||
lhs.title == rhs.title
|
||||
&& lhs.preferred == rhs.preferred
|
||||
&& lhs.style == rhs.style
|
||||
&& lhs.action.isEqual(to: rhs.action)
|
||||
}
|
||||
}
|
||||
|
||||
public struct AlertModel: Codable, Identifiable, AlertModelProtocol {
|
||||
public struct AlertModel: Codable, Identifiable, Equatable, AlertModelProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
|
||||
public var title: String?
|
||||
public var message: String?
|
||||
public var preferredStyle: UIAlertController.Style = .alert
|
||||
@ -78,7 +86,7 @@ public struct AlertModel: Codable, Identifiable, AlertModelProtocol {
|
||||
public var id: String
|
||||
|
||||
public var delegateObject: DelegateObject?
|
||||
|
||||
|
||||
public var actions: [UIAlertAction] {
|
||||
get {
|
||||
buttonModels.map({ alertButtonModel in
|
||||
@ -94,8 +102,7 @@ public struct AlertModel: Codable, Identifiable, AlertModelProtocol {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Init
|
||||
//--------------------------------------------------
|
||||
@ -149,6 +156,14 @@ public struct AlertModel: Codable, Identifiable, AlertModelProtocol {
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
try container.encode(id, forKey: .id)
|
||||
}
|
||||
|
||||
public static func == (lhs: AlertModel, rhs: AlertModel) -> Bool {
|
||||
lhs.title == rhs.title
|
||||
&& lhs.message == rhs.message
|
||||
&& lhs.preferredStyle == rhs.preferredStyle
|
||||
&& lhs.buttonModels == rhs.buttonModels
|
||||
&& lhs.analyticsData == rhs.analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
public extension AlertButtonModel {
|
||||
|
||||
@ -79,4 +79,16 @@ public class ButtonGroupModel: ParentMoleculeModelProtocol {
|
||||
try container.encodeIfPresent(childWidthValue, forKey: .childWidthValue)
|
||||
try container.encodeIfPresent(childWidthPercentage, forKey: .childWidthPercentage)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return buttons.count == model.buttons.count
|
||||
&& surface == model.surface
|
||||
&& enabled == model.enabled
|
||||
&& alignment == model.alignment
|
||||
&& rowQuantityPhone == model.rowQuantityPhone
|
||||
&& rowQuantityTablet == model.rowQuantityTablet
|
||||
&& childWidthValue == model.childWidthValue
|
||||
&& childWidthPercentage == model.childWidthPercentage
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,4 +186,34 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
|
||||
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
|
||||
try container.encodeIfPresent(disabledAccessibilityTraits, forKey: .disabledAccessibilityTraits)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return title == model.title
|
||||
&& enabled == model.enabled
|
||||
&& inverted == model.inverted
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityIdentifier == model.accessibilityIdentifier
|
||||
&& accessibilityTraits == model.accessibilityTraits
|
||||
&& disabledAccessibilityTraits == model.disabledAccessibilityTraits
|
||||
&& style == model.style
|
||||
&& size == model.size
|
||||
&& groupName == model.groupName
|
||||
&& width == model.width
|
||||
&& action.isEqual(to: model.action)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return title == model.title
|
||||
&& enabled == model.enabled
|
||||
&& inverted == model.inverted
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityIdentifier == model.accessibilityIdentifier
|
||||
&& accessibilityTraits == model.accessibilityTraits
|
||||
&& disabledAccessibilityTraits == model.disabledAccessibilityTraits
|
||||
&& style == model.style
|
||||
&& size == model.size
|
||||
&& width == model.width
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
|
||||
[image].compactMap({$0})
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(at: &image, with: molecule)
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +99,30 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
|
||||
try container.encodeIfPresent(size, forKey: .size)
|
||||
try container.encode(shouldMaskRecordedView, forKey: .shouldMaskRecordedView)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& title == model.title
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityIdentifier == model.accessibilityIdentifier
|
||||
&& inverted == model.inverted
|
||||
&& enabled == model.enabled
|
||||
&& size == model.size
|
||||
&& shouldMaskRecordedView == model.shouldMaskRecordedView
|
||||
&& action.isEqual(to: model.action)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& title == model.title
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityIdentifier == model.accessibilityIdentifier
|
||||
&& inverted == model.inverted
|
||||
&& enabled == model.enabled
|
||||
&& size == model.size
|
||||
}
|
||||
}
|
||||
|
||||
extension LinkModel {
|
||||
|
||||
@ -74,7 +74,7 @@ extension BaseItemPickerEntryField {
|
||||
|
||||
@objc open override func setAccessibilityString(_ accessibilityString: String?) {
|
||||
|
||||
var accessibilityString = accessibilityString ?? ""
|
||||
let accessibilityString = accessibilityString ?? ""
|
||||
textField.accessibilityTraits = .staticText
|
||||
textField.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item")
|
||||
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
|
||||
|
||||
@ -397,7 +397,7 @@ extension TextEntryField {
|
||||
|
||||
@objc open override func setAccessibilityString(_ accessibilityString: String?) {
|
||||
|
||||
var accessibilityString = accessibilityString ?? ""
|
||||
let accessibilityString = accessibilityString ?? ""
|
||||
|
||||
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
|
||||
}
|
||||
|
||||
@ -125,5 +125,19 @@ open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
|
||||
try container.encode(width, forKey: .width)
|
||||
try container.encode(height, forKey: .height)
|
||||
try container.encode(enabled, forKey: .enabled)
|
||||
try container.encode(inverted, forKey: .inverted)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& disabledColor == model.disabledColor
|
||||
&& color == model.color
|
||||
&& degrees == model.degrees
|
||||
&& lineWidth == model.lineWidth
|
||||
&& width == model.width
|
||||
&& height == model.height
|
||||
&& enabled == model.enabled
|
||||
&& inverted == model.inverted
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,17 +262,12 @@ open class BarsIndicatorView: CarouselIndicator {
|
||||
refreshAccessibilityLabels()
|
||||
}
|
||||
|
||||
let expression = { [self] in
|
||||
barReferences[previousIndex].backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||
barReferences[previousIndex].constraint?.constant = IndicatorBar.unselectedHeight
|
||||
barReferences[previousIndex].layer.cornerRadius = IndicatorBar.unselectedCornerRadius
|
||||
barReferences[newIndex].backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
||||
barReferences[newIndex].constraint?.constant = IndicatorBar.selectedHeight
|
||||
barReferences[newIndex].layer.cornerRadius = IndicatorBar.selectedCornerRadius
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
// Perform the animation.
|
||||
isAnimated ? UIView.animate(withDuration: 0.25) { expression() } : expression()
|
||||
barReferences[previousIndex].backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor
|
||||
barReferences[previousIndex].constraint?.constant = IndicatorBar.unselectedHeight
|
||||
barReferences[previousIndex].layer.cornerRadius = IndicatorBar.unselectedCornerRadius
|
||||
barReferences[newIndex].backgroundColor = isEnabled ? currentIndicatorColor : disabledIndicatorColor
|
||||
barReferences[newIndex].constraint?.constant = IndicatorBar.selectedHeight
|
||||
barReferences[newIndex].layer.cornerRadius = IndicatorBar.selectedCornerRadius
|
||||
layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,12 +21,14 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
||||
public var id: String = UUID().uuidString
|
||||
public var backgroundColor: Color?
|
||||
public var moleculeName: String?
|
||||
|
||||
// Assigned and computed by parent.
|
||||
public var numberOfPages: Int = 0
|
||||
|
||||
/// Sets the current Index to focus on.
|
||||
public var currentIndex: Int = 0
|
||||
public var animated: Bool = true
|
||||
public var hidesForSinglePage: Bool = false
|
||||
public var hidesForSinglePage: Bool = true
|
||||
public var inverted: Bool = false
|
||||
/// Set true to make the accessibility value as "Slide #currentPage of #totalPage", otherwise will be "Page #currentPage of #totalPage", default is false
|
||||
public var accessibilityHasSlidesInsteadOfPage: Bool = false
|
||||
@ -49,7 +51,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
||||
case moleculeName
|
||||
case backgroundColor
|
||||
case currentIndex
|
||||
case numberOfPages
|
||||
case alwaysSendAction
|
||||
case animated
|
||||
case hidesForSinglePage
|
||||
@ -118,7 +119,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encodeIfPresent(moleculeName, forKey: .moleculeName)
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
try container.encode(numberOfPages, forKey: .numberOfPages)
|
||||
try container.encode(currentIndex, forKey: .currentIndex)
|
||||
try container.encode(alwaysSendAction, forKey: .alwaysSendAction)
|
||||
try container.encode(animated, forKey: .animated)
|
||||
@ -130,4 +130,31 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
|
||||
try container.encode(indicatorColor, forKey: .indicatorColor)
|
||||
try container.encodeIfPresent(position, forKey: .position)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& currentIndex == model.currentIndex
|
||||
&& alwaysSendAction == model.alwaysSendAction
|
||||
&& animated == model.animated
|
||||
&& hidesForSinglePage == model.hidesForSinglePage
|
||||
&& accessibilityHasSlidesInsteadOfPage == model.accessibilityHasSlidesInsteadOfPage
|
||||
&& enabled == model.enabled
|
||||
&& inverted == model.inverted
|
||||
&& disabledIndicatorColor == model.disabledIndicatorColor
|
||||
&& indicatorColor == model.indicatorColor
|
||||
&& position == model.position
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& animated == model.animated
|
||||
&& hidesForSinglePage == model.hidesForSinglePage
|
||||
&& accessibilityHasSlidesInsteadOfPage == model.accessibilityHasSlidesInsteadOfPage
|
||||
&& enabled == model.enabled
|
||||
&& inverted == model.inverted
|
||||
&& disabledIndicatorColor == model.disabledIndicatorColor
|
||||
&& indicatorColor == model.indicatorColor
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,4 +61,20 @@
|
||||
case shouldMaskRecordedView
|
||||
case allowServerParameters
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& image == model.image
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& fallbackImage == model.fallbackImage
|
||||
&& imageFormat == model.imageFormat
|
||||
&& width == model.width
|
||||
&& height == model.height
|
||||
&& contentMode == model.contentMode
|
||||
&& cornerRadius == model.cornerRadius
|
||||
&& clipsImage == model.clipsImage
|
||||
&& allowServerParameters == model.allowServerParameters
|
||||
&& shouldMaskRecordedView == model.shouldMaskRecordedView
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,4 +48,9 @@ open class LabelAttributeActionModel: LabelAttributeModel {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeModel(action, forKey: .action)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return action.isEqual(to: model.action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,4 +43,9 @@
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeIfPresent(textColor, forKey: .textColor)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return textColor == model.textColor
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,4 +55,11 @@
|
||||
try container.encodeIfPresent(name, forKey: .name)
|
||||
try container.encodeIfPresent(size, forKey: .size)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return style == model.style
|
||||
&& name == model.name
|
||||
&& size == model.size
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,4 +69,12 @@ class LabelAttributeImageModel: LabelAttributeModel {
|
||||
try container.encodeIfPresent(URL, forKey: .URL)
|
||||
try container.encodeIfPresent(tintColor, forKey: .tintColor)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return URL == model.URL
|
||||
&& name == model.name
|
||||
&& size == model.size
|
||||
&& tintColor == model.tintColor
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
@objcMembers open class LabelAttributeModel: ModelProtocol {
|
||||
@objcMembers open class LabelAttributeModel: ModelProtocol, ModelComparisonProtocol, MoleculeModelComparisonProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -75,4 +75,16 @@
|
||||
try container.encode(location, forKey: .location)
|
||||
try container.encode(length, forKey: .length)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return location == model.location
|
||||
&& length == model.length
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return location == model.location
|
||||
&& length == model.length
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +66,13 @@ import UIKit
|
||||
try container.encode(style, forKey: .style)
|
||||
try container.encodeIfPresent(pattern, forKey: .pattern)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return style == model.style
|
||||
&& color == model.color
|
||||
&& pattern == model.pattern
|
||||
}
|
||||
}
|
||||
|
||||
public enum UnderlineStyle: String, Codable {
|
||||
|
||||
@ -136,6 +136,45 @@ import VDS
|
||||
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
|
||||
try container.encodeIfPresent(animationEnabled, forKey: .animationEnabled)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& text == model.text
|
||||
&& textColor == model.textColor
|
||||
&& fontStyle == model.fontStyle
|
||||
&& fontName == model.fontName
|
||||
&& fontSize == model.fontSize
|
||||
&& textAlignment == model.textAlignment
|
||||
&& html == model.html
|
||||
&& hero == model.hero
|
||||
&& makeWholeViewClickable == model.makeWholeViewClickable
|
||||
&& numberOfLines == model.numberOfLines
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityTraits == model.accessibilityTraits
|
||||
&& inverted == inverted
|
||||
&& shouldMaskRecordedView == model.shouldMaskRecordedView
|
||||
&& attributes.isEqual(to: model.attributes)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& text == model.text
|
||||
&& textColor == model.textColor
|
||||
&& fontStyle == model.fontStyle
|
||||
&& fontName == model.fontName
|
||||
&& fontSize == model.fontSize
|
||||
&& textAlignment == model.textAlignment
|
||||
&& html == model.html
|
||||
&& hero == model.hero
|
||||
&& makeWholeViewClickable == model.makeWholeViewClickable
|
||||
&& numberOfLines == model.numberOfLines
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityTraits == model.accessibilityTraits
|
||||
&& inverted == inverted
|
||||
&& attributes.isVisuallyEquivalent(to: model.attributes)
|
||||
}
|
||||
}
|
||||
|
||||
extension LabelModel {
|
||||
|
||||
@ -129,4 +129,12 @@ public class LineModel: MoleculeModelProtocol, Invertable {
|
||||
try container.encodeIfPresent(frequency, forKey: .frequency)
|
||||
try container.encode(orientation == .vertical, forKey: .useVerticalLine)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return type == model.type
|
||||
&& inverted == model.inverted
|
||||
&& frequency == model.frequency
|
||||
&& orientation == model.orientation
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,4 +136,8 @@ open class TileContainer: VDS.TileContainer, VDSMoleculeViewProtocol{
|
||||
|
||||
extension TileContainer: MVMCoreUIViewConstrainingProtocol {
|
||||
public func horizontalAlignment() -> UIStackView.Alignment { .leading }
|
||||
|
||||
public func isClippable() -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import VDS
|
||||
import MVMCore
|
||||
|
||||
open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, TileContainer>, ParentMoleculeModelProtocol, MoleculeModelProtocol {
|
||||
|
||||
@ -24,7 +25,7 @@ open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, Til
|
||||
return [molecule]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(at: &self.molecule, with: molecule)
|
||||
}
|
||||
|
||||
@ -36,7 +37,7 @@ open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, Til
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
||||
molecule = try container.decodeModelIfPresent(codingKey: .molecule)
|
||||
molecule = try container.decodeMoleculeIfPresent(codingKey: .molecule)
|
||||
try super.init(from: decoder)
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,7 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{
|
||||
} else if let percentage = viewModel.textPercentage {
|
||||
textWidth = .percentage(percentage)
|
||||
}
|
||||
eyebrowModel = viewModel.eyebrowModel(delegateObject: delegateObject, additionalData: additionalData)
|
||||
titleModel = viewModel.titleModel(delegateObject: delegateObject, additionalData: additionalData)
|
||||
subTitleModel = viewModel.subTitleModel(delegateObject: delegateObject, additionalData: additionalData)
|
||||
badgeModel = viewModel.badge
|
||||
@ -133,3 +134,13 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Tilelet: MVMCoreUIViewConstrainingProtocol {
|
||||
|
||||
// Investigate later.
|
||||
//public func horizontalAlignment() -> UIStackView.Alignment { .leading }
|
||||
|
||||
public func isClippable() -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,8 +19,12 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
|
||||
public var backgroundColor: Color?
|
||||
|
||||
public var badge: Tilelet.BadgeModel?
|
||||
public var eyebrow: LabelModel?
|
||||
public var eyebrowColor: TitleLockup.TextColor = .primary
|
||||
public var title: LabelModel?
|
||||
public var titleColor: TitleLockup.TitleTextColor = .primary
|
||||
public var subTitle: LabelModel?
|
||||
public var subTitleColor: TitleLockup.TextColor = .primary
|
||||
public var descriptiveIcon: Tilelet.DescriptiveIcon?
|
||||
public var directionalIcon: Tilelet.DirectionalIcon?
|
||||
public var textWidth: CGFloat?
|
||||
@ -30,8 +34,12 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
|
||||
case id
|
||||
case moleculeName
|
||||
case badge
|
||||
case eyebrow
|
||||
case eyebrowColor
|
||||
case title
|
||||
case titleColor
|
||||
case subTitle
|
||||
case subTitleColor
|
||||
case descriptiveIcon
|
||||
case directionalIcon
|
||||
case textWidth
|
||||
@ -41,15 +49,64 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
||||
badge = try container.decodeIfPresent(Tilelet.BadgeModel.self, forKey: .badge)
|
||||
eyebrow = try container.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
|
||||
title = try container.decodeIfPresent(LabelModel.self, forKey: .title)
|
||||
subTitle = try container.decodeIfPresent(LabelModel.self, forKey: .subTitle)
|
||||
descriptiveIcon = try container.decodeIfPresent(Tilelet.DescriptiveIcon.self, forKey: .descriptiveIcon)
|
||||
directionalIcon = try container.decodeIfPresent(Tilelet.DirectionalIcon.self, forKey: .directionalIcon)
|
||||
textWidth = try container.decodeIfPresent(CGFloat.self, forKey: .textWidth)
|
||||
textPercentage = try container.decodeIfPresent(CGFloat.self, forKey: .textPercentage)
|
||||
|
||||
if let color = eyebrow?.textColor?.uiColor {
|
||||
self.eyebrowColor = .custom(color)
|
||||
|
||||
} else if let eyebrowColor = try? container.decodeIfPresent(TitleLockup.TextColor.self, forKey: .eyebrowColor) {
|
||||
self.eyebrowColor = eyebrowColor
|
||||
|
||||
} else {
|
||||
eyebrowColor = .primary
|
||||
}
|
||||
|
||||
if let color = title?.textColor?.uiColor {
|
||||
self.titleColor = .custom(color)
|
||||
|
||||
} else if let titleColor = try? container.decodeIfPresent(TitleLockup.TitleTextColor.self, forKey: .titleColor) {
|
||||
self.titleColor = titleColor
|
||||
|
||||
} else {
|
||||
titleColor = .primary
|
||||
}
|
||||
|
||||
if let color = subTitle?.textColor?.uiColor {
|
||||
self.subTitleColor = .custom(color)
|
||||
|
||||
} else if let subTitleColor = try? container.decodeIfPresent(TitleLockup.TextColor.self, forKey: .subTitleColor) {
|
||||
self.subTitleColor = subTitleColor
|
||||
|
||||
} else {
|
||||
subTitleColor = .primary
|
||||
}
|
||||
|
||||
try super.init(from: decoder)
|
||||
}
|
||||
|
||||
public func eyebrowModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.EyebrowModel? {
|
||||
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,
|
||||
textColor: eyebrowColor,
|
||||
textAttributes: attrs, isBold: style.isBold(),
|
||||
standardStyle: try style.vdsSubsetStyle())
|
||||
}
|
||||
} catch MVMCoreError.errorObject(let object) {
|
||||
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
|
||||
} catch { }
|
||||
|
||||
return .init(text: eyebrow.text, textColor: eyebrowColor, textAttributes: attrs)
|
||||
}
|
||||
|
||||
public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? {
|
||||
guard let title else { return nil }
|
||||
let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
|
||||
@ -57,6 +114,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
|
||||
do {
|
||||
if let style = title.fontStyle {
|
||||
return .init(text: title.text,
|
||||
textColor: titleColor,
|
||||
textAttributes: attrs,
|
||||
standardStyle: try style.vdsSubsetStyle())
|
||||
}
|
||||
@ -65,7 +123,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
|
||||
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
|
||||
} catch { }
|
||||
|
||||
return .init(text: title.text, textAttributes: attrs)
|
||||
return .init(text: title.text, textColor: titleColor, textAttributes: attrs)
|
||||
}
|
||||
|
||||
public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? {
|
||||
@ -75,13 +133,14 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
|
||||
if let style = subTitle.fontStyle {
|
||||
return .init(text: subTitle.text,
|
||||
otherStandardStyle: try style.vdsSubsetStyle(),
|
||||
textColor: subTitleColor,
|
||||
textAttributes: attrs)
|
||||
}
|
||||
} catch MVMCoreError.errorObject(let object) {
|
||||
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
|
||||
} catch { }
|
||||
|
||||
return .init(text: subTitle.text, textAttributes: attrs)
|
||||
return .init(text: subTitle.text, textColor: subTitleColor, textAttributes: attrs)
|
||||
}
|
||||
|
||||
public override func encode(to encoder: Encoder) throws {
|
||||
@ -89,8 +148,12 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(moleculeName, forKey: .moleculeName)
|
||||
try container.encodeIfPresent(badge, forKey: .badge)
|
||||
try container.encodeIfPresent(title, forKey: .title)
|
||||
try container.encodeIfPresent(subTitle, forKey: .subTitle)
|
||||
try container.encodeModelIfPresent(eyebrow, forKey: .eyebrow)
|
||||
try container.encode(eyebrowColor, forKey: .eyebrowColor)
|
||||
try container.encodeModelIfPresent(title, forKey: .title)
|
||||
try container.encode(titleColor, forKey: .titleColor)
|
||||
try container.encodeModelIfPresent(subTitle, forKey: .subTitle)
|
||||
try container.encode(subTitleColor, forKey: .subTitleColor)
|
||||
try container.encodeIfPresent(descriptiveIcon, forKey: .descriptiveIcon)
|
||||
try container.encodeIfPresent(directionalIcon, forKey: .directionalIcon)
|
||||
try container.encodeIfPresent(textWidth, forKey: .textWidth)
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,7 +75,7 @@ extension VDS.TileContainerBase.BackgroundColor: Codable {
|
||||
var container = encoder.singleValueContainer()
|
||||
switch self {
|
||||
case .custom(let value):
|
||||
try container.encode(value)
|
||||
try container.encode(Color(uiColor: value))
|
||||
default:
|
||||
try container.encode(String(reflecting: self))
|
||||
}
|
||||
@ -95,7 +95,11 @@ extension VDS.TileContainerBase.BackgroundColor: Codable {
|
||||
case "black":
|
||||
self = .black
|
||||
default:
|
||||
self = .custom(type)
|
||||
if let color = try? Color(from: decoder) {
|
||||
self = .custom(color.uiColor)
|
||||
} else {
|
||||
self = .custom(UIColor(hexString: type))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,9 +128,9 @@ extension VDS.TileContainerBase.BackgroundEffect: Codable {
|
||||
case .none:
|
||||
self = .none
|
||||
case .gradient:
|
||||
let firstColor = try container.decode(String.self, forKey: .firstColor)
|
||||
let secondColor = try container.decode(String.self, forKey: .secondColor)
|
||||
self = .gradient(firstColor, secondColor)
|
||||
let firstColor = try container.decode(Color.self, forKey: .firstColor)
|
||||
let secondColor = try container.decode(Color.self, forKey: .secondColor)
|
||||
self = .gradient(firstColor.uiColor, secondColor.uiColor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,8 +143,8 @@ extension VDS.TileContainerBase.BackgroundEffect: Codable {
|
||||
try container.encode(BackgroundEffectType.none.rawValue, forKey: .type)
|
||||
case .gradient(let firstColor, let secondColor):
|
||||
try container.encode(BackgroundEffectType.gradient.rawValue, forKey: .type)
|
||||
try container.encode(firstColor, forKey: .firstColor)
|
||||
try container.encode(secondColor, forKey: .secondColor)
|
||||
try container.encode(Color(uiColor: firstColor), forKey: .firstColor)
|
||||
try container.encode(Color(uiColor: secondColor), forKey: .secondColor)
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
@ -188,3 +192,86 @@ extension VDS.TileContainer.Padding: Codable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension VDS.TitleLockup.TextColor: Codable {
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case color
|
||||
}
|
||||
|
||||
enum CustomColorType: String, Codable {
|
||||
case primary
|
||||
case secondary
|
||||
case custom
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let type = try container.decode(CustomColorType.self, forKey: .type)
|
||||
|
||||
switch type {
|
||||
case .primary:
|
||||
self = .primary
|
||||
case .secondary:
|
||||
self = .secondary
|
||||
case .custom:
|
||||
let color = try container.decode(Color.self, forKey: .color)
|
||||
self = .custom(color.uiColor)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .primary:
|
||||
try container.encode(CustomColorType.primary.rawValue, forKey: .type)
|
||||
case .secondary:
|
||||
try container.encode(CustomColorType.secondary.rawValue, forKey: .type)
|
||||
case .custom(let color):
|
||||
try container.encode(CustomColorType.custom.rawValue, forKey: .type)
|
||||
try container.encode(Color(uiColor: color), forKey: .color)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension VDS.TitleLockup.TitleTextColor: Codable {
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case color
|
||||
}
|
||||
|
||||
enum CustomColorType: String, Codable {
|
||||
case primary
|
||||
case custom
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let type = try container.decode(CustomColorType.self, forKey: .type)
|
||||
|
||||
switch type {
|
||||
case .primary:
|
||||
self = .primary
|
||||
case .custom:
|
||||
let color = try container.decode(Color.self, forKey: .color)
|
||||
self = .custom(color.uiColor)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .primary:
|
||||
try container.encode(CustomColorType.primary.rawValue, forKey: .type)
|
||||
case .custom(let color):
|
||||
try container.encode(CustomColorType.custom.rawValue, forKey: .type)
|
||||
try container.encode(Color(uiColor: color), forKey: .color)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,41 +35,71 @@ extension Tilelet.BadgeModel: Codable {
|
||||
|
||||
extension Tilelet.DescriptiveIcon: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name, size, surface
|
||||
case name, size, color, accessibleText
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let name = try container.decode(Icon.Name.self, forKey: .name)
|
||||
let size = try container.decodeIfPresent(Icon.Size.self, forKey: .size) ?? .medium
|
||||
let surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .dark
|
||||
self.init(name: name, size: size, surface: surface)
|
||||
let color = try container.decodeIfPresent(Color.self, forKey: .color)
|
||||
let accessibleText = try container.decodeIfPresent(String.self, forKey: .accessibleText)
|
||||
if let uiColor = color?.uiColor {
|
||||
self.init(name: name,
|
||||
iconColor: .custom(uiColor),
|
||||
size: size,
|
||||
accessibleText: accessibleText)
|
||||
} else {
|
||||
self.init(name: name,
|
||||
size: size,
|
||||
accessibleText: accessibleText)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(size, forKey: .size)
|
||||
try container.encode(surface, forKey: .surface)
|
||||
try container.encodeIfPresent(accessibleText, forKey: .accessibleText)
|
||||
if let color = iconColor?.uiColor {
|
||||
try container.encode(Color(uiColor: color), forKey: .color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Tilelet.DirectionalIcon.IconType : Codable {}
|
||||
extension Tilelet.DirectionalIcon.IconSize : Codable {}
|
||||
|
||||
extension Tilelet.DirectionalIcon: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case size, surface
|
||||
case name, size, color, accessibleText
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let size = try container.decodeIfPresent(Icon.Size.self, forKey: .size) ?? .medium
|
||||
let surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .dark
|
||||
self.init(size: size, surface: surface)
|
||||
let iconType = try container.decodeIfPresent(IconType.self, forKey: .name) ?? .rightArrow
|
||||
let size = try container.decodeIfPresent(IconSize.self, forKey: .size) ?? .medium
|
||||
let color = try container.decodeIfPresent(Color.self, forKey: .color)
|
||||
let accessibleText = try container.decodeIfPresent(String.self, forKey: .accessibleText)
|
||||
if let uiColor = color?.uiColor {
|
||||
self.init(iconType: iconType,
|
||||
iconColor: .custom(uiColor),
|
||||
size: size,
|
||||
accessibleText: accessibleText)
|
||||
} else {
|
||||
self.init(iconType: iconType,
|
||||
size: size,
|
||||
accessibleText: accessibleText)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(iconType, forKey: .name)
|
||||
try container.encode(size, forKey: .size)
|
||||
try container.encode(surface, forKey: .surface)
|
||||
try container.encodeIfPresent(accessibleText, forKey: .accessibleText)
|
||||
if let color = iconColor?.uiColor {
|
||||
try container.encode(Color(uiColor: color), forKey: .color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,9 +21,13 @@ public class HeadersH1ButtonModel: HeaderModel, MoleculeModelProtocol, ParentMol
|
||||
[titleLockup, buttons]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &titleLockup, with: molecule)
|
||||
|| replaceChildMolecule(at: &buttons, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -24,13 +24,17 @@ public class HeadersH1LandingPageHeaderModel: HeaderModel, MoleculeModelProtocol
|
||||
[headline, headline2, subHeadline, body, link, buttons]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &headline, with: molecule)
|
||||
|| replaceChildMolecule(at: &headline2, with: molecule)
|
||||
|| replaceChildMolecule(at: &subHeadline, with: molecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule)
|
||||
|| replaceChildMolecule(at: &link, with: molecule)
|
||||
|| replaceChildMolecule(at: &buttons, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &headline2, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &subHeadline, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -19,7 +19,7 @@ public class HeadersH1NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
|
||||
[titleLockup]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(at: &titleLockup, with: molecule)
|
||||
}
|
||||
|
||||
@ -91,25 +91,37 @@ public struct DeprecatedHeadlineBodyHelper {
|
||||
|
||||
public func createTitleLockupModel(defaultStyle: HeadlineBodyModel.Style = .header, headlineBody: HeadlineBodyModel) throws -> TitleLockupModel {
|
||||
guard let headline = headlineBody.headline else { throw ModelRegistry.Error.decoderOther(message: "headline is required for this use case.") }
|
||||
var body = headlineBody.body
|
||||
let body = headlineBody.body
|
||||
var defaultHeadlineStyle: Styler.Font
|
||||
var defaultBodyStyle: Styler.Font
|
||||
switch headlineBody.style ?? defaultStyle {
|
||||
case .landingHeader:
|
||||
headline.fontStyle = Styler.Font.RegularTitle2XLarge
|
||||
body?.fontStyle = Styler.Font.RegularTitleMedium
|
||||
defaultHeadlineStyle = Styler.Font.RegularTitle2XLarge
|
||||
defaultBodyStyle = Styler.Font.RegularTitleMedium
|
||||
case .itemHeader:
|
||||
headline.fontStyle = Styler.Font.BoldTitleLarge
|
||||
body?.fontStyle = Styler.Font.RegularBodyLarge
|
||||
defaultHeadlineStyle = Styler.Font.BoldTitleLarge
|
||||
defaultBodyStyle = Styler.Font.RegularBodyLarge
|
||||
default:
|
||||
headline.fontStyle = Styler.Font.RegularTitleXLarge
|
||||
body?.fontStyle = Styler.Font.RegularTitleMedium
|
||||
defaultHeadlineStyle = Styler.Font.RegularTitleXLarge
|
||||
defaultBodyStyle = Styler.Font.RegularTitleMedium
|
||||
}
|
||||
let model = try TitleLockupModel(title: headline, subTitle: body)
|
||||
if headline.fontStyle == nil {
|
||||
headline.fontStyle = defaultHeadlineStyle
|
||||
}
|
||||
if body?.fontStyle == nil {
|
||||
body?.fontStyle = defaultBodyStyle
|
||||
}
|
||||
let model = TitleLockupModel(title: headline, subTitle: body)
|
||||
model.id = headlineBody.id
|
||||
if let textAlignment = headline.textAlignment ?? body?.textAlignment,
|
||||
textAlignment == .center {
|
||||
model.textAlignment = .center
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
public func createHeadlineBodyModel(titleLockup: TitleLockupModel) -> HeadlineBodyModel {
|
||||
var headlineBody = HeadlineBodyModel(headline: titleLockup.title)
|
||||
let headlineBody = HeadlineBodyModel(headline: titleLockup.title)
|
||||
headlineBody.body = titleLockup.subTitle
|
||||
headlineBody.id = titleLockup.id
|
||||
return headlineBody
|
||||
|
||||
@ -23,9 +23,13 @@ public class HeadersH2ButtonsModel: HeaderModel, MoleculeModelProtocol, ParentMo
|
||||
[titleLockup, buttons]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &titleLockup, with: molecule)
|
||||
|| replaceChildMolecule(at: &buttons, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -20,9 +20,13 @@ public class HeadersH2CaretLinkModel: HeaderModel, MoleculeModelProtocol, Parent
|
||||
[titleLockup, caretLink]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &titleLockup, with: molecule)
|
||||
|| replaceChildMolecule(at: &caretLink, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &caretLink, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMoleculeModelProtocol {
|
||||
public class HeadersH2LinkModel: HeaderModel, ParentMoleculeModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -22,9 +22,13 @@ public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMolec
|
||||
[titleLockup, link]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &titleLockup, with: molecule)
|
||||
|| replaceChildMolecule(at: &link, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -22,7 +22,7 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
|
||||
[titleLockup]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(at: &titleLockup, with: molecule)
|
||||
}
|
||||
|
||||
|
||||
@ -25,15 +25,18 @@ public class HeadersH2PricingTwoRowsModel: HeaderModel, MoleculeModelProtocol, P
|
||||
[headline, body, subBody, body2, subBody2, body3, subBody3].compactMap({$0})
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &headline, with: molecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule)
|
||||
|| replaceChildMolecule(at: &subBody, with: molecule)
|
||||
|| replaceChildMolecule(at: &body2, with: molecule)
|
||||
|| replaceChildMolecule(at: &body2, with: molecule)
|
||||
|| replaceChildMolecule(at: &subBody2, with: molecule)
|
||||
|| replaceChildMolecule(at: &body3, with: molecule)
|
||||
|| replaceChildMolecule(at: &subBody3, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &subBody, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body2, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &subBody2, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body3, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &subBody3, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -23,9 +23,13 @@ public class HeadersH2TinyButtonModel: HeaderModel, MoleculeModelProtocol, Paren
|
||||
[titleLockup, button]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &titleLockup, with: molecule)
|
||||
|| replaceChildMolecule(at: &button, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &button, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -20,9 +20,13 @@ open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelPr
|
||||
[checkbox, headlineBody]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &checkbox, with: molecule)
|
||||
|| replaceChildMolecule(at: &headlineBody, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &checkbox, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &headlineBody, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -20,9 +20,13 @@ public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModel
|
||||
return [image, eyebrowHeadlineBodyLink]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &image, with: molecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel,
|
||||
return [image, eyebrowHeadlineBodyLink, rightLabel]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &image, with: molecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule)
|
||||
|| replaceChildMolecule(at: &rightLabel, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
|
||||
@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Par
|
||||
[image, headlineBody, rightLabel]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &image, with: molecule)
|
||||
|| replaceChildMolecule(at: &headlineBody, with: molecule)
|
||||
|| replaceChildMolecule(at: &rightLabel, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &headlineBody, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
|
||||
@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, ParentMolec
|
||||
return [image, leftLabel, rightLabel]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &image, with: molecule)
|
||||
|| replaceChildMolecule(at: &leftLabel, with: molecule)
|
||||
|| replaceChildMolecule(at: &rightLabel, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &leftLabel, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
|
||||
@ -20,9 +20,13 @@ open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, ParentMolecu
|
||||
[radioButton, headlineBody]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &radioButton, with: replacementMolecule)
|
||||
|| replaceChildMolecule(at: &headlineBody, with: replacementMolecule)
|
||||
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &radioButton, with: replacementMolecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &headlineBody, with: replacementMolecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
|
||||
@ -39,7 +39,7 @@ public class ListOneColumnFullWidthTextBodyTextModel: ListItemModel, MoleculeMod
|
||||
return [headlineBody]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(at: &headlineBody, with: molecule)
|
||||
}
|
||||
|
||||
|
||||
@ -40,9 +40,13 @@ public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, Molecul
|
||||
return [button, eyebrowHeadlineBodyLink]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &button, with: molecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &button, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -20,9 +20,13 @@ public class ListRightVariableRightCaretAllTextAndLinksModel: ListItemModel, Par
|
||||
[rightLabel, eyebrowHeadlineBodyLink]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &rightLabel, with: molecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
import VDSTokens
|
||||
import VDS
|
||||
|
||||
public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
|
||||
public class TitleLockupModel: ParentMoleculeModelProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
@ -21,9 +21,11 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
|
||||
public var textAlignment: TitleLockup.TextAlignment = .left
|
||||
public var eyebrow: LabelModel?
|
||||
public var eyebrowColor: TitleLockup.TextColor = .primary
|
||||
public var title: LabelModel
|
||||
public var titleColor: TitleLockup.TitleTextColor = .primary
|
||||
public var subTitle: LabelModel?
|
||||
public var subTitleColor: Use = .primary
|
||||
public var subTitleColor: TitleLockup.TextColor = .primary
|
||||
|
||||
public var alignment: VDS.TitleLockup.TextAlignment = .left
|
||||
public var inverted: Bool = false
|
||||
@ -34,10 +36,26 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
[eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule }
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &eyebrow, with: molecule)
|
||||
|| replaceChildMolecule(at: &title, with: molecule)
|
||||
|| replaceChildMolecule(at: &subTitle, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &eyebrow, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &title, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &subTitle, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return textAlignment == model.textAlignment
|
||||
&& alignment == model.alignment
|
||||
&& titleColor == model.titleColor
|
||||
&& subTitleColor == model.subTitleColor
|
||||
&& inverted == model.inverted
|
||||
&& backgroundColor == model.backgroundColor
|
||||
&& eyebrow.matchExistence(with: model.eyebrow)
|
||||
&& subTitle.matchExistence(with: model.subTitle)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -59,7 +77,9 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
case moleculeName
|
||||
case textAlignment
|
||||
case eyebrow
|
||||
case eyebrowColor
|
||||
case title
|
||||
case titleColor
|
||||
case subTitle
|
||||
case subTitleColor
|
||||
case inverted
|
||||
@ -78,17 +98,36 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
|
||||
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 color = eyebrow?.textColor?.uiColor {
|
||||
self.eyebrowColor = .custom(color)
|
||||
|
||||
} else if let subTitleColor = try? typeContainer.decodeIfPresent(Use.self, forKey: .subTitleColor) {
|
||||
} else if let eyebrowColor = try? typeContainer.decodeIfPresent(TitleLockup.TextColor.self, forKey: .eyebrowColor) {
|
||||
self.eyebrowColor = eyebrowColor
|
||||
|
||||
} else {
|
||||
eyebrowColor = .primary
|
||||
}
|
||||
|
||||
if let color = title.textColor?.uiColor {
|
||||
self.titleColor = .custom(color)
|
||||
|
||||
} else if let titleColor = try? typeContainer.decodeIfPresent(TitleLockup.TitleTextColor.self, forKey: .titleColor) {
|
||||
self.titleColor = titleColor
|
||||
|
||||
} else {
|
||||
titleColor = .primary
|
||||
}
|
||||
|
||||
if let color = subTitle?.textColor?.uiColor {
|
||||
self.subTitleColor = .custom(color)
|
||||
|
||||
} else if let subTitleColor = try? typeContainer.decodeIfPresent(TitleLockup.TextColor.self, forKey: .subTitleColor) {
|
||||
self.subTitleColor = subTitleColor
|
||||
|
||||
} else {
|
||||
subTitleColor = .primary
|
||||
}
|
||||
|
||||
|
||||
if let newAlignment = try typeContainer.decodeIfPresent(VDS.TitleLockup.TextAlignment.self, forKey: .alignment) {
|
||||
alignment = newAlignment
|
||||
}
|
||||
@ -107,7 +146,9 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
try container.encode(moleculeName, forKey: .moleculeName)
|
||||
try container.encode(textAlignment, forKey: .textAlignment)
|
||||
try container.encodeIfPresent(eyebrow, forKey: .eyebrow)
|
||||
try container.encode(eyebrowColor, forKey: .eyebrowColor)
|
||||
try container.encodeModel(title, forKey: .title)
|
||||
try container.encode(titleColor, forKey: .titleColor)
|
||||
try container.encodeIfPresent(subTitle, forKey: .subTitle)
|
||||
try container.encode(subTitleColor, forKey: .subTitleColor)
|
||||
try container.encode(alignment, forKey: .alignment)
|
||||
@ -120,6 +161,7 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
do {
|
||||
if let style = eyebrow.fontStyle {
|
||||
return .init(text: eyebrow.text,
|
||||
textColor: eyebrowColor,
|
||||
isBold: style.isBold(),
|
||||
standardStyle: try style.vdsSubsetStyle(),
|
||||
textAttributes: attrs,
|
||||
@ -129,7 +171,7 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
|
||||
} catch { }
|
||||
|
||||
return .init(text: eyebrow.text, textAttributes: attrs, numberOfLines: eyebrow.numberOfLines ?? 0)
|
||||
return .init(text: eyebrow.text, textColor: eyebrowColor, textAttributes: attrs, numberOfLines: eyebrow.numberOfLines ?? 0)
|
||||
}
|
||||
|
||||
public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.TitleModel {
|
||||
@ -137,6 +179,7 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
do {
|
||||
if let style = title.fontStyle {
|
||||
return .init(text: title.text,
|
||||
textColor: titleColor,
|
||||
textAttributes: attrs,
|
||||
isBold: style.isBold(),
|
||||
standardStyle: try style.vdsSubsetStyle(),
|
||||
@ -147,7 +190,7 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
|
||||
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
|
||||
} catch { }
|
||||
|
||||
return .init(text: title.text, textAttributes: attrs, numberOfLines: title.numberOfLines ?? 0)
|
||||
return .init(text: title.text, textColor: titleColor, textAttributes: attrs, numberOfLines: title.numberOfLines ?? 0)
|
||||
}
|
||||
|
||||
public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> VDS.TitleLockup.SubTitleModel? {
|
||||
|
||||
@ -22,9 +22,13 @@ public class ListOneColumnFullWidthTextDividerSubsectionModel: ListItemModel, Mo
|
||||
[headline, body].compactMap({$0})
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &headline, with: molecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -22,9 +22,13 @@ public class ListOneColumnTextWithWhitespaceDividerShortModel: ListItemModel, Mo
|
||||
[headline, body].compactMap({$0})
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &headline, with: molecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -22,9 +22,13 @@ public class ListOneColumnTextWithWhitespaceDividerTallModel: ListItemModel, Mol
|
||||
[headline, body].compactMap({$0})
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &headline, with: molecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -68,4 +68,9 @@
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(line, forKey: .line)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return line.matchExistence(with: model.line)
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,9 +106,30 @@ open class TabBarModel: MoleculeModelProtocol {
|
||||
try container.encode(selectedTab, forKey: .selectedTab)
|
||||
try container.encodeIfPresent(style, forKey: .style)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& selectedColor == model.selectedColor
|
||||
&& selectedTab == model.selectedTab
|
||||
&& unSelectedColor == model.unSelectedColor
|
||||
&& style == model.style
|
||||
&& tabs == model.tabs
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& selectedColor == model.selectedColor
|
||||
&& selectedTab == model.selectedTab
|
||||
&& unSelectedColor == model.unSelectedColor
|
||||
&& style == model.style
|
||||
&& tabs.isVisuallyEquivalent(to: model.tabs)
|
||||
}
|
||||
}
|
||||
|
||||
open class TabBarItemModel: Codable {
|
||||
open class TabBarItemModel: Codable, Equatable, MoleculeModelComparisonProtocol {
|
||||
|
||||
open var title: String?
|
||||
open var image: String
|
||||
open var action: ActionModelProtocol
|
||||
@ -142,4 +163,18 @@ open class TabBarItemModel: Codable {
|
||||
try container.encodeModel(action, forKey: .action)
|
||||
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
|
||||
}
|
||||
|
||||
public static func == (lhs: TabBarItemModel, rhs: TabBarItemModel) -> Bool {
|
||||
return lhs.title == rhs.title
|
||||
&& lhs.image == rhs.image
|
||||
&& lhs.accessibilityText == rhs.accessibilityText
|
||||
&& lhs.action.isEqual(to: rhs.action)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return image == model.image
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& title == model.title
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,13 +105,42 @@ open class TabsModel: MoleculeModelProtocol {
|
||||
try container.encode(borderLine, forKey: .borderLine)
|
||||
try container.encodeIfPresent(minWidth, forKey: .minWidth)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& style == model.style
|
||||
&& orientation == model.orientation
|
||||
&& indicatorPosition == model.indicatorPosition
|
||||
&& overflow == model.overflow
|
||||
&& fillContainer == model.fillContainer
|
||||
&& size == model.size
|
||||
&& borderLine == model.borderLine
|
||||
&& minWidth == model.minWidth
|
||||
&& selectedIndex == model.selectedIndex
|
||||
&& tabs == model.tabs
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& style == model.style
|
||||
&& orientation == model.orientation
|
||||
&& indicatorPosition == model.indicatorPosition
|
||||
&& overflow == model.overflow
|
||||
&& fillContainer == model.fillContainer
|
||||
&& size == model.size
|
||||
&& borderLine == model.borderLine
|
||||
&& minWidth == model.minWidth
|
||||
//&& selectedIndex == model.selectedIndex // Selected index could have been either reset locally or by server. For now ignore.
|
||||
&& tabs.isVisuallyEquivalent(to: model.tabs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
open class TabItemModel: Codable {
|
||||
open class TabItemModel: Codable, Equatable, MoleculeModelComparisonProtocol {
|
||||
open var label: LabelModel
|
||||
open var action: ActionModelProtocol?
|
||||
public var analyticsData: JSONValueDictionary?
|
||||
|
||||
public init(label: LabelModel) {
|
||||
self.label = label
|
||||
@ -120,6 +149,7 @@ open class TabItemModel: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case label
|
||||
case action
|
||||
case analyticsData
|
||||
}
|
||||
|
||||
open func setDefaults() {
|
||||
@ -138,12 +168,24 @@ open class TabItemModel: Codable {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
label = try typeContainer.decode(LabelModel.self, forKey: .label)
|
||||
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
|
||||
analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData)
|
||||
setDefaults()
|
||||
}
|
||||
|
||||
|
||||
open func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeModel(label, forKey: .label)
|
||||
try container.encodeModelIfPresent(action, forKey: .action)
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
}
|
||||
|
||||
public static func == (lhs: TabItemModel, rhs: TabItemModel) -> Bool {
|
||||
return lhs.label.isEqual(to: rhs.label)
|
||||
&& lhs.action.isEqual(to: rhs.action)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return label.isVisuallyEquivalent(to: model.label)
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,9 +23,13 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
|
||||
|
||||
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)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &primaryButton, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &secondaryButton, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -82,4 +86,12 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
|
||||
try container.encodeIfPresent(secondaryButton, forKey: .secondaryButton)
|
||||
try container.encodeIfPresent(fillContainer, forKey: .fillContainer)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& fillContainer == model.fillContainer
|
||||
&& primaryButton.matchExistence(with: model.primaryButton)
|
||||
&& secondaryButton.matchExistence(with: model.secondaryButton)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +80,14 @@ class AccordionListItemModel: MoleculeListItemModel {
|
||||
try container.encodeModelIfPresent(expandAction, forKey: .expandAction)
|
||||
try container.encodeModelIfPresent(collapseAction, forKey: .collapseAction)
|
||||
}
|
||||
|
||||
override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return selected == model.selected
|
||||
&& hideLineWhenExpanded == model.hideLineWhenExpanded
|
||||
&& expandAction.matchExistence(with: model.expandAction)
|
||||
&& collapseAction.matchExistence(with: model.collapseAction)
|
||||
}
|
||||
}
|
||||
|
||||
extension AccordionListItemModel: PageBehaviorProtocolRequirer {
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import VDS
|
||||
|
||||
open class CarouselItem: MoleculeCollectionViewCell, CarouselItemProtocol {
|
||||
|
||||
@ -17,13 +18,15 @@ open class CarouselItem: MoleculeCollectionViewCell, CarouselItemProtocol {
|
||||
|
||||
open override func addMolecule(_ molecule: MoleculeViewProtocol) {
|
||||
super.addMolecule(molecule)
|
||||
|
||||
clipsToBounds = (molecule as? MVMCoreUIViewConstrainingProtocol)?.isClippable?() ?? true
|
||||
|
||||
contentView.sendSubviewToBack(molecule)
|
||||
}
|
||||
|
||||
open override func setupView() {
|
||||
super.setupView()
|
||||
clipsToBounds = true
|
||||
|
||||
|
||||
// Covers the card when peaking.
|
||||
peakingCover.backgroundColor = .white
|
||||
peakingCover.alpha = 0
|
||||
|
||||
@ -85,8 +85,27 @@
|
||||
try container.encodeIfPresent(peakingUI, forKey: .peakingUI)
|
||||
try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor)
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
|
||||
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
|
||||
try container.encode(enabled, forKey: .enabled)
|
||||
try container.encode(readOnly, forKey: .readOnly)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return peakingUI == model.peakingUI
|
||||
&& peakingArrowColor == model.peakingArrowColor
|
||||
&& analyticsData == model.analyticsData
|
||||
&& fieldValue == model.fieldValue
|
||||
&& enabled == model.enabled
|
||||
&& readOnly == model.readOnly
|
||||
}
|
||||
|
||||
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false }
|
||||
return peakingUI == model.peakingUI
|
||||
&& peakingArrowColor == model.peakingArrowColor
|
||||
&& enabled == model.enabled
|
||||
&& readOnly == model.readOnly
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
// 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, MoleculeModelComparisonProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -22,6 +23,7 @@ import MVMCore
|
||||
public var accessibilityTraits: UIAccessibilityTraits?
|
||||
public var accessibilityValue: String?
|
||||
public var accessibilityText: String?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
//--------------------------------------------------
|
||||
@ -128,4 +130,29 @@ import MVMCore
|
||||
try container.encodeIfPresent(accessibilityValue, forKey: .accessibilityValue)
|
||||
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& hideArrow == model.hideArrow
|
||||
&& style == model.style
|
||||
&& gone == model.gone
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityValue == model.accessibilityValue
|
||||
&& accessibilityTraits == model.accessibilityTraits
|
||||
&& line.matchExistence(with: model.line)
|
||||
&& action.isEqual(to: model.action)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& hideArrow == model.hideArrow
|
||||
&& style == model.style
|
||||
&& gone == model.gone
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& accessibilityValue == model.accessibilityValue
|
||||
&& accessibilityTraits == model.accessibilityTraits
|
||||
&& line.matchExistence(with: model.line)
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,4 +73,15 @@
|
||||
try container.encodeIfPresent(border, forKey: .border)
|
||||
try super.encode(to: encoder)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return border == border
|
||||
&& action.isEqual(to: model.action)
|
||||
}
|
||||
|
||||
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false }
|
||||
return border == model.border
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,4 +58,11 @@
|
||||
try container.encodeIfPresent(percent, forKey: .percent)
|
||||
try container.encode(gone, forKey: .gone)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return spacing == model.spacing
|
||||
&& percent == model.percent
|
||||
&& gone == model.gone
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ import UIKit
|
||||
}
|
||||
|
||||
public override class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
|
||||
MoleculeContainer.nameForReuse(with: model, delegateObject)
|
||||
return MoleculeContainer.nameForReuse(with: model, delegateObject)
|
||||
}
|
||||
|
||||
public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
|
||||
|
||||
@ -36,4 +36,12 @@
|
||||
required public init(from decoder: Decoder) throws {
|
||||
fatalError("init(from:) has not been implemented")
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& spacing == model.spacing
|
||||
&& percent == model.percent
|
||||
&& gone == model.gone
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,20 +20,21 @@ public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol {
|
||||
private var addedMolecules: [ListItemModelProtocol & MoleculeModelProtocol]?
|
||||
|
||||
public var children: [MoleculeModelProtocol] {
|
||||
return molecules.flatMap { $0 }
|
||||
return [tabs] + molecules.flatMap { $0 }
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
|
||||
guard let replacementMolecule = replacementMolecule as? ListItemModelProtocol & MoleculeModelProtocol else { return false }
|
||||
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
guard let replacementMolecule = replacementMolecule as? ListItemModelProtocol & MoleculeModelProtocol else { return nil }
|
||||
for (tabIndex, _) in molecules.enumerated() {
|
||||
for (elementIndex, _) in molecules[tabIndex].enumerated() {
|
||||
if molecules[tabIndex][elementIndex].id == replacementMolecule.id {
|
||||
let replacedMolecule = molecules[tabIndex][elementIndex]
|
||||
molecules[tabIndex][elementIndex] = replacementMolecule
|
||||
return true
|
||||
return replacedMolecule
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -90,6 +91,11 @@ public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol {
|
||||
try container.encode(tabs, forKey: .tabs)
|
||||
try container.encodeModels2D(molecules, forKey: .molecules)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return molecules.count == model.molecules.count
|
||||
}
|
||||
}
|
||||
|
||||
extension TabsListItemModel: PageBehaviorProtocolRequirer {
|
||||
|
||||
@ -66,6 +66,11 @@ extension TabsTableViewCell: TabsDelegate {
|
||||
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: model.tabs, additionalData: nil, delegateObject: delegateObject)
|
||||
}
|
||||
MVMCoreUIActionHandler.performActionUnstructured(with: SwapMoleculesActionModel(index < previousTabIndex ? .left : .right), sourceModel: model, additionalData: nil, delegateObject: delegateObject)
|
||||
|
||||
if let analyticsData = try? model.tabs.tabs[index].analyticsData?.toJSONAny(),
|
||||
let controller = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol {
|
||||
MVMCoreUILoggingHandler.shared()?.defaultLogPageUpdate(forController: controller, from: ["analyticsData": analyticsData])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,12 +23,16 @@ public class CornerLabelsModel: ParentMoleculeModelProtocol {
|
||||
[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 func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &self.molecule, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &topLeftLabel, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &topRightLabel, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &bottomLeftLabel, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &bottomRightLabel, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public init(with molecule: MoleculeModelProtocol?) {
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
let actionObject = ActionDelegate()
|
||||
let button = self.init(title: title, style: .plain, target: actionObject, action: #selector(actionObject.callActionBlock(_:)))
|
||||
button.actionDelegate = actionObject
|
||||
button.setTitleTextAttributes([NSAttributedString.Key.font: Styler.Font.RegularBodySmall.getFont()], for: .normal)
|
||||
button.setTitleTextAttributes([NSAttributedString.Key.font: Styler.Font.BoldBodySmall.getFont()], for: .normal)
|
||||
return button
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,25 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule
|
||||
try container.encodeIfPresent(imageRenderingMode, forKey: .imageRenderingMode)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return true }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& accessibilityIdentifier == model.accessibilityIdentifier
|
||||
&& image == model.image
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& imageRenderingMode == model.imageRenderingMode
|
||||
&& action.isEqual(to: action)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return true }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& accessibilityIdentifier == model.accessibilityIdentifier
|
||||
&& image == model.image
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& imageRenderingMode == model.imageRenderingMode
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Method
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -20,16 +20,25 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
|
||||
|
||||
open class var identifier: String { "navigationBar" }
|
||||
public var id: String = UUID().uuidString
|
||||
|
||||
private let defaultHidesSystemBackButton = true
|
||||
|
||||
|
||||
open var title: String?
|
||||
open var hidden = false
|
||||
open var line: LineModel? = LineModel(type: .secondary)
|
||||
open var hidesSystemBackButton = true
|
||||
open var hidden: Bool?
|
||||
open var hidesSystemBackButton: Bool?
|
||||
open var style: NavigationItemStyle?
|
||||
|
||||
private var _backgroundColor: Color?
|
||||
|
||||
open var _line: LineModel?
|
||||
open var line: LineModel? {
|
||||
get {
|
||||
let line = _line ?? LineModel(type: .secondary)
|
||||
line.inverted = style == .dark
|
||||
return line
|
||||
}
|
||||
set {
|
||||
_line = newValue
|
||||
}
|
||||
}
|
||||
|
||||
open var _backgroundColor: Color?
|
||||
open var backgroundColor: Color? {
|
||||
get {
|
||||
if let backgroundColor = _backgroundColor { return backgroundColor }
|
||||
@ -41,8 +50,8 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
|
||||
_backgroundColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var _tintColor: Color?
|
||||
|
||||
open var _tintColor: Color?
|
||||
open var tintColor: Color {
|
||||
get {
|
||||
if let tintColor = _tintColor { return tintColor }
|
||||
@ -54,7 +63,6 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
|
||||
_tintColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// If true, we add the button in the backButton property. If false we do not add the button. If nil, we add the button if the controller is not the bottom of the stack
|
||||
open var alwaysShowBackButton: Bool?
|
||||
open var backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol)?
|
||||
@ -62,7 +70,7 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
|
||||
open var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]?
|
||||
open var additionalRightButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]?
|
||||
open var titleView: MoleculeModelProtocol?
|
||||
open var titleOffset: UIOffset? = UIOffset(horizontal: -CGFloat.greatestFiniteMagnitude, vertical: 0)
|
||||
open var titleOffset: UIOffset?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
@ -100,27 +108,18 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
||||
title = try typeContainer.decodeIfPresent(String.self, forKey: .title)
|
||||
if let hidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidden) {
|
||||
self.hidden = hidden
|
||||
}
|
||||
hidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidden)
|
||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||
_tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor)
|
||||
if let line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) {
|
||||
self.line = line
|
||||
}
|
||||
if let hidesSystemBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidesSystemBackButton) {
|
||||
self.hidesSystemBackButton = hidesSystemBackButton
|
||||
}
|
||||
_line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line)
|
||||
hidesSystemBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidesSystemBackButton)
|
||||
alwaysShowBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .alwaysShowBackButton)
|
||||
backButton = try typeContainer.decodeModelIfPresent(codingKey: .backButton)
|
||||
additionalLeftButtons = try typeContainer.decodeModelsIfPresent(codingKey: .additionalLeftButtons)
|
||||
additionalRightButtons = try typeContainer.decodeModelsIfPresent(codingKey: .additionalRightButtons)
|
||||
titleView = try typeContainer.decodeModelIfPresent(codingKey: .titleView)
|
||||
style = try typeContainer.decodeIfPresent(NavigationItemStyle.self, forKey: .style)
|
||||
if let titleOffset = try typeContainer.decodeIfPresent(UIOffset.self, forKey: .titleOffset) {
|
||||
self.titleOffset = titleOffset
|
||||
}
|
||||
line?.inverted = style == .dark
|
||||
titleOffset = try typeContainer.decodeIfPresent(UIOffset.self, forKey: .titleOffset)
|
||||
}
|
||||
|
||||
open func encode(to encoder: Encoder) throws {
|
||||
@ -128,11 +127,11 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(moleculeName, forKey: .moleculeName)
|
||||
try container.encodeIfPresent(title, forKey: .title)
|
||||
try container.encode(hidden, forKey: .hidden)
|
||||
try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor)
|
||||
try container.encodeIfPresent(hidden, forKey: .hidden)
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
try container.encodeIfPresent(_tintColor, forKey: .tintColor)
|
||||
try container.encodeIfPresent(line, forKey: .line)
|
||||
try container.encode(hidesSystemBackButton, forKey: .hidesSystemBackButton)
|
||||
try container.encodeIfPresent(_line, forKey: .line)
|
||||
try container.encodeIfPresent(hidesSystemBackButton, forKey: .hidesSystemBackButton)
|
||||
try container.encodeIfPresent(alwaysShowBackButton, forKey: .alwaysShowBackButton)
|
||||
try container.encodeModelIfPresent(backButton, forKey: .backButton)
|
||||
try container.encodeModelsIfPresent(additionalLeftButtons, forKey: .additionalLeftButtons)
|
||||
@ -141,6 +140,24 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc
|
||||
try container.encodeIfPresent(style, forKey: .style)
|
||||
try container.encodeIfPresent(titleOffset, forKey: .titleOffset)
|
||||
}
|
||||
|
||||
open func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& title == model.title
|
||||
&& hidden == model.hidden
|
||||
&& tintColor == model.tintColor
|
||||
&& line.isEqual(to: model.line)
|
||||
&& hidesSystemBackButton == model.hidesSystemBackButton
|
||||
&& style == model.style
|
||||
&& titleOffset == model.titleOffset
|
||||
&& titleView.isEqual(to: model.titleView)
|
||||
&& additionalLeftButtons.isEqual(to: model.additionalLeftButtons)
|
||||
&& additionalRightButtons.isEqual(to: model.additionalRightButtons)
|
||||
&& backButton.isEqual(to: model.backButton)
|
||||
&& alwaysShowBackButton == model.alwaysShowBackButton
|
||||
&& hidesSystemBackButton == model.hidesSystemBackButton
|
||||
}
|
||||
}
|
||||
|
||||
extension NavigationItemModel: ParentMoleculeModelProtocol {
|
||||
|
||||
@ -20,7 +20,7 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco
|
||||
return [molecule]
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(at: &self.molecule, with: molecule)
|
||||
}
|
||||
|
||||
@ -61,4 +61,14 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco
|
||||
try container.encodeModel(molecule, forKey: .molecule)
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
}
|
||||
|
||||
// Declare for overrides.
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
return isEqual(to: model)
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ public extension MoleculeContainerModelProtocol {
|
||||
}
|
||||
|
||||
public extension MoleculeContainerModelProtocol where Self: AnyObject {
|
||||
mutating func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
|
||||
mutating func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(at: &molecule, with: replacementMolecule)
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,13 @@ import VDS
|
||||
if let closeButton = viewModel.closeButton {
|
||||
onCloseClick = { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.executeAction(model: closeButton, delegateObject: self.delegateObject, additionalData: self.additionalData) }
|
||||
if closeButton.action.actionType == ActionNoopModel.identifier {
|
||||
closeButton.action = ActionDismissNotificationModel()
|
||||
self.executeAction(model: closeButton, delegateObject: self.delegateObject, additionalData: self.additionalData)
|
||||
} else {
|
||||
self.executeAction(model: closeButton, delegateObject: self.delegateObject, additionalData: self.additionalData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideCloseButton = viewModel.closeButton == nil
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
//
|
||||
// NotificationXButton.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Scott Pfeil on 9/17/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MVMCore
|
||||
|
||||
@objcMembers open class NotificationXButton: Button {
|
||||
|
||||
open func closeTopAlert(with delegateObject: MVMCoreUIDelegateObject?) {
|
||||
MVMCoreUIActionHandler.performActionUnstructured(with: ActionDismissNotificationModel(), sourceModel: model, additionalData: nil, delegateObject: delegateObject)
|
||||
}
|
||||
|
||||
open override func setupView() {
|
||||
if let image = MVMCoreUIUtility.imageNamed("nav_close")?.withRenderingMode(.alwaysTemplate) {
|
||||
setImage(image, for: .normal)
|
||||
}
|
||||
tintColor = .white
|
||||
adjustsImageWhenHighlighted = false
|
||||
accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "AccCloseButton")
|
||||
setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
heightAnchor.constraint(equalToConstant: 16.0).isActive = true
|
||||
widthAnchor.constraint(equalToConstant: 16.0).isActive = true
|
||||
}
|
||||
|
||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
||||
super.set(with: model, delegateObject, additionalData)
|
||||
guard let model = model as? NotificationXButtonModel else { return }
|
||||
|
||||
// TODO: Temporary, consider action for dismissing top alert
|
||||
if model.action.actionType == ActionNoopModel.identifier {
|
||||
addActionBlock(event: .touchUpInside) { (button) in
|
||||
(button as? NotificationXButton)?.closeTopAlert(with: delegateObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
|
||||
public class EyebrowHeadlineBodyLinkModel: ParentMoleculeModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -25,11 +25,15 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule
|
||||
[eyebrow, headline, body, link].compactMap { (molecule: MoleculeModelProtocol?) in molecule }
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &eyebrow, with: molecule)
|
||||
|| replaceChildMolecule(at: &headline, with: molecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule)
|
||||
|| replaceChildMolecule(at: &link, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &eyebrow, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -102,4 +106,13 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule
|
||||
try container.encodeIfPresent(body, forKey: .body)
|
||||
try container.encodeIfPresent(link, forKey: .link)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& eyebrow.matchExistence(with: model.eyebrow)
|
||||
&& headline.matchExistence(with: model.headline)
|
||||
&& body.matchExistence(with: model.body)
|
||||
&& link.matchExistence(with: model.link)
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,9 +24,13 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol {
|
||||
[headline, body].compactMap { $0 }
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at:&headline, with: replacementMolecule)
|
||||
|| replaceChildMolecule(at:&body, with: replacementMolecule)
|
||||
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at:&headline, with: replacementMolecule, replaced: &replacedMolecule)
|
||||
|| replaceChildMolecule(at:&body, with: replacementMolecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -89,12 +93,18 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol {
|
||||
try container.encodeIfPresent(style, forKey: .style)
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return style == style
|
||||
&& backgroundColor == model.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
public extension HeadlineBodyModel {
|
||||
func createHeaderTitleLockupModel(defaultStyle: Style = .header) throws -> TitleLockupModel {
|
||||
guard let headline = headline else { throw ModelRegistry.Error.decoderOther(message: "headline is required for this use case.") }
|
||||
var body = self.body
|
||||
let body = self.body
|
||||
switch style ?? defaultStyle {
|
||||
case .landingHeader:
|
||||
headline.fontStyle = Styler.Font.RegularTitle2XLarge
|
||||
@ -106,7 +116,7 @@ public extension HeadlineBodyModel {
|
||||
headline.fontStyle = Styler.Font.RegularTitleXLarge
|
||||
body?.fontStyle = Styler.Font.RegularTitleMedium
|
||||
}
|
||||
let model = try TitleLockupModel(title: headline, subTitle: body)
|
||||
let model = TitleLockupModel(title: headline, subTitle: body)
|
||||
model.id = id
|
||||
return model
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ open class Carousel: View {
|
||||
public var delegateObject: MVMCoreUIDelegateObject?
|
||||
|
||||
private var size: CGFloat?
|
||||
|
||||
|
||||
// Updates the model and index.
|
||||
public func updateModelIndex() {
|
||||
(model as? CarouselModel)?.index = pageIndex
|
||||
@ -90,12 +90,29 @@ open class Carousel: View {
|
||||
showPeaking(false)
|
||||
|
||||
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
|
||||
guard let model = model as? CarouselModel, !model.molecules.isEmpty,
|
||||
(model.paging == true || loop == true) else { return }
|
||||
guard let model = model as? CarouselModel, !model.molecules.isEmpty else { return }
|
||||
guard (model.paging == true || loop == true) else {
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePagerVisibility()
|
||||
}
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async { [self] in
|
||||
collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: false)
|
||||
collectionView.layoutIfNeeded()
|
||||
showPeaking(true)
|
||||
updatePagerVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates if the pager is visible or not.
|
||||
private func updatePagerVisibility() {
|
||||
guard let pagingView = pagingView else { return }
|
||||
let shouldHidePager = collectionView.contentSize.width < bounds.width
|
||||
if (shouldHidePager && !pagingView.isHidden) || (!shouldHidePager && pagingView.isHidden) {
|
||||
pagingView.isHidden = shouldHidePager
|
||||
pagingBottomPin?.isActive = !shouldHidePager
|
||||
delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,6 +125,7 @@ open class Carousel: View {
|
||||
collectionView.dataSource = self
|
||||
collectionView.delegate = self
|
||||
collectionView.bounces = false
|
||||
collectionView.clipsToBounds = false
|
||||
addSubview(collectionView)
|
||||
bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint
|
||||
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
||||
@ -140,16 +158,6 @@ open class Carousel: View {
|
||||
(cell as? MVMCoreViewProtocol)?.updateView(size)
|
||||
}
|
||||
layoutCollection()
|
||||
|
||||
// Check must be dispatched to main for the layout to complete in layoutCollection.
|
||||
DispatchQueue.main.async { [self] in
|
||||
let shouldHidePager = molecules?.count ?? 0 < 2 || collectionView.contentSize.width < bounds.width
|
||||
if let pagingView = pagingView, shouldHidePager != pagingView.isHidden {
|
||||
pagingView.isHidden = shouldHidePager
|
||||
pagingBottomPin?.isActive = !shouldHidePager
|
||||
delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -158,9 +166,26 @@ open class Carousel: View {
|
||||
|
||||
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
self.delegateObject = delegateObject
|
||||
let originalModel = self.model as? CarouselModel
|
||||
super.set(with: model, delegateObject, additionalData)
|
||||
|
||||
guard let carouselModel = model as? CarouselModel else { return }
|
||||
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)")
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
if let originalModel, carouselModel.isDeeplyVisuallyEquivalent(to: originalModel) {
|
||||
// Prevents a carousel reset while still updating the cell backing data through reconfigureItems.
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...")
|
||||
FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate)
|
||||
updateModelIndex() // Ensure the new model indexing matches the old.
|
||||
pagingView?.currentIndex = pageIndex // Trigger a paging view render.
|
||||
collectionView.reconfigureItems(at: collectionView.indexPathsForVisibleItems)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is new. Rebuilding carousel.")
|
||||
accessibilityLabel = carouselModel.accessibilityText
|
||||
collectionView.layer.borderColor = UIColor.mvmCoolGray3.cgColor
|
||||
collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0
|
||||
@ -244,7 +269,7 @@ open class Carousel: View {
|
||||
|
||||
var pagingView: (MoleculeViewProtocol & CarouselPageControlProtocol)? = nil
|
||||
if let molecule = molecule,
|
||||
(!molecule.hidesForSinglePage || numberOfPages > 1) {
|
||||
(numberOfPages > 1 || !molecule.hidesForSinglePage) {
|
||||
pagingView = ModelRegistry.createMolecule(molecule, delegateObject: delegateObject) as? (MoleculeViewProtocol & CarouselPageControlProtocol)
|
||||
pagingMoleculeName = molecule.moleculeName
|
||||
} else {
|
||||
|
||||
@ -171,6 +171,48 @@ import UIKit
|
||||
try container.encode(enabled, forKey: .enabled)
|
||||
try container.encode(readOnly, forKey: .readOnly)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == backgroundColor
|
||||
&& spacing == model.spacing
|
||||
&& border == model.border
|
||||
&& loop == model.loop
|
||||
&& height == model.height
|
||||
&& itemWidthPercent == model.itemWidthPercent
|
||||
&& itemAlignment == model.itemAlignment
|
||||
&& paging == model.paging
|
||||
&& useHorizontalMargins == model.useHorizontalMargins
|
||||
&& leftPadding == model.leftPadding
|
||||
&& rightPadding == model.rightPadding
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& baseValue == model.baseValue
|
||||
&& fieldKey == model.fieldKey
|
||||
&& groupName == model.groupName
|
||||
&& enabled == model.enabled
|
||||
&& readOnly == model.readOnly
|
||||
&& molecules.count == model.molecules.count
|
||||
&& pagingMolecule.matchExistence(with: model.pagingMolecule)
|
||||
}
|
||||
|
||||
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == backgroundColor
|
||||
&& spacing == model.spacing
|
||||
&& border == model.border
|
||||
&& loop == model.loop
|
||||
&& height == model.height
|
||||
&& itemWidthPercent == model.itemWidthPercent
|
||||
&& itemAlignment == model.itemAlignment
|
||||
&& paging == model.paging
|
||||
&& useHorizontalMargins == model.useHorizontalMargins
|
||||
&& leftPadding == model.leftPadding
|
||||
&& rightPadding == model.rightPadding
|
||||
&& accessibilityText == model.accessibilityText
|
||||
&& baseValue == model.baseValue
|
||||
&& enabled == model.enabled
|
||||
&& readOnly == model.readOnly
|
||||
}
|
||||
}
|
||||
|
||||
extension CarouselModel {
|
||||
@ -179,8 +221,16 @@ extension CarouselModel {
|
||||
return molecules
|
||||
}
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(in: &molecules, with: molecule)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CarouselModel: CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
return "\(molecules.count) \(molecules.map { ($0 as? CarouselItemModel)?.molecule.moleculeName ?? "unknown" } )"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -79,4 +79,11 @@
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return backgroundColor == model.backgroundColor
|
||||
&& axis == model.axis
|
||||
&& spacing == model.spacing
|
||||
&& molecules.count == model.molecules.count
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ extension StackModelProtocol {
|
||||
|
||||
extension StackModelProtocol where Self: AnyObject {
|
||||
|
||||
public mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
public mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
return try replaceChildMolecule(in: &molecules, with: molecule)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
//
|
||||
// MoleculeComparisonProtocol.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Kyle Hedden on 4/29/24.
|
||||
// Copyright © 2024 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol MoleculeModelComparisonProtocol: ModelComparisonProtocol {
|
||||
|
||||
/**
|
||||
Shallow check if there are no visual differences between models.
|
||||
|
||||
By default if the models are equal then they are visually equivalent. However, if there are parts of models that can be updated without a UI update, this could be subset of properties.
|
||||
**/
|
||||
func isVisuallyEquivalent(to model: MoleculeModelComparisonProtocol) -> Bool
|
||||
}
|
||||
|
||||
extension MoleculeModelComparisonProtocol {
|
||||
|
||||
public func isVisuallyEquivalent(to model: MoleculeModelComparisonProtocol) -> Bool {
|
||||
return isEqual(to: model)
|
||||
}
|
||||
}
|
||||
|
||||
public extension MoleculeModelComparisonProtocol where Self: ParentModelProtocol {
|
||||
func isDeeplyVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return !findFirst(in: model, failing: { mine, theirs in
|
||||
guard let mine = mine as? MoleculeModelComparisonProtocol, let theirs = theirs as? MoleculeModelComparisonProtocol else { return false }
|
||||
return mine.isVisuallyEquivalent(to: theirs)
|
||||
}).matched
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional {
|
||||
|
||||
/// Checks if the current model is equal to another model.
|
||||
func isVisuallyEquivalent(to model: MoleculeModelComparisonProtocol?) -> Bool {
|
||||
guard let self = self as? MoleculeModelComparisonProtocol else {
|
||||
return model == nil
|
||||
}
|
||||
guard let model = model else {
|
||||
return false
|
||||
}
|
||||
return self.isVisuallyEquivalent(to: model)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Collection {
|
||||
/// Checks if all the models in the given collection match another given collection.
|
||||
func isVisuallyEquivalent(to models: [MoleculeModelComparisonProtocol]) -> Bool {
|
||||
guard count == models.count, let self = self as? [MoleculeModelComparisonProtocol] else { return false }
|
||||
return models.indices.allSatisfy { index in
|
||||
self[index].isVisuallyEquivalent(to: models[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional where Wrapped: Collection {
|
||||
func isVisuallyEquivalent(to models: [MoleculeModelComparisonProtocol]?) -> Bool {
|
||||
guard let self = self as? [MoleculeModelComparisonProtocol] else {
|
||||
return models == nil
|
||||
}
|
||||
guard let models = models else {
|
||||
return false
|
||||
}
|
||||
return self.isVisuallyEquivalent(to: models)
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ public enum MolecularError: Swift.Error {
|
||||
case countImbalance(String)
|
||||
}
|
||||
|
||||
public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol, MoleculeTreeTraversalProtocol, MoleculeMaskingProtocol {
|
||||
public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol, MoleculeTreeTraversalProtocol, MoleculeMaskingProtocol, MoleculeModelComparisonProtocol, CustomDebugStringConvertible {
|
||||
var moleculeName: String { get }
|
||||
var backgroundColor: Color? { get set }
|
||||
var id: String { get }
|
||||
@ -18,6 +18,16 @@ public extension MoleculeModelProtocol {
|
||||
static var categoryName: String { "\(MoleculeModelProtocol.self)" }
|
||||
|
||||
static var categoryCodingKey: String { "moleculeName" }
|
||||
|
||||
func isEqual(to model: ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return moleculeName == model.moleculeName
|
||||
&& backgroundColor == model.backgroundColor
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
return "\(moleculeName): \(id)"
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers made due to swift not able to reconcile which category.
|
||||
|
||||
@ -10,11 +10,11 @@ import Foundation
|
||||
|
||||
public protocol NavigationItemModelProtocol {
|
||||
var title: String? { get set }
|
||||
var hidden: Bool { get set }
|
||||
var hidden: Bool? { get set }
|
||||
var backgroundColor: Color? { get set }
|
||||
var tintColor: Color { get set }
|
||||
var line: LineModel? { get set }
|
||||
var hidesSystemBackButton: Bool { get set }
|
||||
var hidesSystemBackButton: Bool? { get set }
|
||||
var alwaysShowBackButton: Bool? { get set }
|
||||
var backButton: (NavigationButtonModelProtocol & MoleculeModelProtocol)? { get set }
|
||||
var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? { get set }
|
||||
|
||||
@ -8,48 +8,61 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol {
|
||||
public protocol ParentModelProtocol: ModelProtocol, MoleculeTreeTraversalProtocol {
|
||||
|
||||
/// Returns the direct children of this component. (Does not recurse.)
|
||||
var children: [MoleculeModelProtocol] { get }
|
||||
|
||||
/// Method for replacing surface level children. (Does not recurse.)
|
||||
mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool
|
||||
mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol?
|
||||
}
|
||||
|
||||
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 }
|
||||
func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return nil }
|
||||
|
||||
func replaceChildMolecule<T>(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
return try replaceChildMolecule(at: &childMolecule, with: replacementMolecule, replaced: &replacedMolecule) ? replacedMolecule : nil
|
||||
}
|
||||
|
||||
/// Helper function for replacing a single molecules with type and optionality checks.
|
||||
func replaceChildMolecule<T>(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
|
||||
func replaceChildMolecule<T>(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol, replaced: inout MoleculeModelProtocol?) throws -> Bool {
|
||||
guard let childIdMolecule = childMolecule as? MoleculeModelProtocol else { return false }
|
||||
if childIdMolecule.id == replacementMolecule.id {
|
||||
guard let replacementMolecule = replacementMolecule as? T else {
|
||||
guard let typedReplacementMolecule = 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
|
||||
replaced = childIdMolecule
|
||||
childMolecule = typedReplacementMolecule
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
return try replaceChildMolecule(at: &molecules, with: replacementMolecule, replaced: &replacedMolecule) ? replacedMolecule : nil
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol, replaced: inout MoleculeModelProtocol?) throws -> Bool {
|
||||
guard let moleculeIdModels = molecules as? [MoleculeModelProtocol],
|
||||
let matchingIndex = moleculeIdModels.firstIndex(where: {
|
||||
$0.id == replacementMolecule.id
|
||||
})
|
||||
else { return false }
|
||||
guard let typedReplacementMolecule = 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
|
||||
replaced = molecules[matchingIndex] as? MoleculeModelProtocol
|
||||
molecules[matchingIndex] = typedReplacementMolecule
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelProtocol {
|
||||
|
||||
}
|
||||
|
||||
public extension ParentMoleculeModelProtocol {
|
||||
@ -64,7 +77,7 @@ public extension ParentMoleculeModelProtocol {
|
||||
// Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol.
|
||||
return additionalParent.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult)
|
||||
}
|
||||
return molecule.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult)
|
||||
return nextPartialResult(result, molecule, depth + 1)
|
||||
}
|
||||
if (options == .childFirst) {
|
||||
result = nextPartialResult(result, self, depth)
|
||||
@ -88,7 +101,7 @@ public extension ParentMoleculeModelProtocol {
|
||||
// Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol.
|
||||
additionalParent.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept)
|
||||
} else {
|
||||
child.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept)
|
||||
onVisit(depth, child, &shouldStop)
|
||||
}
|
||||
guard !shouldStop else { return }
|
||||
}
|
||||
@ -98,3 +111,83 @@ public extension ParentMoleculeModelProtocol {
|
||||
// if options == .leafOnly don't call on self.
|
||||
}
|
||||
}
|
||||
|
||||
public extension ParentModelProtocol {
|
||||
|
||||
typealias DeepCompareResult = (matched: Bool, mine: ModelProtocol?, theirs: ModelProtocol?)
|
||||
|
||||
typealias ModelPair = (mine: ModelProtocol, theirs: ModelProtocol)
|
||||
|
||||
func deepEquals(to model: any ModelProtocol) -> Bool {
|
||||
guard let model = model as? ParentModelProtocol else { return false }
|
||||
return !findFirst(in: model, failing: { $0.isEqual(to: $1) }).matched
|
||||
}
|
||||
|
||||
func findFirst(in anotherParent: ParentModelProtocol, failing test: (ModelProtocol, ModelProtocol)->Bool, options: TreeTraversalOptions = .childFirst) -> DeepCompareResult {
|
||||
|
||||
guard options != .parentFirst, test(self, anotherParent) else { return (true, mine: self, theirs: anotherParent)}
|
||||
|
||||
let myChildren = children
|
||||
let theirChildren = anotherParent.children
|
||||
guard myChildren.count == theirChildren.count else { return (true, mine: self, theirs: anotherParent) }
|
||||
for index in myChildren.indices {
|
||||
if let myChild = myChildren[index] as? ParentModelProtocol {
|
||||
if let theirChild = theirChildren[index] as? ParentModelProtocol {
|
||||
let result = myChild.findFirst(in: theirChild, failing: test)
|
||||
guard !result.0 else { return result }
|
||||
} else {
|
||||
return (true, mine: myChild, theirs: theirChildren[index])
|
||||
}
|
||||
} else if !test(myChildren[index], theirChildren[index]) {
|
||||
return (true, mine: myChildren[index], theirs: theirChildren[index])
|
||||
}
|
||||
}
|
||||
|
||||
guard options == .childFirst, test(self, anotherParent) else { return (true, mine: self, theirs: anotherParent)}
|
||||
|
||||
return (false, nil, nil)
|
||||
}
|
||||
|
||||
func findAllTheirsNotEqual<T>(against anotherParent: ParentModelProtocol) -> [T] {
|
||||
return deepCompare(against: anotherParent) { $0.isEqual(to: $1) }.compactMap { $0.theirs as? T }
|
||||
}
|
||||
|
||||
func findAllNotEqual(against anotherParent: ParentModelProtocol) -> [ModelPair] {
|
||||
return deepCompare(against: anotherParent) { $0.isEqual(to: $1) }
|
||||
}
|
||||
|
||||
func deepCompare(against anotherParent: ParentModelProtocol, where test: (ModelProtocol, ModelProtocol)->Bool) -> [ModelPair] {
|
||||
|
||||
var allDiffs = [ModelPair]()
|
||||
|
||||
let myChildren = children
|
||||
let theirChildren = anotherParent.children
|
||||
guard myChildren.count == theirChildren.count else { return [(self, anotherParent)] }
|
||||
|
||||
if !test(self, anotherParent) {
|
||||
allDiffs.append((self, anotherParent))
|
||||
}
|
||||
|
||||
for index in myChildren.indices {
|
||||
if let myChild = myChildren[index] as? ParentModelProtocol,
|
||||
let theirChild = theirChildren[index] as? ParentModelProtocol {
|
||||
let childDiffs = myChild.deepCompare(against: theirChild, where: test)
|
||||
allDiffs.append(contentsOf: childDiffs)
|
||||
} else if !test(myChildren[index], theirChildren[index]) {
|
||||
allDiffs.append((myChildren[index], theirChildren[index]))
|
||||
}
|
||||
}
|
||||
|
||||
return allDiffs
|
||||
}
|
||||
}
|
||||
|
||||
public extension ModelComparisonProtocol where Self: MoleculeModelProtocol {
|
||||
|
||||
func deepEquals(to model: ModelProtocol) -> Bool {
|
||||
if let self = self as? ParentModelProtocol {
|
||||
return self.deepEquals(to: model)
|
||||
}
|
||||
return isEqual(to: model)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,9 +7,11 @@
|
||||
//
|
||||
|
||||
|
||||
public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol, ParentModelProtocol {
|
||||
public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol, ParentModelProtocol, MoleculeModelComparisonProtocol {
|
||||
var template: String { get }
|
||||
var rootMolecules: [MoleculeModelProtocol] { get }
|
||||
/// Page rendering ID. Unique betwen JSON parses.
|
||||
var id: String { get }
|
||||
}
|
||||
|
||||
public extension TemplateModelProtocol {
|
||||
@ -42,27 +44,27 @@ public extension TemplateModelProtocol {
|
||||
extension TemplateModelProtocol {
|
||||
|
||||
/// Recursively finds and replaces the first child matching the replacement molecule id property.
|
||||
mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
|
||||
mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
// Attempt root level replacement on the template model first.
|
||||
if try replaceChildMolecule(with: replacementMolecule) {
|
||||
return true
|
||||
if let replacedMolecule = try replaceChildMolecule(with: replacementMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
|
||||
var didReplaceMolecule = false
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
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)
|
||||
replacedMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule)
|
||||
} catch {
|
||||
possibleError = error
|
||||
}
|
||||
stop = didReplaceMolecule || possibleError != nil
|
||||
stop = replacedMolecule != nil || possibleError != nil
|
||||
}
|
||||
if let error = possibleError {
|
||||
throw error
|
||||
}
|
||||
return didReplaceMolecule
|
||||
return replacedMolecule
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,9 +21,6 @@ public protocol MoleculeDelegateProtocol: AnyObject {
|
||||
|
||||
/// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. Mainly used for list or collections.
|
||||
func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) //optional
|
||||
|
||||
/// Attempts to replace the molecules provided. Returns the ones that replaced successfully.
|
||||
func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol], completionHandler: (([MoleculeModelProtocol])->Void)?)
|
||||
}
|
||||
|
||||
extension MoleculeDelegateProtocol {
|
||||
|
||||
@ -38,7 +38,7 @@ public extension MoleculeTreeTraversalProtocol {
|
||||
|
||||
func printMolecules(options: TreeTraversalOptions = .parentFirst) {
|
||||
depthFirstTraverse(options: options, depth: 1) { depth, molecule, stop in
|
||||
print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule): \(molecule.id)]")
|
||||
print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule)]")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,19 +35,26 @@ public extension TemplateProtocol {
|
||||
|
||||
public extension TemplateProtocol where Self: PageBehaviorHandlerProtocol, Self: MVMCoreViewControllerProtocol {
|
||||
|
||||
func parseTemplate(loadObject: MVMCoreLoadObject) throws -> TemplateModelProtocol {
|
||||
guard let pageJSON = loadObject.pageJSON else {
|
||||
throw MVMCoreError.error(code: ErrorCode.parsingJSON.rawValue, messageToDisplay: "", messageToLog: "Load object is missing its page JSON!")
|
||||
}
|
||||
return try parseTemplate(pageJSON: pageJSON)
|
||||
}
|
||||
|
||||
/// Helper function to do common parsing logic.
|
||||
func parseTemplate(json: [AnyHashable: Any]?) throws {
|
||||
guard let pageJSON = json else { return }
|
||||
func parseTemplate(pageJSON: [AnyHashable: Any]) throws -> TemplateModelProtocol {
|
||||
let delegateObject = delegateObject?() as? MVMCoreUIDelegateObject
|
||||
let data = try JSONSerialization.data(withJSONObject: pageJSON)
|
||||
let decoder = JSONDecoder.create(with: delegateObject)
|
||||
templateModel = try decodeTemplate(using: decoder, from: data)
|
||||
let templateModel = try decodeTemplate(using: decoder, from: data)
|
||||
|
||||
// Add additional required behaviors if applicable.
|
||||
guard var pageBehaviorsModel = templateModel as? TemplateModelProtocol & PageBehaviorContainerModelProtocol else { return }
|
||||
// Add additional required behavior models to the template if applicable.
|
||||
guard var templateBehaviorsModel = templateModel as? TemplateModelProtocol & PageBehaviorContainerModelProtocol else {
|
||||
return templateModel
|
||||
}
|
||||
templateBehaviorsModel.traverseAndAddRequiredBehaviors()
|
||||
|
||||
pageBehaviorsModel.traverseAndAddRequiredBehaviors()
|
||||
var behaviorHandler = self
|
||||
behaviorHandler.applyBehaviors(pageBehaviorModel: pageBehaviorsModel, delegateObject: delegateObject)
|
||||
return templateModel
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,11 +10,14 @@ import Foundation
|
||||
|
||||
|
||||
@objcMembers open class BaseTemplateModel: MVMControllerModelProtocol, TabPageModelProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
open class var identifier: String { "" }
|
||||
|
||||
public var id: String = UUID().uuidString
|
||||
|
||||
public var pageType: String
|
||||
public var template: String {
|
||||
// Although this is done in the extension, it is needed for the encoding.
|
||||
@ -35,8 +38,12 @@ import Foundation
|
||||
public var hideLeftPanel: Bool?
|
||||
public var hideRightPanel: Bool?
|
||||
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
|
||||
return try replaceChildMolecule(at: &navigationBar, with: molecule)
|
||||
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try replaceChildMolecule(at: &navigationBar, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -114,4 +121,18 @@ import Foundation
|
||||
try container.encodeIfPresent(tabBarIndex, forKey: .tabBarIndex)
|
||||
try container.encode(shouldMaskScreenWhileRecording, forKey: .shouldMaskScreenWhileRecording)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return pageType == model.pageType
|
||||
&& backgroundColor == model.backgroundColor
|
||||
&& screenHeading == model.screenHeading
|
||||
&& formRules?.count ?? 0 == model.formRules?.count ?? 0
|
||||
&& navigationBar.matchExistence(with: model.navigationBar)
|
||||
&& tabBarHidden == model.tabBarHidden
|
||||
&& hideLeftPanel == model.hideLeftPanel
|
||||
&& hideRightPanel == model.hideRightPanel
|
||||
&& tabBarIndex == model.tabBarIndex
|
||||
&& shouldMaskScreenWhileRecording == model.shouldMaskScreenWhileRecording
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,9 +21,8 @@
|
||||
//--------------------------------------------------
|
||||
// MARK: - Computed Properties
|
||||
//--------------------------------------------------
|
||||
open override func parsePageJSON() throws {
|
||||
try parseTemplate(json: loadObject?.pageJSON)
|
||||
try super.parsePageJSON()
|
||||
open override func parsePageJSON(loadObject: MVMCoreLoadObject) throws -> PageModelProtocol {
|
||||
return try parseTemplate(loadObject: loadObject)
|
||||
}
|
||||
|
||||
open override var loadObject: MVMCoreLoadObject? {
|
||||
@ -80,10 +79,10 @@
|
||||
}
|
||||
|
||||
|
||||
open override func handleNewData() {
|
||||
open override func handleNewData(_ pageModel: PageModelProtocol? = nil) {
|
||||
setup()
|
||||
registerCells()
|
||||
super.handleNewData()
|
||||
super.handleNewData(pageModel)
|
||||
}
|
||||
|
||||
open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
|
||||
@ -134,7 +133,7 @@
|
||||
}
|
||||
update(cell: cell, size: view.frame.width)
|
||||
// Neded to fix an apple defect where the cell is not the correct size on certain devices for certain cells
|
||||
cell.layoutIfNeeded()
|
||||
cell.setNeedsLayout()
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
@ -23,9 +23,14 @@
|
||||
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))
|
||||
public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
if let replacedMolecule = try super.replaceChildMolecule(with: molecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
if molecules != nil, let replacedMolecule = try replaceChildMolecule(in: &(molecules!), with: molecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -28,10 +28,16 @@
|
||||
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)
|
||||
public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
|
||||
if let replacedMolecule = try super.replaceChildMolecule(with: molecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
var replacedMolecule: MoleculeModelProtocol?
|
||||
if try (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule, replaced: &replacedMolecule))
|
||||
|| replaceChildMolecule(at: &line, with: molecule, replaced: &replacedMolecule) {
|
||||
return replacedMolecule
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// This template requires content.
|
||||
@ -94,4 +100,14 @@
|
||||
try container.encodeIfPresent(footerlessSpacerColor, forKey: .footerlessSpacerColor)
|
||||
try container.encodeIfPresent(footerlessSpacerHeight, forKey: .footerlessSpacerHeight)
|
||||
}
|
||||
|
||||
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard super.isEqual(to: model), let model = model as? Self else { return false }
|
||||
return line.matchExistence(with: model.line)
|
||||
&& scrollToRowIndex == model.scrollToRowIndex
|
||||
&& singleCellSelection == model.singleCellSelection
|
||||
&& footerlessSpacerColor == model.footerlessSpacerColor
|
||||
&& footerlessSpacerHeight == model.footerlessSpacerHeight
|
||||
&& molecules?.count ?? 0 == model.molecules?.count ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,8 +25,8 @@ open class ModalMoleculeListTemplate: MoleculeListTemplate {
|
||||
try decoder.decode(ModalListPageTemplateModel.self, from: data)
|
||||
}
|
||||
|
||||
override open func handleNewData() {
|
||||
super.handleNewData()
|
||||
override open func handleNewData(_ pageModel: PageModelProtocol? = nil) {
|
||||
super.handleNewData(pageModel)
|
||||
|
||||
closeButton = MVMCoreUICommonViewsUtility.addCloseButton(to: view, tintColor: self.pageModel?.navigationBar?.tintColor.uiColor, action: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
||||
@ -23,9 +23,8 @@ open class ModalMoleculeStackTemplate: MoleculeStackTemplate {
|
||||
// MARK: - Lifecycle
|
||||
//--------------------------------------------------
|
||||
|
||||
override open func handleNewData() {
|
||||
super.handleNewData()
|
||||
|
||||
override open func handleNewData(_ pageModel: PageModelProtocol? = nil) {
|
||||
super.handleNewData(pageModel)
|
||||
_ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, tintColor: self.pageModel?.navigationBar?.tintColor.uiColor, action: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
let closeAction = (self.templateModel as? ModalStackPageTemplateModel)?.closeAction ??
|
||||
|
||||
@ -19,7 +19,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
|
||||
public var moleculesInfo: [MoleculeInfo]?
|
||||
|
||||
var observer: NSKeyValueObservation?
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Computed Properties
|
||||
//--------------------------------------------------
|
||||
@ -46,9 +46,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
open override func parsePageJSON() throws {
|
||||
try parseTemplate(json: loadObject?.pageJSON)
|
||||
try super.parsePageJSON()
|
||||
open override func parsePageJSON(loadObject: MVMCoreLoadObject) throws -> PageModelProtocol {
|
||||
return try parseTemplate(loadObject: loadObject)
|
||||
}
|
||||
|
||||
// For subclassing the model.
|
||||
@ -86,23 +85,31 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
|
||||
return view
|
||||
}
|
||||
|
||||
open override func handleNewData() {
|
||||
setup()
|
||||
registerWithTable()
|
||||
super.handleNewData() // Currently stuck as MoleculeListProtocol being called from AddRemoveMoleculesBehaviorModel.
|
||||
open override func handleNewData(_ pageModel: PageModelProtocol? = nil) {
|
||||
super.handleNewData(pageModel)
|
||||
|
||||
if pageModel != nil {
|
||||
setup()
|
||||
registerWithTable()
|
||||
}
|
||||
}
|
||||
|
||||
open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
|
||||
topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
|
||||
bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
|
||||
|
||||
super.updateUI(for: molecules)
|
||||
|
||||
molecules?.forEach({ molecule in
|
||||
guard let molecules else { return }
|
||||
|
||||
// For updating individual specfied molecules. (Not a full table reload done in the base class.) These molecule types should remain the same type by replacement standards.
|
||||
molecules.forEach({ molecule in
|
||||
// Replace any top level cell data if required.
|
||||
if let index = moleculesInfo?.firstIndex(where: { $0.molecule.id == molecule.id }) {
|
||||
moleculesInfo?[index].molecule = molecule
|
||||
}
|
||||
newData(for: molecule)
|
||||
})
|
||||
newData(for: molecules)
|
||||
}
|
||||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
@ -169,7 +176,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
|
||||
}
|
||||
(cell as? MVMCoreViewProtocol)?.updateView(tableView.bounds.width)
|
||||
// Neded to fix an apple defect where the cell is not the correct size on certain devices for certain cells
|
||||
cell.layoutIfNeeded()
|
||||
cell.setNeedsLayout()
|
||||
return cell
|
||||
}
|
||||
|
||||
@ -233,18 +240,27 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
|
||||
}
|
||||
|
||||
open func newData(for molecule: MoleculeModelProtocol) {
|
||||
newData(for: [molecule])
|
||||
}
|
||||
|
||||
/// Refreshes all relevant cells for a given set of molecule models.
|
||||
open func newData(for molecules: [MoleculeModelProtocol]) {
|
||||
|
||||
//Check header and footer if replace happens then return.
|
||||
if updateHeaderFooterView(topView, with: molecule) ||
|
||||
updateHeaderFooterView(bottomView, with: molecule, isHeader: false) {
|
||||
return
|
||||
molecules.forEach {
|
||||
if updateHeaderFooterView(topView, with: $0) ||
|
||||
updateHeaderFooterView(bottomView, with: $0, isHeader: false) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard let moleculesInfo = moleculesInfo else { return }
|
||||
|
||||
let indicies = moleculesInfo.indices.filter({ index -> Bool in
|
||||
return moleculesInfo[index].molecule.findFirstMolecule(by: {
|
||||
$0.moleculeName == molecule.moleculeName && equal(moleculeA: molecule, moleculeB: $0)
|
||||
return moleculesInfo[index].molecule.findFirstMolecule(by: { existingMolecule in
|
||||
molecules.contains { newMolecule in
|
||||
existingMolecule.moleculeName == newMolecule.moleculeName && equal(moleculeA: existingMolecule, moleculeB: newMolecule)
|
||||
}
|
||||
}) != nil
|
||||
})
|
||||
|
||||
@ -253,7 +269,16 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
|
||||
let indexPaths = indicies.map {
|
||||
return IndexPath(row: $0, section: 0)
|
||||
}
|
||||
tableView.reloadRows(at: indexPaths, with: .automatic)
|
||||
|
||||
debugLog("Refreshing rows \(indexPaths.map { $0.row })")
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
// All rows should have been layed out already on the first newDataBuildScreen reload with the getMoleculeInfoList call. Therefore, we can be safe to assume the top level cell configuration will not be modified and only the child content will be updated allowing us to leverage this more efficient method.
|
||||
tableView.reconfigureRows(at: indexPaths)
|
||||
} else {
|
||||
// A full reload can cause a flicker / animation. Better to avoid with above reconfigure method.
|
||||
tableView.reloadRows(at: indexPaths, with: .automatic)
|
||||
}
|
||||
if let selectedIndex = selectedIndex {
|
||||
tableView.selectRow(at: selectedIndex, animated: false, scrollPosition: .none)
|
||||
}
|
||||
@ -348,17 +373,17 @@ extension MoleculeListTemplate: MoleculeListProtocol {
|
||||
let removeIndex = indexPath.row - index
|
||||
moleculesInfo?.remove(at: removeIndex)
|
||||
}
|
||||
|
||||
|
||||
guard let animation = animation,
|
||||
indexPaths.count > 0 else { return }
|
||||
tableView?.deleteRows(at: indexPaths, with: animation)
|
||||
updateViewConstraints()
|
||||
view.layoutIfNeeded()
|
||||
view.setNeedsLayout()
|
||||
}
|
||||
|
||||
public func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) {
|
||||
var indexPaths: [IndexPath] = []
|
||||
|
||||
|
||||
for molecule in molecules {
|
||||
if let info = self.createMoleculeInfo(with: molecule) {
|
||||
self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier)
|
||||
@ -367,12 +392,12 @@ extension MoleculeListTemplate: MoleculeListProtocol {
|
||||
indexPaths.append(IndexPath(row: index, section: 0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
guard let animation = animation,
|
||||
indexPaths.count > 0 else { return }
|
||||
self.tableView?.insertRows(at: indexPaths, with: animation)
|
||||
self.updateViewConstraints()
|
||||
self.view.layoutIfNeeded()
|
||||
self.view.setNeedsLayout()
|
||||
}
|
||||
|
||||
public func swapMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], with replacements: [ListItemModelProtocol & MoleculeModelProtocol], at indexPath: IndexPath, animation: UITableView.RowAnimation?) {
|
||||
@ -420,7 +445,7 @@ extension MoleculeListTemplate: MoleculeListProtocol {
|
||||
tableView.endUpdates()
|
||||
|
||||
self.updateViewConstraints()
|
||||
self.view.layoutIfNeeded()
|
||||
self.view.setNeedsLayout()
|
||||
}
|
||||
|
||||
public func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? {
|
||||
|
||||
@ -20,10 +20,10 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol {
|
||||
// MARK: - Lifecycle
|
||||
//--------------------------------------------------
|
||||
|
||||
open override func handleNewData() {
|
||||
open override func handleNewData(_ pageModel: PageModelProtocol? = nil) {
|
||||
topViewOutsideOfScroll = templateModel?.anchorHeader ?? false
|
||||
bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false
|
||||
super.handleNewData()
|
||||
super.handleNewData(pageModel)
|
||||
}
|
||||
|
||||
// For subclassing the model.
|
||||
@ -31,9 +31,8 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol {
|
||||
return try decoder.decode(StackPageTemplateModel.self, from: data)
|
||||
}
|
||||
|
||||
open override func parsePageJSON() throws {
|
||||
try parseTemplate(json: loadObject?.pageJSON)
|
||||
try super.parsePageJSON()
|
||||
open override func parsePageJSON(loadObject: MVMCoreLoadObject) throws -> PageModelProtocol {
|
||||
return try parseTemplate(loadObject: loadObject)
|
||||
}
|
||||
|
||||
open override var loadObject: MVMCoreLoadObject? {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user