diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index ab5f9887..b263a72f 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 48; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -61,6 +61,10 @@ 01EB369323609801006832FA /* HeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368C23609801006832FA /* HeaderModel.swift */; }; 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368D23609801006832FA /* HeadlineBodyModel.swift */; }; 01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */; }; + 01F2C20227C81F9700DC3D36 /* SubNavManagerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2C1FE27C81F9700DC3D36 /* SubNavManagerController.swift */; }; + 01F2C20327C81F9700DC3D36 /* SubNavManagerNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2C1FF27C81F9700DC3D36 /* SubNavManagerNavigationController.swift */; }; + 01F2C20427C81F9700DC3D36 /* SubNavInteractor.swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2C20027C81F9700DC3D36 /* SubNavInteractor.swift.swift */; }; + 01F2C20527C81F9700DC3D36 /* SubNavSwipeAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2C20127C81F9700DC3D36 /* SubNavSwipeAnimator.swift */; }; 0A0FEC7425D42A5E00AF2548 /* BaseItemPickerEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0FEC7325D42A5E00AF2548 /* BaseItemPickerEntryField.swift */; }; 0A0FEC7825D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0FEC7725D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift */; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; @@ -122,6 +126,7 @@ 0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB423FF18D2004C5109 /* Arrow.swift */; }; 0AE98BB723FF18E9004C5109 /* ArrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB623FF18E9004C5109 /* ArrowModel.swift */; }; 0AF60F0926B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AF60F0826B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift */; }; + 187FEB2A2844D2A600BF29C2 /* VDSFormControlsTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 187FEB292844D2A600BF29C2 /* VDSFormControlsTokens.xcframework */; }; 1D6D258826899B0C00DEBB08 /* ImageButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6D258626899B0B00DEBB08 /* ImageButtonModel.swift */; }; 1D6D258926899B0C00DEBB08 /* ImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6D258726899B0B00DEBB08 /* ImageButton.swift */; }; 279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279B1568242BBC2F00921D6C /* ActionModelAdapter.swift */; }; @@ -141,6 +146,9 @@ 32D2609724C19E2100B56344 /* LockupsPlanSMLXLModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D2609524C19E2100B56344 /* LockupsPlanSMLXLModel.swift */; }; 32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F8804524765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift */; }; 32F8804824765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F8804724765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift */; }; + 444FB7C12821B73200DFE692 /* TitleLockup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444FB7C02821B73200DFE692 /* TitleLockup.swift */; }; + 444FB7C32821B76B00DFE692 /* TitleLockupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */; }; + 4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */; }; 522679C123FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */; }; 522679C223FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */; }; 52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52267A0623FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift */; }; @@ -268,6 +276,8 @@ AAE7270E24AC8B9300A3ED0E /* HeadersH2CaretLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE7270D24AC8B9300A3ED0E /* HeadersH2CaretLink.swift */; }; AAE96FA225341F6A0037A989 /* ListStoreLocatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */; }; AAE96FA525341F7D0037A989 /* ListStoreLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */; }; + AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */; }; + AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; }; BB105859248DEFF70069D008 /* UICollectionViewLeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */; }; BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; }; BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; }; @@ -341,7 +351,7 @@ D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479932316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift */; }; D22479962316AF6E003FCCF9 /* HeadlineBodyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479952316AF6D003FCCF9 /* HeadlineBodyLink.swift */; }; D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */; }; - D22D8393241C27B100D3DF69 /* TemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8392241C27B100D3DF69 /* TemplateModel.swift */; }; + D22D8393241C27B100D3DF69 /* BaseTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8392241C27B100D3DF69 /* BaseTemplateModel.swift */; }; D22D8395241FB41200D3DF69 /* UIStackView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22D8394241FB41200D3DF69 /* UIStackView+Extension.swift */; }; D23118B325124E18001C8440 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23118B225124E18001C8440 /* Notification.swift */; }; D2351C7A24A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2351C7924A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift */; }; @@ -569,8 +579,20 @@ DBC4391922442197001AB423 /* DashLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391722442197001AB423 /* DashLine.swift */; }; DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391A224421A0001AB423 /* CaretLink.swift */; }; DBEFFA04225A829700230692 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB891E822253FA8500022516 /* Label.swift */; }; + EA05EFA9278DDE2C00828819 /* ClearFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */; }; + EA05EFAB278DE53600828819 /* ClearableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */; }; + EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; }; EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; }; EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; }; + EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */; }; + EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */; }; + EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */; }; + EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */; }; + EAA0CFB3275E831E00D65EB0 /* DisableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */; }; + EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */; }; + EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */; }; + EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC1402763BB8D00E78B40 /* FormLabel.swift */; }; + EABFC152276913E800E78B40 /* FormLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC151276913E800E78B40 /* FormLabelModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -628,6 +650,10 @@ 01EB368C23609801006832FA /* HeaderModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderModel.swift; sourceTree = ""; }; 01EB368D23609801006832FA /* HeadlineBodyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyModel.swift; sourceTree = ""; }; 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinkModel.swift; sourceTree = ""; }; + 01F2C1FE27C81F9700DC3D36 /* SubNavManagerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubNavManagerController.swift; sourceTree = ""; }; + 01F2C1FF27C81F9700DC3D36 /* SubNavManagerNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubNavManagerNavigationController.swift; sourceTree = ""; }; + 01F2C20027C81F9700DC3D36 /* SubNavInteractor.swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubNavInteractor.swift.swift; sourceTree = ""; }; + 01F2C20127C81F9700DC3D36 /* SubNavSwipeAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubNavSwipeAnimator.swift; sourceTree = ""; }; 0A0FEC7325D42A5E00AF2548 /* BaseItemPickerEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemPickerEntryField.swift; sourceTree = ""; }; 0A0FEC7725D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemPickerEntryFieldModel.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; @@ -691,6 +717,7 @@ 0AE98BB423FF18D2004C5109 /* Arrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arrow.swift; sourceTree = ""; }; 0AE98BB623FF18E9004C5109 /* ArrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowModel.swift; sourceTree = ""; }; 0AF60F0826B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIUtility+Extension.swift"; sourceTree = ""; }; + 187FEB292844D2A600BF29C2 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = ""; }; 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; }; 279B1568242BBC2F00921D6C /* ActionModelAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionModelAdapter.swift; sourceTree = ""; }; @@ -710,6 +737,9 @@ 32D2609524C19E2100B56344 /* LockupsPlanSMLXLModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockupsPlanSMLXLModel.swift; sourceTree = ""; }; 32F8804524765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableNumberedListAllTextAndLinksModel.swift; sourceTree = ""; }; 32F8804724765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableNumberedListAllTextAndLinks.swift; sourceTree = ""; }; + 444FB7C02821B73200DFE692 /* TitleLockup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockup.swift; sourceTree = ""; }; + 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockupModel.swift; sourceTree = ""; }; + 4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageRenderingMode+Extension.swift"; sourceTree = ""; }; 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinks.swift; sourceTree = ""; }; 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinksModel.swift; sourceTree = ""; }; 52267A0623FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextAllTextAndLinks.swift; sourceTree = ""; }; @@ -837,6 +867,8 @@ AAE7270D24AC8B9300A3ED0E /* HeadersH2CaretLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2CaretLink.swift; sourceTree = ""; }; AAE96FA125341F6A0037A989 /* ListStoreLocatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocatorModel.swift; sourceTree = ""; }; AAE96FA425341F7D0037A989 /* ListStoreLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListStoreLocator.swift; sourceTree = ""; }; + AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSColorTokens.xcframework; path = ../SharedFrameworks/VDSColorTokens.xcframework; sourceTree = ""; }; + AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; BB105858248DEFF60069D008 /* UICollectionViewLeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICollectionViewLeftAlignedLayout.swift; sourceTree = ""; }; BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = ""; }; BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = ""; }; @@ -910,7 +942,7 @@ D22479932316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraintExtension.swift; sourceTree = ""; }; D22479952316AF6D003FCCF9 /* HeadlineBodyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyLink.swift; sourceTree = ""; }; D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccordionMoleculeTableViewCell.swift; sourceTree = ""; }; - D22D8392241C27B100D3DF69 /* TemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModel.swift; sourceTree = ""; }; + D22D8392241C27B100D3DF69 /* BaseTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTemplateModel.swift; sourceTree = ""; }; D22D8394241FB41200D3DF69 /* UIStackView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+Extension.swift"; sourceTree = ""; }; D23118B225124E18001C8440 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; D2351C7924A4D433007DF0BC /* ListRightVariableToggleAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableToggleAllTextAndLinksModel.swift; sourceTree = ""; }; @@ -1139,8 +1171,20 @@ DBC4391622442196001AB423 /* CaretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretView.swift; sourceTree = ""; }; DBC4391722442197001AB423 /* DashLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashLine.swift; sourceTree = ""; }; DBC4391A224421A0001AB423 /* CaretLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretLink.swift; sourceTree = ""; }; + EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearFormFieldEffectModel.swift; sourceTree = ""; }; + EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearableModelProtocol.swift; sourceTree = ""; }; + EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = ""; }; EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = ""; }; EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = ""; }; + EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableFormFieldEffectModel.swift; sourceTree = ""; }; + EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIUpdatableModelProtocol.swift; sourceTree = ""; }; + EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldEffectProtocol.swift; sourceTree = ""; }; + EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideFormFieldEffectModel.swift; sourceTree = ""; }; + EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableFormFieldEffectModel.swift; sourceTree = ""; }; + EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleCompareModelProtocol.swift; sourceTree = ""; }; + EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyModelProtocol.swift; sourceTree = ""; }; + EABFC1402763BB8D00E78B40 /* FormLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabel.swift; sourceTree = ""; }; + EABFC151276913E800E78B40 /* FormLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabelModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1149,7 +1193,9 @@ buildActionMask = 2147483647; files = ( D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */, + AFE4A1D127DFB5EE00C458D0 /* VDSColorTokens.xcframework in Frameworks */, 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */, + 187FEB2A2844D2A600BF29C2 /* VDSFormControlsTokens.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1165,6 +1211,7 @@ D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */, 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */, 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */, + EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */, 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */, 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */, D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, @@ -1183,12 +1230,14 @@ children = ( 011D95A4240455DC000E3791 /* FormGroupRule.swift */, 011D958424042432000E3791 /* RulesProtocol.swift */, - 011D959A240451E3000E3791 /* RuleRequiredModel.swift */, - 011D959C2404536F000E3791 /* RuleAnyValueChangedModel.swift */, + EAB14BC227D9378D0012AB2C /* RuleAnyModelProtocol.swift */, + EAB14BC027D935F00012AB2C /* RuleCompareModelProtocol.swift */, 011D959E240453A1000E3791 /* RuleAllValueChangedModel.swift */, - 011D95A0240453D0000E3791 /* RuleEqualsModel.swift */, + 011D959A240451E3000E3791 /* RuleRequiredModel.swift */, 011D95A2240453F8000E3791 /* RuleRegexModel.swift */, + 011D959C2404536F000E3791 /* RuleAnyValueChangedModel.swift */, 0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */, + 011D95A0240453D0000E3791 /* RuleEqualsModel.swift */, 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */, ); name = Rules; @@ -1206,6 +1255,7 @@ 01C74D87224298E2009C25A3 /* FormUIHelpers */ = { isa = PBXGroup; children = ( + EAA0CFAD275E7D5A00D65EB0 /* FormFieldEffect */, 011D95882404249B000E3791 /* FormHolderModelProtocol.swift */, 011D95AC2406BB57000E3791 /* FormHolderProtocol.swift */, 011D95AA2405C553000E3791 /* FormItemProtocol.swift */, @@ -1213,11 +1263,31 @@ 011D95A824057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift */, 011D9601240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift */, 0105618A224BBE7700E1557D /* FormValidator.swift */, + EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */, 011D958A24042794000E3791 /* Rules */, ); path = FormUIHelpers; sourceTree = ""; }; + 01F2C1FC27C81F9700DC3D36 /* Managers */ = { + isa = PBXGroup; + children = ( + 01F2C1FD27C81F9700DC3D36 /* SubNav */, + ); + path = Managers; + sourceTree = ""; + }; + 01F2C1FD27C81F9700DC3D36 /* SubNav */ = { + isa = PBXGroup; + children = ( + 01F2C1FE27C81F9700DC3D36 /* SubNavManagerController.swift */, + 01F2C1FF27C81F9700DC3D36 /* SubNavManagerNavigationController.swift */, + 01F2C20027C81F9700DC3D36 /* SubNavInteractor.swift.swift */, + 01F2C20127C81F9700DC3D36 /* SubNavSwipeAnimator.swift */, + ); + path = SubNav; + sourceTree = ""; + }; 0A0FEC7125D4246000AF2548 /* Dropdown Fields */ = { isa = PBXGroup; children = ( @@ -1368,6 +1438,8 @@ 525019DC2406430800EED91C /* ListProgressBarData.swift */, AA45AA0A24BF0263007A6EA7 /* LockUpsPlanNamesModel.swift */, AA45AA0C24BF0276007A6EA7 /* LockUpsPlanNames.swift */, + 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */, + 444FB7C02821B73200DFE692 /* TitleLockup.swift */, ); path = LockUps; sourceTree = ""; @@ -1428,6 +1500,8 @@ 94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */, 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */, DB891E822253FA8500022516 /* Label.swift */, + EABFC151276913E800E78B40 /* FormLabelModel.swift */, + EABFC1402763BB8D00E78B40 /* FormLabel.swift */, ); path = Label; sourceTree = ""; @@ -1477,6 +1551,15 @@ path = Miscellaneous; sourceTree = ""; }; + AFE4A1D427DFBB2700C458D0 /* NavigationController */ = { + isa = PBXGroup; + children = ( + D2B18B93236214AD00A9AEDC /* NavigationController.swift */, + AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */, + ); + path = NavigationController; + sourceTree = ""; + }; D202AFE2242A5F1400E5BEDF /* Extensions */ = { isa = PBXGroup; children = ( @@ -1491,6 +1574,7 @@ D2ED27E6254B0CE600A1C293 /* UIAlertActionStyle+Codable.swift */, D2ED27E7254B0CE600A1C293 /* UIAlertControllerStyle+Extension.swift */, D2E0FFF726AF68530085D696 /* UITableViewRowAnimation+Extension.swift */, + 4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -1886,6 +1970,7 @@ D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = { isa = PBXGroup; children = ( + 01F2C1FC27C81F9700DC3D36 /* Managers */, D2ED27D8254B0C1F00A1C293 /* Alerts */, 27F973512466071600CAB5C5 /* Behaviors */, D2C78CD324252F4E00B69FDE /* Atomic */, @@ -1910,7 +1995,7 @@ D29DF0DF21E418B2003B2FB9 /* Templates */ = { isa = PBXGroup; children = ( - D22D8392241C27B100D3DF69 /* TemplateModel.swift */, + D22D8392241C27B100D3DF69 /* BaseTemplateModel.swift */, D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */, 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */, D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */, @@ -1943,6 +2028,8 @@ D29DF0E421E4F3C7003B2FB9 /* Frameworks */ = { isa = PBXGroup; children = ( + 187FEB292844D2A600BF29C2 /* VDSFormControlsTokens.xcframework */, + AFE4A1D027DFB5EE00C458D0 /* VDSColorTokens.xcframework */, D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */, 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */, ); @@ -2013,9 +2100,9 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( + AFE4A1D427DFBB2700C458D0 /* NavigationController */, 0ABD1369237B18EE0081388D /* Views */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, - D2B18B93236214AD00A9AEDC /* NavigationController.swift */, ); path = Containers; sourceTree = ""; @@ -2400,6 +2487,19 @@ path = Alerts; sourceTree = ""; }; + EAA0CFAD275E7D5A00D65EB0 /* FormFieldEffect */ = { + isa = PBXGroup; + children = ( + EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */, + EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */, + EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */, + EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */, + EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */, + EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */, + ); + path = FormFieldEffect; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2538,6 +2638,7 @@ 324FB6AA249366F3002552C7 /* ListLeftVariableNumberedListBodyTextModel.swift in Sources */, 5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */, AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */, + EABFC152276913E800E78B40 /* FormLabelModel.swift in Sources */, 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */, 0AF60F0926B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift in Sources */, 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */, @@ -2564,12 +2665,15 @@ DBC4391922442197001AB423 /* DashLine.swift in Sources */, D2ED27FC254B0E0300A1C293 /* MVMCoreAlertObject+Swift.swift in Sources */, D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */, + AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */, AAC23FAD24D92A0D009208DF /* ListThreeColumnSpeedTestModel.swift in Sources */, BBC0C4FF24811DCA0087C44F /* TagModel.swift in Sources */, + 01F2C20527C81F9700DC3D36 /* SubNavSwipeAnimator.swift in Sources */, 0AE277EC25D2EE310048A38D /* MultiItemDropdownEntryFieldModel.swift in Sources */, 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, 3265B30424BCA749000D154B /* HeadersH1NoButtonsBodyText.swift in Sources */, AAA7CD69250641F90045B959 /* HeartModel.swift in Sources */, + 444FB7C12821B73200DFE692 /* TitleLockup.swift in Sources */, D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */, D28BA7452481652D00B75CB8 /* TabBarProtocol.swift in Sources */, AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */, @@ -2601,11 +2705,13 @@ D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */, + EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */, 011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */, BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */, D20C700B250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift in Sources */, D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */, 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */, + EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, 942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */, BB47A588241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift in Sources */, @@ -2748,6 +2854,7 @@ D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */, D2ED2803254B0E0300A1C293 /* MVMCoreAlertHandler.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */, + EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */, D20923592450ECE00044AD09 /* TableView.swift in Sources */, BB47A586241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift in Sources */, AA69AAF82445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift in Sources */, @@ -2760,6 +2867,7 @@ 525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */, 32D2609624C19E2100B56344 /* LockupsPlanSMLXL.swift in Sources */, 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */, + 444FB7C32821B76B00DFE692 /* TitleLockupModel.swift in Sources */, D29C94D5242901C9003813BA /* MVMCoreUICommonViewsUtility+Extension.swift in Sources */, D260105323CEA61600764D80 /* ToggleModel.swift in Sources */, 014AA72523C501E2006F3E93 /* ContainerModel.swift in Sources */, @@ -2802,6 +2910,7 @@ BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */, 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, 323AC96C24C837FF00F8E4C4 /* ListThreeColumnBillChanges.swift in Sources */, + EAB14BC327D9378D0012AB2C /* RuleAnyModelProtocol.swift in Sources */, 0A0FEC7825D42A8500AF2548 /* BaseItemPickerEntryFieldModel.swift in Sources */, D28A837923C7D5BC00DFE4FC /* PageModelProtocol.swift in Sources */, D2351C7C24A4D4C3007DF0BC /* ListRightVariableToggleAllTextAndLinks.swift in Sources */, @@ -2816,6 +2925,7 @@ D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, 011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */, D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */, + EA05EFAB278DE53600828819 /* ClearableModelProtocol.swift in Sources */, 8D448E5524050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift in Sources */, BBC0C4FD24811DBC0087C44F /* Tag.swift in Sources */, 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */, @@ -2824,6 +2934,7 @@ C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, 32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, + 4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */, D23118B325124E18001C8440 /* Notification.swift in Sources */, AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, @@ -2930,6 +3041,7 @@ D2CAC7D12511058C00C75681 /* MVMCoreUITopAlertMainView+Extension.swift in Sources */, 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, 0AE98BB723FF18E9004C5109 /* ArrowModel.swift in Sources */, + 01F2C20427C81F9700DC3D36 /* SubNavInteractor.swift.swift in Sources */, D28A837D23CCA86A00DFE4FC /* TabsListItemModel.swift in Sources */, 0A51F3E32475CB73002E08B6 /* LoadingSpinner.swift in Sources */, BB2FB3BB247E7EBC00DF73CD /* TagCollectionViewCell.swift in Sources */, @@ -2942,6 +3054,7 @@ 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */, D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */, + 01F2C20227C81F9700DC3D36 /* SubNavManagerController.swift in Sources */, AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */, 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */, D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */, @@ -2953,12 +3066,15 @@ 1D6D258826899B0C00DEBB08 /* ImageButtonModel.swift in Sources */, AA07EA912510A442009A2AE3 /* StarModel.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, + EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */, 011D95892404249B000E3791 /* FormHolderModelProtocol.swift in Sources */, BB54C5202434D92F0038326C /* ListRightVariableButtonAllTextAndLinks.swift in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, 013F801923FB4A8E00AD8013 /* UIContentMode+Extension.swift in Sources */, AA104AC724472DB0004D2810 /* HeadersH1Button.swift in Sources */, 525239C22407BD1000454969 /* ListTwoColumnPriceDetails.swift in Sources */, + 01F2C20327C81F9700DC3D36 /* SubNavManagerNavigationController.swift in Sources */, + EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */, AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */, @@ -2968,16 +3084,19 @@ 27F973532466074500CAB5C5 /* PageBehaviorProtocol.swift in Sources */, 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */, D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */, + EAA0CFB3275E831E00D65EB0 /* DisableFormFieldEffectModel.swift in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, D23EA7FE247EBBB700D60C34 /* NavigationLabelButtonModel.swift in Sources */, D28A839123CD4FD400DFE4FC /* CornerLabelsModel.swift in Sources */, 012A88F123985E0100FE3DA1 /* Color.swift in Sources */, - D22D8393241C27B100D3DF69 /* TemplateModel.swift in Sources */, + D22D8393241C27B100D3DF69 /* BaseTemplateModel.swift in Sources */, 012A889C23889E8400FE3DA1 /* TemplateModelProtocol.swift in Sources */, + EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */, D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */, 52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */, D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */, 0AA4D2E125CAEC72008DB32D /* AccessibilityModelProtocol.swift in Sources */, + EA05EFA9278DDE2C00828819 /* ClearFormFieldEffectModel.swift in Sources */, C003506123AA94CD00B6AC29 /* Button.swift in Sources */, DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */, D29C559025C095210082E7D6 /* Video.swift in Sources */, @@ -3040,6 +3159,7 @@ 011D95AD2406BB57000E3791 /* FormHolderProtocol.swift in Sources */, D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */, 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */, + EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */, D2169301251E51E7002A6324 /* SectionListTemplate.swift in Sources */, 0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */, D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */, @@ -3180,7 +3300,8 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -3202,8 +3323,13 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks"; INFOPLIST_FILE = MVMCoreUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -3228,8 +3354,13 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks"; INFOPLIST_FILE = MVMCoreUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift b/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift index b0158e2d..6f52ce93 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift +++ b/MVMCoreUI/Alerts/MVMCoreAlertHandler+Extension.swift @@ -52,7 +52,9 @@ public extension MVMCoreAlertHandler { extension MVMCoreAlertHandler: MVMCorePresentationDelegateProtocol { // Update displayable for each top alert operation when page type changes, in top queue priority order. public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { - guard navigationController == MVMCoreUISplitViewController.main()?.navigationController else { return } + guard topAlertQueue.operations.count > 0 else { return } + let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) + guard viewController == MVMCoreUISplitViewController.main()?.getCurrentViewController() else { return } let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType topAlertQueue.operations.compactMap { $0 as? MVMCoreTopAlertOperation diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler.h b/MVMCoreUI/Alerts/MVMCoreAlertHandler.h index 522c5740..311b0c61 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler.h +++ b/MVMCoreUI/Alerts/MVMCoreAlertHandler.h @@ -104,6 +104,9 @@ /// Hides a persistent alert based on the type string. - (void)hidePersistentTopAlertViewOfType:(nullable NSString *)type; +/// Hides a alert based on the type string. +- (void)hideTopAlertViewOfType:(nullable NSString *)type; + /// Removes a scheduled top alert given its top alert object. - (void)removeTopAlertForObject:(nonnull MVMCoreTopAlertObject *)topAlertObject; diff --git a/MVMCoreUI/Alerts/MVMCoreAlertHandler.m b/MVMCoreUI/Alerts/MVMCoreAlertHandler.m index 8b4b02c7..977e6293 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertHandler.m +++ b/MVMCoreUI/Alerts/MVMCoreAlertHandler.m @@ -239,6 +239,19 @@ } } +- (void)hideTopAlertViewOfType:(nullable NSString *)type { + if (type) { + for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { + + // Cancel all operations of this type. + if ([operation.topAlertObject.type isEqualToString:type]) { + operation.reAddAfterCancel = NO; + [operation cancel]; + } + } + } +} + - (void)removeTopAlertForObject:(MVMCoreTopAlertObject *)topAlertObject { for (MVMCoreTopAlertOperation *operation in self.topAlertQueue.operations) { // Finds an cancels top alerts associated with the object. diff --git a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift b/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift index 162dd2d1..c2c5d50f 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift +++ b/MVMCoreUI/Alerts/MVMCoreAlertObject+Swift.swift @@ -40,6 +40,10 @@ public extension MVMCoreAlertObject { alertObject?.alertStyle = UIAlertController.Style(rawValue: alertStyle) } + if let analyticsData = alertJson.optionalDictionaryForKey("analyticsData") { + alertObject?.pageJson = ["analyticsData": analyticsData] + } + return alertObject } } diff --git a/MVMCoreUI/Alerts/MVMCoreAlertObject.m b/MVMCoreUI/Alerts/MVMCoreAlertObject.m index 975c9540..17b7f113 100644 --- a/MVMCoreUI/Alerts/MVMCoreAlertObject.m +++ b/MVMCoreUI/Alerts/MVMCoreAlertObject.m @@ -54,8 +54,8 @@ } __block MVMCoreAlertObject *alert = [[MVMCoreAlertObject alloc] init]; - alert.title = [responseInfo stringForKey:KeyErrorHeading]; - alert.message = [responseInfo stringForKey:KeyUserMessage]; + alert.title = [responseInfo string:KeyErrorHeading] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle]; + alert.message = [responseInfo string:KeyUserMessage] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; NSString *messageStyle = [responseInfo stringForKey:KeyMessageStyle]; if ([ValueTypeFieldErrors isEqualToString:[responseInfo string:KeyType]]) { @@ -151,9 +151,9 @@ + (nullable instancetype)alertObjectWithPage:(nullable NSDictionary *)page isGreedy:(BOOL)isGreedy additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject error:(MVMCoreErrorObject *_Nullable *_Nullable)error { MVMCoreAlertObject *alert = [[MVMCoreAlertObject alloc] init]; - alert.title = [page stringForKey:KeyTitle]; + alert.title = [page string:KeyTitle] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle]; alert.pageJson = page; - alert.message = [page stringForKey:KeyMessage]; + alert.message = [page string:KeyMessage] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; alert.isGreedy = isGreedy; alert.type = MFAlertTypePopup; alert.alertStyle = UIAlertControllerStyleAlert; @@ -217,9 +217,8 @@ + (nullable instancetype)alertObjectForPageType:(nullable NSString *)pageType responseInfo:(nullable NSDictionary *)responseInfo additionalData:(nullable NSDictionary *)additionalData actionDelegate:(nullable NSObject *)actionDelegate { __block MVMCoreAlertObject *alert = [[MVMCoreAlertObject alloc] init]; - alert.title = [responseInfo stringForKey:KeyErrorHeading]; - alert.message = [responseInfo stringForKey:KeyUserMessage]; - + alert.title = [responseInfo string:KeyErrorHeading] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle]; + alert.message = [responseInfo string:KeyUserMessage] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; NSString *messageStyle = [responseInfo stringForKey:KeyMessageStyle]; if ([ValueTypeFieldErrors isEqualToString:[responseInfo string:KeyType]]) { @@ -279,9 +278,9 @@ + (nullable instancetype)alertObjectWithPage:(nullable NSDictionary *)page isGreedy:(BOOL)isGreedy additionalData:(nullable NSDictionary *)additionalData delegate:(nullable NSObject *)delegate error:(MVMCoreErrorObject *_Nullable *_Nullable)error { MVMCoreAlertObject *alert = [[MVMCoreAlertObject alloc] init]; - alert.title = [page stringForKey:KeyTitle]; + alert.title = [page string:KeyTitle] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle]; alert.pageJson = page; - alert.message = [page stringForKey:KeyMessage]; + alert.message = [page string:KeyMessage] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; alert.isGreedy = isGreedy; alert.type = MFAlertTypePopup; alert.alertStyle = UIAlertControllerStyleAlert; diff --git a/MVMCoreUI/Atomic/Actions/ActionAlertModel.swift b/MVMCoreUI/Atomic/Actions/ActionAlertModel.swift index cc6b3f60..67748a4d 100644 --- a/MVMCoreUI/Atomic/Actions/ActionAlertModel.swift +++ b/MVMCoreUI/Atomic/Actions/ActionAlertModel.swift @@ -5,7 +5,7 @@ // Created by Suresh, Kamlesh on 7/9/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import MVMCore @objcMembers public class ActionAlertModel: ActionModelProtocol { //-------------------------------------------------- @@ -17,4 +17,12 @@ public var alert: AlertModel public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(alert: AlertModel) { + self.alert = alert + } } diff --git a/MVMCoreUI/Atomic/Actions/AlertModel.swift b/MVMCoreUI/Atomic/Actions/AlertModel.swift index 866bfb31..76bfb273 100644 --- a/MVMCoreUI/Atomic/Actions/AlertModel.swift +++ b/MVMCoreUI/Atomic/Actions/AlertModel.swift @@ -7,6 +7,7 @@ // import UIKit +import MVMCore public class AlertButtonModel: Codable { @@ -69,6 +70,7 @@ public class AlertModel: Codable { public var message: String public var style: UIAlertController.Style = .alert public var alertActions: [AlertButtonModel] + public var analyticsData: JSONValueDictionary? //-------------------------------------------------- // MARK: - Properties @@ -89,6 +91,7 @@ public class AlertModel: Codable { case message case alertActions case style + case analyticsData } //-------------------------------------------------- @@ -100,7 +103,8 @@ public class AlertModel: Codable { title = try typeContainer.decode(String.self, forKey: .title) message = try typeContainer.decode(String.self, forKey: .message) alertActions = try typeContainer.decode([AlertButtonModel].self, forKey: .alertActions) - + analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) + if let style = try typeContainer.decodeIfPresent(String.self, forKey: .style) { self.style = UIAlertController.Style(rawValue: style) } @@ -112,5 +116,6 @@ public class AlertModel: Codable { try container.encode(message, forKey: .message) try container.encode(alertActions, forKey: .alertActions) try container.encode(style.rawValueString, forKey: .style) + try container.encodeIfPresent(analyticsData, forKey: .analyticsData) } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 9814a44a..7c6f9ef6 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -7,21 +7,23 @@ // import UIKit +import VDSColorTokens public typealias FacadeElements = (fill: UIColor?, text: UIColor?, border: UIColor?) -open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, EnableableModelProtocol { +open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- //Making static property as class property so that subclasses can override getter function of the property open class var identifier: String { "button" } - public var backgroundColor: Color? public var accessibilityIdentifier: String? + public var accessibilityText: String? public var title: String public var action: ActionModelProtocol public var enabled: Bool = true + public var width: CGFloat? public var style: Styler.Button.Style? { didSet { guard let style = style else { return } @@ -56,15 +58,23 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public var disabledTextColor_inverted: Color? public var disabledBorderColor_inverted: Color? + private var _backgroundColor: Color? + public var backgroundColor: Color? { + get { + if let backgroundColor = _backgroundColor { return backgroundColor } + if inverted { + return enabled ? enabledFillColor_inverted : disabledFillColor_inverted + } + return enabled ? enabledFillColor : disabledFillColor + } + set { + _backgroundColor = newValue + } + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - - public func setValidity(_ valid: Bool, group: FormGroupRule) { - enabled = valid - updateUI?() - } - /// Temporary binding mechanism for the view to update on enable changes. public var updateUI: ActionBlock? @@ -75,18 +85,21 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public init(with title: String, action: ActionModelProtocol) { self.title = title self.action = action + setFacade(by: .primary) } public init(secondaryButtonWith title: String, action: ActionModelProtocol) { self.title = title self.action = action style = .secondary + setFacade(by: .secondary) } public init(primaryButtonWith title: String, action: ActionModelProtocol) { self.title = title self.action = action style = .primary + setFacade(by: .primary) } //-------------------------------------------------- @@ -119,40 +132,30 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat /// Defines the default appearance for the primary style. func setPrimaryFacade() { + enabledFillColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) + enabledTextColor = Color(uiColor: VDSColor.elementsPrimaryOndark) + disabledFillColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) + disabledTextColor = Color(uiColor: VDSColor.elementsPrimaryOndark) - if enabledFillColor == nil && enabledTextColor == nil { - enabledFillColor = Color(uiColor: .mvmBlack) - enabledTextColor = Color(uiColor: .mvmWhite) - } - - if disabledFillColor == nil && disabledTextColor == nil { - disabledFillColor = Color(uiColor: .mvmCoolGray6) - disabledTextColor = Color(uiColor: .mvmWhite) - } - - enabledFillColor_inverted = Color(uiColor: .mvmWhite) - enabledTextColor_inverted = Color(uiColor: .mvmBlack) - disabledFillColor_inverted = Color(uiColor: .mvmCoolGray6) - disabledTextColor_inverted = Color(uiColor: .mvmBlack) + enabledFillColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) + enabledTextColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOnlight) + disabledFillColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) + disabledTextColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOnlight) } /// Defines the default appearance for the Secondary style. func setSecondaryFacade() { + enabledTextColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) + enabledFillColor = Color(uiColor: UIColor.clear) + enabledBorderColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) + disabledTextColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) + disabledBorderColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) - if enabledTextColor == nil && enabledBorderColor == nil { - enabledTextColor = Color(uiColor: .mvmBlack) - enabledBorderColor = Color(uiColor: .mvmBlack) - } - - if disabledTextColor == nil && disabledBorderColor == nil { - disabledTextColor = Color(uiColor: .mvmCoolGray6) - disabledBorderColor = Color(uiColor: .mvmCoolGray6) - } - - enabledTextColor_inverted = Color(uiColor: .mvmWhite) - enabledBorderColor_inverted = Color(uiColor: .mvmWhite) - disabledTextColor_inverted = Color(uiColor: .mvmCoolGray6) - disabledBorderColor_inverted = Color(uiColor: .mvmCoolGray6) + enabledTextColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) + enabledFillColor_inverted = Color(uiColor: UIColor.clear) + enabledBorderColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) + disabledTextColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) + disabledBorderColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) } public func setFacade(by style: Styler.Button.Style) { @@ -174,6 +177,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat case moleculeName case backgroundColor case accessibilityIdentifier + case accessibilityText case title case inverted case action @@ -187,6 +191,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat case disabledFillColor case disabledTextColor case disabledBorderColor + case width } //-------------------------------------------------- @@ -196,14 +201,21 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) title = try typeContainer.decode(String.self, forKey: .title) action = try typeContainer.decodeModel(codingKey: .action) + if let style = decoder.context?.value(forKey: CodingKeys.style.stringValue) as? Styler.Button.Style{ + self.style = style + setFacade(by: style) + } + if let style = try typeContainer.decodeIfPresent(Styler.Button.Style.self, forKey: .style) { self.style = style setFacade(by: style) + } else { + setFacade(by: .primary) } if let size = try typeContainer.decodeIfPresent(Styler.Button.Size.self, forKey: .size) { @@ -245,6 +257,9 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat if let disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) { self.disabledBorderColor = disabledBorderColor } + + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width) } open func encode(to encoder: Encoder) throws { @@ -254,8 +269,9 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat try container.encode(enabled, forKey: .enabled) try container.encode(inverted, forKey: .inverted) try container.encodeModel(action, forKey: .action) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) + try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(enabledFillColor, forKey: .fillColor) try container.encodeIfPresent(enabledTextColor, forKey: .textColor) try container.encodeIfPresent(enabledBorderColor, forKey: .borderColor) @@ -265,5 +281,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat try container.encodeIfPresent(style, forKey: .style) try container.encodeIfPresent(size, forKey: .size) try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encodeIfPresent(width, forKey: .width) } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/CaretLinkModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/CaretLinkModel.swift index 25d0a539..71431d0f 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/CaretLinkModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/CaretLinkModel.swift @@ -99,7 +99,7 @@ public class CaretLinkModel: ButtonModelProtocol, MoleculeModelProtocol, Enablea try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encodeModel(action, forKey: .action) - try container.encode(enabled, forKey: .enabledColor) + try container.encode(enabledColor, forKey: .enabledColor) try container.encodeIfPresent(disabledColor, forKey: .disabledColor) try container.encode(enabled, forKey: .enabled) try container.encode(enabledColor_inverted, forKey: .enabledColor_inverted) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift index 2d71ee6e..323236a7 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButton.swift @@ -36,7 +36,7 @@ import Foundation } - private func setState() { + public func setState() { guard let castModel = model as? ImageButtonModel else { return } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift index d8217d0d..560027f3 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift @@ -8,7 +8,7 @@ import Foundation -open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, EnableableModelProtocol { +open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -25,11 +25,6 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro public var disabledTintColor: Color? public var groupName: String = "" - - public func setValidity(_ valid: Bool, group: FormGroupRule) { - enabled = valid - updateUI?() - } public var updateUI: ActionBlock? diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift index 04140d6e..4f4a8b34 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift @@ -7,7 +7,7 @@ // import UIKit - +import VDSColorTokens @objcMembers open class Link: Button { //-------------------------------------------------- @@ -28,8 +28,8 @@ import UIKit // x should be according to the text, not the button let x = textRect.origin.x - // Line is 1 point below the text - let y = textRect.origin.y + textRect.size.height + 1 + // Line is 0 point below the text + let y = textRect.origin.y + textRect.size.height context.move(to: CGPoint(x: x, y: y)) context.addLine(to: CGPoint(x: x + textRect.size.width, y: y)) @@ -38,7 +38,7 @@ import UIKit open override var intrinsicContentSize: CGSize { guard let size = titleLabel?.intrinsicContentSize else { return super.intrinsicContentSize } - return CGSize(width: size.width, height: size.height + 2) + return CGSize(width: size.width, height: size.height + 1) } //-------------------------------------------------- @@ -51,10 +51,14 @@ import UIKit guard let model = model as? LinkModel else { return } setTitle(model.title, for: .normal) - accessibilityLabel = model.title + if let accessibilityText = model.accessibilityText { + accessibilityLabel = accessibilityText + } setTitleColor((model.inverted ? model.enabledColor_inverted : model.enabledColor).uiColor, for: .normal) setTitleColor((model.inverted ? model.disabledColor_inverted : model.disabledColor).uiColor, for: .disabled) + setTitleColor((model.inverted ? model.activeColor_inverted : model.activeColor).uiColor, for: .highlighted) isEnabled = model.enabled + titleLabel?.font = model.getFont(model.size) set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } @@ -66,21 +70,15 @@ extension Link { open override func updateView(_ size: CGFloat) { super.updateView(size) - - var width = size - if MVMCoreGetterUtility.fequal(a: Float.leastNormalMagnitude, b: Float(size)) { - width = MVMCoreUIUtility.getWidth() - } - - titleLabel?.font = MFStyler.fontB2(forWidth: width) } open override func setupView() { super.setupView() backgroundColor = .clear contentMode = .redraw - setTitleColor(.mvmBlack, for: .normal) - setTitleColor(.mvmCoolGray6, for: .disabled) + setTitleColor(VDSColor.elementsPrimaryOnlight, for: .normal) + setTitleColor(VDSColor.interactiveDisabledOnlight, for: .disabled) + setTitleColor(VDSColor.interactiveActiveOnlight, for: .highlighted) titleLabel?.numberOfLines = 1 titleLabel?.lineBreakMode = .byTruncatingTail titleLabel?.textAlignment = .left diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift index f3b48bd4..af507860 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift @@ -7,7 +7,7 @@ // import UIKit - +import VDSColorTokens open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableModelProtocol { //-------------------------------------------------- @@ -19,13 +19,18 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode public var backgroundColor: Color? public var accessibilityIdentifier: String? public var title: String + public var accessibilityText: String? public var action: ActionModelProtocol public var enabled = true - public var enabledColor = Color(uiColor: .mvmBlack) - public var enabledColor_inverted = Color(uiColor: .mvmWhite) - public var disabledColor = Color(uiColor: .mvmCoolGray6) - public var disabledColor_inverted = Color(uiColor: .mvmCoolGray10) + public var enabledColor = Color(uiColor: VDSColor.elementsPrimaryOnlight) + public var enabledColor_inverted = Color(uiColor: VDSColor.elementsPrimaryOndark) + public var disabledColor = Color(uiColor: VDSColor.interactiveDisabledOnlight) + public var disabledColor_inverted = Color(uiColor: VDSColor.interactiveDisabledOndark) + public var activeColor = Color(uiColor: VDSColor.interactiveActiveOnlight) + public var activeColor_inverted = Color(uiColor: VDSColor.interactiveActiveOndark) + public var inverted = false + public var size:linkFontSize = linkFontSize.small //-------------------------------------------------- // MARK: - Initializer @@ -44,6 +49,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode case moleculeName case backgroundColor case accessibilityIdentifier + case accessibilityText case title case action case enabled @@ -51,9 +57,30 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode case enabledColor_inverted case disabledColor case disabledColor_inverted + case activeColor + case activeColor_inverted case inverted + case size } + public enum linkFontSize: String, Codable { + case small + case large + } + + //-------------------------------------------------- + // MARK: - Method + //-------------------------------------------------- + + func getFont(_ type: linkFontSize) -> UIFont { + switch type { + case .small: + return MFStyler.fontRegularBodySmall() + case .large: + return MFStyler.fontRegularBodyLarge() + } + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- @@ -64,6 +91,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) title = try typeContainer.decode(String.self, forKey: .title) + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) action = try typeContainer.decodeModel(codingKey: .action) if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { @@ -89,6 +117,17 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode if let disabledColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor_inverted) { self.disabledColor_inverted = disabledColor_inverted } + + if let activeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .activeColor) { + self.activeColor = activeColor + } + + if let activeColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .activeColor_inverted) { + self.activeColor_inverted = activeColor_inverted + } + if let size = try typeContainer.decodeIfPresent(linkFontSize.self, forKey: .size) { + self.size = size + } } public func encode(to encoder: Encoder) throws { @@ -104,5 +143,8 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode try container.encode(enabledColor_inverted, forKey: .enabledColor_inverted) try container.encode(disabledColor, forKey: .disabledColor) try container.encode(disabledColor_inverted, forKey: .disabledColor_inverted) + try container.encode(activeColor, forKey: .activeColor) + try container.encode(activeColor_inverted, forKey: .activeColor_inverted) + try container.encodeIfPresent(size, forKey: .size) } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift index 1bdf0234..e578fa51 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/PillButton.swift @@ -7,7 +7,7 @@ // import UIKit - +import VDSColorTokens open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { //-------------------------------------------------- @@ -23,21 +23,29 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { /// Need to re-style on set. open override var isEnabled: Bool { - didSet { style() } + didSet { style(with: buttonModel) } } open var buttonSize: Styler.Button.Size = .standard { didSet { buttonModel?.size = buttonSize } } + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + public var widthConstraint: NSLayoutConstraint? + public var minimumWidthConstraint: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @objc public convenience init(asPrimaryButton isPrimary: Bool, makeTiny istiny: Bool) { - self.init() - buttonSize = istiny ? .tiny : .standard - isPrimary ? stylePrimary() : styleSecondary() + let model = ButtonModel(with: "", action: ActionNoopModel()) + model.style = isPrimary ? .primary : .secondary + model.size = istiny ? .tiny : .standard + self.init(model: model, nil, nil) } //-------------------------------------------------- @@ -68,39 +76,26 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { /// The primary styling for a button. Should be used for main buttons public func stylePrimary() { - - enabledTitleColor = buttonModel?.enabledColors.text ?? .mvmWhite - disabledTitleColor = buttonModel?.disabledColors.text ?? .mvmWhite - layer.borderWidth = 0 - backgroundColor = isEnabled ? buttonModel?.enabledColors.fill ?? .mvmBlack : buttonModel?.disabledColors.fill ?? .mvmCoolGray6 + let buttonModel = ButtonModel(primaryButtonWith: "", action: ActionNoopModel()) + style(with: buttonModel) } /// The secondary styling for a button. Should be used for secondary buttons public func styleSecondary() { - - enabledTitleColor = buttonModel?.enabledColors.text ?? .mvmBlack - disabledTitleColor = buttonModel?.disabledColors.text ?? .mvmCoolGray6 - backgroundColor = .clear - layer.borderWidth = 1 - borderColor = isEnabled ? buttonModel?.enabledColors.border ?? .mvmBlack : buttonModel?.disabledColors.border ?? .mvmCoolGray6 + let buttonModel = ButtonModel(secondaryButtonWith: "", action: ActionNoopModel()) + style(with: buttonModel) } /// Styles the button based on the model style - private func style() { + private func style(with model: ButtonModel?) { - switch buttonModel?.style { - case .secondary: - styleSecondary() - - default: - stylePrimary() - } + layer.borderWidth = model?.style == .secondary ? 1 : 0 - if let titleColor = buttonModel?.enabledColors.text { + if let titleColor = model?.enabledColors.text { enabledTitleColor = titleColor } - if let disabledTitleColor = buttonModel?.disabledColors.text { + if let disabledTitleColor = model?.disabledColors.text { self.disabledTitleColor = disabledTitleColor } @@ -110,72 +105,46 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { #endif if isEnabled { - if let fillColor = buttonModel?.enabledColors.fill { + if let fillColor = model?.enabledColors.fill { backgroundColor = fillColor } - if let borderColor = buttonModel?.enabledColors.border { - layer.borderWidth = 1 + if let borderColor = model?.enabledColors.border { self.borderColor = borderColor } } else { - if let fillColor = buttonModel?.disabledColors.fill { + if let fillColor = model?.disabledColors.fill { backgroundColor = fillColor } - if let borderColor = buttonModel?.disabledColors.border { - layer.borderWidth = 1 + if let borderColor = model?.disabledColors.border { self.borderColor = borderColor } } } private func getInnerPadding() -> CGFloat { - getHeight() / 2.0 + buttonSize.getHeight() / 2.0 } - - private func getHeight() -> CGFloat { - PillButton.getHeight(for: buttonSize, size: size) - } - - public static func getHeight(for buttonSize: Styler.Button.Size?, size: CGFloat) -> CGFloat { + private func getContentEdgeInsets() -> UIEdgeInsets { + var verticalPadding = 0.0 + var horizontalPadding = 0.0 switch buttonSize { + case .standard: + verticalPadding = Padding.Three + horizontalPadding = Padding.Five + break + case .small: + verticalPadding = Padding.Two + horizontalPadding = Padding.Four + break case .tiny: - let tinyHeight = Styler.Button.Size.tiny.getHeight() - return MFSizeObject(standardSize: tinyHeight, - standardiPadPortraitSize: 34, - iPadProLandscapeSize: 38)?.getValueBased(onSize: size) ?? tinyHeight - - default: - let standardHeight = Styler.Button.Size.standard.getHeight() - return MFSizeObject(standardSize: standardHeight, - standardiPadPortraitSize: 46, - iPadProLandscapeSize: 50)?.getValueBased(onSize: size) ?? standardHeight - } - } - - private func getMinimumWidth() -> CGFloat { - - switch buttonSize { - case .tiny: - return MFSizeObject(standardSize: 49, - standardiPadPortraitSize: 90, - iPadProLandscapeSize: 135)?.getValueBased(onSize: size) ?? 49 - - default: return 151 - } - } - - open override var intrinsicContentSize: CGSize { - if buttonSize == .tiny { - let size = super.intrinsicContentSize - let width = size.width + (2 * getInnerPadding()) - return CGSize(width: max(width, getMinimumWidth()), height: getHeight()) - } else { - let width = Padding.Component.gutterForApplicationWidth + (2.0 * Padding.Component.columnFor(size: MVMCoreUISplitViewController.getApplicationViewWidth())) - return CGSize(width: min(292, width), height: getHeight()) + verticalPadding = Padding.One + horizontalPadding = Padding.Two + break } + return UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding) } //-------------------------------------------------- @@ -187,7 +156,11 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { super.set(with: model, delegateObject, additionalData) guard let model = model as? ButtonModel else { return } + setTitle(model.title, for: .normal) + if let accessibilityText = model.accessibilityText { + accessibilityLabel = accessibilityText + } if let size = model.size { buttonSize = size @@ -203,24 +176,44 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol { } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - PillButton.getHeight(for: (model as? ButtonModel)?.size, size: MVMCoreUIUtility.getWidth()) + return (model as? ButtonModel)?.size?.getHeight() } open override func updateView(_ size: CGFloat) { super.updateView(size) self.size = size - invalidateIntrinsicContentSize() - switch buttonSize { case .tiny: - titleLabel?.font = MFFonts.mfFont75Bd(11 * (intrinsicContentSize.height / Styler.Button.Size.tiny.getHeight())) - - default: - titleLabel?.font = MFFonts.mfFont75Bd(13 * (intrinsicContentSize.height / Styler.Button.Size.standard.getHeight())) + titleLabel?.font = Styler.Font.BoldMicro.getFont(false) + case .small: + titleLabel?.font = Styler.Font.BoldBodySmall.getFont(false) + case .standard: + titleLabel?.font = Styler.Font.BoldBodyLarge.getFont(false) } layer.cornerRadius = getInnerPadding() + contentEdgeInsets = getContentEdgeInsets() + + if let contraint = buttonModel?.width { + + if widthConstraint == nil { + widthConstraint = widthAnchor.constraint(equalToConstant: contraint) + } else if widthConstraint?.constant != contraint { + widthConstraint?.constant = contraint + } + widthConstraint?.isActive = true + minimumWidthConstraint?.isActive = false + } else { + + if minimumWidthConstraint == nil { + minimumWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: buttonSize.minimumWidth()) + } else { + minimumWidthConstraint?.constant = buttonSize.minimumWidth() + } + minimumWidthConstraint?.isActive = true + widthConstraint?.isActive = false + } } open override func setupView() { diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagModel.swift index 56a86742..823d2478 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagModel.swift @@ -7,6 +7,7 @@ // import Foundation +import MVMCore @objcMembers public class TagModel: MoleculeModelProtocol { public static var identifier: String = "tag" @@ -27,6 +28,14 @@ import Foundation } } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(label: LabelModel) { + self.label = label + } + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) label = try typeContainer.decode(LabelModel.self, forKey: .label) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagsModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagsModel.swift index 009f8fa1..6330bb94 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagsModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/TagsModel.swift @@ -7,6 +7,7 @@ // import Foundation +import MVMCore @objcMembers public class TagsModel: MoleculeModelProtocol { public static var identifier: String = "tags" @@ -19,6 +20,14 @@ import Foundation case tags } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with tags: [TagModel]) { + self.tags = tags + } + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) tags = try typeContainer.decode([TagModel].self, forKey: .tags) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift index 912b0891..399cedac 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitBox.swift @@ -38,29 +38,6 @@ import UIKit // Default dimensions of the DigitBox static let size: CGSize = CGSize(width: 39, height: 44) - //-------------------------------------------------- - // MARK: - Computed Properties - //-------------------------------------------------- - - public override var showError: Bool { - get { super.showError } - set (error) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.borderStrokeColor = error ? .mvmOrange : .mvmCoolGray3 - - let barHeight: CGFloat = self.showError ? 4 : 1 - self.bottomBar?.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight) - self.bottomBar?.backgroundColor = self.showError ? UIColor.mvmOrange.cgColor : UIColor.mvmBlack.cgColor - - self.setNeedsDisplay() - self.layoutIfNeeded() - } - super.showError = error - } - } - //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift index c70d458e..2e098ad9 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift @@ -105,7 +105,7 @@ import UIKit super.isLocked = locked } } - + public override var placeholder: String? { get { var string = "" @@ -249,6 +249,10 @@ import UIKit //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- + public override func showErrorView(_ show: Bool) { + //do nothing since you should show the error view + //within a digitEntryField + } @objc public func setAsSecureTextEntry(_ secureEntry: Bool) { @@ -311,7 +315,6 @@ import UIKit //-------------------------------------------------- @objc override func startEditing() { - selectedDigitBox?.isSelected = true selectedDigitBox?.digitField.becomeFirstResponder() } @@ -328,7 +331,6 @@ import UIKit } @objc public override func dismissFieldInput(_ sender: Any?) { - digitBoxes.forEach { if $0.isSelected { $0.digitField.resignFirstResponder() @@ -398,7 +400,6 @@ extension DigitEntryField { digitEntryModel?.text = text return false } - return true } @@ -411,9 +412,9 @@ extension DigitEntryField { } @objc public func textFieldDidBeginEditing(_ textField: UITextField) { - digitBoxes.forEach { if $0.digitField === textField { + startEditing() selectedDigitBox = $0 $0.isSelected = true return @@ -429,15 +430,15 @@ extension DigitEntryField { } @objc public func textFieldDidEndEditing(_ textField: UITextField) { - // There should only be one digitBox to deselect. selectedDigitBox?.isSelected = false selectedDigitBox = nil if !switchFieldsAutomatically && validateWhenDoneEditing { validateText() + endInputing() } - + proprietorTextDelegate?.textFieldDidEndEditing?(textField) } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift index d9b42a41..6e8be5f1 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/BaseDropdownEntryField.swift @@ -44,7 +44,7 @@ import UIKit super.isEnabled = enabled } } - + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift index bf7416e6..75d2c887 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryFieldModel.swift @@ -21,6 +21,7 @@ //-------------------------------------------------- public override func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } guard !options.isEmpty, let index = selectedIndex else { return nil } @@ -57,6 +58,6 @@ try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(options, forKey: .options) - try container.encodeIfPresent(options, forKey: .selectedIndex) + try container.encodeIfPresent(selectedIndex, forKey: .selectedIndex) } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryFieldModel.swift index 8799f6aa..42dbf463 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryFieldModel.swift @@ -25,7 +25,7 @@ import Foundation //-------------------------------------------------- public override func formFieldValue() -> AnyHashable? { - + guard enabled else { return nil } guard !components.isEmpty && !selectedIndexes.isEmpty else { return nil } return selectedRowText diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift index db6ffe53..c583638c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryField.swift @@ -17,11 +17,8 @@ import UIKit //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- - - public private(set) var titleLabel: Label = { - let label = Label() - label.font = Styler.Font.RegularMicro.getFont() - label.textColor = .mvmBlack + public private(set) var titleLabel: FormLabel = { + let label = FormLabel() label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() @@ -29,14 +26,33 @@ import UIKit public private(set) var entryFieldContainer = EntryFieldContainer() /// Provides contextual information on the TextField. - public private(set) var feedbackLabel: Label = { + public private(set) var feedbackLabel: FormLabel = { + let label = FormLabel() + label.setContentCompressionResistancePriority(.required, for: .vertical) + return label + }() + + public private(set) var errorLabel: Label = { let label = Label() - label.font = Styler.Font.RegularMicro.getFont() + label.setFontStyle(.RegularMicro) label.textColor = .mvmBlack label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() - + + public lazy var stack: UIStackView = { + errorLabel.isHidden = true + let stack = UIStackView(arrangedSubviews: [titleLabel, entryFieldContainer, errorLabel, feedbackLabel]) + stack.axis = .vertical + stack.alignment = .fill + stack.distribution = .fill + stack.setCustomSpacing(Padding.One, after: titleLabel) + stack.setCustomSpacing(Padding.Two, after: entryFieldContainer) + stack.setCustomSpacing(Padding.One, after: errorLabel) + stack.translatesAutoresizingMaskIntoConstraints = false + return stack + }() + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -46,7 +62,6 @@ import UIKit //-------------------------------------------------- // MARK: - Stored Properties //-------------------------------------------------- - public var isValid: Bool = false /// Validate on each entry in the textField. Default: true @@ -60,19 +75,32 @@ import UIKit public var isEnabled: Bool { get { entryFieldContainer.isEnabled } set (enabled) { - titleLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3 - feedbackLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3 - entryFieldContainer.isEnabled = enabled - entryFieldModel?.enabled = enabled + if(entryFieldContainer.isEnabled != enabled){ + titleLabel.isEnabled = enabled + feedbackLabel.isEnabled = enabled + entryFieldContainer.isEnabled = enabled + entryFieldModel?.enabled = enabled + } } } + /// Toggles enabled (original) or disabled UI. + public var isRequired: Bool = true { + didSet{ + titleLabel.isRequired = isRequired + } + } + /// Toggles error or original UI. public var showError: Bool { get { entryFieldContainer.showError } set (error) { - feedback = error ? errorMessage : entryFieldModel?.feedback - feedbackLabel.textColor = error ? entryFieldModel?.errorTextColor?.uiColor ?? .mvmBlack : .mvmBlack + if error { + errorLabel.text = self.errorMessage ?? "" + errorLabel.isHidden = false + } else { + errorLabel.isHidden = true + } entryFieldContainer.showError = error entryFieldModel?.showError = error } @@ -104,11 +132,11 @@ import UIKit public var title: String? { get { titleLabel.text } set { - titleLabel.text = newValue + titleLabel.set(text: newValue ?? "") setAccessibilityString(newValue) } } - + /// Override this to conveniently get/set the textfield(s). public var text: String? { get { nil } @@ -119,7 +147,7 @@ import UIKit public var feedback: String? { get { feedbackLabel.text } set { - feedbackLabel.text = newValue + feedbackLabel.set(text: newValue ?? "") feedbackLabel.accessibilityElementsHidden = feedbackLabel.text?.isEmpty ?? true } } @@ -128,22 +156,6 @@ import UIKit model as? EntryFieldModel } - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - public var entryFieldContainerLeading: NSLayoutConstraint? - public var entryFieldContainerTrailing: NSLayoutConstraint? - - public var feedbackLabelTrailing: NSLayoutConstraint? - public var feedbackLabelLeading: NSLayoutConstraint? - - public var titleLabelLeading: NSLayoutConstraint? - public var titleLabelTrailing: NSLayoutConstraint? - - public var titleContainerDistance: NSLayoutConstraint? - public var feedbackContainerDistance: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -183,37 +195,18 @@ import UIKit isAccessibilityElement = false setContentCompressionResistancePriority(.required, for: .vertical) - accessibilityElements = [titleLabel, feedbackLabel] + accessibilityElements = [titleLabel, errorLabel, feedbackLabel] backgroundColor = .mvmWhite - - addSubview(titleLabel) - - titleLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true - titleLabelLeading = titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - titleLabelLeading?.isActive = true - titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor) - titleLabelLeading?.isActive = true - - addSubview(entryFieldContainer) + + addSubview(stack) + entryFieldContainer.setContentCompressionResistancePriority(.required, for: .vertical) setupFieldContainerContent(entryFieldContainer) - - titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: Padding.One) - titleContainerDistance?.isActive = true - entryFieldContainerLeading = entryFieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - entryFieldContainerLeading?.isActive = true - entryFieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryFieldContainer.trailingAnchor) - entryFieldContainerTrailing?.isActive = true - - addSubview(feedbackLabel) - - feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryFieldContainer.bottomAnchor, constant: Padding.Two) - feedbackContainerDistance?.isActive = true - feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - feedbackLabelLeading?.isActive = true - feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) - feedbackLabelTrailing?.isActive = true - layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true + + stack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + stack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: stack.trailingAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true } @objc open override func layoutSubviews() { @@ -229,10 +222,7 @@ import UIKit @objc open override func updateView(_ size: CGFloat) { super.updateView(size) - - titleLabel.updateView(size) - feedbackLabel.updateView(size) - entryFieldContainer.updateView(size) + stack.updateView(size) } //-------------------------------------------------- @@ -290,19 +280,30 @@ import UIKit titleLabel.textColor = .mvmBlack feedbackLabel.font = Styler.Font.RegularMicro.getFont() feedbackLabel.textColor = .mvmBlack + errorLabel.font = Styler.Font.RegularMicro.getFont() + errorLabel.textColor = .mvmBlack + errorLabel.text = nil entryFieldContainer.disableAllBorders = false feedbackLabel.text = nil entryFieldContainer.reset() + entryFieldModel?.updateUI = nil } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject - guard let model = model as? EntryFieldModel else { return } + guard let model = entryFieldModel else { return } entryFieldContainer.set(with: model, delegateObject, additionalData) + //setup the properties for setting models + //later with enabled/showError + titleLabel.setup(model: model.titleStateLabel, delegateObject, additionalData) + feedbackLabel.setup(model: model.feedbackStateLabel, delegateObject, additionalData) + + isEnabled = model.enabled && !model.readOnly + isRequired = model.required model.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { guard let self = self else { return } @@ -313,6 +314,8 @@ import UIKit } else if model.isValid ?? true && self.showError { self.showError = false } + self.isEnabled = model.enabled + self.text = model.text }) } @@ -328,13 +331,10 @@ import UIKit self.updateValidation(validState) }) } - - title = model.title - feedback = model.feedback - isEnabled = model.enabled + entryFieldContainer.disableAllBorders = model.hideBorders accessibilityIdentifier = model.accessibilityIdentifier ?? model.fieldKey - + if let isLocked = model.locked { self.isLocked = isLocked @@ -364,3 +364,11 @@ extension EntryField { // To Be Overriden } } + +extension LabelModel { + public convenience init(text: String = "", fontStyle: Styler.Font, textColor: Color) { + self.init(text: text) + self.fontStyle = fontStyle + self.textColor = textColor + } +} diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift index 2a2e737c..962580a6 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift @@ -9,7 +9,8 @@ import Foundation -@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, EnableableModelProtocol { +@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol, ClearableModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -18,8 +19,6 @@ import Foundation public var backgroundColor: Color? public var accessibilityIdentifier: String? - public var title: String? - public var feedback: String? public var shouldClearText: Bool = false public var dynamicErrorMessage: String? { didSet { @@ -30,6 +29,8 @@ import Foundation public var errorMessage: String? public var errorTextColor: Color? public var enabled: Bool = true + public var required: Bool = true + public var readOnly: Bool = false public var showError: Bool? public var hideBorders = false public var locked: Bool? @@ -39,7 +40,13 @@ import Foundation public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? public var wasInitiallySelected: Bool = false - + public var title: String? + public var feedback: String? + + //used to drive the EntryFieldView UI + public var titleStateLabel: FormLabelModel + public var feedbackStateLabel: FormLabelModel + public var isValid: Bool? = true { didSet { updateUI?() } } @@ -60,6 +67,7 @@ import Foundation case accessibilityIdentifier case title case enabled + case readOnly case feedback case errorMessage case errorTextColor @@ -70,6 +78,7 @@ import Foundation case text case fieldKey case groupName + case required } //-------------------------------------------------- @@ -77,22 +86,24 @@ import Foundation //-------------------------------------------------- public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + if dynamicErrorMessage != nil { dynamicErrorMessage = nil } return text } - public func setValidity(_ valid: Bool, rule: RulesProtocol) { + public func setValidity(_ valid: Bool, errorMessage: String?) { - if let fieldKey = fieldKey, let ruleErrorMessage = rule.errorMessage?[fieldKey] { + if let ruleErrorMessage = errorMessage, fieldKey != nil { self.errorMessage = ruleErrorMessage } self.isValid = valid updateUI?() } - + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -100,6 +111,15 @@ import Foundation public init(with text: String) { self.text = text baseValue = text + self.titleStateLabel = FormLabelModel(text: "") + self.feedbackStateLabel = FormLabelModel(text: "") + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public func clear() { + self.text = "" } //-------------------------------------------------- @@ -115,16 +135,21 @@ import Foundation errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor) enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false locked = try typeContainer.decodeIfPresent(Bool.self, forKey: .locked) selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) text = try typeContainer.decodeIfPresent(String.self, forKey: .text) hideBorders = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideBorders) ?? false baseValue = text fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + self.titleStateLabel = FormLabelModel(text: title ?? "") + self.feedbackStateLabel = FormLabelModel(model: LabelModel(text: feedback ?? "", + fontStyle: FormLabelModel.defaultFontStyle, + textColor: Color(uiColor: .mvmCoolGray6))) } public func encode(to encoder: Encoder) throws { @@ -142,7 +167,10 @@ import Foundation try container.encodeIfPresent(errorMessage, forKey: .errorMessage) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) + + try container.encode(readOnly, forKey: .readOnly) try container.encode(enabled, forKey: .enabled) + try container.encode(required, forKey: .required) try container.encode(hideBorders, forKey: .hideBorders) } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index 7973d652..d081b717 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -84,7 +84,7 @@ import UIKit textField.accessibilityValue = nil } - if textField.isSecureTextEntry { + if !textField.isSecureTextEntry { showErrorView(error) } @@ -247,6 +247,10 @@ import UIKit /// Executes on UITextField.textDidChangeNotification (each character entry) @objc override func valueChanged() { super.valueChanged() + if (textEntryFieldModel?.type == .email) { + // remove spaces (either user entered Or auto-correct suggestion) for the email field + textField.text = textField.text?.replacingOccurrences(of: " ", with: "") + } validateText() } @@ -290,8 +294,7 @@ import UIKit resignFirstResponder() } - private func showErrorView(_ show: Bool) { - + open func showErrorView(_ show: Bool) { if show { entryFieldContainer.addSubview(errorImage) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift index 3e600734..7d2eb987 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift @@ -38,6 +38,14 @@ public var displayFormat: String? public var displayMask: String? + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(with text: String){ + super.init(with: text) + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -137,7 +145,7 @@ } } - public override func encode(to encoder: Encoder) throws { + open override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(placeholder, forKey: .placeholder) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift index 543469c1..3221f043 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextViewEntryField.swift @@ -46,7 +46,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele if self.textView.isShowingPlaceholder { self.textView.textColor = self.textView.placeholderTextColor } else { - self.textView.textColor = (enabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor + self.textView.textColor = (self.textView.isEnabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor } } } @@ -282,8 +282,5 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele adjustMarginConstraints(constant: 0) } - if !model.enabled { - isEnabled = false - } } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift index a5657dd4..4bd64c7c 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift @@ -95,7 +95,7 @@ import MVMCore } } } - + public var disabledBackgroundColor: UIColor = .clear public var disabledBorderColor: UIColor = .mvmCoolGray3 public var disabledCheckColor: UIColor = .mvmCoolGray3 @@ -423,7 +423,14 @@ import MVMCore checkAndBypassAnimations(selected: model.selected) } - isEnabled = model.enabled + model.updateUI = { [weak self] in + MVMCoreDispatchUtility.performBlock(onMainThread: { + guard let self = self else { return } + self.isEnabled = model.enabled + }) + } + + isEnabled = model.enabled && !model.readOnly if (model.action != nil || model.offAction != nil) { actionBlock = { [weak self] in diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift index c4f69f27..063c322a 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift @@ -13,7 +13,8 @@ var selected: Bool { get set } } -@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol { +@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol, UIUpdatableModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -23,6 +24,7 @@ public var accessibilityIdentifier: String? public var selected: Bool = false public var enabled: Bool = true + public var readOnly: Bool = false public var animated: Bool = true public var inverted: Bool = false public var round: Bool = false @@ -42,6 +44,7 @@ public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? + public var updateUI: ActionBlock? //-------------------------------------------------- // MARK: - Keys @@ -52,6 +55,7 @@ case accessibilityIdentifier case checked case enabled + case readOnly case inverted case animated case round @@ -75,7 +79,10 @@ // MARK: - Form Validation //-------------------------------------------------- - public func formFieldValue() -> AnyHashable? { selected } + public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + return selected + } //-------------------------------------------------- // MARK: - Initializer @@ -153,10 +160,8 @@ self.inverted = inverted } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false action = try typeContainer.decodeModelIfPresent(codingKey: .action) fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) @@ -186,7 +191,8 @@ try container.encodeIfPresent(disabledCheckColor, forKey: .disabledCheckColor) try container.encodeIfPresent(animated, forKey: .animated) try container.encodeIfPresent(round, forKey: .round) - try container.encodeIfPresent(enabled, forKey: .enabled) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) try container.encodeModelIfPresent(action, forKey: .action) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeModelIfPresent(offAction, forKey: .offAction) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift index e694e194..eb0b0b4c 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Heart.swift @@ -62,7 +62,7 @@ import UIKit heartPath.close() heart.path = heartPath.cgPath heart.fillColor = isSelected ? heartModel?.activeColor.cgColor : heartModel?.inActiveColor.cgColor - heart.opacity = 1.0 + heart.opacity = isEnabled ? 1.0 : 0.5 heart.lineWidth = 1 heart.strokeColor = isSelected ? UIColor.clear.cgColor : UIColor.mvmBlack.cgColor return heart @@ -88,7 +88,7 @@ import UIKit self.additionalData = additionalData guard let model = model as? HeartModel else { return } isSelected = model.isActive - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly updateAccessibilityLabel() } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift index aea065ef..4be1343a 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/HeartModel.swift @@ -5,7 +5,7 @@ // Created by Lekshmi S on 07/09/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import MVMCore open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { @@ -21,8 +21,8 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { public var inActiveColor: Color = Color(uiColor: .clear) public var action: ActionModelProtocol = ActionNoopModel() public var enabled: Bool = true - - //-------------------------------------------------- + public var readOnly: Bool = false + // MARK: - Keys //-------------------------------------------------- @@ -35,8 +35,14 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { case inActiveColor case action case enabled + case readOnly } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init() { } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- @@ -61,9 +67,8 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) { self.action = action } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false } public func encode(to encoder: Encoder) throws { @@ -76,5 +81,6 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol { try container.encode(inActiveColor, forKey: .inActiveColor) try container.encodeModel(action, forKey: .action) try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift index 1d8c6960..27323048 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -39,7 +39,7 @@ open class RadioBox: Control, MFButtonProtocol { public override var isEnabled: Bool { didSet { updateAccessibility() } } - + //-------------------------------------------------- // MARK: - MVMCoreViewProtocol //-------------------------------------------------- @@ -89,7 +89,7 @@ open class RadioBox: Control, MFButtonProtocol { accentColor = color } isSelected = model.selected - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly } open override func reset() { diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift index 5d5443d0..27defd48 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift @@ -5,8 +5,9 @@ // Created by Dhamodaram Nandi on 31/03/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import MVMCore -@objcMembers public class RadioBoxModel: MoleculeModelProtocol { +@objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,6 +20,7 @@ public var selectedAccentColor: Color? public var selected: Bool = false public var enabled: Bool = true + public var readOnly: Bool = false public var strikethrough: Bool = false public var fieldValue: String? public var action: ActionModelProtocol? @@ -39,6 +41,15 @@ case strikethrough case fieldValue case action + case readOnly + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(text: String) { + self.text = text } //-------------------------------------------------- @@ -57,9 +68,8 @@ selected = isSelected } - if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - enabled = isEnabled - } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { strikethrough = isStrikeTrough @@ -79,6 +89,7 @@ try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encode(selected, forKey: .selected) try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeModelIfPresent(action, forKey: .action) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift index bd1f99d1..852cd5c1 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift @@ -5,6 +5,7 @@ // Created by Dhamodaram Nandi on 31/03/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import MVMCore @objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- @@ -20,13 +21,15 @@ public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? - + public var enabled: Bool = true + public var readOnly: Bool = false //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- /// Returns the fieldValue of the selected box, otherwise the text of the selected box. public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } let selectedBox = boxes.first { (box) -> Bool in return box.selected } @@ -39,6 +42,8 @@ private enum CodingKeys: String, CodingKey { case moleculeName + case enabled + case readOnly case selectedAccentColor case backgroundColor case accessibilityIdentifier @@ -48,6 +53,14 @@ case groupName } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with boxes: [RadioBoxModel]){ + self.boxes = boxes + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- @@ -63,6 +76,8 @@ if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false baseValue = formFieldValue() } @@ -75,5 +90,7 @@ try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encode(groupName, forKey: .groupName) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift index 1098ad3d..1854f943 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift @@ -66,7 +66,7 @@ import UIKit open override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } - let color = isEnabled ? enabledColor.cgColor : disabledColor.cgColor + let color = isEnabled == false ? disabledColor.cgColor : enabledColor.cgColor layer.cornerRadius = bounds.width * 0.5 layer.borderColor = color layer.borderWidth = bounds.width * 0.0333 @@ -118,7 +118,8 @@ import UIKit } public func formFieldValue() -> AnyHashable? { - radioModel?.fieldValue + guard let radioModel = radioModel, radioModel.enabled else { return nil } + return radioModel.fieldValue } //-------------------------------------------------- @@ -162,7 +163,7 @@ import UIKit guard let model = model as? RadioButtonModel else { return } isSelected = model.state - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift index 239a8d41..79fcb9cb 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift @@ -19,6 +19,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { public var accessibilityIdentifier: String? public var state: Bool = false public var enabled: Bool = true + public var readOnly: Bool = false /// The specific value to send to server. TODO: update this to be more generic. public var fieldValue: String? @@ -42,6 +43,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { case fieldKey case groupName case action + case readOnly } //-------------------------------------------------- @@ -57,7 +59,10 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { // MARK: - Validation //-------------------------------------------------- - public func formFieldValue() -> AnyHashable? { fieldValue } + public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + return fieldValue + } //-------------------------------------------------- // MARK: - Codec @@ -69,11 +74,9 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { self.state = state } - - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - + + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) @@ -93,6 +96,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { try container.encode(moleculeName, forKey: .moleculeName) try container.encode(state, forKey: .state) try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift index a5000284..da7ef0c2 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift @@ -17,6 +17,8 @@ private var selectedRadioButton: RadioButton? private var selectedRadioButtonModel: RadioButtonModel? public var baseValue: AnyHashable? + public var enabled: Bool = true + public var readOnly: Bool = false //-------------------------------------------------- // MARK: - Initializer @@ -39,6 +41,8 @@ } else { radioButton.isSelected = false } + self.enabled = radioButtonModel.enabled + self.readOnly = radioButtonModel.readOnly } //-------------------------------------------------- @@ -75,5 +79,8 @@ // MARK: - FormValidationFormFieldProtocol extension RadioButtonSelectionHelper { - public func formFieldValue() -> AnyHashable? { selectedRadioButtonModel?.fieldValue } + public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + return selectedRadioButtonModel?.fieldValue + } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift index e80f8813..eb569810 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift @@ -38,7 +38,7 @@ open class RadioSwatch: Control, MFButtonProtocol { updateAccessibility() } } - + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -64,7 +64,7 @@ open class RadioSwatch: Control, MFButtonProtocol { self.additionalData = additionalData bottomText.text = model.text isSelected = model.selected - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly } public override func reset() { diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift index c961e306..f0a194e9 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift @@ -5,9 +5,9 @@ // Created by Scott Pfeil on 4/17/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import MVMCore - -@objcMembers public class RadioSwatchModel: MoleculeModelProtocol { +@objcMembers public class RadioSwatchModel: MoleculeModelProtocol, EnableableModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -19,6 +19,7 @@ public var text: String? public var selected: Bool = false public var enabled: Bool = true + public var readOnly: Bool = false public var strikethrough: Bool = false public var fieldValue: String? public var action: ActionModelProtocol? @@ -38,8 +39,15 @@ case strikethrough case fieldValue case action + case readOnly } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init() {} + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- @@ -58,17 +66,15 @@ if let selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { self.selected = selected } - - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - + if let strikethrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { self.strikethrough = strikethrough } fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) action = try typeContainer.decodeModelIfPresent(codingKey: .action) + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false } public func encode(to encoder: Encoder) throws { @@ -83,5 +89,6 @@ try container.encode(strikethrough, forKey: .strikethrough) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeModelIfPresent(action, forKey: .action) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift index d44b631a..21541d35 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift @@ -5,7 +5,7 @@ // Created by Lekshmi S on 31/03/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import MVMCore @objcMembers public class RadioSwatchesModel: MoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- @@ -19,6 +19,8 @@ public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? + public var enabled: Bool = true + public var readOnly: Bool = false //-------------------------------------------------- // MARK: - Methods @@ -26,6 +28,7 @@ /// Returns the fieldValue of the selected swatch, otherwise the text of selected swatch. public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } let selectedSwatch = swatches.first { (swatch) -> Bool in return swatch.selected } @@ -43,6 +46,16 @@ case swatches case fieldKey case groupName + case enabled + case readOnly + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with swatches: [RadioSwatchModel]){ + self.swatches = swatches } //-------------------------------------------------- @@ -58,6 +71,8 @@ if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false baseValue = formFieldValue() } @@ -69,5 +84,7 @@ try container.encode(swatches, forKey: .swatches) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encode(groupName, forKey: .groupName) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift index 3513a03b..04c34da2 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Toggle.swift @@ -66,7 +66,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: isEnabled ? "AccToggleHint" : "AccDisabled") } } - + /// Simple means to prevent user interaction with the toggle. public var isLocked: Bool = false { didSet { isUserInteractionEnabled = !isLocked } @@ -384,7 +384,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) isOn = model.selected changeStateNoAnimation(isOn) isAnimated = model.animated - isEnabled = model.enabled + isEnabled = model.enabled && !model.readOnly if let accessibileString = model.accessibilityText { accessibilityLabel = accessibileString diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift index 5be014fb..4c860342 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/ToggleModel.swift @@ -7,7 +7,7 @@ // -public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableModelProtocol { +public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -18,6 +18,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo public var selected: Bool = false public var animated: Bool = true public var enabled: Bool = true + public var readOnly: Bool = false public var action: ActionModelProtocol? public var alternateAction: ActionModelProtocol? public var accessibilityText: String? @@ -39,6 +40,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo case state case animated case enabled + case readOnly case action case backgroundColor case accessibilityIdentifier @@ -56,7 +58,10 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo // MARK: - Form Valdiation //-------------------------------------------------- - public func formFieldValue() -> AnyHashable? { selected } + public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + return selected + } //-------------------------------------------------- // MARK: - Initializer @@ -78,10 +83,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo self.selected = state } - if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { - self.enabled = enabled - } - if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) { self.animated = animated } @@ -114,6 +115,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false } public func encode(to encoder: Encoder) throws { @@ -133,5 +136,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/ArrowModel.swift b/MVMCoreUI/Atomic/Atoms/Views/ArrowModel.swift index 35dbd906..388f1669 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/ArrowModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/ArrowModel.swift @@ -9,7 +9,7 @@ import UIKit -open class ArrowModel: MoleculeModelProtocol { +open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/CaretViewModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CaretViewModel.swift index 9dc43286..1e2595ec 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CaretViewModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CaretViewModel.swift @@ -7,7 +7,7 @@ // import Foundation - +import MVMCore @objcMembers public class CaretViewModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -38,6 +38,12 @@ import Foundation case inverted } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init() {} + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift index 975c7ebb..2eefd716 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/BarsIndicatorView.swift @@ -20,6 +20,8 @@ open class BarsIndicatorView: CarouselIndicator { static let width: CGFloat = 24 static let selectedHeight: CGFloat = 4 static let unselectedHeight: CGFloat = 1 + static let selectedCornerRadius: CGFloat = 2 + static let unselectedCornerRadius: CGFloat = 0.5 var constraint: NSLayoutConstraint? @@ -38,6 +40,7 @@ open class BarsIndicatorView: CarouselIndicator { override func setupView() { super.setupView() isAccessibilityElement = true + layer.cornerRadius = 2 accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint") widthAnchor.constraint(equalToConstant: BarsIndicatorView.IndicatorBar.width).isActive = true accessibilityTraits = .button @@ -52,7 +55,7 @@ open class BarsIndicatorView: CarouselIndicator { stackView.axis = .horizontal stackView.alignment = .bottom stackView.distribution = .equalSpacing - stackView.spacing = 6 + stackView.spacing = 4 return stackView }() @@ -79,7 +82,7 @@ open class BarsIndicatorView: CarouselIndicator { } } } - + /// Colors the currently selected index, unique from other indicators public var currentIndicatorColor: UIColor { get { barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor } @@ -189,6 +192,7 @@ open class BarsIndicatorView: CarouselIndicator { let bar = IndicatorBar() setAccessibilityLabel(view: bar, index: index) bar.backgroundColor = isEnabled ? (index == currentIndex ? currentIndicatorColor : indicatorColor) : disabledIndicatorColor + bar.layer.cornerRadius = index == currentIndex ? BarsIndicatorView.IndicatorBar.selectedCornerRadius : BarsIndicatorView.IndicatorBar.unselectedCornerRadius let barHeight = index == currentIndex ? BarsIndicatorView.IndicatorBar.selectedHeight : BarsIndicatorView.IndicatorBar.unselectedHeight let heightConstraint = bar.heightAnchor.constraint(equalToConstant: barHeight) heightConstraint.isActive = true @@ -251,6 +255,7 @@ open class BarsIndicatorView: CarouselIndicator { barReferences.forEach { $0.backgroundColor = isEnabled ? indicatorColor : disabledIndicatorColor $0.constraint?.constant = IndicatorBar.unselectedHeight + $0.layer.cornerRadius = IndicatorBar.unselectedCornerRadius } balanceBarCount(numberOfPages - barReferences.count) @@ -260,8 +265,10 @@ open class BarsIndicatorView: CarouselIndicator { 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() } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicator.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicator.swift index 6a82ef80..1d7808bd 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicator.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicator.swift @@ -5,7 +5,7 @@ // Created by Kevin Christiano on 1/30/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import VDSColorTokens open class CarouselIndicator: Control, CarouselPageControlProtocol { //-------------------------------------------------- @@ -71,13 +71,22 @@ open class CarouselIndicator: Control, CarouselPageControlProtocol { } public var disabledIndicatorColor: UIColor { - get { carouselIndicatorModel?.disabledIndicatorColor.uiColor ?? .mvmCoolGray3 } - set { carouselIndicatorModel?.disabledIndicatorColor = Color(uiColor: newValue) } + get { + guard let model = carouselIndicatorModel else { return VDSColor.elementsSecondaryOnlight } + return model.inverted ? model.disabledIndicatorColor_inverted.uiColor : model.disabledIndicatorColor.uiColor + } + set { + if let model = carouselIndicatorModel, model.inverted { + model.disabledIndicatorColor_inverted = Color(uiColor: newValue) + } else { + carouselIndicatorModel?.disabledIndicatorColor = Color(uiColor: newValue) + } + } } public var indicatorColor: UIColor { get { - guard let model = carouselIndicatorModel else { return .mvmBlack } + guard let model = carouselIndicatorModel else { return VDSColor.elementsPrimaryOnlight} return model.inverted ? model.indicatorColor_inverted.uiColor : model.indicatorColor.uiColor } set { diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift index 1a9486f6..164bb306 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift @@ -7,9 +7,9 @@ // import Foundation +import VDSColorTokens - -open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelProtocol { +open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelProtocol, EnableableModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -30,9 +30,10 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro /// 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 public var enabled: Bool = true - public var disabledIndicatorColor: Color = Color(uiColor: .mvmCoolGray3) - public var indicatorColor: Color = Color(uiColor: .mvmBlack) - public var indicatorColor_inverted: Color = Color(uiColor: .mvmWhite) + public var disabledIndicatorColor: Color = Color(uiColor: VDSColor.elementsSecondaryOnlight) + public var disabledIndicatorColor_inverted: Color = Color(uiColor: VDSColor.elementsSecondaryOndark) + public var indicatorColor: Color = Color(uiColor: VDSColor.elementsPrimaryOnlight) + public var indicatorColor_inverted: Color = Color(uiColor: VDSColor.elementsPrimaryOndark) public var position: CGFloat? /// Allows sendActions() to trigger even if index is already at min/max index. diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift index eb3a8ebd..f07fe123 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/NumericIndicatorView.swift @@ -44,7 +44,7 @@ open class NumericIndicatorView: CarouselIndicator { open override var isEnabled: Bool { didSet { setViewColor(isEnabled ? indicatorColor : disabledIndicatorColor) } } - + /// Sets the color for pageCount text, left arrow and right arrow. public override var indicatorColor: UIColor { get { super.indicatorColor } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift index f3665eb0..858984b0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift @@ -7,6 +7,7 @@ // import Foundation +import MVMCore public enum CheckboxPosition: String, Codable { case center @@ -16,9 +17,19 @@ public enum CheckboxPosition: String, Codable { @objcMembers public class CheckboxLabelModel: MoleculeModelProtocol { public static var identifier: String = "checkboxLabel" - public var moleculeName: String + public var moleculeName: String = CheckboxLabelModel.identifier public var backgroundColor: Color? public var checkboxAlignment: CheckboxPosition? public var checkbox: CheckboxModel public var label: LabelModel + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(checkbox: CheckboxModel, label: LabelModel) { + self.checkbox = checkbox + self.label = label + } + } diff --git a/MVMCoreUI/Atomic/Atoms/Views/DashLineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/DashLineModel.swift index c4375d53..95cc40fd 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/DashLineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/DashLineModel.swift @@ -7,7 +7,7 @@ // import Foundation - +import MVMCore @objcMembers public class DashLineModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -43,6 +43,12 @@ import Foundation case isHidden } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init() {} + //-------------------------------------------------- // MARK: - codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift new file mode 100644 index 00000000..08b7267e --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabel.swift @@ -0,0 +1,59 @@ +// +// StateLabel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 12/10/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// Subclass of label that helps with different states +public class FormLabel: Label { + //properties used in setting label + private var delegateObject: MVMCoreUIDelegateObject? + private var additionalData: [AnyHashable: Any]? + + //models that drive the label UI + private var formModel: FormLabelModel! + + //public properties + public override var isEnabled: Bool { + didSet{ + self.formModel.enabled = isEnabled + self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData) + } + } + + public var isRequired: Bool = true { + didSet{ + self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData) + } + } + + public override func reset(){ + super.reset() + self.isEnabled = true + } + + /// Used in setting up the Label for use + /// - Parameters: + /// - text: If there is no Model, there should be a TEXT only version of the label that will be applied to the default font / color styles + /// - model: Model takes priority over a text value. The model has its own text value that will be looked at to draw the screen, this model is used for both enabled/disabled models + /// - delegateObject: passed in from the creator + /// - additionalData: passed in from the creator + public func setup(model: FormLabelModel, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?){ + self.additionalData = additionalData + self.delegateObject = delegateObject + self.formModel = model + + //default to enabled state + self.reset() + } + + /// Text change that will update both enabledModel and disabledModel text values + /// - Parameter text: text you want to see + public func set(text: String?){ + self.formModel.set(text: text ?? "") + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift new file mode 100644 index 00000000..c7552047 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/FormLabelModel.swift @@ -0,0 +1,81 @@ +// +// StateLabelModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 12/14/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class FormLabelModel: EnableableModelProtocol { + static let defaultFontStyle = Styler.Font.RegularMicro + static let defaultEnabledTextColor = Color(uiColor: .mvmBlack) + static let defaultDisabledTextColor = Color(uiColor: .mvmCoolGray3) + static let defaultRequiredTextColor = Color(uiColor: .mvmCoolGray6) + + private var enabledModel: LabelModel + private var disabledModel = LabelModel(fontStyle: FormLabelModel.defaultFontStyle, textColor: FormLabelModel.defaultDisabledTextColor) + + //current state + public var enabled: Bool = true + + //model is based on current state + public var model: LabelModel { + return enabled ? enabledModel: disabledModel + } + + //init + public init(model: LabelModel){ + + //ensure the fontStyle exist and if so, + //make sure the disabledModel font matches + //otherwise set it to defaultFontStyle + if let modelFontStyle = model.fontStyle { + self.disabledModel.fontStyle = modelFontStyle + } else { + model.fontStyle = FormLabelModel.defaultFontStyle + } + + //ensure the textColor is set + //otherwise use the defaultEnabledTextColor + if model.textColor == nil { + model.textColor = FormLabelModel.defaultEnabledTextColor + } + + //set the enabledModel to the model passed in + self.enabledModel = model + + //make sure the enabled & disabled text match + self.disabledModel.text = self.enabledModel.text + } + + public init(text: String){ + //create the enabled model + self.enabledModel = LabelModel(fontStyle: FormLabelModel.defaultFontStyle, textColor: FormLabelModel.defaultEnabledTextColor) + self.enabledModel.text = text + + //make sure the enabled & disabled text match + self.disabledModel.text = self.enabledModel.text + } + + public var requiredModel: LabelModel { + let required = LabelModel(fontStyle: model.fontStyle!, textColor: model.textColor!) + if enabled { + required.attributes = [LabelAttributeColorModel(FormLabelModel.defaultRequiredTextColor, model.text.count + 1, 8)] + } + required.text = "\(model.text) Optional" + return required + } + + //methods + public func reset(){ + self.enabled = true + } + + //set text for state + public func set(text:String){ + self.enabledModel.text = text + self.disabledModel.text = text + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 395ab923..c524b7b3 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -238,7 +238,7 @@ public typealias ActionBlock = () -> () case left } - public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject? = nil, _ additionalData: [AnyHashable: Any]? = nil) { clauses = [] text = nil @@ -256,21 +256,19 @@ public typealias ActionBlock = () -> () * appropriately called. * Only other reference found of issue: https://www.thetopsites.net/article/58142205.shtml */ - if #available(iOS 13, *) { - if let attributedText = attributedText, let text = text, !text.isEmpty { - let attributedString = NSMutableAttributedString(string: text) - let range = NSRange(location: 0, length: text.count) - for attribute in attributedText.attributes(at: 0, effectiveRange: nil) { - if attribute.key == .underlineStyle { - attributedString.addAttribute(.underlineStyle, value: 0, range: range) - } - if attribute.key == .strikethroughStyle { - attributedString.addAttribute(.strikethroughStyle, value: 0, range: range) - } + if let attributedText = attributedText, let text = text, !text.isEmpty { + let attributedString = NSMutableAttributedString(string: text) + let range = NSRange(location: 0, length: text.count) + for attribute in attributedText.attributes(at: 0, effectiveRange: nil) { + if attribute.key == .underlineStyle { + attributedString.addAttribute(.underlineStyle, value: 0, range: range) + } + if attribute.key == .strikethroughStyle { + attributedString.addAttribute(.strikethroughStyle, value: 0, range: range) } - - self.attributedText = attributedString } + + self.attributedText = attributedString } hero = labelModel.hero @@ -297,8 +295,8 @@ public typealias ActionBlock = () -> () accessibilityLabel = accessibilityText } - if let fontStyle = labelModel.fontStyle?.rawValue { - MFStyler.styleLabel(self, withStyle: fontStyle, genericScaling: false) + if let fontStyle = labelModel.fontStyle { + fontStyle.styleLabel(self, genericScaling: false) standardFontSize = font.pointSize } else { let fontSize = labelModel.fontSize @@ -430,7 +428,7 @@ public typealias ActionBlock = () -> () label.accessibilityLabel = json?.optionalStringForKey("accessibilityText") if let fontStyle = json?.optionalStringForKey("fontStyle") { - MFStyler.styleLabel(label, withStyle: fontStyle, genericScaling: mvmLabel == nil) + MFStyler.style(label: label, styleString: fontStyle, genericScaling: mvmLabel == nil) mvmLabel?.standardFontSize = label.font.pointSize } else { let fontSize = json?["fontSize"] as? CGFloat @@ -492,7 +490,7 @@ public typealias ActionBlock = () -> () case "font": if let fontStyle = attribute.optionalStringForKey("style") { - let styles = MFStyler.styleGetAttributedString("0", withStyle: fontStyle, genericScaling: mvmLabel == nil) + let styles = MFStyler.getAttributedString(for: "0", styleString: fontStyle, genericScaling: mvmLabel == nil) attributedString.removeAttribute(.font, range: range) attributedString.removeAttribute(.foregroundColor, range: range) attributedString.addAttributes(styles.attributes(at: 0, effectiveRange: nil), range: range) @@ -533,8 +531,7 @@ public typealias ActionBlock = () -> () //------------------------------------------------------ public func setFontStyle(_ fontStyle: Styler.Font, _ scale: Bool = true) { - font = fontStyle.getFont(false) - textColor = .mvmBlack + fontStyle.styleLabel(self, genericScaling: false) setScale(scale) } @@ -584,9 +581,8 @@ public typealias ActionBlock = () -> () /// Will remove the values contained in attributedText. func clearAttributes() { - - guard let labelText = text, - let attributes = attributedText?.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: labelText.count)) + guard let labelText = text, !labelText.isEmpty else { return } + guard let attributes = attributedText?.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: labelText.count)) else { return } let attributedString = NSMutableAttributedString(string: labelText) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift index 94579d02..8fa1aa58 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift @@ -24,10 +24,14 @@ case textColor } + public init(_ textColor: Color?, _ location: Int, _ length: Int){ + self.textColor = textColor + super.init(location, length) + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- - required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift index 72d57e40..6a2a1af5 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift @@ -18,6 +18,14 @@ var name: String? var size: CGFloat? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public override init(_ location: Int, _ length: Int) { + super.init(location, length) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift index 12e8d68d..7396e749 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift @@ -18,6 +18,14 @@ class LabelAttributeImageModel: LabelAttributeModel { var name: String? var URL: String? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public override init(_ location: Int, _ length: Int) { + super.init(location, length) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index 22d33b1f..e36c8d23 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -18,10 +18,6 @@ import UIKit get { return model as? LineModel } } - var lineBackgroundColor: Color? { - return (lineModel?.inverted ?? false) ? lineModel?.backgroundColor_inverted : lineModel?.backgroundColor - } - //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -50,28 +46,36 @@ import UIKit addLine(to: view, edge: edge, useMargin: useMargin) } + public init() { + super.init(frame: .zero) + model = LineModel(type: .secondary) + setStyle(.secondary) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + model = LineModel(type: .secondary) + setStyle(.secondary) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + model = LineModel(type: .secondary) + setStyle(.secondary) + } + + public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.init(model: model, delegateObject, additionalData) + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- open func setStyle(_ style: LineModel.Style) { - - switch style { - case .standard: - updateLineConstraints(constant: 1) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmCoolGray3 - case .thin: - updateLineConstraints(constant: 1) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack - case .medium: - updateLineConstraints(constant: 2) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack - case .heavy: - updateLineConstraints(constant: 4) - backgroundColor = lineModel?.backgroundColor?.uiColor ?? .mvmBlack - case .none: - updateLineConstraints(constant: 0) - } + lineModel?.type = style + backgroundColor = lineModel?.backgroundColor?.uiColor + updateLineConstraints(constant: lineModel?.thickness ?? 1) } open func shouldBeVisible() -> Bool { @@ -90,12 +94,10 @@ import UIKit open override func setupView() { super.setupView() - heightConstraint = heightAnchor.constraint(equalToConstant: 1) heightConstraint?.isActive = true widthConstraint = widthAnchor.constraint(equalToConstant: 1) widthConstraint?.isActive = false - setStyle(.standard) } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { @@ -107,23 +109,11 @@ import UIKit } open override func reset() { - setStyle(.standard) + setStyle(.secondary) } public override static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { - - guard let type = (model as? LineModel)?.type else { return 1 } - - switch type { - case .none: - return 0 - case .medium: - return 2 - case .heavy: - return 4 - default: - return 1 - } + return (model as? LineModel)?.thickness ?? 1 } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift index 0f67b3f0..cc9e7333 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift @@ -7,7 +7,7 @@ // import UIKit - +import VDSColorTokens @objcMembers public class LineModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -30,13 +30,17 @@ import UIKit /** The style of the line: - - standard (1 height, silver) - - thin (1 height, black) + - secondary (1 height, silver) + - primary (1 height, black) + - standard (1 height, silver) - deprecated + - thin (1 height, black) - deprecated - medium (2 height, black) - heavy (4 height, black) - none (hidden) */ public enum Style: String, Codable { + case secondary + case primary case standard case thin case medium @@ -49,13 +53,47 @@ import UIKit //-------------------------------------------------- public static var identifier: String = "line" - public var type: Style = .standard + public var type: Style = .secondary public var frequency: Frequency? = .allExceptTop //TODO: use color insted of backgroundColor. Needs server changes // public var color: Color? - public var backgroundColor: Color? - public var backgroundColor_inverted: Color = Color(uiColor: .mvmWhite) + private var _backgroundColor: Color? + public var backgroundColor: Color? { + get { + if let backgroundColor = _backgroundColor { return backgroundColor } + if inverted { + if type == .secondary || type == .standard { return Color(uiColor: VDSColor.paletteGray20) } + return Color(uiColor: VDSColor.elementsPrimaryOndark) + } + if type == .secondary || type == .standard { return Color(uiColor: VDSColor.paletteGray85) } + return Color(uiColor: VDSColor.elementsPrimaryOnlight) + } + set { + _backgroundColor = newValue + } + } + + private var _thickness: CGFloat? + public var thickness: CGFloat { + get { + if let thickness = _thickness { return thickness } + switch type { + case .heavy: + return 4 + case .medium: + return 2 + case .none: + return 0 + default: + return 1 + } + } + set { + _thickness = newValue + } + } + public var inverted: Bool = false // Use this to show vertical line @@ -90,6 +128,7 @@ import UIKit case frequency case inverted case useVerticalLine + case thickness } //-------------------------------------------------- @@ -111,12 +150,9 @@ import UIKit self.inverted = inverted } - if let backgroundColor_inverted = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor_inverted) { - self.backgroundColor_inverted = backgroundColor_inverted - } - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) useVerticalLine = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalLine) + _thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness) } public func encode(to encoder: Encoder) throws { @@ -125,8 +161,8 @@ import UIKit try container.encode(type, forKey: .type) try container.encode(inverted, forKey: .inverted) try container.encodeIfPresent(frequency, forKey: .frequency) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(backgroundColor_inverted, forKey: .backgroundColor_inverted) + try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(useVerticalLine, forKey: .useVerticalLine) + try container.encodeIfPresent(_thickness, forKey: .thickness) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadingSpinnerModel.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadingSpinnerModel.swift index bdef3c86..ddfba326 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadingSpinnerModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadingSpinnerModel.swift @@ -7,7 +7,7 @@ // import Foundation - +import MVMCore open class LoadingSpinnerModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -32,6 +32,12 @@ open class LoadingSpinnerModel: MoleculeModelProtocol { case diameter } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init() {} + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift index dc404a71..56f4f7ad 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift @@ -5,6 +5,7 @@ // Created by Lekshmi S on 15/09/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import MVMCore open class StarModel: MoleculeModelProtocol { @@ -30,6 +31,12 @@ open class StarModel: MoleculeModelProtocol { case size } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init() {} + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift index 91470100..f79f638e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift @@ -5,6 +5,7 @@ // Created by Lekshmi S on 21/09/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import MVMCore @objcMembers public class StarsModel: MoleculeModelProtocol { @@ -34,6 +35,14 @@ case size } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with stars: [StarModel]) { + self.stars = stars + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift index baee04f6..a378b854 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Video/Video.swift @@ -89,7 +89,7 @@ open class Video: View { }) case .failed: if let errorObject = item.loadFailedError { - MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject) + MVMCoreLoggingHandler.addError(toLog: errorObject) } default: break diff --git a/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideoModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideoModel.swift index 45734dac..44137b96 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideoModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Video/VisibleBehaviorForVideoModel.swift @@ -14,7 +14,7 @@ open class VisibleBehaviorForVideoModel: PageBehaviorModelProtocol { public var shouldAllowMultipleInstances: Bool = true public weak var videoModel: VideoModel? - init(with videoModel: VideoModel) { + public init(with videoModel: VideoModel) { self.videoModel = videoModel } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/WebView.swift b/MVMCoreUI/Atomic/Atoms/Views/WebView.swift index e6708055..d3a91920 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/WebView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/WebView.swift @@ -170,8 +170,11 @@ extension WebView : WKNavigationDelegate { public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { //validate request url //all validated link should be open in safari - if (navigationAction.navigationType == .linkActivated), let urlString = navigationAction.request.url?.absoluteString.removingPercentEncoding, !urlString.contains("#") { - MVMCoreActionHandler.shared()?.openURL(inWebView: navigationAction.request.url, actionInformation: nil, additionalData: nil, delegateObject: nil) + if (navigationAction.navigationType == .linkActivated), + let url = navigationAction.request.url, + let urlString = url.absoluteString.removingPercentEncoding, + !urlString.contains("#") { + MVMCoreUIActionHandler.shared()?.openURL(inSafariWebView: url) decisionHandler(.cancel) } else { decisionHandler(.allow) diff --git a/MVMCoreUI/Atomic/Atoms/Views/WebViewModel.swift b/MVMCoreUI/Atomic/Atoms/Views/WebViewModel.swift index 3e8d4340..4cb2bd55 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/WebViewModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/WebViewModel.swift @@ -7,6 +7,7 @@ // import Foundation +import MVMCore @objcMembers public class WebViewModel: MoleculeModelProtocol { public static var identifier: String = "webview" @@ -30,6 +31,12 @@ import Foundation case borderColor } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init() {} + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Wheel.swift b/MVMCoreUI/Atomic/Atoms/Views/Wheel.swift index dc50f8f3..9ae1335c 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Wheel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Wheel.swift @@ -49,125 +49,28 @@ import UIKit } heightConstraint?.constant = graphObject.diameter - // iOS 12 uses the conic gradient and a mask for simplicity. - if #available(iOS 12, *) { - let gradient = CAGradientLayer() - gradient.type = .conic - gradient.startPoint = CGPoint(x: 0.5, y: 0.5) - gradient.endPoint = CGPoint(x: 0.5, y: 0.0) - gradient.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter) - gradient.colors = graphObject.colors.map({ (color) -> CGColor in - return color.cgColor - }) - gradientLayer = gradient - layer.addSublayer(gradient) - - let center = CGPoint(x: gradient.bounds.midX, y: gradient.bounds.midY) - let radius = (graphObject.diameter - graphObject.lineWidth) / 2.0 - let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: (3 / 2 * .pi), endAngle: -(1 / 2 * .pi), clockwise: false) - let mask = CAShapeLayer() - mask.fillColor = UIColor.clear.cgColor - mask.strokeColor = UIColor.white.cgColor - mask.lineWidth = graphObject.lineWidth - mask.path = path.cgPath - gradient.mask = mask - return - } + let gradient = CAGradientLayer() + gradient.type = .conic + gradient.startPoint = CGPoint(x: 0.5, y: 0.5) + gradient.endPoint = CGPoint(x: 0.5, y: 0.0) + gradient.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter) + gradient.colors = graphObject.colors.map({ (color) -> CGColor in + return color.cgColor + }) + gradientLayer = gradient + layer.addSublayer(gradient) - //create circle path - let radius = graphObject.diameter / 2.0 - - //begin point will be at the bottom, clockwise direction - let path = UIBezierPath(arcCenter: CGPoint(x: radius - , y: radius), radius: radius - graphObject.lineWidth/2.0, startAngle: CGFloat(Wheel.getPiValue(90.0)), endAngle: CGFloat(Wheel.getPiValue(90.0 + 360.0)), clockwise: true) - path.lineWidth = graphObject.lineWidth - - let circleLayer = CAShapeLayer() - circleLayer.path = path.cgPath - circleLayer.lineCap = .round - circleLayer.lineWidth = graphObject.lineWidth - circleLayer.fillColor = UIColor.clear.cgColor - circleLayer.strokeColor = UIColor.black.cgColor - - //create gradient layer - let gradientLayer = createGradientLayer(graphObject) - gradientLayer.mask = circleLayer - layer.addSublayer(gradientLayer) - self.gradientLayer = gradientLayer + let center = CGPoint(x: gradient.bounds.midX, y: gradient.bounds.midY) + let radius = (graphObject.diameter - graphObject.lineWidth) / 2.0 + let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: (3 / 2 * .pi), endAngle: -(1 / 2 * .pi), clockwise: false) + let mask = CAShapeLayer() + mask.fillColor = UIColor.clear.cgColor + mask.strokeColor = UIColor.white.cgColor + mask.lineWidth = graphObject.lineWidth + mask.path = path.cgPath + gradient.mask = mask } -/* - create three gradient layer for circle layout. - _____________ - | → | top layer for smooth gradient - ------------- - | | | - | ↑ | ↓ | - | | | - ------------- -*/ - func createGradientLayer(_ graphObject: WheelModel) -> CALayer { - let containLayer = CALayer() - containLayer.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter) - let radius = graphObject.diameter / 2.0 - - //create graident layers - guard graphObject.colors.count > 1 else { - containLayer.backgroundColor = graphObject.colors.first?.cgColor - return containLayer - } - var topGradientHeight : CGFloat = 0.0 - var leftColors = graphObject.colors.prefix(through: graphObject.colors.count/2) - let rightColors = graphObject.colors.suffix(from: graphObject.colors.count/2) - - // make the top layer higher than line width for smooth look - topGradientHeight = min(max(graphObject.lineWidth, 1.0/(1.0+CGFloat(graphObject.colors.count))*graphObject.diameter), graphObject.diameter) - let topLayer = CAGradientLayer() - topLayer.frame = CGRect(x: 0.0, y: 0.0, width: graphObject.diameter, height: topGradientHeight) - //make the graident edge more smoothy - topLayer.startPoint = CGPoint(x: 0.25, y: 0.0) - topLayer.endPoint = CGPoint(x: 0.75, y: 0.0) - //if number of colors is even, need to display gradient layer, otherwise make top layer as solid color layer - if graphObject.colors.count % 2 == 0 { - leftColors.removeLast() - let firstColor = leftColors.last!.cgColor - let secondColor = rightColors.first!.cgColor - topLayer.colors = [firstColor, secondColor] - } else { - topLayer.backgroundColor = leftColors.last?.cgColor - } - containLayer.addSublayer(topLayer) - - let leftLayer = CAGradientLayer() - leftLayer.frame = CGRect(x: 0, y: topGradientHeight, width: radius, height: graphObject.diameter - topGradientHeight) - leftLayer.startPoint = CGPoint(x: 0, y: 1) - leftLayer.endPoint = CGPoint(x: 0, y: 0) - - //count of graidentLayer.colors must be bigger than 1, otherwise set backgroundColor - if leftColors.count > 1 { - leftLayer.colors = leftColors.map({ (color) -> CGColor in - return color.cgColor - }) - } else { - leftLayer.backgroundColor = leftColors.first?.cgColor - } - containLayer.addSublayer(leftLayer) - - let rightLayer = CAGradientLayer() - rightLayer.frame = CGRect(x: radius, y: topGradientHeight, width: radius, height: graphObject.diameter - topGradientHeight) - rightLayer.startPoint = CGPoint(x: 0, y: 0) - rightLayer.endPoint = CGPoint(x: 0, y: 1) - if rightColors.count > 1 { - rightLayer.colors = rightColors.map({ (color) -> CGColor in - return color.cgColor - }) - } else { - rightLayer.backgroundColor = rightColors.first?.cgColor - } - containLayer.addSublayer(rightLayer) - - return containLayer - } //MARK: MVMCoreUIViewConstrainingProtocol public func needsToBeConstrained() -> Bool { return true diff --git a/MVMCoreUI/Atomic/Extensions/UIImageRenderingMode+Extension.swift b/MVMCoreUI/Atomic/Extensions/UIImageRenderingMode+Extension.swift new file mode 100644 index 00000000..21a1cea2 --- /dev/null +++ b/MVMCoreUI/Atomic/Extensions/UIImageRenderingMode+Extension.swift @@ -0,0 +1,59 @@ +// +// UIImageRenderingMode+Extension.swift +// MVMCoreUI +// +// Created by Nadigadda, Sumanth on 24/03/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation +import UIKit + +enum RenderingModeError: Error { + case notAnRenderingMode +} + +extension UIImage.RenderingMode: RawRepresentable { + + init?(rawValue: String) { + switch rawValue { + case "alwaysOriginal": + self = .alwaysOriginal + case "alwaysTemplate": + self = .alwaysTemplate + case "automatic": + self = .automatic + default: + return nil + } + } + + var rawValueString: String { + switch self { + case .alwaysOriginal: + return "alwaysOriginal" + case .alwaysTemplate: + return "alwaysTemplate" + case .automatic: + return "automatic" + @unknown default: + return "" + } + } +} + +extension UIImage.RenderingMode: Codable { + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.singleValueContainer() + let string = try typeContainer.decode(String.self) + guard let renderingMode = UIImage.RenderingMode(rawValue: string) else { + throw RenderingModeError.notAnRenderingMode + } + self = renderingMode + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValueString) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift new file mode 100644 index 00000000..b48e4ddb --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockup.swift @@ -0,0 +1,123 @@ +// +// TitleLockup.swift +// MVMCoreUI +// +// Created by Nadigadda, Sumanth on 04/05/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +@objcMembers open class TitleLockup: View { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public let eyebrow = Label(fontStyle: .RegularBodySmall) + public let title = Label(fontStyle: .RegularBodySmall) + public let subTitle = Label(fontStyle: .RegularBodySmall) + public lazy var stack: UIStackView = { + let stack = UIStackView(arrangedSubviews: [eyebrow, title, subTitle]) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + return stack + }() + + var castModel: TitleLockupModel? { + get { return model as? TitleLockupModel } + } + + //-------------------------------------------------- + // MARK: - Initialization + //-------------------------------------------------- + + public convenience init() { + self.init(frame: .zero) + } + + //-------------------------------------------------- + // MARK: - MFViewProtocol + //-------------------------------------------------- + + open override func setupView() { + super.setupView() + addSubview(stack) + NSLayoutConstraint.constraintPinSubview(toSuperview: stack) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + stack.updateView(size) + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func reset() { + super.reset() + stack.reset() + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? TitleLockupModel else { return } + stack.setCustomSpacing(model.defaultEyebrowTitleSpacing(), after: eyebrow) + stack.setCustomSpacing(model.defaultTitleSubTitleSpacing(), after: title) + stack.updateContainedMolecules(with: [model.eyebrow, + model.title, + model.subTitle], + delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 65 + } + + //-------------------------------------------------- + // MARK: - Accessibility Helpers + //-------------------------------------------------- + + /// Returns the labels text in one message. + func getAccessibilityMessage() -> String? { + + var message = "" + + if let eyebrowLabel = eyebrow.text { + message += eyebrowLabel + ", " + } + + if let headlineLabel = title.text { + message += headlineLabel + ", " + } + + if let bodyLabel = subTitle.text { + message += bodyLabel + } + + return message.count > 0 ? message : nil + } + + /// Returns an array of the appropriate accessibility elements. + func getAccessibilityElements() -> [Any]? { + + var elements: [UIView] = [] + + if eyebrow.hasText { + elements.append(eyebrow) + } + + if title.hasText { + elements.append(title) + } + + if subTitle.hasText { + elements.append(subTitle) + } + + return elements.count > 0 ? elements : nil + } +} + diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift new file mode 100644 index 00000000..4548207d --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift @@ -0,0 +1,215 @@ +// +// TitleLockupModel.swift +// MVMCoreUI +// +// Created by Nadigadda, Sumanth on 04/05/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import VDSColorTokens + +public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "titleLockup" + public var moleculeName: String = TitleLockupModel.identifier + + public var eyebrow: LabelModel? + public var title: LabelModel + public var subTitle: LabelModel? + + public var alignment: Alignment = .left { + didSet { + ///Updating the text alignment for all labels + if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) { + eyebrow?.textAlignment = textAlignment + title.textAlignment = textAlignment + subTitle?.textAlignment = textAlignment + } + } + } + + public var inverted: Bool = false { + didSet { + ///Updating the text color + eyebrow?.textColor = titleColor + title.textColor = titleColor + subTitle?.textColor = subTitleColor + } + } + + private var _backgroundColor: Color? + public var backgroundColor: Color? { + get { + return inverted ? Color(uiColor: VDSColor.backgroundPrimaryDark) : Color(uiColor: VDSColor.backgroundPrimaryLight) + } + set { + _backgroundColor = newValue + } + } + + public var titleColor: Color? { + return inverted ? Color(uiColor: VDSColor.elementsPrimaryOndark) : Color(uiColor: VDSColor.elementsPrimaryOnlight) + } + + public var subTitleColor: Color? { + return inverted ? Color(uiColor: VDSColor.elementsSecondaryOndark) : Color(uiColor: VDSColor.elementsSecondaryOnlight) + } + + public var children: [MoleculeModelProtocol] { + [eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(eyebrow: LabelModel? = nil, title: LabelModel, subTitle: LabelModel? = nil) throws { + self.eyebrow = eyebrow + self.title = title + self.subTitle = subTitle + updateLabelAttributes() + } + + //-------------------------------------------------- + // MARK: - Enum + //-------------------------------------------------- + + public enum Alignment: String, Codable { + case left + case center + } + + //-------------------------------------------------- + // MARK: - Styling + //-------------------------------------------------- + + /// Returns the default fontStyle for the subtitle, based on the title fontStyle. + func defaultSubtitleFontStyle() -> Styler.Font { + switch title.fontStyle { + case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall: + return .RegularBodyLarge + case .RegularFeatureSmall, .RegularFeatureMedium: + return .RegularTitleLarge + default: + return .RegularBodySmall + } + } + + /// Returns the default spacing between the eyebrow and title, based on the title fontStyle. + func defaultEyebrowTitleSpacing() -> CGFloat { + switch title.fontStyle { + case .RegularTitleXLarge, .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall: + return Padding.Three + case .RegularFeatureMedium: + return subTitle?.fontStyle == .RegularBodyLarge ? Padding.Three : Padding.Four + default: + return Padding.Two + } + } + + /// Returns the default spacing between the title and subTitle, based on the title fontStyle. + func defaultTitleSubTitleSpacing() -> CGFloat { + switch title.fontStyle { + case .RegularTitleXLarge: + return Padding.Three + case .RegularTitle2XLarge, .RegularFeatureXSmall, .RegularFeatureSmall: + return Padding.Four + case .RegularFeatureMedium: + return Padding.Five + default: + return Padding.Two + } + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case eyebrow + case title + case subTitle + case inverted + case alignment + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + title = try typeContainer.decodeMolecule(codingKey: .title) + eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) + subTitle = try typeContainer.decodeMoleculeIfPresent(codingKey: .subTitle) + + if let newAlignment = try typeContainer.decodeIfPresent(Alignment.self, forKey: .alignment) { + alignment = newAlignment + } + + if let invertedStatus = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { + inverted = invertedStatus + } + + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + + updateLabelAttributes() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encodeModel(title, forKey: .title) + try container.encodeIfPresent(subTitle, forKey: .subTitle) + try container.encode(alignment, forKey: .alignment) + try container.encode(inverted, forKey: .inverted) + try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) + } + + //-------------------------------------------------- + // MARK: - Model updates + //-------------------------------------------------- + + private func updateLabelAttributes() { + // If subtitle style is not available, will set font style based on the component + if subTitle?.fontStyle == nil { + subTitle?.fontStyle = defaultSubtitleFontStyle() + } + + // If eyebrow style is not available, will set font style based on the component. Eyebrow and subtitle share the same font size + if eyebrow?.fontStyle == nil { + eyebrow?.fontStyle = subTitle?.fontStyle + } + + // Updating the text color + if eyebrow?.textColor == nil { + eyebrow?.textColor = subTitleColor + } + if title.textColor == nil { + title.textColor = titleColor + } + if subTitle?.textColor == nil { + subTitle?.textColor = subTitleColor + } + + // Updating the text alignment for all labels + if let textAlignment = NSTextAlignment(rawValue: alignment.rawValue) { + if eyebrow?.textAlignment == nil { + eyebrow?.textAlignment = textAlignment + } + if title.textAlignment == nil { + title.textAlignment = textAlignment + } + if subTitle?.textAlignment == nil { + subTitle?.textAlignment = textAlignment + } + } + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnInternationalDataDividerModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnInternationalDataDividerModel.swift index f7df0c3a..7e402ca9 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnInternationalDataDividerModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnInternationalDataDividerModel.swift @@ -23,7 +23,7 @@ public class ListThreeColumnInternationalDataDividerModel: ListItemModel, Molecu // MARK: - Initializers //------------------------------------------------------ - public init (leftLabel: LabelModel, centerLabel: LabelModel, rightLabel: LabelModel) { + public init(leftLabel: LabelModel, centerLabel: LabelModel, rightLabel: LabelModel) { self.leftLabel = leftLabel self.centerLabel = centerLabel self.rightLabel = rightLabel diff --git a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/Header.swift b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/Header.swift index 73b42dea..9d061f76 100644 --- a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/Header.swift +++ b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/Header.swift @@ -33,7 +33,7 @@ open class HeaderView: Container { public override func setupView() { super.setupView() - line.setStyle(.heavy) + line.setStyle(.none) addSubview(line) NSLayoutConstraint.pinViewBottom(toSuperview: line, useMargins: false, constant: 0).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true @@ -43,7 +43,7 @@ open class HeaderView: Container { // MARK: - MoleculeViewProtocol open override func reset() { super.reset() - line.setStyle(.heavy) + line.setStyle(.none) molecule?.reset() } diff --git a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeHeaderView.swift b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeHeaderView.swift index 9e4afed6..4e999997 100644 --- a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeHeaderView.swift +++ b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeHeaderView.swift @@ -31,7 +31,7 @@ public class MoleculeHeaderView: MoleculeContainer { public override func setupView() { super.setupView() - line.setStyle(.heavy) + line.setStyle(.none) addSubview(line) NSLayoutConstraint.pinViewBottom(toSuperview: line, useMargins: false, constant: 0).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true @@ -40,7 +40,7 @@ public class MoleculeHeaderView: MoleculeContainer { open override func reset() { super.reset() - line.setStyle(.heavy) + line.setStyle(.none) } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeader.swift b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeader.swift index ee80ca46..27bee7b3 100644 --- a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeader.swift +++ b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeader.swift @@ -22,7 +22,7 @@ import Foundation public override func setupView() { super.setupView() - line.setStyle(.thin) + line.setStyle(.primary) contentView.addSubview(line) NSLayoutConstraint.pinViewBottom(toSuperview: line, useMargins: false, constant: 0).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true @@ -51,7 +51,7 @@ import Foundation open override func reset() { super.reset() - line.setStyle(.thin) + line.setStyle(.primary) molecule?.reset() } diff --git a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeaderModel.swift b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeaderModel.swift index 18360b30..12201722 100644 --- a/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeaderModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HeadersAndFooters/MoleculeSectionHeaderModel.swift @@ -14,6 +14,13 @@ public class override var identifier: String { "sectionHeader" } public var line: LineModel? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public override init(with moleculeModel: MoleculeModelProtocol) { + super.init(with: moleculeModel) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -42,7 +49,7 @@ bottomPadding = PaddingDefaultVerticalSpacing3 } if line == nil { - line = LineModel(type: .thin) + line = LineModel(type: .primary) } } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift index 014030ce..69bca3fc 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/RadioButtonLabelModel.swift @@ -7,7 +7,7 @@ // import Foundation - +import MVMCore @objcMembers public class RadioButtonLabelModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -16,7 +16,17 @@ import Foundation public static var identifier: String = "radioButtonLabel" public var backgroundColor: Color? - public var moleculeName: String + public var moleculeName: String = RadioButtonLabelModel.identifier public var radioButton: RadioButtonModel public var label: LabelModel + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(radioButton: RadioButtonModel, label: LabelModel) { + self.radioButton = radioButton + self.label = label + } + } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift index 25bfa988..527ed054 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBar.swift @@ -5,7 +5,7 @@ // Created by Scott Pfeil on 5/28/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import VDSColorTokens @objcMembers open class TabBar: UITabBar, MoleculeViewProtocol, TabBarProtocol, UITabBarDelegate { @@ -21,7 +21,6 @@ delegate = self translatesAutoresizingMaskIntoConstraints = false line.addLine(to: self, edge: .top, useMargin: false) - line.backgroundColor = .mvmCoolGray3 set(with: model, delegateObject, additionalData) } @@ -34,47 +33,36 @@ self.model = model // Set appearance - if #available(iOS 13.0, *) { - let appearance = UITabBarAppearance() - appearance.backgroundColor = model.backgroundColor?.uiColor - set(tabItemAppearance: appearance.stackedLayoutAppearance, model: model) - set(tabItemAppearance: appearance.inlineLayoutAppearance, model: model) - set(tabItemAppearance: appearance.compactInlineLayoutAppearance, model: model) - standardAppearance = appearance - } else { - // Fallback on earlier versions - backgroundColor = model.backgroundColor?.uiColor - tintColor = model.selectedColor.uiColor - unselectedItemTintColor = model.unSelectedColor.uiColor - barTintColor = model.backgroundColor?.uiColor - isTranslucent = false - } + let appearance = UITabBarAppearance() + appearance.backgroundColor = model.backgroundColor?.uiColor + set(tabItemAppearance: appearance.stackedLayoutAppearance, model: model) + set(tabItemAppearance: appearance.inlineLayoutAppearance, model: model) + set(tabItemAppearance: appearance.compactInlineLayoutAppearance, model: model) + standardAppearance = appearance // Add buttons var tabs: [UITabBarItem] = [] for (index, tab) in model.tabs.enumerated() { let tabBarItem = UITabBarItem(title: tab.title, image: MVMCoreCache.shared()?.getImageFromRegisteredBundles(tab.image), tag: index) tabBarItem.accessibilityLabel = tab.accessibilityText - if #available(iOS 13.0, *) { - } else { - tabBarItem.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -3) - tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font: MFFonts.mfFontTXRegular(8)], for: .normal) - } tabs.append(tabBarItem) } setItems(tabs, animated: false) selectedItem = tabs[model.selectedTab] + + guard let lineModel = line.lineModel else { return } + lineModel.inverted = model.style == .dark + line.set(with: lineModel, delegateObject, additionalData) } /// Sets the item colors. - @available(iOS 13.0, *) private func set(tabItemAppearance: UITabBarItemAppearance, model: TabBarModel) { tabItemAppearance.normal.iconColor = model.unSelectedColor.uiColor - tabItemAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor: model.unSelectedColor.uiColor, NSAttributedString.Key.font: MFFonts.mfFontTXRegular(8)] + tabItemAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor: model.unSelectedColor.uiColor, NSAttributedString.Key.font: MFFonts.mfFontTXBold(10)] tabItemAppearance.normal.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -3) tabItemAppearance.selected.iconColor = model.selectedColor.uiColor - tabItemAppearance.selected.titleTextAttributes = [NSAttributedString.Key.foregroundColor: model.selectedColor.uiColor, NSAttributedString.Key.font: MFFonts.mfFontTXRegular(8)] + tabItemAppearance.selected.titleTextAttributes = [NSAttributedString.Key.foregroundColor: model.selectedColor.uiColor, NSAttributedString.Key.font: MFFonts.mfFontTXBold(10)] tabItemAppearance.selected.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -3) } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift index fc3e9449..3b62210c 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift @@ -7,16 +7,55 @@ // import Foundation +import VDSColorTokens -public class TabBarModel: MoleculeModelProtocol { +open class TabBarModel: MoleculeModelProtocol { public static var identifier: String = "tabBar" - public var backgroundColor: Color? = Color(uiColor: .white) - public var tabs: [TabBarItemModel] - public var selectedColor = Color(uiColor: .mvmBlack) - public var unSelectedColor = Color(uiColor: .mvmCoolGray6) + open var tabs: [TabBarItemModel] + + private var _backgroundColor: Color? + open var backgroundColor: Color? { + get { + if let backgroundColor = _backgroundColor { return backgroundColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.backgroundPrimaryDark) } + return Color(uiColor: VDSColor.backgroundPrimaryLight) + } + set { + _backgroundColor = newValue + } + } + + private var _selectedColor: Color? + open var selectedColor: Color { + get { + if let selectedColor = _selectedColor { return selectedColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.elementsPrimaryOndark) } + return Color(uiColor: VDSColor.elementsPrimaryOnlight) + } + set { + _selectedColor = newValue + } + } + + private var _unSelectedColor: Color? + open var unSelectedColor: Color { + get { + if let unSelectedColor = _unSelectedColor { return unSelectedColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.elementsSecondaryOndark) } + return Color(uiColor: VDSColor.elementsSecondaryOnlight) + } + set { + _unSelectedColor = newValue + } + } + + open var style: NavigationItemStyle? = .dark // Must be capped to 0...(tabs.count - 1) - public var selectedTab: Int = 0 + open var selectedTab: Int = 0 private enum CodingKeys: String, CodingKey { case moleculeName @@ -25,6 +64,7 @@ public class TabBarModel: MoleculeModelProtocol { case selectedColor case unSelectedColor case selectedTab + case style } public init(with tabs: [TabBarItemModel]) { @@ -46,24 +86,28 @@ public class TabBarModel: MoleculeModelProtocol { if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedTab) { selectedTab = index } + if let navStyle = try typeContainer.decodeIfPresent(NavigationItemStyle.self, forKey: .style) { + style = navStyle + } } - public func encode(to encoder: Encoder) throws { + open func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(tabs, forKey: .tabs) - try container.encode(backgroundColor, forKey: .backgroundColor) - try container.encode(selectedColor, forKey: .selectedColor) - try container.encode(unSelectedColor, forKey: .unSelectedColor) + try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(_selectedColor, forKey: .selectedColor) + try container.encodeIfPresent(_unSelectedColor, forKey: .unSelectedColor) try container.encode(selectedTab, forKey: .selectedTab) + try container.encodeIfPresent(style, forKey: .style) } } -public class TabBarItemModel: Codable { - var title: String? - var image: String - var action: ActionModelProtocol - var accessibilityText: String? +open class TabBarItemModel: Codable { + open var title: String? + open var image: String + open var action: ActionModelProtocol + open var accessibilityText: String? private enum CodingKeys: String, CodingKey { case title @@ -86,7 +130,7 @@ public class TabBarItemModel: Codable { accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) } - public func encode(to encoder: Encoder) throws { + open func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(title, forKey: .title) try container.encode(image, forKey: .image) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift index 139a3d06..4e424549 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift @@ -7,6 +7,7 @@ // import UIKit +import VDSColorTokens @objc public protocol TabsDelegate { func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool @@ -15,7 +16,6 @@ import UIKit @objcMembers open class Tabs: View, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol { - public var tabsModel: TabsModel? { get { return model as? TabsModel } } @@ -24,7 +24,7 @@ import UIKit var additionalData: [AnyHashable: Any]? let layout = UICollectionViewFlowLayout() - public var collectionView: UICollectionView? + public var collectionView: CollectionView? let bottomScrollView = UIScrollView(frame: .zero) let bottomContentView = View() @@ -39,17 +39,15 @@ import UIKit weak public var delegate: TabsDelegate? //control var - public var heightConstraint: NSLayoutConstraint? public var selectedIndex: Int = 0 public var paddingBeforeFirstTab: Bool = true //constant let TabCellId = "TabCell" - public let sectionPadding: CGFloat = 20.0 - public let cellSpacing: CGFloat = 34.0 - public let cellHeight: CGFloat = 27.0 + public let itemSpacing: CGFloat = 20.0 + public let cellHeight: CGFloat = 28.0 public let selectionLineHeight: CGFloat = 4.0 - public let selectionLineWidth: CGFloat = 32.0 + public let minimumItemWidth: CGFloat = 32.0 public let selectionLineMovingTime: TimeInterval = 0.2 //------------------------------------------------- @@ -64,11 +62,12 @@ import UIKit open override func updateView(_ size: CGFloat) { super.updateView(size) + collectionView?.updateView(size) } open override func setupView() { super.setupView() - backgroundColor = .white + backgroundColor = VDSColor.backgroundPrimaryLight addSubview(bottomLine) setupCollectionView() setupSelectionLine() @@ -77,9 +76,9 @@ import UIKit func setupCollectionView () { layout.scrollDirection = .horizontal - layout.minimumLineSpacing = cellSpacing + layout.minimumLineSpacing = 0 - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + let collectionView = CollectionView(frame: .zero, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.register(TabItemCell.self, forCellWithReuseIdentifier: TabCellId) collectionView.backgroundColor = .clear @@ -96,7 +95,7 @@ import UIKit bottomScrollView.delegate = self addSubview(bottomScrollView) bottomScrollView.addSubview(bottomContentView) - selectionLine.backgroundColor = .mvmRed + selectionLine.backgroundColor = VDSColor.paletteRed bottomContentView.addSubview(selectionLine) bringSubviewToFront(bottomScrollView) } @@ -114,7 +113,7 @@ import UIKit selectionLine.heightAnchor.constraint(equalToConstant: selectionLineHeight).isActive = true selectionLineLeftConstraint = selectionLine.leftAnchor.constraint(equalTo: bottomContentView.leftAnchor) selectionLineLeftConstraint?.isActive = true - selectionLineWidthConstraint = selectionLine.widthAnchor.constraint(equalToConstant: selectionLineWidth) + selectionLineWidthConstraint = selectionLine.widthAnchor.constraint(equalToConstant: minimumItemWidth) selectionLineWidthConstraint?.isActive = true NSLayoutConstraint.constraintPinSubview(toSuperview: bottomContentView) @@ -155,8 +154,7 @@ import UIKit self.delegateObject = delegateObject self.additionalData = additionalData selectedIndex = tabsModel?.selectedIndex ?? 0 - // TODO: Commented out until we have model support for bar color. Should also do unselected color. - //selectionLine.backgroundColor = tabsModel?.selectedColor.uiColor + selectionLine.backgroundColor = tabsModel?.selectedBarColor.uiColor reloadData() } } @@ -171,10 +169,11 @@ extension Tabs: UICollectionViewDataSource { } public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let labelModel = tabsModel?.tabs[indexPath.row].label, let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TabCellId, for: indexPath) as? TabItemCell else { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TabCellId, for: indexPath) as? TabItemCell else { return UICollectionViewCell() } - cell.updateCell(labelModel: labelModel, indexPath: indexPath, delegateObject: delegateObject, additionalData: additionalData, selected: indexPath.row == selectedIndex, tabsModel: tabsModel) + cell.updateCell(indexPath: indexPath, delegateObject: delegateObject, additionalData: additionalData, selected: indexPath.row == selectedIndex, tabsModel: tabsModel) + updateView(collectionView.bounds.width) return cell } } @@ -182,10 +181,16 @@ extension Tabs: UICollectionViewDataSource { extension Tabs: UICollectionViewDelegateFlowLayout { public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + guard self.collectionView(collectionView, numberOfItemsInSection: indexPath.section) != 2 else { + // If two tabs, take up the screen + let insets = self.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: indexPath.section) + let width = (collectionView.bounds.width / 2.0) - insets.left - insets.right + return CGSize(width: width, height: cellHeight) + } guard let labelModel = tabsModel?.tabs[indexPath.row].label else { return .zero } - return CGSize(width: getLabelWidth(labelModel).width, height: cellHeight) + return CGSize(width: max(minimumItemWidth, getLabelWidth(labelModel).width), height: cellHeight) } //pre calculate the width of the collection cell @@ -200,7 +205,7 @@ extension Tabs: UICollectionViewDelegateFlowLayout { public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { guard section == 0 else { - return UIEdgeInsets(top: 0, left: sectionPadding, bottom: 0, right: 0) + return UIEdgeInsets(top: 0, left: itemSpacing, bottom: 0, right: 0) } guard paddingBeforeFirstTab else { return .zero @@ -209,7 +214,11 @@ extension Tabs: UICollectionViewDelegateFlowLayout { } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - return sectionPadding + // If two tabs, take up the screen, no space between items + guard self.collectionView(collectionView, numberOfItemsInSection: section) != 2 else { + return 0 + } + return itemSpacing } public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { @@ -237,7 +246,7 @@ extension Tabs: UICollectionViewDelegateFlowLayout { guard let collect = collectionView, tabsModel?.tabs.count ?? 0 > 0 else { return } collect.selectItem(at: indexPath, animated: animated, scrollPosition: .centeredHorizontally) - guard let tabCell = collect.cellForItem(at: indexPath) as? TabItemCell, let tabsModel = self.tabsModel else { return } + guard let tabCell = collect.cellForItem(at: indexPath) as? TabItemCell, let tabsModel = tabsModel else { return } moveSelectionLine(toIndex: indexPath, animated: animated, cell: tabCell) tabCell.label.textColor = tabsModel.selectedColor.uiColor tabCell.updateAccessibility(indexPath: indexPath, selected: true, tabsModel: tabsModel) @@ -270,15 +279,13 @@ extension Tabs: UIScrollViewDelegate { //------------------------------------------------- extension Tabs { func moveSelectionLine(toIndex indexPath: IndexPath, animated: Bool, cell: TabItemCell) { - guard let collect = self.collectionView else {return} + guard let collect = collectionView else {return} let size = collectionView(collect, layout: layout, sizeForItemAt: indexPath) - let barWidth = max(size.width, selectionLineWidth) let animationBlock = { [weak self] in - let x = cell.frame.origin.x - self?.selectionLineWidthConstraint?.constant = barWidth - self?.selectionLineLeftConstraint?.constant = x + (size.width - barWidth) / 2.0 + self?.selectionLineWidthConstraint?.constant = size.width + self?.selectionLineLeftConstraint?.constant = cell.frame.origin.x self?.bottomContentView.layoutIfNeeded() } if animated { @@ -287,11 +294,31 @@ extension Tabs { animationBlock() } } + + /// Adjust the line based on the percentage + func progress(from index: Int, toIndex: Int, percentage: CGFloat) { + let fromIndexPath = IndexPath(row: index, section: 0) + let toIndexPath = IndexPath(row: toIndex, section: 0) + guard let collection = collectionView, + let fromCell = collection.cellForItem(at: fromIndexPath), + let toCell = collection.cellForItem(at: toIndexPath) else { return } + + // setting the width for percentage + selectionLineWidthConstraint?.constant = (toCell.bounds.width - fromCell.bounds.width) * percentage + fromCell.bounds.width + + // setting the x for percentage + let originalX = fromCell.frame.origin.x + let toX = toCell.frame.origin.x + let xDifference = toX - originalX + let finalX = (xDifference * percentage) + originalX + selectionLineLeftConstraint?.constant = finalX + + bottomContentView.layoutIfNeeded() + } } @objcMembers public class TabItemCell: CollectionViewCell { public let label = Label() - public var labelModel: LabelModel? public override func setupView() { super.setupView() @@ -300,14 +327,19 @@ extension Tabs { label.baselineAdjustment = .alignCenters } - public func updateCell(labelModel: LabelModel, indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, selected: Bool, tabsModel: TabsModel?) { + public override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + } + + public func updateCell(indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, selected: Bool, tabsModel: TabsModel?) { + guard let tabsModel = tabsModel else { return } label.reset() - label.set(with: labelModel, delegateObject, additionalData) - self.labelModel = labelModel + label.set(with: tabsModel.tabs[indexPath.row].label, delegateObject, additionalData) if selected { - label.textColor = tabsModel?.selectedColor.uiColor ?? .black + label.textColor = tabsModel.selectedColor.uiColor } else { - label.textColor = .mvmCoolGray6 + label.textColor = tabsModel.unselectedColor.uiColor } updateAccessibility(indexPath: indexPath, selected: selected, tabsModel: tabsModel) } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift index 58990428..6e154895 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift @@ -7,22 +7,78 @@ // import UIKit +import VDSColorTokens -public class TabsModel: MoleculeModelProtocol { +open class TabsModel: MoleculeModelProtocol { public static var identifier: String = "tabs" - public var backgroundColor: Color? - public var tabs: [TabItemModel] - public var selectedColor = Color(uiColor: .black) + open var tabs: [TabItemModel] + + open var style: NavigationItemStyle? + + private var _backgroundColor: Color? + open var backgroundColor: Color? { + get { + if let backgroundColor = _backgroundColor { return backgroundColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.backgroundPrimaryDark) } + return Color(uiColor: VDSColor.backgroundPrimaryLight) + } + set { + _backgroundColor = newValue + } + } + + private var _selectedColor: Color? + open var selectedColor: Color { + get { + if let selectedColor = _selectedColor { return selectedColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.elementsPrimaryOndark) } + return Color(uiColor: VDSColor.elementsPrimaryOnlight) + } + set { + _selectedColor = newValue + } + } + + private var _unselectedColor: Color? + open var unselectedColor: Color { + get { + if let unselectedColor = _unselectedColor { return unselectedColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.elementsSecondaryOndark) } + return Color(uiColor: VDSColor.elementsSecondaryOnlight) + } + set { + _unselectedColor = newValue + } + } + + private var _selectedBarColor: Color? + open var selectedBarColor: Color { + get { + if let selectedBarColor = _selectedBarColor { return selectedBarColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.elementsPrimaryOndark) } + return Color(uiColor: VDSColor.paletteRed) + } + set { + _selectedBarColor = newValue + } + } // Must be capped to 0...(tabs.count - 1) - public var selectedIndex: Int = 0 + open var selectedIndex: Int = 0 private enum CodingKeys: String, CodingKey { + case moleculeName case tabs case backgroundColor case selectedColor + case unselectedColor + case selectedBarColor case selectedIndex - case moleculeName + case style } public init(with tabs: [TabItemModel]) { @@ -33,31 +89,35 @@ public class TabsModel: MoleculeModelProtocol { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) tabs = try typeContainer.decode([TabItemModel].self, forKey: .tabs) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor) { - selectedColor = color - } + _selectedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor) + _unselectedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .unselectedColor) + _selectedBarColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedBarColor) + style = try typeContainer.decodeIfPresent(NavigationItemStyle.self, forKey: .style) if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) { selectedIndex = index } } - public func encode(to encoder: Encoder) throws { + open func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(tabs, forKey: .tabs) - try container.encode(backgroundColor, forKey: .backgroundColor) - try container.encode(selectedColor, forKey: .selectedColor) + try container.encodeIfPresent(_backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(_selectedColor, forKey: .selectedColor) + try container.encodeIfPresent(_unselectedColor, forKey: .unselectedColor) + try container.encodeIfPresent(_selectedBarColor, forKey: .selectedBarColor) try container.encode(selectedIndex, forKey: .selectedIndex) + try container.encodeIfPresent(style, forKey: .style) } } -public class TabItemModel: Codable { - var label: LabelModel - var action: ActionModelProtocol? +open class TabItemModel: Codable { + open var label: LabelModel + open var action: ActionModelProtocol? - init(label: LabelModel) { + public init(label: LabelModel) { self.label = label } @@ -66,14 +126,26 @@ public class TabItemModel: Codable { case action } + open func setDefaults() { + if label.textAlignment == nil { + label.textAlignment = .center + } + } + + public init(with label: LabelModel, action: ActionModelProtocol?) { + self.label = label + self.action = action + setDefaults() + } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) label = try typeContainer.decode(LabelModel.self, forKey: .label) action = try typeContainer.decodeModelIfPresent(codingKey: .action) + setDefaults() } - public func encode(to encoder: Encoder) throws { + 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) diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift index 4d843e6a..718cfc30 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -50,13 +50,17 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol { required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - primaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .primaryButton) - if primaryButton?.style == nil { - primaryButton?.style = .primary + + //set context value for 'primary' style to be set for the primaryButton in case the + //property is not returned in the JSON and once decoded, this value is removed from the context + try decoder.setContext(value: Styler.Button.Style.primary, for: "style") { + self.primaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .primaryButton) } - secondaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .secondaryButton) - if secondaryButton?.style == nil { - secondaryButton?.style = .secondary + + //set context value for 'secondary' style to be set for the primaryButton in case the + //property is not returned in the JSON and once decoded, this value is removed from the context + try decoder.setContext(value: Styler.Button.Style.secondary, for: "style") { + self.secondaryButton = try typeContainer.decodeMoleculeIfPresent(codingKey: .secondaryButton) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift index b4f39881..a864d1c2 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift @@ -42,6 +42,15 @@ class AccordionListItemModel: MoleculeListItemModel { hideArrow = true } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with moleculeModel: MoleculeModelProtocol, molecules:[ListItemModelProtocol & MoleculeModelProtocol]) { + self.molecules = molecules + super.init(with: moleculeModel) + } + //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index 1d55cec3..9858190d 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -8,6 +8,7 @@ @objcMembers open class CarouselItemModel: MoleculeCollectionItemModel, CarouselItemModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -18,8 +19,16 @@ public var peakingArrowColor: Color? public var analyticsData: JSONValueDictionary? public var fieldValue: String? + public var enabled: Bool = true + public var readOnly: Bool = false + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? - public func formFieldValue() -> AnyHashable? { fieldValue } + public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + return fieldValue + } //-------------------------------------------------- // MARK: - Keys @@ -30,6 +39,10 @@ case peakingArrowColor case analyticsData case fieldValue + case fieldKey + case groupName + case enabled + case readOnly } //-------------------------------------------------- @@ -42,6 +55,17 @@ peakingArrowColor = try typeContainer.decodeIfPresent(Color.self, forKey: .peakingArrowColor) analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + self.groupName = groupName + } + baseValue = fieldValue + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) { + self.readOnly = readOnly + } try super.init(from: decoder) } @@ -52,5 +76,7 @@ try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) try container.encodeIfPresent(analyticsData, forKey: .analyticsData) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImageModel.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImageModel.swift index 4cd2db26..7c8bd202 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImageModel.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ActionDetailWithImageModel.swift @@ -14,4 +14,14 @@ public class ActionDetailWithImageModel: MoleculeModelProtocol { public var backgroundColor: Color? public var headlineBodyButton: HeadlineBodyButtonModel public var image: ImageViewModel + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(headlineBodyButton: HeadlineBodyButtonModel, image: ImageViewModel) { + self.headlineBodyButton = headlineBodyButton + self.image = image + } + } diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift index 5c910298..aabd29e7 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift @@ -7,6 +7,7 @@ // import UIKit +import MVMCore public class CornerLabelsModel: ParentMoleculeModelProtocol { public static var identifier: String = "cornerLabels" @@ -20,7 +21,7 @@ public class CornerLabelsModel: ParentMoleculeModelProtocol { [molecule, topLeftLabel, topRightLabel, bottomLeftLabel, bottomRightLabel].compactMap { $0 } } - init(with molecule: MoleculeModelProtocol?) { + public init(with molecule: MoleculeModelProtocol?) { self.molecule = molecule } diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggleModel.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggleModel.swift index 29e7a609..ee0c5f78 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggleModel.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggleModel.swift @@ -7,6 +7,7 @@ // import Foundation +import MVMCore public class LabelToggleModel: MoleculeModelProtocol { public static var identifier: String = "labelToggle" @@ -15,7 +16,7 @@ public class LabelToggleModel: MoleculeModelProtocol { public var label: LabelModel public var toggle: ToggleModel - init(_ label: LabelModel, _ toggle: ToggleModel) { + public init(_ label: LabelModel, _ toggle: ToggleModel) { self.label = label self.toggle = toggle } diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift index 8ba75d1a..f0b0bbc0 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/Buttons/NavigationImageButtonModel.swift @@ -5,6 +5,8 @@ // Created by Scott Pfeil on 5/18/20. // +import UIKit + public class NavigationImageButtonModel: NavigationButtonModelProtocol, MoleculeModelProtocol { //-------------------------------------------------- @@ -17,6 +19,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule public var image: String public var action: ActionModelProtocol public var accessibilityText: String? + public var imageRenderingMode: UIImage.RenderingMode = .automatic //-------------------------------------------------- // MARK: - Initializer @@ -37,6 +40,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule case accessibilityIdentifier case moleculeName case accessibilityText + case imageRenderingMode } //-------------------------------------------------- @@ -49,6 +53,9 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule image = try typeContainer.decode(String.self, forKey: .image) action = try typeContainer.decodeModel(codingKey: .action) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + if let mode = try typeContainer.decodeIfPresent(UIImage.RenderingMode.self, forKey: .imageRenderingMode) { + imageRenderingMode = mode + } } open func encode(to encoder: Encoder) throws { @@ -58,6 +65,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule try container.encode(moleculeName, forKey: .moleculeName) try container.encodeModel(action, forKey: .action) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) + try container.encodeIfPresent(imageRenderingMode, forKey: .imageRenderingMode) } //-------------------------------------------------- @@ -66,7 +74,7 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule /// Convenience function that creates a BarButtonItem for the model. public func createNavigationItemButton(delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> UIBarButtonItem { - let uiImage = MVMCoreCache.shared()?.getImageFromRegisteredBundles(image) + let uiImage = MVMCoreCache.shared()?.getImageFromRegisteredBundles(image)?.withRenderingMode(imageRenderingMode) let buttonItem = ImageBarButtonItem.create(with: uiImage, model: self, delegateObject: delegateObject, additionalData: additionalData) buttonItem.accessibilityIdentifier = accessibilityIdentifier ?? image if let accessibilityString = accessibilityText { diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift index 6a75718b..64b61480 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift @@ -6,6 +6,12 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // +import VDSColorTokens + +public enum NavigationItemStyle: String, Codable { + case light + case dark +} open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtocol { //-------------------------------------------------- @@ -14,12 +20,39 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc open class var identifier: String { "navigationBar" } + private let defaultHidesSystemBackButton = true + open var title: String? - open var hidden: Bool - open var backgroundColor: Color? - open var tintColor: Color - open var line: LineModel? + open var hidden = false + open var line: LineModel? = LineModel(type: .secondary) open var hidesSystemBackButton = true + open var style: NavigationItemStyle? + + private var _backgroundColor: Color? + open var backgroundColor: Color? { + get { + if let backgroundColor = _backgroundColor { return backgroundColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.backgroundPrimaryDark) } + return Color(uiColor: VDSColor.backgroundPrimaryLight) + } + set { + _backgroundColor = newValue + } + } + + private var _tintColor: Color? + open var tintColor: Color { + get { + if let tintColor = _tintColor { return tintColor } + if let style = style, + style == .dark { return Color(uiColor: VDSColor.elementsPrimaryOndark) } + return Color(uiColor: VDSColor.elementsPrimaryOnlight) + } + set { + _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? @@ -28,17 +61,13 @@ 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) //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public init() { - hidden = false - backgroundColor = Color(uiColor: .mvmWhite) - tintColor = Color(uiColor: .mvmBlack) - line = LineModel(type: .standard) - } + public init() {} //-------------------------------------------------- // MARK: - Keys @@ -51,11 +80,14 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc case backgroundColor case tintColor case line + case hidesSystemBackButton case alwaysShowBackButton case backButton case additionalLeftButtons case additionalRightButtons case titleView + case style + case titleOffset } //-------------------------------------------------- @@ -65,15 +97,27 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) title = try typeContainer.decodeIfPresent(String.self, forKey: .title) - hidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidden) ?? false - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) ?? Color(uiColor: .mvmWhite) - tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor) ?? Color(uiColor: .mvmBlack) - line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) ?? LineModel(type: .standard) + if let hidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidden) { + self.hidden = 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 + } 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 } open func encode(to encoder: Encoder) throws { @@ -81,14 +125,17 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc 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.encode(tintColor, forKey: .tintColor) + 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(alwaysShowBackButton, forKey: .alwaysShowBackButton) try container.encodeModelIfPresent(backButton, forKey: .backButton) try container.encodeModelsIfPresent(additionalLeftButtons, forKey: .additionalLeftButtons) try container.encodeModelsIfPresent(additionalRightButtons, forKey: .additionalRightButtons) try container.encodeModelIfPresent(titleView, forKey: .titleView) + try container.encodeIfPresent(style, forKey: .style) + try container.encodeIfPresent(titleOffset, forKey: .titleOffset) } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift index 7c4215c2..99ee1567 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift @@ -22,7 +22,7 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc // MARK: - Initializer //-------------------------------------------------- - init(headlineBody: HeadlineBodyModel, image: ImageViewModel) { + public init(headlineBody: HeadlineBodyModel, image: ImageViewModel) { self.headlineBody = headlineBody self.image = image super.init() diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift index 43c22dcc..ae0d22c4 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -54,27 +54,27 @@ open class HeadlineBody: View { } public func styleLandingPageHeader() { - headlineLabel.setFontStyle(.Title2XLarge) - messageLabel.setFontStyle(.RegularBodySmall) - spaceBetweenLabelsConstant = Padding.Two + headlineLabel.setFontStyle(.RegularTitle2XLarge) + messageLabel.setFontStyle(.RegularTitleMedium) + spaceBetweenLabelsConstant = Padding.Four } public func stylePageHeader() { - headlineLabel.setFontStyle(.BoldTitleLarge) + headlineLabel.setFontStyle(.RegularTitleLarge) + messageLabel.setFontStyle(.RegularBodyLarge) + spaceBetweenLabelsConstant = Padding.Two + } + + public func styleListItem() { + headlineLabel.setFontStyle(.RegularTitleSmall) messageLabel.setFontStyle(.RegularBodySmall) spaceBetweenLabelsConstant = Padding.One } - public func styleListItem() { - headlineLabel.setFontStyle(.BoldBodySmall) - messageLabel.setFontStyle(.RegularBodySmall) - spaceBetweenLabelsConstant = 0 - } - public func styleListItemDivider() { - headlineLabel.setFontStyle(.BoldTitleMedium) + headlineLabel.setFontStyle(.BoldTitleSmall) messageLabel.setFontStyle(.RegularBodySmall) - spaceBetweenLabelsConstant = 0 + spaceBetweenLabelsConstant = Padding.Two } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift index cff8be23..70a08d3b 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift @@ -5,7 +5,7 @@ // Created by Scott Pfeil on 1/22/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import MVMCore public class HeadlineBodyButtonModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -20,6 +20,16 @@ public class HeadlineBodyButtonModel: MoleculeModelProtocol { public var button: ButtonModel public var buttonHeadlinePadding: CGFloat + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(headlineBody: HeadlineBodyModel, button: ButtonModel, buttonHeadlinePadding: CGFloat) { + self.headlineBody = headlineBody + self.button = button + self.buttonHeadlinePadding = buttonHeadlinePadding + } + //-------------------------------------------------- // MARK: - Method //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift index 2a3d844a..5d7752fe 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift @@ -22,7 +22,7 @@ public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProto // MARK: - Initializer //-------------------------------------------------- - init(headlineBody: HeadlineBodyModel, image: ImageViewModel) { + public init(headlineBody: HeadlineBodyModel, image: ImageViewModel) { self.headlineBody = headlineBody self.image = image super.init() diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift index 874868e5..00098d91 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift @@ -20,6 +20,15 @@ import Foundation case list case numberColor } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(numberColor: Color, molecules: [StackItemModelProtocol & MoleculeModelProtocol], axis: NSLayoutConstraint.Axis? = nil, spacing: CGFloat? = nil) { + self.numberColor = numberColor + super.init(molecules: molecules, axis: axis, spacing: spacing) + } // Numbered list model comes in the from of list = [MoleculeModelProtocol] public required init(from decoder: Decoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift index 4013f3aa..3bff1575 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift @@ -22,7 +22,17 @@ import Foundation case bulletChar case bulletColor } - + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(bulletChar: String, bulletColor: Color, molecules: [StackItemModelProtocol & MoleculeModelProtocol], axis: NSLayoutConstraint.Axis? = nil, spacing: CGFloat? = nil) { + self.bulletChar = bulletChar + self.bulletColor = bulletColor + super.init(molecules: molecules, axis: axis, spacing: spacing) + } + // Numbered list model comes in the from of list = [MoleculeModelProtocol] public required init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) @@ -36,7 +46,7 @@ import Foundation for molecule in list { models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: bulletChar, molecule: molecule, stringColor: bulletColor))) } - super.init(molecules: models, spacing: 0) + super.init(molecules: models, spacing: Padding.Four) } public override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index 8721db72..857984cf 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -37,6 +37,8 @@ import UIKit public var baseValue: AnyHashable? public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName + public var enabled: Bool = true + public var readOnly: Bool = false public var selectable = false public var selectedIndex: Int? @@ -46,6 +48,8 @@ import UIKit } public func formFieldValue() -> AnyHashable? { + guard enabled else { return nil } + guard selectable else { // Use visible item value, else index if let fieldValue = molecules[index].formFieldValue() { @@ -84,6 +88,8 @@ import UIKit case fieldKey case selectable case selectedIndex + case enabled + case readOnly } //-------------------------------------------------- @@ -117,10 +123,16 @@ import UIKit if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { self.groupName = groupName } + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) { + self.readOnly = readOnly + } baseValue = formFieldValue() - } - - public func encode(to encoder: Encoder) throws { + } + + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) @@ -142,6 +154,8 @@ import UIKit try container.encode(index, forKey: .index) try container.encode(selectable, forKey: .selectable) try container.encode(selectedIndex, forKey: .selectedIndex) + try container.encode(enabled, forKey: .enabled) + try container.encode(readOnly, forKey: .readOnly) } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift index afc5133c..ed09d90e 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift @@ -7,17 +7,13 @@ // -public protocol CarouselItemModelProtocol: ContainerModelProtocol { +public protocol CarouselItemModelProtocol: FormFieldProtocol, ContainerModelProtocol { var analyticsData: JSONValueDictionary? { get set } - func formFieldValue() -> AnyHashable? } public extension CarouselItemModelProtocol { - var analyticsData: JSONValueDictionary? { get { nil } set { analyticsData = newValue } } - - func formFieldValue() -> AnyHashable? { nil } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ClearableModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ClearableModelProtocol.swift new file mode 100644 index 00000000..046e23d5 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ClearableModelProtocol.swift @@ -0,0 +1,13 @@ +// +// ClearableModelProtocol.swift +// MVMCoreUI +// +// Created by Matt Bruce on 1/11/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ClearableModelProtocol { + func clear() +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/DisableableModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/DisableableModelProtocol.swift index db0cfd77..910d707e 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/DisableableModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/DisableableModelProtocol.swift @@ -9,6 +9,6 @@ import Foundation -public protocol EnableableModelProtocol { +public protocol EnableableModelProtocol: AnyObject { var enabled: Bool { get set } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift index 8f82c946..b9fb111a 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift @@ -20,4 +20,5 @@ public protocol NavigationItemModelProtocol { var additionalLeftButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? { get set } var additionalRightButtons: [(NavigationButtonModelProtocol & MoleculeModelProtocol)]? { get set } var titleView: MoleculeModelProtocol? { get set } + var titleOffset: UIOffset? { get } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift index 1d0a3f61..f2f01b6c 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift @@ -23,4 +23,33 @@ public protocol MoleculeDelegateProtocol: AnyObject { extension MoleculeDelegateProtocol { public func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } + + public func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? { + let moduleJSON: [AnyHashable: Any]? = getModuleWithName(moleculeName) + guard let moduleJSON = moduleJSON as? [String: Any], + let moleculeName = moduleJSON.optionalStringForKey("moleculeName"), + let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) + else { return nil } + + do { + return try modelType.decode(jsonDict: moduleJSON as [String : Any]) as? MoleculeModelProtocol + } catch { + MVMCoreUILoggingHandler.logDebugMessage(withDelegate: "error: \(error)") + } + + return nil + } +} + +extension MoleculeDelegateProtocol where Self: TemplateProtocol { + public func getRootMolecules() -> [MoleculeModelProtocol] { + templateModel?.rootMolecules ?? [] + } +} + +extension MoleculeDelegateProtocol where Self: MVMCoreViewControllerProtocol { + public func getModuleWithName(_ name: String?) -> [AnyHashable : Any]? { + guard let name = name else { return nil } + return loadObject??.modulesJSON?.optionalDictionaryForKey(name) + } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index b0e6b01c..624f99a6 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -70,7 +70,7 @@ public extension ModelRegistry { return type } catch { if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) { - MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject) + MVMCoreLoggingHandler.addError(toLog: errorObject) } return nil } diff --git a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift index 3d1bea9c..082f2da3 100644 --- a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift @@ -8,43 +8,42 @@ import Foundation -public protocol TemplateProtocol: AnyObject { +public protocol TemplateProtocol: AnyObject, PageProtocol { associatedtype TemplateModel: TemplateModelProtocol var templateModel: TemplateModel? { get set } func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel } -public extension TemplateProtocol where Self: ViewController { +public extension TemplateProtocol { + // Utilize existing underlying property + var templateModel: TemplateModel? { + get { + pageModel as? TemplateModel + } + set { + var mutableSelf = self + mutableSelf.pageModel = newValue + } + } + + /// Helper function to do common parsing logic. func parseTemplate(json: [AnyHashable: Any]?) throws { guard let pageJSON = json else { return } + let delegateObject = (self as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject let data = try JSONSerialization.data(withJSONObject: pageJSON) - let decoder = JSONDecoder() - try decoder.add(delegateObject: delegateObjectIVar) + let decoder = JSONDecoder.create(with: delegateObject) templateModel = try decodeTemplate(using: decoder, from: data) - model = templateModel as? MVMControllerModelProtocol - guard let model = model else { return } - traverseAndAddRequiredBehaviors() - var behaviorHandler = self - behaviorHandler.createBehaviors(for: model, delegateObject: delegateObjectIVar) + + // Add additional required behaviors if applicable. + guard var behaviorHandlerModel = templateModel as? TemplateModelProtocol & PageBehaviorHandlerModelProtocol, + var behaviorHandler = self as? PageBehaviorHandlerProtocol else { return } + behaviorHandlerModel.traverseAndAddRequiredBehaviors() + behaviorHandler.createBehaviors(for: behaviorHandlerModel, delegateObject: delegateObject) } func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel { try decoder.decode(TemplateModel.self, from: data) } - - /// Traverses all models and adds any required behavior models. - func traverseAndAddRequiredBehaviors() { - guard var model = model else { return } - let behaviorModels: [PageBehaviorModelProtocol] = model.reduceDepthFirstTraverse(options: .childFirst, depth: 0, initialResult: []) { (accumulator, molecule, depth) in - if let behaviorRequirer = molecule as? PageBehaviorProtocolRequirer { - return accumulator + behaviorRequirer.getRequiredBehaviors() - } - return accumulator - } - for behavior in behaviorModels { - model.add(behavior: behavior) - } - } } diff --git a/MVMCoreUI/Atomic/Templates/TemplateModel.swift b/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift similarity index 87% rename from MVMCoreUI/Atomic/Templates/TemplateModel.swift rename to MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift index 45c2ed1a..21024474 100644 --- a/MVMCoreUI/Atomic/Templates/TemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift @@ -1,5 +1,5 @@ // -// TemplateModel.swift +// BaseTemplateModel.swift // MVMCoreUI // // Created by Scott Pfeil on 3/13/20. @@ -9,7 +9,7 @@ import Foundation -@objcMembers open class TemplateModel: MVMControllerModelProtocol, TabPageModelProtocol { +@objcMembers open class BaseTemplateModel: MVMControllerModelProtocol, TabPageModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -58,10 +58,16 @@ import Foundation //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- + + open class func defaultPageType() -> String? { return nil } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - pageType = try typeContainer.decode(String.self, forKey: .pageType) + if let defaultPageType = Self.defaultPageType() { + pageType = try typeContainer.decodeIfPresent(String.self, forKey: .pageType) ?? defaultPageType + } else { + pageType = try typeContainer.decode(String.self, forKey: .pageType) + } screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) formRules = try typeContainer.decodeIfPresent([FormGroupRule].self, forKey: .formRules) diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index f3bd5d83..b1128d8e 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -13,7 +13,6 @@ //-------------------------------------------------- public typealias TemplateModel = CollectionTemplateModel - public var templateModel: CollectionTemplateModel? public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? diff --git a/MVMCoreUI/Atomic/Templates/ModalListPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ModalListPageTemplateModel.swift index 9459aab6..f1208274 100644 --- a/MVMCoreUI/Atomic/Templates/ModalListPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ModalListPageTemplateModel.swift @@ -16,6 +16,14 @@ public var closeAction: ActionModelProtocol? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public override init(pageType: String, screenHeading: String?, molecules: [ListItemModelProtocol & MoleculeModelProtocol]) { + super.init(pageType: pageType, screenHeading: screenHeading, molecules: molecules) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ModalSectionListTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ModalSectionListTemplateModel.swift index d0f4d456..582ce0ed 100644 --- a/MVMCoreUI/Atomic/Templates/ModalSectionListTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ModalSectionListTemplateModel.swift @@ -5,7 +5,7 @@ // Created by Scott Pfeil on 10/8/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // - +import MVMCore @objcMembers public class ModalSectionListTemplateModel: SectionListTemplateModel { //-------------------------------------------------- @@ -15,6 +15,14 @@ public override class var identifier: String { "modalSectionList" } public var closeAction: ActionModelProtocol? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public override init(with sections: [SectionModel], pageType: String, molecules: [ListItemModelProtocol & MoleculeModelProtocol], screenHeading: String?) { + super.init(with: sections, pageType: pageType, molecules: molecules, screenHeading: screenHeading) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ModalStackPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ModalStackPageTemplateModel.swift index 3e8d2363..5cbb864a 100644 --- a/MVMCoreUI/Atomic/Templates/ModalStackPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ModalStackPageTemplateModel.swift @@ -15,6 +15,14 @@ public override class var identifier: String { "modalStack" } public var closeAction: ActionModelProtocol? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public override init(pageType: String, moleculeStack: StackModel) { + super.init(pageType: pageType, moleculeStack: moleculeStack) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -24,7 +32,7 @@ } //-------------------------------------------------- - // MARK: - Init + // MARK: - Initializer //-------------------------------------------------- required public init(from decoder: Decoder) throws { diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index fa404172..d797038d 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -19,9 +19,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol public var moleculesInfo: [MoleculeInfo]? var observer: NSKeyValueObservation? - - public var templateModel: ListPageTemplateModel? - + //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift index 046469a8..61b247fb 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift @@ -15,7 +15,6 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { //-------------------------------------------------- var observer: NSKeyValueObservation? - public var templateModel: StackPageTemplateModel? //-------------------------------------------------- // MARK: - Lifecycle diff --git a/MVMCoreUI/Atomic/Templates/SectionListTemplateModel.swift b/MVMCoreUI/Atomic/Templates/SectionListTemplateModel.swift index 609e4cc0..023acae5 100644 --- a/MVMCoreUI/Atomic/Templates/SectionListTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/SectionListTemplateModel.swift @@ -19,6 +19,14 @@ public protocol SectionListHeaderFooterModel { } public var footer: (SectionListHeaderFooterModel & MoleculeModelProtocol)? public var rows: [ListItemModelProtocol & MoleculeModelProtocol] + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with rows: [ListItemModelProtocol & MoleculeModelProtocol]) { + self.rows = rows + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -65,6 +73,15 @@ public protocol SectionListHeaderFooterModel { } } } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with sections: [SectionModel], pageType: String, molecules: [ListItemModelProtocol & MoleculeModelProtocol], screenHeading: String? = nil) { + self.sections = sections + super.init(pageType: pageType, screenHeading: screenHeading, molecules: molecules) + } + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift index 06d813ef..9f130089 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift @@ -7,7 +7,7 @@ // -@objcMembers open class ThreeLayerModelBase: TemplateModel, ThreeLayerTemplateModelProtocol { +@objcMembers open class ThreeLayerModelBase: BaseTemplateModel, ThreeLayerTemplateModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift index 502b269f..4d1a4a66 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift @@ -9,11 +9,6 @@ import UIKit @objcMembers open class ThreeLayerTemplate: ThreeLayerViewController, TemplateProtocol { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - public var templateModel: TemplateModel? //-------------------------------------------------- // MARK: - Lifecycle diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index a8fde2e5..865e3a8e 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -13,7 +13,6 @@ import UIKit //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var model: MoleculeModelProtocol? private var initialSetupPerformed = false diff --git a/MVMCoreUI/BaseClasses/TableViewCell.swift b/MVMCoreUI/BaseClasses/TableViewCell.swift index d0bb9c7e..053bedd0 100644 --- a/MVMCoreUI/BaseClasses/TableViewCell.swift +++ b/MVMCoreUI/BaseClasses/TableViewCell.swift @@ -19,7 +19,7 @@ import UIKit public let containerHelper = ContainerHelper() // For the accessory view convenience. - private var caretView: CaretView? + private var caretView: UIImageView? private var caretViewWidthSizeObject: MFSizeObject? private var caretViewHeightSizeObject: MFSizeObject? @@ -42,13 +42,13 @@ import UIKit switch style { case .standard?: topSeparatorView?.setStyle(.none) - bottomSeparatorView?.setStyle(.standard) + bottomSeparatorView?.setStyle(.secondary) case .shortDivider?: topSeparatorView?.setStyle(.none) - bottomSeparatorView?.setStyle(.thin) + bottomSeparatorView?.setStyle(.none) case .tallDivider?: topSeparatorView?.setStyle(.none) - bottomSeparatorView?.setStyle(.thin) + bottomSeparatorView?.setStyle(.none) case .sectionFooter?: topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.none) @@ -185,20 +185,22 @@ import UIKit @objc public func addCaretViewAccessory() { guard accessoryView == nil else { return } + + let peakingImageView = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) - let caret = CaretView(lineWidth: 1) - caret.translatesAutoresizingMaskIntoConstraints = true - caret.isAccessibilityElement = true - caret.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint") - caret.accessibilityTraits = .button - caret.size = .small(.vertical) - if let size = caret.size?.dimensions() { - caret.frame = CGRect(origin: .zero, size: size) - caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9) - caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16) - } - caretView = caret - accessoryView = caret + peakingImageView.translatesAutoresizingMaskIntoConstraints = true + peakingImageView.alpha = 0 + peakingImageView.tintColor = .black + peakingImageView.isAccessibilityElement = true + peakingImageView.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint") + peakingImageView.accessibilityTraits = .button + let accessorySize = CGRect(origin: .zero, size: CGSize(width: 13.3, height: 13.3)) + peakingImageView.frame = accessorySize + caretViewWidthSizeObject = MFSizeObject(standardSize: accessorySize.width, standardiPadPortraitSize: 16.6) + caretViewHeightSizeObject = MFSizeObject(standardSize: accessorySize.height, standardiPadPortraitSize: 16.6) + caretView = peakingImageView + + accessoryView = peakingImageView } /// NOTE: Should only be called when displayed or about to be displayed. @@ -244,8 +246,8 @@ import UIKit topSeparatorView?.set(with: model, delegateObject, additionalData) bottomSeparatorView?.set(with: model, delegateObject, additionalData) } else { - topSeparatorView?.setStyle(.standard) - bottomSeparatorView?.setStyle(.standard) + topSeparatorView?.setStyle(.secondary) + bottomSeparatorView?.setStyle(.secondary) } setSeparatorFrequency(model?.frequency ?? .allExceptTop, indexPath: indexPath) } diff --git a/MVMCoreUI/BaseClasses/View.swift b/MVMCoreUI/BaseClasses/View.swift index deb1bdfa..c4c36ae8 100644 --- a/MVMCoreUI/BaseClasses/View.swift +++ b/MVMCoreUI/BaseClasses/View.swift @@ -36,7 +36,7 @@ import UIKit initialSetup() } - public func initialSetup() { + open func initialSetup() { if !initialSetupPerformed { initialSetupPerformed = true setupView() diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 4a516dd8..772742b4 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -43,9 +43,6 @@ import UIKit public var selectedField: UIView? - // Stores the previous tab bar index. - public var tabBarIndex: Int? - /// Checks if the screen width has changed open func screenSizeChanged() -> Bool { !MVMCoreGetterUtility.cgfequalwiththreshold(previousScreenSize.width, view.bounds.size.width, 0.1) @@ -117,7 +114,7 @@ import UIKit }) } catch { if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") { - MVMCoreLoggingHandler.shared()?.addError(toLog: coreError) + MVMCoreLoggingHandler.addError(toLog: coreError) } } } @@ -244,12 +241,6 @@ import UIKit view.backgroundColor = backgroundColor.uiColor } - // Update splitview properties - if self == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() { - MVMCoreUISplitViewController.main()?.setBottomProgressBarProgress(bottomProgress() ?? 0) - updateTabBar() - } - // Notify the manager of new data manager?.newDataReceived?(in: self) } @@ -267,34 +258,6 @@ import UIKit return model?.navigationBar } - //-------------------------------------------------- - // MARK: - TabBar - //-------------------------------------------------- - - open func updateTabBar() { - guard MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() == self else { return } - MVMCoreUISplitViewController.main()?.tabBar?.delegateObject = delegateObjectIVar - - if let index = (model as? TabPageModelProtocol)?.tabBarIndex { - MVMCoreUISplitViewController.main()?.tabBar?.highlightTab(at: index) - } else if let index = loadObject?.requestParameters?.actionMap?["tabBarIndex"] as? Int { - MVMCoreUISplitViewController.main()?.tabBar?.highlightTab(at: index) - } else if let index = self.tabBarIndex { - MVMCoreUISplitViewController.main()?.tabBar?.highlightTab(at: index) - } else if let index = MVMCoreUISplitViewController.main()?.tabBar?.currentTabIndex() { - // Store current tab index for cases like back button. - self.tabBarIndex = index - } - - if let hidden = (model as? TabPageModelProtocol)?.tabBarHidden { - MVMCoreUISplitViewController.main()?.updateTabBarShowing(!hidden) - } else if let hidden = loadObject?.requestParameters?.actionMap?["tabBarHidden"] as? Bool { - MVMCoreUISplitViewController.main()?.updateTabBarShowing(!hidden) - } else { - MVMCoreUISplitViewController.main()?.updateTabBarShowing(true) - } - } - //-------------------------------------------------- // MARK: - View Lifecycle //-------------------------------------------------- @@ -349,13 +312,6 @@ import UIKit } open func pageShown() { - // Update split view properties if this is the current detail controller. - if self == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() { - MVMCoreUISplitViewController.main()?.setupPanels() - MVMCoreUISplitViewController.main()?.setBottomProgressBarProgress(bottomProgress() ?? 0) - updateTabBar() - } - // Track. MVMCoreUISession.sharedGlobal()?.currentPageType = pageType MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self) @@ -485,26 +441,6 @@ import UIKit model?.rootMolecules ?? [] } - open func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? { - guard let name = name else { return nil } - return loadObject?.modulesJSON?.optionalDictionaryForKey(name) - } - - open func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? { - guard let moduleJSON = loadObject?.modulesJSON?.optionalDictionaryForKey(moleculeName), - let moleculeName = moduleJSON.optionalStringForKey("moleculeName"), - let modelType = ModelRegistry.getType(for: moleculeName, with: MoleculeModelProtocol.self) - else { return nil } - - do { - return try modelType.decode(jsonDict: moduleJSON) as? MoleculeModelProtocol - } catch { - MVMCoreUILoggingHandler.logDebugMessage(withDelegate: "error: \(error)") - } - - return nil - } - // Needed otherwise when subclassed, the extension gets called. open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } @@ -619,12 +555,4 @@ import UIKit selectedField = nil } } - - //-------------------------------------------------- - // MARK: - Behavior Execution - //-------------------------------------------------- - - public func executeBehaviors(_ behaviorBlock: (_ behavior: T) -> Void) { - behaviors?.compactMap { $0 as? T }.forEach { behaviorBlock($0) } - } } diff --git a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift index ee4d9587..a3be96f5 100644 --- a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift @@ -75,7 +75,7 @@ public class AddRemoveMoleculesBehavior: PageCustomActionHandlerBehavior, PageMo self.delegate = delegateObject } - public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject) { + public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) { guard let list = delegate?.moleculeListDelegate else { return } for case let model as (MoleculeModelProtocol & ListItemModelProtocol & AddMolecules) in rootMolecules { if let moleculesToAdd = model.getRecursiveMoleculesToAdd(), diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerModelProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerModelProtocol.swift index d9b8da9f..bb752694 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerModelProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerModelProtocol.swift @@ -23,3 +23,19 @@ public extension PageBehaviorHandlerModelProtocol { self.behaviors = newBehaviors } } + +public extension PageBehaviorHandlerModelProtocol where Self: MoleculeTreeTraversalProtocol { + + /// Traverses all models and adds any required behavior models. + mutating func traverseAndAddRequiredBehaviors() { + let behaviorModels: [PageBehaviorModelProtocol] = reduceDepthFirstTraverse(options: .childFirst, depth: 0, initialResult: []) { (accumulator, molecule, depth) in + if let behaviorRequirer = molecule as? PageBehaviorProtocolRequirer { + return accumulator + behaviorRequirer.getRequiredBehaviors() + } + return accumulator + } + for behavior in behaviorModels { + add(behavior: behavior) + } + } +} diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift index 1d020aeb..79f3364f 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorHandlerProtocol.swift @@ -28,10 +28,15 @@ public extension PageBehaviorHandlerProtocol { behaviors.append(behavior) } catch { if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) { - MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject) + MVMCoreLoggingHandler.addError(toLog: errorObject) } } } self.behaviors = behaviors.count > 0 ? behaviors : nil } + + /// Executes all behaviors of type. + func executeBehaviors(_ behaviorBlock: (_ behavior: T) -> Void) { + behaviors?.compactMap { $0 as? T }.forEach { behaviorBlock($0) } + } } diff --git a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift index 3f14f3cc..eadd5e4e 100644 --- a/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift +++ b/MVMCoreUI/Behaviors/Protocols/PageBehaviorProtocol.swift @@ -19,7 +19,7 @@ public protocol PageBehaviorProtocol: ModelHandlerProtocol { public protocol PageMoleculeTransformationBehavior: PageBehaviorProtocol { - func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject) + func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) } public protocol PageVisibilityBehavior: PageBehaviorProtocol { @@ -50,8 +50,11 @@ public protocol PageCustomActionHandlerBehavior: PageBehaviorProtocol { } public extension MVMCoreUIDelegateObject { + var behaviorModelDelegate: PageBehaviorHandlerModelProtocol? { + (moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorHandlerModelProtocol + } weak var behaviorTemplateDelegate: (PageBehaviorHandlerProtocol & NSObjectProtocol)? { - (moleculeDelegate as? PageProtocol)?.pageModel as? (PageBehaviorHandlerProtocol & NSObjectProtocol) + moleculeDelegate as? (PageBehaviorHandlerProtocol & NSObjectProtocol) } } diff --git a/MVMCoreUI/Categories/UIColor+Extension.swift b/MVMCoreUI/Categories/UIColor+Extension.swift index ca763ac5..78363630 100644 --- a/MVMCoreUI/Categories/UIColor+Extension.swift +++ b/MVMCoreUI/Categories/UIColor+Extension.swift @@ -186,7 +186,7 @@ extension UIColor { //-------------------------------------------------- /// HEX: #F6F6F6 - public static let mvmCoolGray1 = UIColor.assetColor(named: "coolGray1") + @objc public static let mvmCoolGray1 = UIColor.assetColor(named: "coolGray1") /// HEX: #D8DADA public static let mvmCoolGray3 = UIColor.assetColor(named: "coolGray3") diff --git a/MVMCoreUI/Categories/colors.xcassets/Contents.json b/MVMCoreUI/Categories/colors.xcassets/Contents.json index da4a164c..73c00596 100644 --- a/MVMCoreUI/Categories/colors.xcassets/Contents.json +++ b/MVMCoreUI/Categories/colors.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift deleted file mode 100644 index 0e16b577..00000000 --- a/MVMCoreUI/Containers/NavigationController.swift +++ /dev/null @@ -1,187 +0,0 @@ -// -// NavigationController.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 10/24/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -@objcMembers open class NavigationController: UINavigationController, MVMCoreViewManagerViewControllerProtocol { - public var separatorView: Line? - public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)? - - /// Getter for the main navigation controller - public static func navigationController() -> Self? { - return MVMCoreActionUtility.initializerClassCheck(MVMCoreUISession.sharedGlobal()?.navigationController, classToVerify: self) as? Self - } - - /// Provides MVM styling to the navigation bar. Returns a reference to the line. - public static func style(_ navigationBar: UINavigationBar) -> Line { - navigationBar.backgroundColor = .white - navigationBar.shadowImage = UIImage() - navigationBar.isOpaque = true - navigationBar.tintColor = .black - navigationBar.titleTextAttributes = [NSAttributedString.Key.font: MFStyler.fontBoldBodySmall(false)]; - return Line(pinTo: navigationBar, edge: .bottom, useMargin: false) - } - - /// Sets up the application with a navigation controller - public static func setupNavigationController() -> Self? { - let navigationController = self.init() - navigationController.separatorView = style(navigationController.navigationBar) - MVMCoreUISession.sharedGlobal()?.navigationController = navigationController - MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController - MVMCoreNavigationHandler.shared()?.navigationController = navigationController - MVMCoreNavigationHandler.shared()?.addDelegate(navigationController) - return navigationController - } - - /// Sets up the application with a navigation controller as the main container. - public static func setupNavigationControllerAsMainController() -> Self? { - guard let navigationController = setupNavigationController() else { return nil } - MVMCoreUISession.sharedGlobal()?.setup(asStandardLoadViewDelegate: navigationController) - return navigationController - } - - /// Convenience function for setting the navigation item. - public static func setNavigationItem(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { - viewController.navigationItem.title = navigationItemModel.title - viewController.navigationItem.accessibilityLabel = navigationItemModel.title - viewController.navigationItem.hidesBackButton = navigationItemModel.hidesSystemBackButton - viewController.navigationItem.leftItemsSupplementBackButton = !navigationItemModel.hidesSystemBackButton - setNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) - setNavigationTitleView(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) - } - - /// Convenience function for setting the navigation buttons. - public static func setNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { - let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject - var leftItems: [UIBarButtonItem] = [] - if navigationItemModel.alwaysShowBackButton != false { - if let backButtonModel = navigationItemModel.backButton, - MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 0 > 1 || navigationItemModel.alwaysShowBackButton ?? false { - leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } - if let leftItemModels = navigationItemModel.additionalLeftButtons { - for item in leftItemModels { - leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } - } - } - viewController.navigationItem.leftBarButtonItems = leftItems.count > 0 ? leftItems : nil - - var rightItems: [UIBarButtonItem] = [] - if let rightItemModels = navigationItemModel.additionalRightButtons { - for item in rightItemModels { - rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } - } - viewController.navigationItem.rightBarButtonItems = rightItems.count > 0 ? rightItems : nil - } - - /// Convenience function for setting the navigation bar ui, except for the buttons. - public static func setNavigationBarUI(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { - navigationController.setNavigationBarHidden(navigationItemModel.hidden, animated: true) - navigationController.navigationBar.barTintColor = navigationItemModel.backgroundColor?.uiColor ?? .white - - let tint = navigationItemModel.tintColor.uiColor - navigationController.navigationBar.tintColor = tint - - // Have the navigation title match the tint color - navigationController.navigationBar.titleTextAttributes?.updateValue(tint, forKey: .foregroundColor) - - // Update line. - if let navigationController = navigationController as? NavigationController { - navigationController.separatorView?.isHidden = navigationItemModel.line?.type ?? .standard == .none - } - } - - /// Convenience function for setting the navigation titleView. - public static func setNavigationTitleView(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) { - let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject - if let titleViewModel = navigationItemModel?.titleView, let molecule = ModelRegistry.createMolecule(titleViewModel, delegateObject: delegate, additionalData: nil) { - viewController.navigationItem.titleView = molecule - } - } - - /// Convenience function to return the navigation model of the lowest controller traversing managers if applicable. - public func getNavigationModel(from viewController: UIViewController) -> NavigationItemModelProtocol? { - guard let topViewController = topViewController, - viewController == MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController), - let model = (viewController as? PageProtocol)?.pageModel?.navigationBar else { return nil } - return model - } -} - -extension NavigationController: MVMCoreViewManagerProtocol { - public func getCurrentViewController() -> UIViewController? { - guard let topViewController = topViewController else { return nil } - return MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) - } - - public func containsPage(withPageType pageType: String?) -> Bool { - for controller in viewControllers { - if let manager = controller as? MVMCoreViewManagerProtocol, - manager.containsPage(withPageType: pageType) { - return true - } else if let controller = controller as? MVMCoreViewControllerProtocol, - controller.pageType == pageType { - return true - } - } - return false - } - - public func newDataReceived(in viewController: UIViewController) { - if let topViewController = topViewController, - let model = getNavigationModel(from: viewController) { - Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController) - Self.setNavigationBarUI(navigationController: self, navigationItemModel: model, viewController: topViewController) - } - manager?.newDataReceived?(in: viewController) - } - - public func willDisplay(_ viewController: UIViewController) { - if let topViewController = topViewController, - let model = getNavigationModel(from: viewController) { - Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController) - } - manager?.willDisplay?(viewController) - } - - public func displayedViewController(_ viewController: UIViewController) { - if let topViewController = topViewController, - let model = getNavigationModel(from: viewController) { - Self.setNavigationBarUI(navigationController: self, navigationItemModel: model, viewController: topViewController) - } - manager?.displayedViewController?(viewController) - } -} - -extension NavigationController: MVMCorePresentationDelegateProtocol { - public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) { - guard self == navigationController else { return } - if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { - MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) - } - guard let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } - if let model = getNavigationModel(from: newViewController) { - Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: viewController) - } - manager?.willDisplay?(newViewController) - } - - public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { - guard self == navigationController, - let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } - if let model = getNavigationModel(from: newViewController) { - Self.setNavigationBarUI(navigationController: self, navigationItemModel: model, viewController: viewController) - } - manager?.displayedViewController?(newViewController) - if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { - controller.viewControllerReady?(inManager: self) - } - } -} diff --git a/MVMCoreUI/Containers/NavigationController/NavigationController.swift b/MVMCoreUI/Containers/NavigationController/NavigationController.swift new file mode 100644 index 00000000..9b4b332a --- /dev/null +++ b/MVMCoreUI/Containers/NavigationController/NavigationController.swift @@ -0,0 +1,137 @@ +// +// NavigationController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class NavigationController: UINavigationController, MVMCoreViewManagerViewControllerProtocol { + public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)? + + /// Getter for the main navigation controller + public static func navigationController() -> Self? { + return MVMCoreActionUtility.initializerClassCheck(MVMCoreUISession.sharedGlobal()?.navigationController, classToVerify: self) as? Self + } + + /// Sets up the application with a navigation controller + public static func setupNavigationController() -> Self? { + let navigationController = self.init() + MVMCoreUISession.sharedGlobal()?.navigationController = navigationController + MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController + MVMCoreNavigationHandler.shared()?.navigationController = navigationController + MVMCoreNavigationHandler.shared()?.addDelegate(navigationController) + navigationController.setNavigationBarUI(with: NavigationItemModel()) + return navigationController + } + + /// Sets up the application with a navigation controller as the main container. + public static func setupNavigationControllerAsMainController() -> Self? { + guard let navigationController = setupNavigationController() else { return nil } + MVMCoreUISession.sharedGlobal()?.setup(asStandardLoadViewDelegate: navigationController) + return navigationController + } + + /// Convenience function to return the navigation model of the lowest controller traversing managers if applicable. + public func getNavigationModel(from viewController: UIViewController) -> NavigationItemModelProtocol? { + return (viewController as? PageProtocol)?.pageModel?.navigationBar + } + + /// Verifies the controller is the currently displayed controller. + public func isDisplayed(viewController: UIViewController) -> Bool { + guard let topViewController = topViewController, + viewController == MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) else { + return false + } + return true + } +} + +extension NavigationController: MVMCoreViewManagerProtocol { + public func getCurrentViewController() -> UIViewController? { + guard let topViewController = topViewController else { return nil } + return MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) + } + + public func containsPage(withPageType pageType: String?) -> Bool { + for controller in viewControllers { + if let manager = controller as? MVMCoreViewManagerProtocol, + manager.containsPage(withPageType: pageType) { + return true + } else if let controller = controller as? MVMCoreViewControllerProtocol, + controller.pageType == pageType { + return true + } + } + return false + } + + public func newDataReceived(in viewController: UIViewController) { + if isDisplayed(viewController: viewController), + let topViewController = topViewController, + let model = getNavigationModel(from: viewController) { + setNavigationItem(with: model, for: topViewController) + setNavigationBarUI(with: model) + + navigationBar.setNeedsLayout() + navigationBar.layoutIfNeeded() + } + manager?.newDataReceived?(in: viewController) + } + + public func willDisplay(_ viewController: UIViewController) { + if let topViewController = topViewController, + isDisplayed(viewController: viewController), + let model = getNavigationModel(from: viewController) { + setNavigationItem(with: model, for: topViewController) + setNavigationBarUI(with: model) + + navigationBar.setNeedsLayout() + navigationBar.layoutIfNeeded() + } + manager?.willDisplay?(viewController) + } + + public func displayedViewController(_ viewController: UIViewController) { + manager?.displayedViewController?(viewController) + } +} + +extension NavigationController: MVMCorePresentationDelegateProtocol { + public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { + if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { + MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) + } + guard self == navigationController, + let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController), + let model = getNavigationModel(from: newViewController) else { return } + setNavigationItem(with: model, for: viewController) + setNavigationBarUI(with: model) + } + + public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) { + guard self == navigationController, + let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } + manager?.willDisplay?(newViewController) + } + + public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { + guard self == navigationController, + let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } + manager?.displayedViewController?(newViewController) + if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { + controller.viewControllerReady?(inManager: self) + } + } +} + +extension UIColor { + func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { + return UIGraphicsImageRenderer(size: size).image { rendererContext in + self.setFill() + rendererContext.fill(CGRect(origin: .zero, size: size)) + } + } +} diff --git a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift new file mode 100644 index 00000000..e1c7f6a3 --- /dev/null +++ b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift @@ -0,0 +1,88 @@ +// +// UINavigationController+Extension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 3/14/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +public extension UINavigationController { + + /// Convenience function for setting the navigation item. + func setNavigationItem(with model: NavigationItemModelProtocol, for viewController: UIViewController) { + viewController.navigationItem.title = model.title + viewController.navigationItem.accessibilityLabel = model.title + viewController.navigationItem.hidesBackButton = model.hidesSystemBackButton + viewController.navigationItem.leftItemsSupplementBackButton = !model.hidesSystemBackButton + setNavigationButtons(with: model, for: viewController) + setNavigationTitleView(with: model, for: viewController) + } + + /// Convenience function for setting the navigation buttons. + func setNavigationButtons(with model: NavigationItemModelProtocol, for viewController: UIViewController) { + let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject + var leftItems: [UIBarButtonItem] = [] + if model.hidesSystemBackButton, + model.alwaysShowBackButton != false { + if let backButtonModel = model.backButton, + MVMCoreNavigationHandler.shared()?.getViewControllers(for: self)?.count ?? 0 > 1 || model.alwaysShowBackButton ?? false { + leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } + if let leftItemModels = model.additionalLeftButtons { + for item in leftItemModels { + leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } + } + } + viewController.navigationItem.leftBarButtonItems = leftItems.count > 0 ? leftItems : nil + + var rightItems: [UIBarButtonItem] = [] + if let rightItemModels = model.additionalRightButtons { + for item in rightItemModels { + rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } + } + viewController.navigationItem.rightBarButtonItems = rightItems.count > 0 ? rightItems : nil + } + + /// Convenience function for setting the navigation titleView. + func setNavigationTitleView(with model: NavigationItemModelProtocol, for viewController: UIViewController) { + let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject + if let titleViewModel = model.titleView, + let molecule = ModelRegistry.createMolecule(titleViewModel, delegateObject: delegate, additionalData: nil) { + viewController.navigationItem.titleView = molecule + } + } + + /// Returns a ShadowImage based on the line property of NavigationItemModelProtocol + func getNavigationBarShadowImage(for navigationItemModel: NavigationItemModelProtocol) -> UIImage? { + guard let thickness = navigationItemModel.line?.thickness, + let backgroundColor = navigationItemModel.line?.backgroundColor else { return nil } + return backgroundColor.uiColor.image(CGSize(width: thickness, height: thickness)) + } + + /// Convenience function for setting the navigation bar ui + func setNavigationBarUI(with model: NavigationItemModelProtocol) { + let navigationBar = navigationBar + let font = MFStyler.fontBoldBodyLarge(false) + let backgroundColor = model.backgroundColor?.uiColor + let tint = model.tintColor.uiColor + navigationBar.tintColor = tint + + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.titleTextAttributes = [NSAttributedString.Key.font: font, + NSAttributedString.Key.foregroundColor: tint]; + appearance.backgroundColor = backgroundColor + appearance.titleTextAttributes.updateValue(tint, forKey: .foregroundColor) + appearance.titlePositionAdjustment = model.titleOffset ?? .zero + appearance.shadowColor = model.line?.backgroundColor?.uiColor ?? .clear + appearance.shadowImage = getNavigationBarShadowImage(for: model)?.withRenderingMode(.alwaysTemplate) + navigationBar.standardAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance + + setNavigationBarHidden(model.hidden, animated: true) + } +} diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift index bb1b24d5..c6ec9382 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift @@ -7,23 +7,79 @@ // import Foundation +import UIKit // Navigation bar update functions public extension MVMCoreUISplitViewController { + /// Updates the state for various controls (navigation, tab, progress) for the controller. + func updateState(with viewController: UIViewController) { + guard let navigationController = navigationController, + navigationController.isDisplayed(viewController: viewController) else { return } + updateNavigationBarFor(viewController: viewController) + updateProgressBar(for: viewController) + updateTabBar(for: viewController) + } + + // MARK: - Progress Bar + /// Updates the progress bar based on the page json for the view controller. + func updateProgressBar(for viewController: UIViewController) { + guard let viewController = viewController as? MVMCoreViewControllerProtocol else { return } + var progress: Float = 0.0 + if let progressString = viewController.loadObject??.pageJSON?.optionalStringForKey(KeyProgressPercent), + let floatValue = Float(progressString) { + progress = floatValue/100 + } + setBottomProgressBarProgress(progress) + } + + // MARK: - Tab Bar + /// Updates the tab bar based on the page json for the view controller. + func updateTabBar(for viewController: UIViewController) { + let mvmViewController = viewController as? MVMCoreViewControllerProtocol + tabBar?.delegateObject = mvmViewController?.delegateObject?() as? MVMCoreUIDelegateObject + + let navigationIndex = (MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 1) - 1 + + // Set the highlighted index. In terms of priority, Page > Action > Previous. + if let index = ((viewController as? PageProtocol)?.pageModel as? TabPageModelProtocol)?.tabBarIndex { + tabBar?.highlightTab(at: index) + } else if let index = mvmViewController?.loadObject??.requestParameters?.actionMap?["tabBarIndex"] as? Int { + tabBar?.highlightTab(at: index) + } else if navigationIndex < tabBarIndices.count, + let index = (tabBarIndices[navigationIndex] as? NSNumber)?.intValue { + tabBar?.highlightTab(at: index) + } + + // Store current tab index, so we can switch back when going back in hierarchy. + if tabBarIndices.count > navigationIndex { + tabBarIndices.removeObjects(in: NSRange(location: navigationIndex, length: tabBarIndices.count - navigationIndex)) + } + if let currentIndex = tabBar?.currentTabIndex() { + tabBarIndices.add(NSNumber(integerLiteral: currentIndex)) + } else { + tabBarIndices.add(NSNull()) + } + + // Show/Hide. In terms of priority, Page > Action > Always Show. + if let hidden = ((viewController as? PageProtocol)?.pageModel as? TabPageModelProtocol)?.tabBarHidden { + updateTabBarShowing(!hidden) + } else if let hidden = mvmViewController?.loadObject??.requestParameters?.actionMap?["tabBarHidden"] as? Bool { + updateTabBarShowing(!hidden) + } else { + updateTabBarShowing(true) + } + } + + // MARK: - Navigation Bar /// Convenience function. Sets the navigation and split view properties for the view controller. Panel access is determined if view controller is a detail view protocol. - static func setNavigationBarUI(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) { - guard let splitView = MVMCoreUISplitViewController.main(), - navigationController == splitView.navigationController, - viewController == MVMCoreUISplitViewController.main()?.getCurrentDetailViewController() else { + func setNavigationBar(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) { + guard navigationController == self.navigationController, + viewController == getCurrentDetailViewController() else { /// Not the split view navigation controller, skip split functions. return } - splitView.set(for: viewController, navigationController: navigationController, navigationItemModel: navigationItemModel) - } - - /// Sets the navigation item for the view controller based on the model and splitview controller - private func set(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) { + setLeftPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isLeftPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false) setRightPanelIsAccessible((viewController as? MVMCoreUIDetailViewProtocol)?.isRightPanelAccessible?() ?? false, for: viewController, updateNavigationButtons: false) @@ -41,18 +97,20 @@ public extension MVMCoreUISplitViewController { let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject // Add back button first. - var showBackButton: Bool - if let forceBackButton = navigationItemModel?.alwaysShowBackButton { - showBackButton = forceBackButton - } else { - showBackButton = MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 0 > 1 - } - if showBackButton { - if let backButtonModel = navigationItemModel?.backButton { - leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) - } else if let backButton = backButton { - // Default to legacy if we have default back button. - leftItems.append(backButton) + if navigationItemModel?.hidesSystemBackButton == true { + var showBackButton: Bool + if let forceBackButton = navigationItemModel?.alwaysShowBackButton { + showBackButton = forceBackButton + } else { + showBackButton = MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 0 > 1 + } + if showBackButton { + if let backButtonModel = navigationItemModel?.backButton { + leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) + } else if let backButton = backButton { + // Default to legacy if we have default back button. + leftItems.append(backButton) + } } } @@ -92,6 +150,11 @@ public extension MVMCoreUISplitViewController { rightItems.append(panelButton) } + // Add any buttons added by the splitview. + if let additionalRightButtons = additionalRightButtons(for: viewController) { + rightItems.append(contentsOf: additionalRightButtons) + } + // Add other model buttons if let rightItemModels = navigationItemModel?.additionalRightButtons { for item in rightItemModels { @@ -99,11 +162,6 @@ public extension MVMCoreUISplitViewController { } } - // Add any buttons added by the splitview. - if let additionalRightButtons = additionalRightButtons(for: viewController) { - rightItems.append(contentsOf: additionalRightButtons) - } - topViewController.navigationItem.setRightBarButtonItems(rightItems.count > 0 ? rightItems : nil, animated: !DisableAnimations.boolValue) } @@ -117,8 +175,33 @@ public extension MVMCoreUISplitViewController { /// Convenience function to update the navigation bar if the controller is the current lowest controller. @objc func updateNavigationBarFor(viewController: UIViewController) { guard let navigationController = navigationController, + navigationController.isDisplayed(viewController: viewController), let model = navigationController.getNavigationModel(from: viewController) else { return } - set(for: viewController, navigationController: navigationController, navigationItemModel: model) + setNavigationBar(for: viewController, navigationController: navigationController, navigationItemModel: model) + guard !(topAlertView?.overridingStatusBar() ?? false) else { return } + setStatusBarForCurrentViewController() + } + + // MARK: - Status Bar + /// Returns the bar style for the background color. Light if on a dark background, otherwise default. + func getStatusBarStyle(for backgroundColor: UIColor?) -> UIStatusBarStyle { + var greyScale: CGFloat = 0 + if backgroundColor?.getWhite(&greyScale, alpha: nil) == true, + greyScale < 0.5 { return .lightContent } + return .default + } + + /// Updates the status bar background color and style. + @objc func setStatusBarForCurrentViewController() { + let viewController = getCurrentViewController() as? MVMCoreUIDetailViewProtocol + let backgroundColor = viewController?.defaultStatusBarBackgroundColor?() ?? + navigationController?.navigationBar.standardAppearance.backgroundColor ?? + statusBarView?.backgroundColor + + let style = viewController?.defaultStatusBarStyle?() ?? + getStatusBarStyle(for: backgroundColor) + + setStatusBarBackgroundColor(backgroundColor, style: style) } } @@ -131,11 +214,12 @@ extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol { navigationController?.containsPage(withPageType: pageType) ?? false } - public func displayedViewController(_ viewController: UIViewController) { - updateNavigationBarFor(viewController: viewController) + public func willDisplay(_ viewController: UIViewController) { + setupPanels() + updateState(with: viewController) } public func newDataReceived(in viewController: UIViewController) { - updateNavigationBarFor(viewController: viewController) + updateState(with: viewController) } } diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h index 9c07374d..589c1cbb 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h @@ -44,6 +44,9 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { // Reference to the top alert view @property (nullable, weak, nonatomic) MVMCoreUITopAlertView *topAlertView; +// Reference to the status bar view +@property (nullable, weak, nonatomic) UIView *statusBarView; + // References to the current navigation item settings. @property (nonatomic, readonly) BOOL leftPanelIsAccessible; @property (nonatomic, readonly) BOOL rightPanelIsAccessible; @@ -52,6 +55,9 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { /// Reference to the tabbar. @property (nullable, weak, nonatomic) UIView *tabBar; +/// Tab bar index history. Contains either indices (0, 1, etc) or NSNull if there was no tab bar indice to set. +@property (nonnull, strong, nonatomic) NSMutableArray *tabBarIndices; + // Convenience getter + (nullable instancetype)mainSplitViewController; @@ -122,10 +128,6 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { + (CGFloat)getApplicationViewWidth; + (CGFloat)getApplicationViewMaxSize; -// return subviewcontrollers' prefer status bar style -- (UIStatusBarStyle)getDefaultStatusBarStyle; -- (nullable UIColor *)getDefaultStatusBarBackgroundColor; - /// Returns true if a panel is showing. - (BOOL)isAPanelShowing; @@ -166,4 +168,15 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { /// Updates if the tab bar is showing or not. - (void)updateTabBarShowing:(BOOL)showing; +#pragma mark - Status Bar + +/// Updates the status bar with the given style and background color +- (void)setStatusBarBackgroundColor:(nullable UIColor *)backgroundColor style:(UIStatusBarStyle)style; + +/// Shows the view under the status bar. +- (void)expandStatusBarView; + +/// Hides the view under the status bar. +- (void)collapseStatusBarView; + @end diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m index d8639b56..e005763b 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m @@ -63,6 +63,10 @@ typedef NS_OPTIONS(NSInteger, MFExtendedDrawer) { @property (weak, nonatomic, readwrite) UIViewController *navigationItemViewController; @property (strong, nonatomic) NSNumber *transitionWidth; +@property (nonatomic) UIStatusBarStyle statusBarStyle; +@property (strong, nonatomic) NSLayoutConstraint *statusBarHeightConstraint; +@property (strong, nonatomic) NSLayoutConstraint *statusBarBottomConstraint; + // Dismisses any panel - (void)dismissPanels:(id)sender; @@ -89,13 +93,20 @@ CGFloat const PanelAnimationDuration = 0.2; } - (nullable instancetype)initWithLeftPanel:(nullable UIViewController *)leftPanel rightPanel:(nullable UIViewController *)rightPanel { - if (self = [super init]) { + if (self = [self init]) { self.globalLeftPanel = leftPanel; self.globalRightPanel = rightPanel; } return self; } +- (nullable instancetype)init { + if (self = [super init]) { + self.tabBarIndices = [NSMutableArray array]; + } + return self; +} + #pragma mark - Main Subclassables - (MFNumberOfDrawers)numberOfDrawersShouldShow:(NSNumber *)forWidth { @@ -337,6 +348,7 @@ CGFloat const PanelAnimationDuration = 0.2; }; void (^completion)(BOOL) = ^(BOOL finished){ + self.leftView.hidden = true; self.mainViewCoverView.hidden = YES; [self panelDidDisappear:self.leftPanel animated:animated]; self.mainView.accessibilityElementsHidden = NO; @@ -360,6 +372,7 @@ CGFloat const PanelAnimationDuration = 0.2; return; } [MVMCoreDispatchUtility performBlockOnMainThread:^{ + self.leftView.hidden = false; if (self.mainViewLeading.constant < .1) { BOOL shouldExtendLeftPanel = [self shouldExtendLeftPanel]; @@ -540,6 +553,7 @@ CGFloat const PanelAnimationDuration = 0.2; }; void (^completion)(BOOL) = ^(BOOL finished){ + self.rightView.hidden = true; self.mainViewCoverView.hidden = YES; [self panelDidDisappear:self.rightPanel animated:animated]; self.mainView.accessibilityElementsHidden = NO; @@ -563,6 +577,7 @@ CGFloat const PanelAnimationDuration = 0.2; return; } [MVMCoreDispatchUtility performBlockOnMainThread:^{ + self.rightView.hidden = false; if (self.mainViewTrailing.constant < .1) { BOOL shouldExtendRightPanel = [self shouldExtendRightPanel]; @@ -726,9 +741,9 @@ CGFloat const PanelAnimationDuration = 0.2; [self addPanel:panel]; self.leftView = panel.view; self.leftPanel = panel; + self.leftView.hidden = YES; self.leftView.translatesAutoresizingMaskIntoConstraints = NO; NSLayoutConstraint *leftPanelWidth = [NSLayoutConstraint constraintWithItem:self.leftView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:270]; - self.leftView.translatesAutoresizingMaskIntoConstraints = NO; leftPanelWidth.active = YES; self.leftPanelWidth = leftPanelWidth; [NSLayoutConstraint constraintWithItem:self.mainView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.leftView attribute:NSLayoutAttributeRight multiplier:1.0 constant:0].active = YES; @@ -762,6 +777,7 @@ CGFloat const PanelAnimationDuration = 0.2; [self addPanel:panel]; self.rightView = panel.view; self.rightPanel = panel; + self.rightView.hidden = YES; self.rightView.translatesAutoresizingMaskIntoConstraints = NO; NSLayoutConstraint *rightPanelWidth = [NSLayoutConstraint constraintWithItem:self.rightView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:270]; rightPanelWidth.active = YES; @@ -836,6 +852,38 @@ CGFloat const PanelAnimationDuration = 0.2; }]; } +#pragma mark - Status Bar + +- (void)setStatusBarBackgroundColor:(UIColor *)backgroundColor style:(UIStatusBarStyle)style { + self.statusBarView.backgroundColor = backgroundColor; + self.statusBarStyle = style; + + // Triggers preferredStatusBarStyle + [self.parentViewController setNeedsStatusBarAppearanceUpdate]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return self.statusBarStyle; +} + +- (void)expandStatusBarView { + __weak typeof(self) weakSelf = self; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + weakSelf.statusBarBottomConstraint.active = YES; + weakSelf.statusBarHeightConstraint.active = NO; + [weakSelf.view layoutIfNeeded]; + }]; +} + +- (void)collapseStatusBarView { + __weak typeof(self) weakSelf = self; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + weakSelf.statusBarBottomConstraint.active = NO; + weakSelf.statusBarHeightConstraint.active = YES; + [weakSelf.view layoutIfNeeded]; + }]; +} + #pragma mark - View Cyle - (void)loadView { @@ -844,6 +892,17 @@ CGFloat const PanelAnimationDuration = 0.2; view.translatesAutoresizingMaskIntoConstraints = NO; self.view = view; + // Status bar + UIView *statusBarView = [MVMCoreUICommonViewsUtility commonView]; + statusBarView.backgroundColor = [UIColor whiteColor]; + [self.view addSubview:statusBarView]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[statusBarView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView)]]; + id topGuide = view.safeAreaLayoutGuide; + self.statusBarBottomConstraint = [NSLayoutConstraint constraintWithItem:statusBarView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:topGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; + self.statusBarBottomConstraint.active = YES; + self.statusBarHeightConstraint = [statusBarView.heightAnchor constraintEqualToConstant:0]; + self.statusBarView = statusBarView; + // Top Alert MVMCoreUITopAlertView *topAlertView = nil; if ([[CoreUIObject sharedInstance].globalTopAlertDelegate respondsToSelector:@selector(getTopAlertView)]) { @@ -889,9 +948,9 @@ CGFloat const PanelAnimationDuration = 0.2; self.bottomProgressBarHeightConstraint = bottomProgressHeight; if (topAlertView) { - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[topAlertView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topAlertView, mainView, progressView)]]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[topAlertView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,topAlertView, mainView, progressView)]]; } else { - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(mainView, progressView)]]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[mainView]-0-[progressView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,mainView, progressView)]]; } // Add tabbar if we have it. @@ -919,7 +978,6 @@ CGFloat const PanelAnimationDuration = 0.2; - (void)viewDidLoad { [super viewDidLoad]; - [self.topAlertView pinATopViewController:self]; // Creates the back button self.backButton = [[UIBarButtonItem alloc] initWithImage:[self imageForBackButton] style:UIBarButtonItemStylePlain target:self action:@selector(backButtonPressed:)]; @@ -969,8 +1027,9 @@ CGFloat const PanelAnimationDuration = 0.2; - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - [self forceHideBothDrawers]; self.transitionWidth = @(size.width); + + [self forceHideBothDrawers]; void (^animate)(id) = nil; if (self.leftPanelIsAccessible || self.rightPanelIsAccessible) { @@ -1002,36 +1061,6 @@ CGFloat const PanelAnimationDuration = 0.2; } } -- (UIStatusBarStyle)preferredStatusBarStyle { - if (self.topAlertView.topAlertObject) { - return self.topAlertView.statusBarStyle; - } else { - UIStatusBarStyle style = [self getDefaultStatusBarStyle]; - [self.topAlertView resetDefaultBackgroundColor:[self getDefaultStatusBarBackgroundColor] basedOnStatusBarStyle:style]; - return style; - } -} - -- (UIStatusBarStyle)getDefaultStatusBarStyle { - UIViewController *viewController = [self getCurrentDetailViewController]; - if ([viewController conformsToProtocol:@protocol(MVMCoreUIDetailViewProtocol)] - && [viewController respondsToSelector:@selector(defaultStatusBarStyle)] - && [((UIViewController *)viewController) defaultStatusBarStyle]) { - return [((UIViewController *)viewController) defaultStatusBarStyle]; - } - return UIStatusBarStyleDefault; -} - -- (UIColor *)getDefaultStatusBarBackgroundColor { - UIViewController *viewController = [self getCurrentDetailViewController]; - if ([viewController conformsToProtocol:@protocol(MVMCoreUIDetailViewProtocol)] - && [viewController respondsToSelector:@selector(defaultStatusBarBackgroundColor)] - && [((UIViewController *)viewController) defaultStatusBarBackgroundColor]) { - return [((UIViewController *)viewController) defaultStatusBarBackgroundColor]; - } - return nil; -} - - (BOOL)isAPanelShowing { return fabs(self.mainViewLeading.constant) > 1; } diff --git a/MVMCoreUI/Containers/Views/Container.swift b/MVMCoreUI/Containers/Views/Container.swift index 5b2e83eb..32c22b54 100644 --- a/MVMCoreUI/Containers/Views/Container.swift +++ b/MVMCoreUI/Containers/Views/Container.swift @@ -75,7 +75,7 @@ open class Container: View, ContainerProtocol { self.view = view } - convenience init(andContain view: UIView) { + public convenience init(andContain view: UIView) { self.init() addAndContain(view) } diff --git a/MVMCoreUI/FormUIHelpers/FormFieldEffect/ClearFormFieldEffectModel.swift b/MVMCoreUI/FormUIHelpers/FormFieldEffect/ClearFormFieldEffectModel.swift new file mode 100644 index 00000000..32a18c6b --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/FormFieldEffect/ClearFormFieldEffectModel.swift @@ -0,0 +1,68 @@ +// +// ClearFormFieldEffectModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 1/11/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ClearFormFieldEffectModel: FormFieldEffectProtocol { + + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "clearFormFieldEffect" + public var fieldKey: String = "" + public var activatedRuleIds: [String]? + public var rules: [RulesProtocol] + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ fieldKey: String, activatedRuleIds: [String], rules: [RulesProtocol]) { + self.fieldKey = fieldKey + self.activatedRuleIds = activatedRuleIds + self.rules = rules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case fieldKey + case activatedRuleIds + case rules + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey) + self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds) + self.rules = try typeContainer.decodeModels(codingKey: .rules) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(activatedRuleIds, forKey: .activatedRuleIds) + try container.encodeModels(rules, forKey: .rules) + } + + public func setEffect(validity: Bool, field: FormFieldProtocol,form: FormValidator, group: FormGroupRule) { + guard let field = field as? ClearableModelProtocol, validity else { return } + field.clear() + if let updateField = field as? UIUpdatableModelProtocol { + updateField.updateUI?() + } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormFieldEffect/DisableFormFieldEffectModel.swift b/MVMCoreUI/FormUIHelpers/FormFieldEffect/DisableFormFieldEffectModel.swift new file mode 100644 index 00000000..ea0a718e --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/FormFieldEffect/DisableFormFieldEffectModel.swift @@ -0,0 +1,66 @@ +// +// DisableFormFieldEffectModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 12/6/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class DisableFormFieldEffectModel: FormFieldEffectProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "disableFormFieldEffect" + public var fieldKey: String = "" + public var activatedRuleIds: [String]? + public var rules: [RulesProtocol] + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ fieldKey: String, _ activatedRuleIds: [String], _ rules: [RulesProtocol]) { + self.fieldKey = fieldKey + self.activatedRuleIds = activatedRuleIds + self.rules = rules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case fieldKey + case activatedRuleIds + case rules + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey) + self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds) + self.rules = try typeContainer.decodeModels(codingKey: .rules) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fieldKey, forKey: .fieldKey) + try container.encode(activatedRuleIds, forKey: .activatedRuleIds) + try container.encodeModels(rules, forKey: .rules) + } + + public func setEffect(validity: Bool, field: FormFieldProtocol, form: FormValidator, group: FormGroupRule) { + field.enabled = !validity + if let updateField = field as? UIUpdatableModelProtocol { + updateField.updateUI?() + } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormFieldEffect/DynamicRuleFormFieldEffectModel.swift b/MVMCoreUI/FormUIHelpers/FormFieldEffect/DynamicRuleFormFieldEffectModel.swift new file mode 100644 index 00000000..38bfc79c --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/FormFieldEffect/DynamicRuleFormFieldEffectModel.swift @@ -0,0 +1,62 @@ +// +// Dynamic.swift +// MVMCoreUI +// +// Created by Matt Bruce on 1/6/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class DynamicRuleFormFieldEffectModel: FormFieldEffectProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "dynamicRuleFormFieldEffect" + public var fieldKey: String = "" + public var activatedRuleIds: [String]? + public var rules: [RulesProtocol] + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ fieldKey: String, activatedRuleIds: [String], rules: [RulesProtocol]) { + self.fieldKey = fieldKey + self.activatedRuleIds = activatedRuleIds + self.rules = rules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case fieldKey + case activatedRuleIds + case rules + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey) + self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds) + self.rules = try typeContainer.decodeModels(codingKey: .rules) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fieldKey, forKey: .fieldKey) + try container.encode(activatedRuleIds, forKey: .activatedRuleIds) + try container.encodeModels(rules, forKey: .rules) + } + + public func setEffect(validity: Bool, field: FormFieldProtocol,form: FormValidator, group: FormGroupRule) { } +} + diff --git a/MVMCoreUI/FormUIHelpers/FormFieldEffect/EnableFormFieldEffectModel.swift b/MVMCoreUI/FormUIHelpers/FormFieldEffect/EnableFormFieldEffectModel.swift new file mode 100644 index 00000000..271e57ea --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/FormFieldEffect/EnableFormFieldEffectModel.swift @@ -0,0 +1,68 @@ +// +// EnableFormFieldEffectModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 12/1/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + + +public class EnableFormFieldEffectModel: FormFieldEffectProtocol { + + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "enableFormFieldEffect" + public var fieldKey: String = "" + public var activatedRuleIds: [String]? + public var rules: [RulesProtocol] + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ fieldKey: String, activatedRuleIds: [String], rules: [RulesProtocol]) { + self.fieldKey = fieldKey + self.activatedRuleIds = activatedRuleIds + self.rules = rules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case fieldKey + case activatedRuleIds + case rules + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey) + self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds) + self.rules = try typeContainer.decodeModels(codingKey: .rules) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fieldKey, forKey: .fieldKey) + try container.encode(activatedRuleIds, forKey: .activatedRuleIds) + try container.encodeModels(rules, forKey: .rules) + } + + public func setEffect(validity: Bool, field: FormFieldProtocol,form: FormValidator, group: FormGroupRule) { + field.enabled = validity + if let updateField = field as? UIUpdatableModelProtocol { + updateField.updateUI?() + } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormFieldEffect/FormFieldEffectProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldEffect/FormFieldEffectProtocol.swift new file mode 100644 index 00000000..828f9e2d --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/FormFieldEffect/FormFieldEffectProtocol.swift @@ -0,0 +1,34 @@ +// +// FormFieldEffectProtocol.swift +// MVMCoreUI +// +// Created by Matt Bruce on 12/6/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol FormFieldEffectProtocol: ModelProtocol, RulesContainerProtocol { + + /// This is the key to the form field that will take the effect + var fieldKey: String { get } + + /// This is an array of ruleIds that are a property of RulesProtocol for the array of Validation rules within the FormGroupRule.rules + /// When this effect "rules" are valid (true), the Form will add the fieldKey to the corresponding Validation rule "fields" property. + /// When this effect "rules" are invalid (false), the Form will remove the fieldKey from the corresponding Validation rule "fields" property. + var activatedRuleIds: [String]? { get } + + /// This function will execute the effect on the field passed in based on the validity. validity is the result of the array of rules run. + /// - Parameters: + /// - validity: This is run against the FormFieldAffect + /// - field: Form Field that is bound to the fieldKey + /// - form: Form that the formGroup is attached + /// - group: FormGroupRule that is attached to this effect + func setEffect(validity: Bool, field: FormFieldProtocol, form: FormValidator, group: FormGroupRule) +} + +public extension FormFieldEffectProtocol { + var type: String { Self.identifier } + static var categoryCodingKey: String { "type" } + static var categoryName: String { "\(FormFieldEffectProtocol.self)" } +} diff --git a/MVMCoreUI/FormUIHelpers/FormFieldEffect/HideFormFieldEffectModel.swift b/MVMCoreUI/FormUIHelpers/FormFieldEffect/HideFormFieldEffectModel.swift new file mode 100644 index 00000000..37929e12 --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/FormFieldEffect/HideFormFieldEffectModel.swift @@ -0,0 +1,66 @@ +// +// HideFormFieldEffectModel.swift +// MVMCoreUI +// +// Created by Matt Bruce on 12/6/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class HideFormFieldEffectModel: FormFieldEffectProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "hideFormFieldEffect" + public var fieldKey: String = "" + public var activatedRuleIds: [String]? + public var rules: [RulesProtocol] + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ fieldKey: String, _ activatedRuleIds: [String], _ rules: [RulesProtocol]) { + self.fieldKey = fieldKey + self.activatedRuleIds = activatedRuleIds + self.rules = rules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case fieldKey + case activatedRuleIds + case rules + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey) + self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds) + self.rules = try typeContainer.decodeModels(codingKey: .rules) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fieldKey, forKey: .fieldKey) + try container.encode(activatedRuleIds, forKey: .activatedRuleIds) + try container.encodeModels(rules, forKey: .rules) + } + + public func setEffect(validity: Bool, field: FormFieldProtocol, form: FormValidator, group: FormGroupRule) { + field.enabled = validity + if let updateField = field as? UIUpdatableModelProtocol { + updateField.updateUI?() + } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift index afdde887..01835ed9 100644 --- a/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormFieldProtocol.swift @@ -16,11 +16,32 @@ public protocol FormFieldProtocol: FormItemProtocol { /// A place to store the initial value of the field for checking if the value has changed. var baseValue: AnyHashable? { get set } + ///Bool to determine a state that is different from disabled. Readonly values will be sent + ///to the server where disabled fields are not + var readOnly: Bool { get set } + /// Returns the value of the field. Used for validations and possibly for sending to server. func formFieldValue() -> AnyHashable? + } -extension FormFieldProtocol { +public extension FormFieldProtocol { var baseValue: AnyHashable? { nil } } + +public class FormFieldValidity{ + public var fieldKey: String + public var valid: Bool = true + public var errorMessages: [String] = [] + + public init(_ fieldKey: String){ + self.fieldKey = fieldKey + } + + public func addError(message: String?){ + if let message = message { + self.errorMessages.append(message) + } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormGroupWatcherFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormGroupWatcherFieldProtocol.swift index 624ea374..6219b0b5 100644 --- a/MVMCoreUI/FormUIHelpers/FormGroupWatcherFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormGroupWatcherFieldProtocol.swift @@ -9,6 +9,11 @@ import Foundation -public protocol FormGroupWatcherFieldProtocol: FormItemProtocol { - func setValidity(_ valid: Bool, group: FormGroupRule) +public protocol FormGroupWatcherFieldProtocol: FormItemProtocol, UIUpdatableModelProtocol {} + +public extension FormGroupWatcherFieldProtocol { + func setValidity(_ valid: Bool){ + self.enabled = valid; + updateUI?() + } } diff --git a/MVMCoreUI/FormUIHelpers/FormItemProtocol.swift b/MVMCoreUI/FormUIHelpers/FormItemProtocol.swift index 0c7f71bd..081f736e 100644 --- a/MVMCoreUI/FormUIHelpers/FormItemProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormItemProtocol.swift @@ -9,7 +9,7 @@ import Foundation -public protocol FormItemProtocol { +public protocol FormItemProtocol: EnableableModelProtocol { /// The group name to used to group this item for validation, enableability, and value getting. var groupName: String { get set } } diff --git a/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift b/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift index 3fc47d25..289bcb39 100644 --- a/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormRuleWatcherFieldProtocol.swift @@ -10,5 +10,5 @@ import Foundation public protocol FormRuleWatcherFieldProtocol { - func setValidity(_ valid: Bool, rule: RulesProtocol) + func setValidity(_ valid: Bool, errorMessage: String?) } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 297d1f9e..bf2f8c2f 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -11,6 +11,11 @@ import MVMCore @objcMembers public class FormValidator: NSObject { + + public enum ValidationError: Error { + case other(error: String) + } + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -80,38 +85,113 @@ import MVMCore /// Validates all rule groups. Returns if valid public func validate() -> Bool { - var valid = true guard let formRules = formRules else { return valid } for group in formRules { - let groupValid = validateGroup(group) - valid = valid && groupValid + + var groupValid = false + do { + groupValid = try validateGroup(group) + } catch { + if let err = MVMCoreErrorObject.createErrorObject(for: error, location: "FormValidator"){ + MVMCoreLoggingHandler.shared()?.addError(toLog: err) + fatalError(err.description) + } + } + valid = valid && groupValid } return valid } + + /// Validates a FormGroupRule + /// - Parameters: + /// - group: FormGroupRule being validated + /// - counter: keeps track of how many times causes another group validation + /// - Returns: validity for the FormGroupRule.rules + public func validateGroup(_ group: FormGroupRule, counter: Int = 0) throws -> Bool { + + //loop the effects + group.effects?.forEach({ effect in + //get the fieldKey for the effect + if let effected = fields[effect.fieldKey] { + //get the validity + let effectTuple = effect.validate(fields) - /// Validates a given rule group. Returns if valid - public func validateGroup(_ group: FormGroupRule) -> Bool { - // Validate each rule. - var valid = true - var previousValidity: [String: Bool] = [:] - for rule in group.rules { - let tuple = rule.validate(fields, previousValidity) - let isValidRule = tuple.valid - let returnedValidity = tuple.fieldValidity - previousValidity = previousValidity.merging(returnedValidity) { (_, new) in new } - valid = valid && isValidRule + //set the effect with the validation + effect.setEffect(validity: effectTuple.valid, field: effected, form: self, group: group) + + //update the group form rules + if let ruleIds = effect.activatedRuleIds { + self.updateRules(for: group, with: effectTuple.valid, for: effect.fieldKey, and: ruleIds) + } + } + }) + + //validate the form + let tuple = group.validate(fields) + + //set the validity for the fields + fields.forEach { (key: String, value: FormFieldProtocol) in + if let formField = value as? FormRuleWatcherFieldProtocol, + let fieldValidity = tuple.fieldValidity[key] { + formField.setValidity(fieldValidity.valid, errorMessage: fieldValidity.errorMessages.last) + } } // Notify the group watchers of validity. - for watcher in groupWatchers { - if watcher.groupName == group.groupName { - watcher.setValidity(valid, group: group) + for watcher in groupWatchers.filter({$0.groupName == group.groupName}) { + watcher.setValidity(tuple.valid) + } + + return tuple.valid + + } + + /// Updates the Fields in which a specific rule within a FormGroupRule validates against. + /// - Parameters: + /// - group: FormGroupRule to update + /// - validity: If you the fieldKey should be added or removed from a rule + /// - fieldKey: FieldKey that will be added or removed from a rule + /// - ruleIds: Array of ruleIds to add or remove the fieldKey into the rule.fields property + @discardableResult + public func updateRules(for group: FormGroupRule, with validity: Bool, for fieldKey: String, and ruleIds: [String]) -> Bool{ + //update the group rules based on the validation of this rule to show/hide + var ruleChange = false + ruleIds.forEach { ruleId in + //only change a rule found + if var mutableRule = group.rules.first(where: { rule in return rule.ruleId == ruleId}) { + //make a copy of the fields + var mutableFields = mutableRule.fields + //if the validity is true + if validity { + //we need to ensure this fieldKey is included in the found rule + //therefore we mush append the fieldKey to the fields array + if !mutableRule.fields.contains(where: { field in return field == fieldKey }){ + mutableFields.append(fieldKey) + mutableRule.fields = mutableFields + //a rule was changed, therefore we need + //to re-run the validation on the group + ruleChange = true + } + } else { + //the validity is false, therefore we must ensure + //that the fieldKey does NOT exist in the found + //rule fields + if let fieldKeyIndex = mutableFields.firstIndex(of: fieldKey) { + mutableFields.remove(at: fieldKeyIndex) + mutableRule.fields = mutableFields + //a rule was changed, therefore we need + //to re-run the validation on the group + ruleChange = true + } + } } } - return valid + + //if any rules updated, run the validateGroup again + return ruleChange } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift index 56a5a2cf..ee6ca7de 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/FormGroupRule.swift @@ -9,22 +9,22 @@ import Foundation - -open class FormGroupRule: Codable { +open class FormGroupRule: RulesContainerProtocol, Codable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - var groupName: String - var rules: [RulesProtocol] - + public var groupName: String + public var rules: [RulesProtocol] + public var effects: [FormFieldEffectProtocol]?; //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - init(_ groupName: String, _ rules: [RulesProtocol]) { + public init(_ groupName: String, _ rules: [RulesProtocol], _ effects: [FormFieldEffectProtocol]) { self.groupName = groupName self.rules = rules + self.effects = effects } //-------------------------------------------------- @@ -34,6 +34,7 @@ open class FormGroupRule: Codable { private enum CodingKeys: String, CodingKey { case groupName case rules + case effects } //-------------------------------------------------- @@ -44,6 +45,7 @@ open class FormGroupRule: Codable { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) self.groupName = try typeContainer.decode(String.self, forKey: .groupName) self.rules = try typeContainer.decodeModels(codingKey: .rules) + self.effects = try typeContainer.decodeModelsIfPresent(codingKey: .effects) } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift index aa80f5b0..5b4f320f 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAllValueChangedModel.swift @@ -15,9 +15,18 @@ public class RuleAllValueChangedModel: RulesProtocol { public static var identifier: String = "allValueChanged" public var type: String = RuleAllValueChangedModel.identifier + public var ruleId: String? public var errorMessage: [String: String]? public var fields: [String] + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(with fields: [String]) { + self.fields = fields + } + //-------------------------------------------------- // MARK: - Validation //-------------------------------------------------- diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyModelProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyModelProtocol.swift new file mode 100644 index 00000000..bdfd937c --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyModelProtocol.swift @@ -0,0 +1,45 @@ +// +// RuleAnyModelProtocol.swift +// MVMCoreUI +// +// Created by Matt Bruce on 3/9/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// RuleAnyModelProtocol was abstracted to 1 place since this code was currently +/// duplicated in 2 classes. +/// This protocol should be used for the rules that need to Loop through all of the fields +/// and if ANY formField's isValid == TRUE, the Rule is then TRUE +public protocol RuleAnyModelProtocol: RulesProtocol{} + +extension RuleAnyModelProtocol { + + /// Overriding the RulesProtocol default implementation + public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity]) { + + for formKey in fields { + guard let formField = fieldMolecules[formKey] else { continue } + + var fieldValidity = isValid(formField) + // If past rule is invalid for a field, the current rule should not flip the validity of a field + if let validity = previousFieldValidity[formKey], !validity.valid, fieldValidity { + fieldValidity = false + } + + // If TRUE the RULE is TRUE, even if there are many fields to check. + if fieldValidity { + return (true, previousFieldValidity) + } + } + + // if the rule breaks all fields should be set to false + fields.forEach { (formKey) in + previousFieldValidity[formKey]?.valid = false + previousFieldValidity[formKey]?.addError(message: errorMessage?[formKey]) + } + return (valid: false, fieldValidity: previousFieldValidity) + } + +} diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift index 67f1d754..c8b7b055 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyRequiredModel.swift @@ -8,17 +8,25 @@ import UIKit - -public class RuleAnyRequiredModel: RulesProtocol { +public class RuleAnyRequiredModel: RuleAnyModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public static var identifier: String = "anyRequired" public var type: String = RuleRequiredModel.identifier + public var ruleId: String? public var fields: [String] public var errorMessage: [String: String]? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(fields: [String]) { + self.fields = fields + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -36,27 +44,4 @@ public class RuleAnyRequiredModel: RulesProtocol { return false } - public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) { - - var previousValidity: [String: Bool] = [:] - for formKey in fields { - guard let formField = fieldMolecules[formKey] else { continue } - - var fieldValidity = isValid(formField) - // If past rule is invalid for a field, the current rule should not flip the validity of a field - if let validity = previousFieldValidity[formKey], !validity, fieldValidity { - fieldValidity = false - } - - if fieldValidity { - return (fieldValidity, previousValidity) - } - } - - // if the rule breaks all fields should be set to false - fields.forEach { (formKey) in - previousValidity[formKey] = false - } - return (valid: false, fieldValidity: previousValidity) - } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift index cd8e2eda..d52f59e2 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift @@ -8,17 +8,25 @@ import Foundation - -public class RuleAnyValueChangedModel: RulesProtocol { +public class RuleAnyValueChangedModel: RuleAnyModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public static var identifier: String = "anyValueChanged" public var type: String = RuleAnyValueChangedModel.identifier + public var ruleId: String? public var errorMessage: [String: String]? public var fields: [String] + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(fields: [String]) { + self.fields = fields + } + //-------------------------------------------------- // MARK: - Validation //-------------------------------------------------- @@ -26,26 +34,4 @@ public class RuleAnyValueChangedModel: RulesProtocol { public func isValid(_ formField: FormFieldProtocol) -> Bool { return formField.baseValue != formField.formFieldValue() } - - public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) { - var previousValidity: [String: Bool] = [:] - for formKey in fields { - guard let formField = fieldMolecules[formKey] else { continue } - var fieldValidity = isValid(formField) - // If past rule is invalid for a field, the current rule should not flip the validity of a field - if let validity = previousFieldValidity[formKey], !validity, fieldValidity { - fieldValidity = false - } - - if fieldValidity { - return (true, previousValidity) - } - } - - // if the rule breaks all fields should be set to false - fields.forEach { (formKey) in - previousValidity[formKey] = false - } - return (valid: false, fieldValidity: previousValidity) - } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleCompareModelProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleCompareModelProtocol.swift new file mode 100644 index 00000000..42022636 --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleCompareModelProtocol.swift @@ -0,0 +1,55 @@ +// +// RuleCompareModelProtocol.swift +// MVMCoreUI +// +// Created by Matt Bruce on 3/9/22. +// Copyright © 2022 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// RuleCompareModelProtocol is meant to be used for rules that compare +/// 2 FormField's values. It is up to the Implementing class to determine what +/// occurs within the "compare" method that is required. This can be anything +/// that returns a Bool. More than likley it will be a <,>, <=, =>, == comparison. +public protocol RuleCompareModelProtocol: RulesProtocol { + associatedtype CompareType + func compare(lhs: CompareType?, rhs: CompareType?) -> Bool +} + +extension RuleCompareModelProtocol { + + /// This overrides the RulesProtocol default implementation to then use your class implementation's "compare" method. + /// A requirement of this rule is that the fields array contains at least 2 fieldKeys and it will pull out these formFields with ONLY + /// the first 2 keys to send to the compare method your class will be implementing. + public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity]) { + var valid = false + + guard fields.count > 1, let firstFormField = fieldMolecules[fields[0]], + let secondFormField = fieldMolecules[fields[1]] else { + return (valid: true, previousFieldValidity) + } + + let result = compare(lhs: firstFormField.formFieldValue() as? CompareType, rhs: secondFormField.formFieldValue() as? CompareType) + + let formKey = fields[1] + + //only check the 2nd value + if result { + valid = true + + // If past rule is invalid for a field, the current rule should not flip the validity of a field + if let validity = previousFieldValidity[formKey], !validity.valid, valid { + valid = false + } + previousFieldValidity[formKey]?.valid = valid + + } else { //false + previousFieldValidity[formKey]?.valid = valid + previousFieldValidity[formKey]?.addError(message: errorMessage?[formKey]) + } + + return (valid: valid, fieldValidity: previousFieldValidity) + + } +} diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 8326cf4f..ab7f21b6 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -9,16 +9,25 @@ import Foundation -public class RuleEqualsIgnoreCaseModel: RulesProtocol { +public class RuleEqualsIgnoreCaseModel: RuleCompareModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public static var identifier: String = "equalsIgnoreCase" public var type: String = RuleEqualsIgnoreCaseModel.identifier + public var ruleId: String? public var fields: [String] public var errorMessage: [String: String]? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(fields: [String]) { + self.fields = fields + } + //-------------------------------------------------- // MARK: - Validation //-------------------------------------------------- @@ -26,40 +35,12 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { public func isValid(_ formField: FormFieldProtocol) -> Bool { return false } - - public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) { - var valid = false - var compareText: String? - - var previousValidity: [String: Bool] = [:] - for formKey in fields { - guard let formField = fieldMolecules[formKey] else { continue } - - guard let compareString = compareText else { - compareText = formField.formFieldValue() as? String - continue - } - - if let fieldValue = formField.formFieldValue() as? String, - compareString.caseInsensitiveCompare(fieldValue) == .orderedSame { - valid = true - - var fieldValidity = valid - // If past rule is invalid for a field, the current rule should not flip the validity of a field - if let validity = previousFieldValidity[formKey], !validity, fieldValidity { - fieldValidity = false - } - - for formKey in fields { - guard let formField = fieldMolecules[formKey] else { continue } - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(true, rule: self) - } - break - } - - previousValidity[formKey] = valid - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) + + ///RuleCompareModelProtocol Method + public func compare(lhs: String?, rhs: String?) -> Bool { + guard let rhs = rhs else { + return false } - return (valid: valid, fieldValidity: previousValidity) + return lhs?.caseInsensitiveCompare(rhs) == .orderedSame } } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift index a405e2e7..be7391ec 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsModel.swift @@ -8,17 +8,25 @@ import Foundation - -public class RuleEqualsModel: RulesProtocol { +public class RuleEqualsModel: RuleCompareModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public static var identifier: String = "equals" public var type: String = RuleEqualsModel.identifier + public var ruleId: String? public var fields: [String] public var errorMessage: [String: String]? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(fields: [String]) { + self.fields = fields + } + //-------------------------------------------------- // MARK: - Validation //-------------------------------------------------- @@ -26,34 +34,10 @@ public class RuleEqualsModel: RulesProtocol { public func isValid(_ formField: FormFieldProtocol) -> Bool { return false } - - public func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) { - var valid = true - var compareValue: AnyHashable? - var previousValidity: [String: Bool] = [:] - - for formKey in fields { - guard let formField = fieldMolecules[formKey] else { continue } - - if compareValue == nil { - compareValue = formField.formFieldValue() - continue - } - - if compareValue != formField.formFieldValue() { - valid = false - previousValidity[formKey] = valid - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) - break - } else { - var fieldValidity = valid - // If past rule is invalid for a field, the current rule should not flip the validity of a field - if let validity = previousFieldValidity[formKey], !validity, fieldValidity { - fieldValidity = false - } - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self) - } - } - return (valid: valid, fieldValidity: previousValidity) + + ///RuleCompareModelProtocol Method + public func compare(lhs: AnyHashable?, rhs: AnyHashable?) -> Bool { + return lhs == rhs } + } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift index 98bec293..e654210a 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRegexModel.swift @@ -6,7 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // - public class RuleRegexModel: RulesProtocol { //-------------------------------------------------- // MARK: - Properties @@ -14,10 +13,20 @@ public class RuleRegexModel: RulesProtocol { public static var identifier: String = "regex" public var type: String = RuleRegexModel.identifier + public var ruleId: String? public var fields: [String] public var regex: String public var errorMessage: [String: String]? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(fields: [String], regex: String) { + self.fields = fields + self.regex = regex + } + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -30,4 +39,5 @@ public class RuleRegexModel: RulesProtocol { return false } + } diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift index 77cdf254..a0b51600 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift @@ -16,9 +16,18 @@ public class RuleRequiredModel: RulesProtocol { public static var identifier: String = "allRequired" public var type: String = RuleRequiredModel.identifier + public var ruleId: String? public var errorMessage: [String: String]? public var fields: [String] + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(fields: [String]) { + self.fields = fields + } + //-------------------------------------------------- // MARK: - Validation //-------------------------------------------------- diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift index db8614ac..d1326ba9 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RulesProtocol.swift @@ -16,7 +16,11 @@ public enum RulesCodingKey: String, CodingKey { public protocol RulesProtocol: ModelProtocol { // The type of rule var type: String { get } - + + //id for the rule + var ruleId: String? { get } + + //error message to show for the rule var errorMessage: [String: String]? { get } // The fields that this rule applies to. @@ -26,7 +30,7 @@ public protocol RulesProtocol: ModelProtocol { func isValid(_ formField: FormFieldProtocol) -> Bool // Validates the rule and returns the result. - func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) + func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity]) } public extension RulesProtocol { @@ -38,20 +42,57 @@ public extension RulesProtocol { static var categoryName: String { "\(RulesProtocol.self)" } // Individual rule can override the function to validate based on the rule type. - func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: Bool]) -> (valid: Bool, fieldValidity: [String: Bool]) { + func validate(_ fieldMolecules: [String: FormFieldProtocol],_ previousFieldValidity: [String: FormFieldValidity]) -> (valid: Bool, fieldValidity: [String: FormFieldValidity]) { var valid = true - var previousValidity: [String: Bool] = [:] for formKey in fields { guard let formField = fieldMolecules[formKey] else { continue } + //check the field isValid var fieldValidity = isValid(formField) + + //add the error message if it exists + if fieldValidity == false { + previousFieldValidity[formKey]?.addError(message: errorMessage?[formKey]) + } + // If past rule is invalid for a field, the current rule should not flip the validity of a field - if let validity = previousFieldValidity[formKey], !validity, fieldValidity { + if let validity = previousFieldValidity[formKey], !validity.valid, fieldValidity { fieldValidity = false } - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self) + //set the valid for the field + previousFieldValidity[formKey]?.valid = fieldValidity + + //set the full validity valid = valid && fieldValidity - previousValidity[formKey] = fieldValidity + } + return (valid: valid, fieldValidity: previousFieldValidity) + } +} + +public protocol RulesContainerProtocol{ + var rules: [RulesProtocol] { get } +} + +public extension RulesContainerProtocol { + /// This validation for Rules for the Validation or for Effects. + /// - Parameters: + /// - fields: Fields for the group + /// - setValidity: Since this function is for validation, this bool determines if you should set the FormFields.setValidity for a rule. + /// this method can be called for Form Validation and Effect Validation (this doesn't affect the submital of the form) + /// - Returns: Tuple(valid, fieldValidity) + /// - valid: bool for all rules + /// - fieldValidity: accumulation of all fieldKey: valid + func validate(_ fields: [String: FormFieldProtocol]) -> (valid: Bool, fieldValidity: [String:FormFieldValidity]) { + // Validate each rule. + var valid = true + var previousValidity: [String: FormFieldValidity] = [:] + fields.keys.forEach { key in + previousValidity[key] = FormFieldValidity(key) + } + for rule in self.rules { + //validate the rule against the fields + let tuple = rule.validate(fields, previousValidity) + valid = valid && tuple.valid } return (valid: valid, fieldValidity: previousValidity) } diff --git a/MVMCoreUI/FormUIHelpers/UIUpdatableModelProtocol.swift b/MVMCoreUI/FormUIHelpers/UIUpdatableModelProtocol.swift new file mode 100644 index 00000000..e67ae896 --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/UIUpdatableModelProtocol.swift @@ -0,0 +1,13 @@ +// +// Updatable.swift +// MVMCoreUI +// +// Created by Matt Bruce on 12/1/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol UIUpdatableModelProtocol { + var updateUI: ActionBlock? { get set } +} diff --git a/MVMCoreUI/Info.plist b/MVMCoreUI/Info.plist index e1fe4cfb..82790df0 100644 --- a/MVMCoreUI/Info.plist +++ b/MVMCoreUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift b/MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift new file mode 100644 index 00000000..233a1f57 --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift @@ -0,0 +1,144 @@ +// +// SubNavInteractor.swift +// MobileFirstFramework +// +// Created by Matt Bruce on 10/22/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc public protocol SubNavSwipeNavigationProtocol: AnyObject { + func swipeLeft() + func swipeRight() + func update(percentage: CGFloat) +} + +fileprivate enum SubNavPanningDirection : Int { + case left + case right + case none +} + +@objcMembers public class SubNavInteractor: UIPercentDrivenInteractiveTransition { + + //// true while panning + public var panning: Bool = false + + ////Pan gesture instance + public var panGesture: UIPanGestureRecognizer = .init() + + ////set pannable percentage 0 to 1 + public var pannablePercentage: CGFloat = 0.0 + + //// can be used to keep track of if we are .. + public var interactive: Bool = false + + //private + private var shouldCompleteTransition: Bool = false + private weak var delegate: SubNavSwipeNavigationProtocol? + private var panningDirection: SubNavPanningDirection = .none + + ////constructor + public convenience init(viewController: (UIViewController & SubNavSwipeNavigationProtocol)) { + self.init(viewController: viewController, delegate: viewController) + } + + public init(viewController: UIViewController, delegate: SubNavSwipeNavigationProtocol) { + pannablePercentage = 0.15 + self.delegate = delegate + super.init() + panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(handlePanGesture)); + viewController.view.addGestureRecognizer(panGesture) + } + + //overrides + public override func cancel() { + if interactive { + delegate?.update(percentage: 0) + super.cancel() + } + interactive = false + panning = false + shouldCompleteTransition = false + } + + public override func finish() { + if interactive { + delegate?.update(percentage: 1) + super.finish() + } + interactive = false + panning = false + shouldCompleteTransition = false + } + + //private + @objc private func handlePanGesture(_ pan: UIPanGestureRecognizer) { + guard let panView = pan.view else { return } + + let velocityX = pan.velocity(in: panView).x + let translation = pan.translation(in: panView) + var percentage = translation.x / panView.bounds.width + let locationInView = pan.location(in: panView) + + // Simulates an edge gesture by only accepting pans at the edge of the screen. Needed because edge gesture doesn't work nicely with extended menus such as on ipad. + let frame = panView.frame + let pannableFrameLeft = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width * pannablePercentage, height: frame.size.height) + let pannableFrameRight = CGRect(x: frame.origin.x + frame.size.width * (1 - pannablePercentage), y: frame.origin.y, width: frame.size.width * pannablePercentage, height: frame.size.height) + + switch (pan.state) { + + case .began: + // Begin the transition to the next page. + panning = true + if velocityX < 0 && pannableFrameRight.contains(locationInView) { + delegate?.swipeLeft() + panningDirection = .left + } else if velocityX > 0 && pannableFrameLeft.contains(locationInView){ + delegate?.swipeRight() + panningDirection = .right + } + + case .changed: + guard interactive else { return } + + if panningDirection == .right && translation.x < 0 || + panningDirection == .left && translation.x > 0 { + percentage = 0 + } + + if percentage < 0 { + percentage = -percentage; + } + if percentage > 1.00 { + percentage = 1.00; + } + shouldCompleteTransition = percentage > 0.65; + update(percentage) + delegate?.update(percentage: percentage) + + case .cancelled: + cancel() + + case .ended: + if ((percentage < 0) != (velocityX < 0)) && abs(velocityX) > 50 { + // If we are moving back toward the previous view we want to cancel. (the speed threshold is for shaky hands) + cancel() + } else if abs(velocityX) > 300.0 || shouldCompleteTransition { + // Need to cancel the transition if we pass the threshold, but from a opposite direction(e.g. Start panning from left edge to right, then move back, pass the start x location, end with a high velocityX) + if ((panningDirection == .right) && translation.x < 0) || ((panningDirection == .left) && translation.x > 0) { + cancel() + } else { + // If we pass the speed or screen percentage thresholds, move on to the next screen. + finish() + } + } else { + cancel() + } + + default: + return + } + } +} diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift new file mode 100644 index 00000000..f5c4023a --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -0,0 +1,342 @@ +// +// SubNavManagerController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/22/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, TabsDelegate, MVMCorePresentationDelegateProtocol, SubNavSwipeNavigationProtocol { + + /// The current managed view controller + private var viewController: UIViewController + + /// A list of cached controllers. + private var viewControllers: [UIViewController?] + + /// Used to layout the ui. + public lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [tabs, subNavigationController.view]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.isAccessibilityElement = false + stackView.axis = .vertical + return stackView + }() + + private var tabsModel: TabsModel + public lazy var tabs: Tabs = { + let tabs = Tabs(model: tabsModel, delegateObjectIVar, nil) + tabs.delegate = self + return tabs + }() + + public lazy var subNavigationController: UINavigationController = { + let subNavigationController = SubNavManagerNavigationController(rootViewController: viewController) + subNavigationController.view.translatesAutoresizingMaskIntoConstraints = false + return subNavigationController + }() + + /// Interactor for swiping. + public var customInteractor: SubNavInteractor? + + /// The index to go to. + private var index: Int? + + /// Flag for if we need to call a track action. + private var needToTrackTabSelect = false + + public init(viewController: UIViewController, tabsModel: TabsModel, loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) { + self.viewController = viewController + self.tabsModel = tabsModel + viewControllers = [UIViewController?](repeating: nil, count: tabsModel.tabs.count) + viewControllers[tabsModel.selectedIndex] = viewController + super.init(nibName: nil, bundle: nil) + setup(with: loadObject, shouldEnableSwipeGestures: shouldEnableSwipeGestures) + } + + public init(viewControllers: [UIViewController], tabsModel: TabsModel, loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) { + self.tabsModel = tabsModel + self.viewControllers = viewControllers + viewController = viewControllers[tabsModel.selectedIndex] + super.init(nibName: nil, bundle: nil) + setup(with: loadObject, shouldEnableSwipeGestures: shouldEnableSwipeGestures) + } + + func setup(with loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) { + self.loadObject = loadObject + pageType = loadObject.pageType + if shouldEnableSwipeGestures { + customInteractor = SubNavInteractor(viewController: self) + } + if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { + MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func loadView() { + let view = View() + view.translatesAutoresizingMaskIntoConstraints = true + + addChild(subNavigationController) + view.addSubview(stackView) + subNavigationController.didMove(toParent: self) + + NSLayoutConstraint.activate([ + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: stackView.trailingAnchor), + stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + view.bottomAnchor.constraint(equalTo: stackView.bottomAnchor) + ]) + self.view = view + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Notify the view controller it is showing. + if manager == nil { + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self) + } + } + + open override func pageShown() { + // Currently not calling super until we can decouple page shown logics for managers. + hideNavigationBarLine(true) + } + + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + // Notify showing view we will disappear. + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.managerWillDisappear?(self) + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + hideNavigationBarLine(true) + } + + /// Hides/Shows the navigation bar for the page. + open func hideNavigationBarLine(_ isHidden: Bool) { + guard self == navigationController?.topViewController else { return } + var color = UIColor.clear + if !isHidden, + let backgroundColor = (getCurrentViewController() as? PageProtocol)?.pageModel?.navigationBar?.line?.backgroundColor?.uiColor { + color = backgroundColor + } + navigationController?.navigationBar.standardAppearance.shadowColor = color + navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor = color + } + + open override func updateViews() { + super.updateViews() + if screenSizeChanged() { + tabs.updateView(view.bounds.size.width) + } + } + + // MARK: - MVMCoreLoadDelegateProtocol + + open func shouldContinue(toErrorPage loadObject: MVMCoreLoadObject, error: MVMCoreErrorObject?) -> Bool { + // Push error screens so they do not replace the tab page. + loadObject.requestParameters?.navigationController = navigationController + loadObject.requestParameters?.loadStyle = .push + return true + } + + // MARK: - Action Related + + /// Logs the action for the selected tab. + open func trackSelectTab() { + guard let action = tabs.tabsModel?.tabs[tabs.selectedIndex].action, + let json = MVMCoreUIActionHandler.shared()?.convertActionToJSON(action, delegateObject: delegateObjectIVar) else { return } + MVMCoreUIActionHandler.shared()?.logAction(json, additionalData: getAdditionalDataForNewTabLoad(indexPath: IndexPath(row: tabs.selectedIndex, section: 0)), delegateObject: delegateObjectIVar) + } + + /// Allow override of additioonal data for tab press action + open func getAdditionalDataForNewTabLoad(indexPath: IndexPath) -> [AnyHashable: Any]? { + return ["tabBarPressed": true, KeySourceModel: tabsModel] + } + + /// Allows modification of requestParameters object for openPage request + open func getRequestParametersForNewTabLoad(requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) -> MVMCoreRequestParameters { + requestParameters.navigationController = subNavigationController + requestParameters.loadStyle = .replaceCurrent + requestParameters.tabWasPressed = true + return requestParameters + } + + open override func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { + var requestParameters = requestParameters + guard let additionalData = additionalData, + additionalData.boolForKey("tabBarPressed") else { + // Pass to delegate + if (viewController as? MVMCoreActionDelegateProtocol)?.handleOpenPage?(for: requestParameters, actionInformation: actionInformation, additionalData: additionalData) == nil { + super.handleOpenPage(for: requestParameters, actionInformation: actionInformation, additionalData: additionalData) + } + return + } + // Allow opportunity to modify parameters. + requestParameters = getRequestParametersForNewTabLoad(requestParameters: requestParameters, actionInformation: actionInformation, additionalData: additionalData) + super.handleOpenPage(for: requestParameters, actionInformation: actionInformation, additionalData: additionalData) + } + + // MARK: - MVMCorePresentationDelegateProtocol + + public func navigationController(_ navigationController: UINavigationController, interactiveTransitionWasCanceled canceled: Bool) { + needToTrackTabSelect = false + index = nil + } + + public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + guard navigationController == subNavigationController, + let index = index else { return nil } + + // Tab bars animate left and right navigation accordingly. + if tabs.selectedIndex < index { + return SubNavSwipeAnimator(swipingDirection: .left, controller: self) + } else if tabs.selectedIndex > index { + return SubNavSwipeAnimator(swipingDirection: .right, controller: self) + } else { + return nil + } + } + + public func navigationController(_ navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + // Only percent interact if we've already loaded the view controller + guard let customInteractor = customInteractor, + let index = index, + customInteractor.panning, + viewControllers[index] != nil else { return nil } + customInteractor.interactive = true + return customInteractor + } + + /// Handles when the controller has committed to be changed. + open func commitTo(controller: UIViewController) { + guard let index = index, + index != tabs.selectedIndex else { return } + viewController = controller + pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType + if let viewController = getCurrentViewController() { + manager?.willDisplay?(viewController) + } + tabs.selectIndex(index, animated: true) + self.index = nil + hideNavigationBarLine(true) + } + + public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) { + guard navigationController == subNavigationController else { return } + + if let viewController = viewController as? UIViewController & MVMCoreViewManagerViewControllerProtocol & MVMCoreViewControllerProtocol { + MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: viewController) + + // Cache the controller. + if viewController.shouldCacheInManager?() ?? true, + let index = index { + viewControllers[index] = viewController + } + } + + // Wait to consider page loaded until after transition if we are custom animating swipe, otherwise do it now. + guard customInteractor?.interactive != true else { return } + commitTo(controller: viewController) + } + + public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { + guard navigationController == subNavigationController else { return } + // Need to track swipe action. + if needToTrackTabSelect { + needToTrackTabSelect = false + trackSelectTab() + } + + // Consider the page loaded if we have not already + commitTo(controller: viewController) + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self) + if let viewController = getCurrentViewController() { + manager?.displayedViewController?(viewController) + } + } + + // MARK: - TabsDelegate + + // We will manually manage tab selection due to possible swipe interactions. + open func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool { + guard tabs.selectedIndex != indexPath.row else { return false } + index = indexPath.row + if let controller = viewControllers[indexPath.row] { + // Load controller from the cache + needToTrackTabSelect = true + MVMCoreNavigationHandler.shared()?.replaceTopViewController(with: controller, navigationController: subNavigationController, animated: true, delegate: self, replaceInStack: false, completionHandler: nil) + } else if let tabsModel = tabs.tabsModel, + let action = tabsModel.tabs[indexPath.row].action { + // Perform the tab action + MVMCoreActionHandler.shared()?.asyncHandleAction(with: action, additionalData: getAdditionalDataForNewTabLoad(indexPath: indexPath), delegateObject: delegateObjectIVar) + } + return false + } + + // Not currently used. + open func didSelectItem(_ indexPath: IndexPath, tabs: Tabs) {} + + // MARK: - MVMCoreViewManagerViewControllerProtocol + + open override func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) { + super.viewControllerReady(inManager: manager) + // Pass on down + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self) + } + + // MARK: - MVMCoreViewManagerProtocol + + open func getCurrentViewController() -> UIViewController? { + MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) + } + + open func containsPage(withPageType pageType: String?) -> Bool { + guard let pageType = pageType else { return false } + return viewControllers.contains { controller in + return (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType + } + } + + open func newDataReceived(in viewController: UIViewController) { + manager?.newDataReceived?(in: viewController) + hideNavigationBarLine(true) + } + + public func willDisplay(_ viewController: UIViewController) { + manager?.willDisplay?(viewController) + hideNavigationBarLine(true) + } + + public func displayedViewController(_ viewController: UIViewController) { + manager?.displayedViewController?(viewController) + } + + // MARK: - MVMCoreUISwipeNavigationProtocol + + public func swipeLeft() { + guard tabs.selectedIndex < (tabs.tabsModel?.tabs.count ?? 0) - 1 else { return } + _ = shouldSelectItem(IndexPath(row: tabs.selectedIndex + 1, section: 0), tabs: tabs) + } + + public func swipeRight() { + guard tabs.selectedIndex != 0 else { return } + _ = shouldSelectItem(IndexPath(row: tabs.selectedIndex - 1, section: 0), tabs: tabs) + } + + public func update(percentage: CGFloat) { + guard customInteractor?.interactive == true, + let index = index else { return } + tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage) + } +} diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerNavigationController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerNavigationController.swift new file mode 100644 index 00000000..16b691d2 --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerNavigationController.swift @@ -0,0 +1,38 @@ +// +// SubNavManagerNavigationController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 11/1/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// The navigation controller that the SubNavManager uses for the children controllers. It always has the navigation bar hidden. +@objc public class SubNavManagerNavigationController: UINavigationController { + + public override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + setNavigationBarHidden(true, animated: false) + } + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + setNavigationBarHidden(true, animated: false) + } + + public override init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) { + super.init(navigationBarClass: navigationBarClass, toolbarClass: toolbarClass) + setNavigationBarHidden(true, animated: false) + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setNavigationBarHidden(true, animated: false) + } + + public override func setNavigationBarHidden(_ hidden: Bool, animated: Bool) { + guard hidden else { return } + super.setNavigationBarHidden(hidden, animated: animated) + } +} diff --git a/MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift b/MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift new file mode 100644 index 00000000..b614cf9a --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift @@ -0,0 +1,60 @@ +// +// SubNavSwipeAnimator.swift +// MobileFirstFramework +// +// Created by Matt Bruce on 10/22/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +@objcMembers public class SubNavSwipeAnimator: NSObject, MVMCoreViewControllerAnimatedTransitioning { + @objc dynamic public var interactiveTransitionCanceled: Bool = false + + private var animationTime: TimeInterval = 0.0 + private var direction: UISwipeGestureRecognizer.Direction + private weak var controller: UIViewController? + + public init(swipingDirection direction: UISwipeGestureRecognizer.Direction, controller: UIViewController) { + self.direction = direction + self.controller = controller + super.init() + } + + public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + animationTime = 0.5 + return animationTime + } + + public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + controller?.view.isUserInteractionEnabled = false + let containerView = transitionContext.containerView + + guard let originalViewController = transitionContext.viewController(forKey: .from), + let destinationViewController = transitionContext.viewController(forKey: .to) else { return } + + var destStartFrame = containerView.bounds + destStartFrame.origin.x = direction == .right ? -destStartFrame.size.width : destStartFrame.size.width + containerView.addSubview(destinationViewController.view) + destinationViewController.view.frame = destStartFrame + + let destEndFrame = containerView.bounds + var origEndFrame = containerView.bounds + origEndFrame.origin.x = direction == .right ? origEndFrame.size.width : -origEndFrame.size.width + + UIView.animate(withDuration: animationTime, animations: { + originalViewController.view.frame = origEndFrame + destinationViewController.view.frame = destEndFrame + }) { finished in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + } + } + + public func animationEnded(_ transitionCompleted: Bool) { + if !transitionCompleted { + interactiveTransitionCanceled = true + } + controller?.view.isUserInteractionEnabled = true + } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 78dd2916..ef051b88 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -204,6 +204,7 @@ open class CoreUIModelMapping: ModelMapping { // MARK:- LockUps ModelRegistry.register(handler: LockUpsPlanNames.self, for: LockUpsPlanNamesModel.self) ModelRegistry.register(handler: LockupsPlanSMLXL.self, for: LockupsPlanSMLXLModel.self) + ModelRegistry.register(handler: TitleLockup.self, for: TitleLockupModel.self) // MARK: - Top Notifications ModelRegistry.register(handler: NotificationView.self, for: NotificationModel.self) @@ -243,5 +244,10 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(RuleEqualsModel.self) ModelRegistry.register(RuleEqualsIgnoreCaseModel.self) ModelRegistry.register(RuleRegexModel.self) + ModelRegistry.register(EnableFormFieldEffectModel.self) + ModelRegistry.register(DynamicRuleFormFieldEffectModel.self) + ModelRegistry.register(DisableFormFieldEffectModel.self) + ModelRegistry.register(HideFormFieldEffectModel.self) + ModelRegistry.register(ClearFormFieldEffectModel.self) } } diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index d61d7be4..d44b49ac 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -16,7 +16,7 @@ import UIKit loadHandler = MVMCoreLoadHandler() cache = MVMCoreCache() sessionHandler = MVMCoreSessionTimeHandler() - actionHandler = MVMCoreActionHandler() + actionHandler = MVMCoreUIActionHandler() session = MVMCoreUISession() viewControllerMapping = MVMCoreUIViewControllerMappingObject() loggingDelegate = MVMCoreUILoggingHandler() diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h index c1dee62a..4c1afd45 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.h @@ -27,6 +27,9 @@ NS_ASSUME_NONNULL_BEGIN // Shows a topnotification new molecular - (void)topNotificationAction:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData delegateObject:(nullable DelegateObject *)delegateObject; +/// Legacy in app safari webview load +- (void)openURLInSafariWebView:(nonnull NSURL *)url; + @end NS_ASSUME_NONNULL_END diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m index e2adbd74..9425547c 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.m @@ -15,6 +15,7 @@ @import MVMCore.NSDictionary_MFConvenience; @import MVMCore.MVMCoreJSONConstants; @import MVMCore.MVMCoreCache; +@import SafariServices; @implementation MVMCoreUIActionHandler @@ -35,7 +36,7 @@ [self topNotificationAction:actionInformation additionalData:additionalData delegateObject:delegateObject]; return YES; } - return NO; + return [super handleOtherActions:actionType actionInformation:actionInformation additionalData:additionalData delegateObject:delegateObject]; } - (void)openPageAction:(NSDictionary *)actionInformation additionalData:(NSDictionary *)additionalData delegateObject:(DelegateObject *)delegateObject { @@ -122,4 +123,9 @@ } } +- (void)openURLInSafariWebView:(nonnull NSURL *)url { + SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:url]; + [[MVMCoreNavigationHandler sharedNavigationHandler] presentViewController:safariViewController animated:YES]; +} + @end diff --git a/MVMCoreUI/Styles/MFStyler.h b/MVMCoreUI/Styles/MFStyler.h index d8676016..541ede8c 100644 --- a/MVMCoreUI/Styles/MFStyler.h +++ b/MVMCoreUI/Styles/MFStyler.h @@ -217,7 +217,6 @@ B3 -> Legal + (nullable UIFont *)fontForHeadlineSmall:(BOOL)genericScaling; + (nullable UIFont *)fontForHeadlineSmall2; + (nullable UIFont *)fontForHeadlineSmall2:(BOOL)genericScaling; -+ (nullable UIFont *)fontForHeadlineSmall2ForWidth:(CGFloat)size; + (nullable UIFont *)fontB1ForWidth:(CGFloat)size; + (nullable UIFont *)fontForBodyWithSize:(CGFloat)size genericScaling:(BOOL)genericScaling; + (nullable UIFont *)fontB2ForWidth:(CGFloat)size; @@ -287,15 +286,8 @@ B3 -> Legal + (void)styleLabelRegularMicro:(nonnull UILabel *)label genericScaling:(BOOL)genericScaling; + (void)styleLabelRegularMicro:(nonnull UILabel *)label; -/// Will style the label with mva 3.0 fonts based on the string. -+ (BOOL)styleMVA3Label:(nonnull UILabel *)label withStyle:(nullable NSString *)style genericScaling:(BOOL)genericScaling; - #pragma mark - 2.0 styles -/// Will style the label based on the string. Accepted values, including mva3.0 fonts and 2.0 fonts H1, H2, H3, H32, B1, B2, B3, B20 -+ (void)styleLabel:(nonnull UILabel *)label withStyle:(nullable NSString *)style; -+ (void)styleLabel:(nonnull UILabel *)label withStyle:(nullable NSString *)style genericScaling:(BOOL)genericScaling; - + (void)styleLabelH1:(nonnull UILabel *)label genericScaling:(BOOL)genericScaling; + (void)styleLabelH1:(nonnull UILabel *)label; @@ -377,11 +369,6 @@ B3 -> Legal + (nonnull NSAttributedString *)styleGetRegularMicroAttributedString:(nullable NSString *)string; + (nonnull NSAttributedString *)styleGetRegularMicroAttributedString:(nullable NSString *)string genericScaling:(BOOL)genericScaling; - -/// Will style the string based on the string. Accepted values, H1, H2, H3, H32, B1, B2, B3, B20 -+ (nonnull NSAttributedString *)styleGetAttributedString:(nullable NSString *)string withStyle:(nullable NSString *)style; -+ (nonnull NSAttributedString *)styleGetAttributedString:(nullable NSString *)string withStyle:(nullable NSString *)style genericScaling:(BOOL)genericScaling; - + (nonnull NSAttributedString *)styleGetAttributedString:(nullable NSString *)string font:(nonnull UIFont *)font color:(nonnull UIColor *)color; + (nonnull NSAttributedString *)styleGetH1AttributedString:(nullable NSString *)string; + (nonnull NSAttributedString *)styleGetH2AttributedString:(nullable NSString *)string; diff --git a/MVMCoreUI/Styles/MFStyler.m b/MVMCoreUI/Styles/MFStyler.m index 48ca21a8..e65fddc5 100644 --- a/MVMCoreUI/Styles/MFStyler.m +++ b/MVMCoreUI/Styles/MFStyler.m @@ -113,27 +113,11 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; #pragma mark - 3.0 fonts + (nonnull UIFont *)getMVA3FontSize:(CGFloat)size bold:(BOOL)isBold { - if (isBold) { - if (size >= 15) { - return [MFFonts mfFontDSBold:size]; - } else { - return [MFFonts mfFontTXBold:size]; - } - } else { - if (size >= 15) { - return [MFFonts mfFontDSRegular:size]; - } else { - return [MFFonts mfFontTXRegular:size]; - } - } + return [self getFontForSize:size isBold:isBold]; } + (nonnull UIFont *)fontTitle2XLarge:(BOOL)genericScaling { - CGFloat size = 36; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:YES]; + return [self getFontForStyleString:@"Title2XLarge" genericScaling:genericScaling]; } + (nonnull UIFont *)fontTitle2XLarge { @@ -141,22 +125,15 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontTitleXLarge:(BOOL)genericScaling { - CGFloat size = 32; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:YES]; + return [self getFontForStyleString:@"TitleXLarge" genericScaling:genericScaling]; } -+ (nonnull UIFont *)fontTitleXLarge{ + ++ (nonnull UIFont *)fontTitleXLarge { return [self fontTitleXLarge:YES]; } + (nonnull UIFont *)fontBoldTitleLarge:(BOOL)genericScaling { - CGFloat size = 24; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:YES]; + return [self getFontForStyleString:@"BoldTitleLarge" genericScaling:genericScaling]; } + (nonnull UIFont *)fontBoldTitleLarge { @@ -164,11 +141,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontRegularTitleLarge:(BOOL)genericScaling { - CGFloat size = 24; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:NO]; + return [self getFontForStyleString:@"RegularTitleLarge" genericScaling:genericScaling]; } + (nonnull UIFont *)fontRegularTitleLarge { @@ -176,11 +149,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontBoldTitleMedium:(BOOL)genericScaling { - CGFloat size = 20; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:YES]; + return [self getFontForStyleString:@"BoldTitleMedium" genericScaling:genericScaling]; } + (nonnull UIFont *)fontBoldTitleMedium { @@ -188,11 +157,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontRegularTitleMedium:(BOOL)genericScaling { - CGFloat size = 20; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:NO]; + return [self getFontForStyleString:@"RegularTitleMedium" genericScaling:genericScaling]; } + (nonnull UIFont *)fontRegularTitleMedium { @@ -200,11 +165,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontBoldBodyLarge:(BOOL)genericScaling { - CGFloat size = 16; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:YES]; + return [self getFontForStyleString:@"BoldBodyLarge" genericScaling:genericScaling]; } + (nonnull UIFont *)fontBoldBodyLarge { @@ -212,11 +173,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontRegularBodyLarge:(BOOL)genericScaling { - CGFloat size = 16; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:NO]; + return [self getFontForStyleString:@"RegularBodyLarge" genericScaling:genericScaling]; } + (nonnull UIFont *)fontRegularBodyLarge { @@ -224,11 +181,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontBoldBodySmall:(BOOL)genericScaling { - CGFloat size = 13; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:YES]; + return [self getFontForStyleString:@"BoldBodySmall" genericScaling:genericScaling]; } + (nonnull UIFont *)fontBoldBodySmall { @@ -236,11 +189,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontRegularBodySmall:(BOOL)genericScaling { - CGFloat size = 13; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:NO]; + return [self getFontForStyleString:@"RegularBodySmall" genericScaling:genericScaling]; } + (nonnull UIFont *)fontRegularBodySmall { @@ -248,11 +197,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontBoldMicro:(BOOL)genericScaling { - CGFloat size = 11; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:YES]; + return [self getFontForStyleString:@"BoldMicro" genericScaling:genericScaling]; } + (nonnull UIFont *)fontBoldMicro { @@ -260,11 +205,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nonnull UIFont *)fontRegularMicro:(BOOL)genericScaling { - CGFloat size = 11; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [self getMVA3FontSize:size bold:NO]; + return [self getFontForStyleString:@"RegularMicro" genericScaling:genericScaling]; } + (nonnull UIFont *)fontRegularMicro { @@ -275,11 +216,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; #pragma mark - 2.0 fonts + (nullable UIFont *)fontH1:(BOOL)genericScaling { - CGFloat size = 40; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont75Bd:size]; + return [self getFontForStyleString:@"H1" genericScaling:genericScaling]; } + (nullable UIFont *)fontH1 { @@ -287,11 +224,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontH2:(BOOL)genericScaling { - CGFloat size = 25; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont75Bd:size]; + return [self getFontForStyleString:@"H2" genericScaling:genericScaling]; } + (nullable UIFont *)fontH2 { @@ -299,11 +232,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontH3:(BOOL)genericScaling { - CGFloat size = 18; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont75Bd:size]; + return [self getFontForStyleString:@"H3" genericScaling:genericScaling]; } + (nullable UIFont *)fontH3 { @@ -311,11 +240,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontH32:(BOOL)genericScaling { - CGFloat size = 32; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont75Bd:size]; + return [self getFontForStyleString:@"H32" genericScaling:genericScaling]; } + (nullable UIFont *)fontH32 { @@ -323,11 +248,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontB1:(BOOL)genericScaling { - CGFloat size = 13; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont75Bd:size]; + return [self getFontForStyleString:@"B1" genericScaling:genericScaling]; } + (nullable UIFont *)fontB1 { @@ -335,11 +256,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontB2:(BOOL)genericScaling { - CGFloat size = 13; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont55Rg:size]; + return [self getFontForStyleString:@"B2" genericScaling:genericScaling]; } + (nullable UIFont *)fontB2 { @@ -347,11 +264,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontB3:(BOOL)genericScaling { - CGFloat size = 11; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont55Rg:size]; + return [self getFontForStyleString:@"B3" genericScaling:genericScaling]; } + (nullable UIFont *)fontB3 { @@ -359,11 +272,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontB20:(BOOL)genericScaling { - CGFloat size = 20; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont55Rg:size]; + return [self getFontForStyleString:@"B20" genericScaling:genericScaling]; } + (nullable UIFont *)fontB20 { @@ -412,16 +321,8 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; return [MFFonts mfFont75Bd:size]; } -+ (nullable UIFont *)fontForHeadlineSmall2ForWidth:(CGFloat)size { - CGFloat pointSize = 16; - pointSize = [[MFStyler sizeObjectGenericForCurrentDevice:pointSize] getValueBasedOnSize:size]; - return [MFFonts mfFont75Bd:pointSize]; -} - + (nullable UIFont *)fontB1ForWidth:(CGFloat)size { - CGFloat pointSize = 13; - pointSize = [[MFStyler sizeObjectGenericForCurrentDevice:pointSize] getValueBasedOnSize:size]; - return [MFFonts mfFont75Bd:pointSize]; + return [self getFontForStyleString:@"B1" scaleValue:size]; } + (nullable UIFont *)fontForBodyWithSize:(CGFloat)size genericScaling:(BOOL)genericScaling { @@ -432,9 +333,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontB2ForWidth:(CGFloat)size { - CGFloat pointSize = 13; - pointSize = [[MFStyler sizeObjectGenericForCurrentDevice:pointSize] getValueBasedOnSize:size]; - return [MFFonts mfFont55Rg:pointSize]; + return [self getFontForStyleString:@"B2" scaleValue:size]; } + (nullable UIFont *)fontForBodyWithSize:(CGFloat)size forWidth:(CGFloat)width { @@ -467,47 +366,27 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontForPrimaryButton:(BOOL)genericScaling { - CGFloat size = 13; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFontTXBold:size]; + return [self getFontForStyleString:@"BoldBodyLarge" genericScaling:genericScaling]; } + (nullable UIFont *)fontForPrimaryButtonForWidth:(CGFloat)size { - CGFloat pointSize = 13; - pointSize = [[MFStyler sizeObjectGenericForCurrentDevice:pointSize] getValueBasedOnSize:size]; - return [MFFonts mfFontTXBold:pointSize]; + return [self getFontForStyleString:@"BoldBodyLarge" scaleValue:size]; } + (nullable UIFont *)fontForSmallButton:(BOOL)genericScaling { - CGFloat size = 11; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFontTXBold:size]; + return [self getFontForStyleString:@"BoldBodySmall" genericScaling:genericScaling]; } + (nullable UIFont *)fontForSmallButtonForWidth:(CGFloat)size { - CGFloat pointSize = 11; - pointSize = [[MFStyler sizeObjectGenericForCurrentDevice:pointSize] getValueBasedOnSize:size]; - return [MFFonts mfFontTXBold:pointSize]; + return [self getFontForStyleString:@"BoldBodySmall" scaleValue:size]; } + (nullable UIFont *)fontForTextField:(BOOL)genericScaling { - CGFloat size = 16; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont55Rg:size]; + return [self getFontForStyleString:@"RegularBodyLarge" genericScaling:genericScaling]; } + (nullable UIFont *)fontForTextFieldUnderLabel:(BOOL)genericScaling { - CGFloat size = 12; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont55Rg:size]; + return [self getFontForStyleString:@"RegularBodySmall" genericScaling:genericScaling]; } + (nullable UIFont *)fontForUnreadMessageOnSupport:(BOOL)genericScaling { @@ -554,11 +433,7 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (nullable UIFont *)fontForFeedCardTitle:(BOOL)genericScaling { - CGFloat size = 16; - if (genericScaling) { - size = [self sizeFontGenericForCurrentDevice:size]; - } - return [MFFonts mfFont75Bd:size]; + return [self getFontForStyleString:@"BoldTitleXLarge" genericScaling:genericScaling]; } + (nullable UIFont *)fontForLargeLoyaltyHeaderTitle:(BOOL)genericScaling { @@ -785,77 +660,8 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; [self styleLabelRegularMicro:label genericScaling:YES]; } -+ (BOOL)styleMVA3Label:(nonnull UILabel *)label withStyle:(nullable NSString *)style genericScaling:(BOOL)genericScaling { - if ([style isEqualToString:@"Title2XLarge"]) { - [self styleLabelTitle2XLarge:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"TitleXLarge"]) { - [self styleLabelTitleXLarge:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"BoldTitleLarge"]) { - [self styleLabelBoldTitleLarge:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"RegularTitleLarge"]) { - [self styleLabelRegularTitleLarge:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"BoldTitleMedium"]) { - [self styleLabelBoldTitleMedium:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"RegularTitleMedium"]) { - [self styleLabelRegularTitleMedium:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"BoldBodyLarge"]) { - [self styleLabelBoldBodyLarge:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"RegularBodyLarge"]) { - [self styleLabelRegularBodyLarge:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"BoldBodySmall"]) { - [self styleLabelBoldBodySmall:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"RegularBodySmall"]) { - [self styleLabelRegularBodySmall:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"BoldMicro"]) { - [self styleLabelBoldMicro:label genericScaling:genericScaling]; - return YES; - } else if ([style isEqualToString:@"RegularMicro"]) { - [self styleLabelRegularMicro:label genericScaling:genericScaling]; - return YES; - } else { - return NO; - } -} - - #pragma mark - 2.0 Styles -+ (void)styleLabel:(nonnull UILabel *)label withStyle:(nullable NSString *)style genericScaling:(BOOL)genericScaling { - if ([self styleMVA3Label:label withStyle:style genericScaling:genericScaling]) { - //try mva 3.0 font first - } else if ([style isEqualToString:@"H1"]) { - [self styleLabelH1:label genericScaling:genericScaling]; - } else if ([style isEqualToString:@"H2"]) { - [self styleLabelH2:label genericScaling:genericScaling]; - } else if ([style isEqualToString:@"H3"]) { - [self styleLabelH3:label genericScaling:genericScaling]; - } else if ([style isEqualToString:@"H32"]) { - [self styleLabelH32:label genericScaling:genericScaling]; - } else if ([style isEqualToString:@"B1"]) { - [self styleLabelB1:label genericScaling:genericScaling]; - } else if ([style isEqualToString:@"B3"]) { - [self styleLabelB3:label genericScaling:genericScaling]; - } else if ([style isEqualToString:@"B20"]) { - [self styleLabelB20:label genericScaling:genericScaling]; - } else { - [self styleLabelB2:label genericScaling:genericScaling]; - } -} - -+ (void)styleLabel:(nonnull UILabel *)label withStyle:(nullable NSString *)style { - [self styleLabel:label withStyle:style genericScaling:YES]; -} - + (void)styleLabelH1:(nonnull UILabel *)label genericScaling:(BOOL)genericScaling { label.font = [MFStyler fontH1:genericScaling]; label.textColor = [UIColor blackColor]; @@ -975,30 +781,6 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; #pragma mark - Attributed Strings -+ (nonnull NSAttributedString *)styleGetAttributedString:(nullable NSString *)string withStyle:(nullable NSString *)style { - return [self styleGetAttributedString:string withStyle:style genericScaling:YES]; -} - -+ (nonnull NSAttributedString *)styleGetAttributedString:(nullable NSString *)string withStyle:(nullable NSString *)style genericScaling:(BOOL)genericScaling { - if ([style isEqualToString:@"H1"]) { - return [self styleGetH1AttributedString:string genericScaling:genericScaling]; - } else if ([style isEqualToString:@"H2"]) { - return [self styleGetH2AttributedString:string genericScaling:genericScaling]; - } else if ([style isEqualToString:@"H3"]) { - return [self styleGetH3AttributedString:string genericScaling:genericScaling]; - } else if ([style isEqualToString:@"H32"]) { - return [self styleGetH32AttributedString:string genericScaling:genericScaling]; - } else if ([style isEqualToString:@"B1"]) { - return [self styleGetB1AttributedString:string genericScaling:genericScaling]; - } else if ([style isEqualToString:@"B3"]) { - return [self styleGetB3AttributedString:string genericScaling:genericScaling]; - } else if ([style isEqualToString:@"B20"]) { - return [self styleGetB20AttributedString:string genericScaling:genericScaling]; - } else { - return [self styleGetB2AttributedString:string genericScaling:genericScaling]; - } -} - + (nonnull NSAttributedString *)styleGetAttributedString:(nullable NSString *)string font:(nonnull UIFont *)font color:(nonnull UIColor *)color { NSAttributedString *attributedString = nil; if (![string isEqual:[NSNull null]] && string.length > 0) { diff --git a/MVMCoreUI/Styles/Styler.swift b/MVMCoreUI/Styles/Styler.swift index 77402295..2cbc9a00 100644 --- a/MVMCoreUI/Styles/Styler.swift +++ b/MVMCoreUI/Styles/Styler.swift @@ -7,69 +7,103 @@ // import Foundation +import UIKit open class Styler { // MARK:- Font Enum public enum Font: String, Codable { - case Title2XLarge - case TitleXLarge - case BoldTitleLarge + case RegularFeatureXLarge + case BoldFeatureXLarge + case RegularFeatureLarge + case BoldFeatureLarge + case RegularFeatureMedium + case BoldFeatureMedium + case RegularFeatureSmall + case BoldFeatureSmall + case RegularFeatureXSmall + case BoldFeatureXSmall + + case RegularTitle2XLarge + case BoldTitle2XLarge + case RegularTitleXLarge + case BoldTitleXLarge case RegularTitleLarge - case BoldTitleMedium + case BoldTitleLarge case RegularTitleMedium - case BoldBodyLarge + case BoldTitleMedium + case RegularTitleSmall + case BoldTitleSmall + case RegularBodyLarge - case BoldBodySmall + case BoldBodyLarge + case RegularBodyMedium + case BoldBodyMedium case RegularBodySmall - case BoldMicro + case BoldBodySmall case RegularMicro + case BoldMicro // Legacy Fonts - case H1 - case H32 - case H2 - case B20 - case H3 - case B1 - case B2 - case B3 + case Title2XLarge // Maps to RegularTitle2XLarge + case TitleXLarge // Maps to RegularTitleXLarge + case H1 // Maps to RegularTitle2XLarge + case H32 // Maps to RegularTitleXLarge + case H2 // Maps to RegularTitleLarge + case B20 // Maps to RegularBodyLarge + case H3 // Maps to BoldTitleMedium + case B1 // Maps to BoldBodySmall + case B2 // Maps to RegularBodySmall + case B3 // Maps to RegularMicro /// Returns the font size of the current enum case. public func pointSize() -> CGFloat { switch self { - case .H1: + case .RegularFeatureXLarge, + .BoldFeatureXLarge: + return 96 + case .RegularFeatureLarge, + .BoldFeatureLarge: + return 80 + case .RegularFeatureMedium, + .BoldFeatureMedium: + return 64 + case .RegularFeatureSmall, + .BoldFeatureSmall: + return 48 + case .RegularFeatureXSmall, + .BoldFeatureXSmall, + .RegularTitle2XLarge, + .BoldTitle2XLarge, + .Title2XLarge, + .H1: return 40 - - case .Title2XLarge: - return 36 - - case .TitleXLarge, .H32: + case .RegularTitleXLarge, + .BoldTitleXLarge, + .TitleXLarge, + .H32: return 32 - - case .H2: - return 25 - case .BoldTitleLarge, - .RegularTitleLarge: + .RegularTitleLarge, + .H2: return 24 - case .BoldTitleMedium, - .RegularTitleMedium, .B20: + .RegularTitleMedium, + .H3: return 20 - - case .H3: - return 18 - - case .BoldBodyLarge, - .RegularBodyLarge: + case .RegularTitleSmall, + .BoldTitleSmall, + .BoldBodyLarge, + .RegularBodyLarge, + .B20: return 16 - + case .RegularBodyMedium, + .BoldBodyMedium: + return 14 case .BoldBodySmall, .B1, .RegularBodySmall, .B2: - return 13 - + return 12 case .BoldMicro, .RegularMicro, .B3: return 11 @@ -90,85 +124,38 @@ open class Styler { public func isBold() -> Bool { switch self { - case .RegularTitleLarge, - .RegularTitleMedium, .B20, - .RegularBodyLarge, - .RegularBodySmall, .B2, - .RegularMicro, .B3: - return false - - case .H1, - .Title2XLarge, - .TitleXLarge, .H32, - .H2, - .BoldTitleLarge, - .BoldTitleMedium, - .H3, - .BoldBodyLarge, - .BoldBodySmall, .B1, - .BoldMicro: - return true - } - } - - /// Determines if the current enum is a legacy or modern font. - public func isLegacyFont() -> Bool { - - switch self { - case .Title2XLarge, - .TitleXLarge, - .RegularTitleLarge, - .RegularTitleMedium, - .RegularBodyLarge, - .RegularBodySmall, - .RegularMicro, + case .BoldFeatureXLarge, + .BoldFeatureLarge, + .BoldFeatureMedium, + .BoldFeatureSmall, + .BoldFeatureXSmall, + .BoldTitle2XLarge, + .BoldTitleXLarge, .BoldTitleLarge, .BoldTitleMedium, + .BoldTitleSmall, .BoldBodyLarge, + .BoldBodyMedium, .BoldBodySmall, - .BoldMicro: - return false - - case .H1, - .H32, - .H2, .H3, .B1, - .B2, - .B3, - .B20: + .BoldMicro: return true + default: + return false } } - + /// Returns the font based on the declared enum case. public func getFont(_ genericScaling: Bool = true) -> UIFont { - let size = genericScaling ? sizeFontGeneric(forCurrentDevice: pointSize()) : pointSize() - - if isLegacyFont() { - switch self { - case .B2, .B3, .B20: - return MFFonts.mfFont55Rg(size) - - default: - return MFFonts.mfFont75Bd(size) - } - } else { - if isBold() { - return size >= 15 ? MFFonts.mfFontDSBold(size) : MFFonts.mfFontTXBold(size) - - } else { - return size >= 15 ? MFFonts.mfFontDSRegular(size) : MFFonts.mfFontTXRegular(size) - } - } + return MFStyler.getFontFor(size: size, isBold: isBold()) } /// Styles the provided label to the declared enum Font case. - public func styleLabel(_ label: UILabel, textColor: UIColor = .mvmBlack, genericScaling: Bool = true) { - + public func styleLabel(_ label: UILabel, genericScaling: Bool = true) { label.font = getFont(genericScaling) - label.textColor = textColor + label.textColor = color() } } @@ -178,20 +165,33 @@ open class Styler { case primary case secondary } - + ///MVA 3.0 - Button sizes are standard(default size), small, Tiny. Tiny button has been depricated as of Rebranding 3.0. public enum Size: String, Codable { case standard + case small case tiny func getHeight() -> CGFloat { switch self { case .standard: - return 42 - + return 44 + case .small: + return 32 case .tiny: return 20 } } + + func minimumWidth() -> CGFloat { + switch self { + case .standard: + return 76 + case .small: + return 0 + case .tiny: + return 49 + } + } } } @@ -254,3 +254,38 @@ open class Styler { } } } + +@objc public extension MFStyler { + /// Creates the appropriate VZW font for a given size and weight. + @objc static func getFontFor(size: CGFloat, isBold: Bool) -> UIFont { + if isBold { + return size >= 13 ? MFFonts.mfFontDSBold(size) : MFFonts.mfFontTXBold(size) + } else { + return size >= 13 ? MFFonts.mfFontDSRegular(size) : MFFonts.mfFontTXRegular(size) + } + } + + /// Creates the appropriate VZW font for a VDS style. + @objc static func getFontFor(styleString: String, genericScaling: Bool = true) -> UIFont? { + return Styler.Font(rawValue: styleString)?.getFont(genericScaling) + } + + /// Creates the appropriate VZW font for a VDS style, scaling based on the scaleValue threshold passed in. + @objc static func getFontFor(styleString: String, scaleValue: CGFloat) -> UIFont? { + guard let font = Styler.Font(rawValue: styleString), + let size = Styler.Font(rawValue: styleString)?.pointSize(), + let newSize = Styler.sizeObjectGeneric(forCurrentDevice: size)?.getValueBased(onSize: scaleValue) else { return nil } + return getFontFor(size: newSize, isBold: font.isBold()) + } + + /// Styles the label accordingly to Styler.Font + @objc static func style(label: UILabel, styleString: String, genericScaling: Bool = true) { + Styler.Font(rawValue: styleString)?.styleLabel(label, genericScaling: genericScaling) + } + + /// Returns an attributed string with the passed in VDS Style. + @objc static func getAttributedString(for string: String, styleString: String, genericScaling: Bool = true) -> NSAttributedString { + let font = Styler.Font(rawValue: styleString)! + return styleGetAttributedString(string, font: font.getFont(genericScaling), color: font.color()) + } +} diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back-1.png b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back-1.png deleted file mode 100644 index 3fae0595..00000000 Binary files a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back-1.png and /dev/null differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back-2.png b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back-2.png deleted file mode 100644 index 48ad2176..00000000 Binary files a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back-2.png and /dev/null differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back.png b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back.png deleted file mode 100644 index 2238b469..00000000 Binary files a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Back.png and /dev/null differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Contents.json index 753f15c2..56ae55a3 100644 --- a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Contents.json +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/Contents.json @@ -1,23 +1,23 @@ { "images" : [ { + "filename" : "nav_back.png", "idiom" : "universal", - "filename" : "Back.png", "scale" : "1x" }, { + "filename" : "nav_back@2x.png", "idiom" : "universal", - "filename" : "Back-1.png", "scale" : "2x" }, { + "filename" : "nav_back@3x.png", "idiom" : "universal", - "filename" : "Back-2.png", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back.png b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back.png new file mode 100644 index 00000000..895fade7 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back@2x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back@2x.png new file mode 100644 index 00000000..e699cb47 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back@2x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back@3x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back@3x.png new file mode 100644 index 00000000..96e839ed Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/nav_back.imageset/nav_back@3x.png differ diff --git a/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h b/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h index e10cec10..e799e858 100644 --- a/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h +++ b/MVMCoreUI/TopAlert/MVMCoreTopAlertViewProtocol.h @@ -13,16 +13,19 @@ @optional -// Show based on the object +/// Show based on the object - (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler; -// Hides +/// Removes the notification - (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler; -// Collapses the notification if it has a short top message. Otherwise removes notification. +/// Collapses the notification if it has a short top message. Otherwise removes notification. - (void)collapseNotification; /// Updates the existing top alert with the new object - (void)updateTopAlertWith:(nullable MVMCoreTopAlertObject *)topAlertObject; +/// Returns if the top alert is currently utilizing the status bar. +- (BOOL)overridingStatusBar; + @end diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m index fa96630f..d2a009c9 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m @@ -204,7 +204,6 @@ // Sets up to use a button action. Always uses the top view controller PillButton *button = [[PillButton alloc] initAsPrimaryButton:false makeTiny:true]; - [button styleSecondary]; [button setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisHorizontal]; [button setContentHuggingPriority:800 forAxis:UILayoutConstraintAxisHorizontal]; diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift index 454ea7a2..46765385 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView+Extension.swift @@ -31,7 +31,7 @@ public extension MVMCoreUITopAlertView { // Dismiss any top alerts that server wants us to dismiss/ if let disableType = loadObject.responseInfoMap?.optionalStringForKey("disableType") { - MVMCoreAlertHandler.shared()?.hidePersistentTopAlertView(ofType: disableType) + MVMCoreAlertHandler.shared()?.hideTopAlertView(ofType: disableType) } // Show any new top alert. @@ -109,7 +109,7 @@ public extension MVMCoreUITopAlertView { // Update status bar. guard let statusBarDelegate = molecule as? StatusBarUI else { return } let statusBarUI = statusBarDelegate.getStatusBarUI() - self.setStatusBarColor(statusBarUI.color, statusBarStyle: statusBarUI.style) + MVMCoreUISplitViewController.main()?.setStatusBarBackgroundColor(statusBarUI.color, style: statusBarUI.style) }) } diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h index 5bb033d7..fad65957 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.h @@ -19,8 +19,6 @@ @interface MVMCoreUITopAlertView : UIView -@property (nonatomic, readonly) UIStatusBarStyle statusBarStyle; - // Delegate for the top alert view @property (nonatomic, nullable, weak) id animationDelegate; @@ -36,16 +34,6 @@ // Returns a TopAlertView with the mvm styling. Also sets the property in the session. + (nullable instancetype)setupTopAlertView; -// Pins the status bar view at the top of the passed in view controller. -- (void)pinATopViewController:(nonnull UIViewController *)viewController; - -// For controlling the status bar view -- (void)expandStatusBarView; -- (void)collapseStatusBarView; - -/// reset status bar background color, when backgroundColor is nil corresponding background color will be set based on style -- (void)resetDefaultBackgroundColor:(nullable UIColor *)backgroundColor basedOnStatusBarStyle:(UIStatusBarStyle)style; - // Can be subclassed for custom views. - (nonnull UIView *)topAlertViewForTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate statusBarColor:(UIColor *_Nullable *_Nullable)statusBarColor statusBarStyle:(UIStatusBarStyle *_Nullable)statusBarStyle; @@ -55,7 +43,4 @@ /// Get the content color based on the type - (nonnull UIColor *)getContentColorForType:(nullable NSString *)type; -// Set the status bar color. Used for updating the status bar when the view changes. -- (void)setStatusBarColor:(nullable UIColor *)statusBarColor statusBarStyle:(UIStatusBarStyle)style; - @end diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index eba4cfcc..c0835541 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -33,12 +33,6 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; @interface MVMCoreUITopAlertView () -@property (nonatomic, readwrite) UIStatusBarStyle statusBarStyle; -@property (weak, nonatomic) UIView *statusBarView; -@property (strong, nonatomic) NSLayoutConstraint *statusBarHeightConstraint; -@property (strong, nonatomic) NSLayoutConstraint *statusBarBottomConstraint; - -@property (weak, nonatomic) UIView *alertView; @property (weak, nullable, nonatomic, readwrite) UIView *currentAlert; @property (strong, nonatomic) NSLayoutConstraint *height; @@ -48,6 +42,8 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; /// Used if we delayed the collapse due to accessibility. @property (copy, nonatomic) void (^ hideCompletionHandler)(BOOL finished); +@property (nonatomic) BOOL currentAlertOverridingStatusBar; + @end @implementation MVMCoreUITopAlertView @@ -88,34 +84,11 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } - (void)setupView { - if (!self.statusBarView) { - self.clipsToBounds = YES; - - UIView *statusBarView = [MVMCoreUICommonViewsUtility commonView]; - UIView *alertView = [MVMCoreUICommonViewsUtility commonView]; - [self addSubview:alertView]; - [self addSubview:statusBarView]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[statusBarView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView)]]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[alertView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(alertView)]]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[statusBarView]-0-[alertView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(statusBarView,alertView)]]; - self.statusBarHeightConstraint = [statusBarView.heightAnchor constraintEqualToConstant:0]; - self.statusBarHeightConstraint.active = YES; - self.height = [alertView.heightAnchor constraintEqualToConstant:0]; - self.height.active = YES; - self.alertView = alertView; - self.statusBarView = statusBarView; - - [self setStatusBarColor:[UIColor whiteColor] statusBarStyle:UIStatusBarStyleDefault]; - - [self registerWithNotificationCenter]; - } -} - -- (void)pinATopViewController:(UIViewController *)viewController { - self.statusBarHeightConstraint.active = NO; - id topGuide = viewController.view.safeAreaLayoutGuide; - self.statusBarBottomConstraint = [NSLayoutConstraint constraintWithItem:self.statusBarView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:topGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; - self.statusBarBottomConstraint.active = YES; + if (self.height) { return; } + self.clipsToBounds = YES; + self.height = [self.heightAnchor constraintEqualToConstant:0]; + self.height.active = YES; + [self registerWithNotificationCenter]; } - (void)updateView:(CGFloat)size { @@ -159,38 +132,6 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } } -- (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { - - self.animationDelegate = animationDelegate; - dispatch_async(dispatch_get_main_queue(), ^{ - - self.topAlertObject = topAlertObject; - self.topAlertClearspotView = nil; - - UIColor *statusBarColor = nil; - UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault; - UIView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle]; - if ([view conformsToProtocol:@protocol(MVMCoreViewProtocol)]) { - [((UIView *)view) updateView:CGRectGetWidth(self.bounds)]; - } - if (!statusBarColor) { - statusBarColor = [UIColor whiteColor]; - } - [self setStatusBarColor:statusBarColor statusBarStyle:statusBarStyle]; - [self showAlertView:view topAlertObject:topAlertObject completionHandler:completionHandler]; - }); -} - -- (void)updateTopAlertWith:(MVMCoreTopAlertObject *)topAlertObject { - [self updateMoleculeWith:topAlertObject]; -} - -- (void)setStatusBarColor:(nullable UIColor *)statusBarColor statusBarStyle:(UIStatusBarStyle)style { - self.statusBarView.backgroundColor = statusBarColor; - self.statusBarStyle = style; - [[MVMCoreUISession sharedGlobal].splitViewController.parentViewController setNeedsStatusBarAppearanceUpdate]; -} - - (void)updateAccessibilityForTopAlert:(nullable UIView *)view { // Update accessibility with top alert if ([view isKindOfClass:[MVMCoreUITopAlertBaseView class]]) { @@ -212,7 +153,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; if (weakSelf.currentAlert.superview) { [weakSelf.currentAlert removeFromSuperview]; } - [weakSelf.alertView addSubview:view]; + [weakSelf addSubview:view]; [NSLayoutConstraint constraintPinSubviewToSuperview:view]; weakSelf.currentAlert = view; @@ -241,6 +182,40 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; [[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation]; } + +/// If the voice over user leaves top alert focus, hide. +- (void)accessibilityFocusChanged:(NSNotification *)notification { + if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil]; + [self hideAlertView:YES completionHandler:self.hideCompletionHandler]; + self.hideCompletionHandler = nil; + } +} + +#pragma mark - MVMCoreTopAlertViewProtocol + +- (void)showWithTopAlertObject:(nullable MVMCoreTopAlertObject *)topAlertObject animationDelegate:(nonnull id )animationDelegate completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { + + self.animationDelegate = animationDelegate; + dispatch_async(dispatch_get_main_queue(), ^{ + + self.topAlertObject = topAlertObject; + self.topAlertClearspotView = nil; + + UIColor *statusBarColor = nil; + UIStatusBarStyle statusBarStyle = UIStatusBarStyleDefault; + UIView *view = [self topAlertViewForTopAlertObject:topAlertObject animationDelegate:animationDelegate statusBarColor:&statusBarColor statusBarStyle:&statusBarStyle]; + if ([view conformsToProtocol:@protocol(MVMCoreViewProtocol)]) { + [((UIView *)view) updateView:CGRectGetWidth(self.bounds)]; + } + if (statusBarColor) { + self.currentAlertOverridingStatusBar = YES; + [[MVMCoreUISplitViewController mainSplitViewController] setStatusBarBackgroundColor:statusBarColor style:statusBarStyle]; + } + [self showAlertView:view topAlertObject:topAlertObject completionHandler:completionHandler]; + }); +} + - (void)hideAlertView:(BOOL)forceful completionHandler:(void (^ __nullable)(BOOL finished))completionHandler { // If accessible and focused, do not collapse until unfocused. if (!forceful && [MVMCoreUIUtility viewContainsAccessiblityFocus:self]) { @@ -278,9 +253,12 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; completionHandler(finished); } weakSelf.topAlertObject = nil; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - [weakSelf setStatusBarColor:[UIColor whiteColor] statusBarStyle:UIStatusBarStyleDefault]; - }]; + if (weakSelf.currentAlertOverridingStatusBar) { + weakSelf.currentAlertOverridingStatusBar = NO; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + [[MVMCoreUISplitViewController mainSplitViewController] setStatusBarForCurrentViewController]; + }]; + } }]; }]; }]; @@ -288,6 +266,11 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; [[MVMCoreNavigationHandler sharedNavigationHandler] addNavigationOperation:operation]; } + +- (void)updateTopAlertWith:(MVMCoreTopAlertObject *)topAlertObject { + [self updateMoleculeWith:topAlertObject]; +} + - (void)collapseNotification { if (self.currentAlert) { if ([self.currentAlert isKindOfClass:[MVMCoreUITopAlertExpandableView class]] && ((MVMCoreUITopAlertExpandableView *)self.currentAlert).shortView.label.text.length > 0) { @@ -301,45 +284,8 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; } } -- (void)expandStatusBarView { - __weak typeof(self) weakSelf = self; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - weakSelf.statusBarBottomConstraint.active = YES; - weakSelf.statusBarHeightConstraint.active = NO; - [weakSelf.superview layoutIfNeeded]; - }]; -} - -- (void)collapseStatusBarView { - __weak typeof(self) weakSelf = self; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - weakSelf.statusBarBottomConstraint.active = NO; - weakSelf.statusBarHeightConstraint.active = YES; - [weakSelf.superview layoutIfNeeded]; - }]; -} - -- (void)resetDefaultBackgroundColor:(UIColor *)backgroundColor basedOnStatusBarStyle:(UIStatusBarStyle)style { - if (!self.topAlertObject) { - UIColor *defaultStatusBarBackgroundColor = backgroundColor; - if (!defaultStatusBarBackgroundColor) { - defaultStatusBarBackgroundColor = style == UIStatusBarStyleDefault ? [UIColor whiteColor] : [UIColor blackColor]; - } - - //color doesn't match the current default value - if (!CGColorEqualToColor(defaultStatusBarBackgroundColor.CGColor, self.statusBarView.backgroundColor.CGColor)) { - self.statusBarView.backgroundColor = defaultStatusBarBackgroundColor; - } - } -} - -/// If the voice over user leaves top alert focus, hide. -- (void)accessibilityFocusChanged:(NSNotification *)notification { - if (notification.userInfo[UIAccessibilityFocusedElementKey] && ![MVMCoreUIUtility viewContainsAccessiblityFocus:self]) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityElementFocusedNotification object:nil]; - [self hideAlertView:YES completionHandler:self.hideCompletionHandler]; - self.hideCompletionHandler = nil; - } +- (BOOL)overridingStatusBar { + return self.currentAlertOverridingStatusBar; } @end diff --git a/MVMCoreUI/TopAlert/TopNotificationModel.swift b/MVMCoreUI/TopAlert/TopNotificationModel.swift index 7c07c980..75300a3f 100644 --- a/MVMCoreUI/TopAlert/TopNotificationModel.swift +++ b/MVMCoreUI/TopAlert/TopNotificationModel.swift @@ -69,10 +69,7 @@ open class TopNotificationModel: Codable { /// Decodes the top alert json to a model. public static func decode(json: [AnyHashable: Any], delegateObject: MVMCoreUIDelegateObject?) throws -> Self { let data = try JSONSerialization.data(withJSONObject: json) - let decoder = JSONDecoder() - if let delegateObject = delegateObject { - try decoder.add(delegateObject: delegateObject) - } + let decoder = JSONDecoder.create(with: delegateObject) return try decoder.decode(self, from: data) } diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift b/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift index aa54760a..ffb74ef9 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift +++ b/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift @@ -56,4 +56,13 @@ public extension MVMCoreUIUtility { return findViews(by: type, views: queue) + matching } + + static func visibleNavigationBarStlye() -> NavigationItemStyle? { + if let vc = MVMCoreUIUtility.getCurrentVisibleController(), + let navController = NavigationController.navigationController(), + let navigationBar = navController.getNavigationModel(from: vc) as? NavigationItemModel { + return navigationBar.style + } + return nil + } }