Compare commits

..

No commits in common. "develop" and "feature/atomic-vds-inputField" have entirely different histories.

169 changed files with 2360 additions and 3621 deletions

View File

@ -153,9 +153,6 @@
444FB7C32821B76B00DFE692 /* TitleLockupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */; };
4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */; };
4B002ACA2BD855EC009BC9C1 /* DateDropdownEntryFieldModel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B002AC92BD855EC009BC9C1 /* DateDropdownEntryFieldModel+Extension.swift */; };
4B3408A22C3873B0003BFABF /* CircularProgressBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3408A12C3873B0003BFABF /* CircularProgressBarModel.swift */; };
4B3408A42C3873E8003BFABF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3408A32C3873E8003BFABF /* CircularProgressBar.swift */; };
4B53AF7B2C45BBBA00274685 /* GraphSizeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B53AF7A2C45BBBA00274685 /* GraphSizeProtocol.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 */; };
@ -307,9 +304,6 @@
AFA4932229E5EF2E001A9663 /* NotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */; };
AFA4933F29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */; };
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */; };
AFB6336E2C65166E00791221 /* GoneableProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFB6336D2C65166E00791221 /* GoneableProtocol.swift */; };
AFB633702C65175800791221 /* ActionUpdateVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFB6336F2C65175800791221 /* ActionUpdateVisibility.swift */; };
AFB633722C653C0900791221 /* ActionUpdateVisibilityModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFB633712C653C0900791221 /* ActionUpdateVisibilityModel.swift */; };
AFE4A1D627DFBB6F00C458D0 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */; };
B4CC8FBD29DF34680005D28B /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBC29DF34680005D28B /* Badge.swift */; };
B4CC8FBF29DF34730005D28B /* BadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CC8FBE29DF34730005D28B /* BadgeModel.swift */; };
@ -594,18 +588,6 @@
EA6642932BCDA97D00D81DC4 /* TileContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */; };
EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B942B504A43000139B4 /* ButtonGroup.swift */; };
EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */; };
EA7AE5472C73C01A00107C74 /* CheckboxesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE5462C73C01A00107C74 /* CheckboxesModel.swift */; };
EA7AE5492C7403DC00107C74 /* Checkboxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE5482C7403DC00107C74 /* Checkboxes.swift */; };
EA7AE54B2C74CACA00107C74 /* RadioButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE54A2C74CACA00107C74 /* RadioButtons.swift */; };
EA7AE54D2C74CAD700107C74 /* RadioButtonsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE54C2C74CAD700107C74 /* RadioButtonsModel.swift */; };
EA7AE54F2C74EB3700107C74 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE54E2C74EB3700107C74 /* CalendarView.swift */; };
EA7AE5512C74EB4500107C74 /* CalendarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE5502C74EB4500107C74 /* CalendarViewModel.swift */; };
EA7AE5532C74F1F600107C74 /* DatePickerEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE5522C74F1F600107C74 /* DatePickerEntryField.swift */; };
EA7AE5552C74F20600107C74 /* DatePickerEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE5542C74F20600107C74 /* DatePickerEntryFieldModel.swift */; };
EA7AE55C2C7D18A100107C74 /* BreadcrumbsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE55B2C7D18A100107C74 /* BreadcrumbsModel.swift */; };
EA7AE55E2C7D234500107C74 /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE55D2C7D234500107C74 /* Breadcrumbs.swift */; };
EA7AE5602C7E554700107C74 /* PaginationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE55F2C7E554700107C74 /* PaginationModel.swift */; };
EA7AE5622C7E555D00107C74 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AE5612C7E555D00107C74 /* Pagination.swift */; };
EA7D81602B2B6E6800D29F9E /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D815F2B2B6E6800D29F9E /* Icon.swift */; };
EA7D81622B2B6E7F00D29F9E /* IconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81612B2B6E7F00D29F9E /* IconModel.swift */; };
EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */; };
@ -791,9 +773,6 @@
444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockupModel.swift; sourceTree = "<group>"; };
4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageRenderingMode+Extension.swift"; sourceTree = "<group>"; };
4B002AC92BD855EC009BC9C1 /* DateDropdownEntryFieldModel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateDropdownEntryFieldModel+Extension.swift"; sourceTree = "<group>"; };
4B3408A12C3873B0003BFABF /* CircularProgressBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBarModel.swift; sourceTree = "<group>"; };
4B3408A32C3873E8003BFABF /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
4B53AF7A2C45BBBA00274685 /* GraphSizeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphSizeProtocol.swift; sourceTree = "<group>"; };
522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinks.swift; sourceTree = "<group>"; };
522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinksModel.swift; sourceTree = "<group>"; };
52267A0623FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextAllTextAndLinks.swift; sourceTree = "<group>"; };
@ -946,9 +925,6 @@
AFA4932129E5EF2E001A9663 /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = "<group>"; };
AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingDelegateProtocol.swift; sourceTree = "<group>"; };
AFA4935629EE3DCC001A9663 /* AlertDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDelegateProtocol.swift; sourceTree = "<group>"; };
AFB6336D2C65166E00791221 /* GoneableProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoneableProtocol.swift; sourceTree = "<group>"; };
AFB6336F2C65175800791221 /* ActionUpdateVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionUpdateVisibility.swift; sourceTree = "<group>"; };
AFB633712C653C0900791221 /* ActionUpdateVisibilityModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionUpdateVisibilityModel.swift; sourceTree = "<group>"; };
AFE4A1D527DFBB6F00C458D0 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = "<group>"; };
B4CC8FBC29DF34680005D28B /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = "<group>"; };
B4CC8FBE29DF34730005D28B /* BadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeModel.swift; sourceTree = "<group>"; };
@ -1235,18 +1211,6 @@
EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainerModel.swift; sourceTree = "<group>"; };
EA6E8B942B504A43000139B4 /* ButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroup.swift; sourceTree = "<group>"; };
EA6E8B962B504A4D000139B4 /* ButtonGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupModel.swift; sourceTree = "<group>"; };
EA7AE5462C73C01A00107C74 /* CheckboxesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxesModel.swift; sourceTree = "<group>"; };
EA7AE5482C7403DC00107C74 /* Checkboxes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkboxes.swift; sourceTree = "<group>"; };
EA7AE54A2C74CACA00107C74 /* RadioButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtons.swift; sourceTree = "<group>"; };
EA7AE54C2C74CAD700107C74 /* RadioButtonsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonsModel.swift; sourceTree = "<group>"; };
EA7AE54E2C74EB3700107C74 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
EA7AE5502C74EB4500107C74 /* CalendarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewModel.swift; sourceTree = "<group>"; };
EA7AE5522C74F1F600107C74 /* DatePickerEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerEntryField.swift; sourceTree = "<group>"; };
EA7AE5542C74F20600107C74 /* DatePickerEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerEntryFieldModel.swift; sourceTree = "<group>"; };
EA7AE55B2C7D18A100107C74 /* BreadcrumbsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsModel.swift; sourceTree = "<group>"; };
EA7AE55D2C7D234500107C74 /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = "<group>"; };
EA7AE55F2C7E554700107C74 /* PaginationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationModel.swift; sourceTree = "<group>"; };
EA7AE5612C7E555D00107C74 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = "<group>"; };
EA7D815F2B2B6E6800D29F9E /* Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = "<group>"; };
EA7D81612B2B6E7F00D29F9E /* IconModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconModel.swift; sourceTree = "<group>"; };
EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipModel.swift; sourceTree = "<group>"; };
@ -1317,7 +1281,6 @@
27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */,
27577DCC286CA959001EC47E /* MoleculeMaskingProtocol.swift */,
58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */,
AFB6336D2C65166E00791221 /* GoneableProtocol.swift */,
);
path = ModelProtocols;
sourceTree = "<group>";
@ -1629,8 +1592,6 @@
AF1C33722885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift */,
AF60A7F52892D2E300919EEB /* ActionDismissNotificationModel.swift */,
AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */,
AFB633712C653C0900791221 /* ActionUpdateVisibilityModel.swift */,
AFB6336F2C65175800791221 /* ActionUpdateVisibility.swift */,
);
path = Actions;
sourceTree = "<group>";
@ -2055,12 +2016,8 @@
BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */,
D264FAA6243FE13B00D98315 /* RadioBox.swift */,
0116A4E4228B19640094F3ED /* RadioButtonSelectionHelper.swift */,
EA7AE54C2C74CAD700107C74 /* RadioButtonsModel.swift */,
EA7AE54A2C74CACA00107C74 /* RadioButtons.swift */,
011D95AE2407266E000E3791 /* RadioButtonModel.swift */,
01004F2F22721C3800991ECC /* RadioButton.swift */,
EA7AE5462C73C01A00107C74 /* CheckboxesModel.swift */,
EA7AE5482C7403DC00107C74 /* Checkboxes.swift */,
31BE15CA23D8924C00452370 /* CheckboxModel.swift */,
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */,
AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */,
@ -2211,7 +2168,6 @@
D29DF10E21E67A77003B2FB9 /* Molecules */ = {
isa = PBXGroup;
children = (
EA7AE55A2C7D188900107C74 /* Breadcrumbs */,
D2EC7BD22527A1E400F540AF /* HeadersAndFooters */,
D2CAC7C9251104CB00C75681 /* TopNotification */,
D2509ED42472EE0B001BFB9D /* NavigationBar */,
@ -2360,11 +2316,8 @@
94C2D9822386F3E30006CF46 /* Label */,
31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */,
0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */,
4B53AF7A2C45BBBA00274685 /* GraphSizeProtocol.swift */,
D28A838223CCBD3F00DFE4FC /* WheelModel.swift */,
943784F3236B77BB006A1E82 /* Wheel.swift */,
4B3408A12C3873B0003BFABF /* CircularProgressBarModel.swift */,
4B3408A32C3873E8003BFABF /* CircularProgressBar.swift */,
943784F4236B77BB006A1E82 /* WheelAnimationHandler.swift */,
0AE98BB623FF18E9004C5109 /* ArrowModel.swift */,
0AE98BB423FF18D2004C5109 /* Arrow.swift */,
@ -2373,8 +2326,6 @@
D20492A524329CE200A5EED6 /* LoadImageView.swift */,
0A51F3E02475CB73002E08B6 /* LoadingSpinnerModel.swift */,
0A51F3E12475CB73002E08B6 /* LoadingSpinner.swift */,
EA7AE55F2C7E554700107C74 /* PaginationModel.swift */,
EA7AE5612C7E555D00107C74 /* Pagination.swift */,
AA37CBD2251907200027344C /* StarsModel.swift */,
AA37CBD42519072F0027344C /* Stars.swift */,
AA07EA902510A442009A2AE3 /* StarModel.swift */,
@ -2393,8 +2344,6 @@
EA7D815F2B2B6E6800D29F9E /* Icon.swift */,
EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */,
EA7D81652B2BABD200D29F9E /* Tooltip.swift */,
EA7AE5502C74EB4500107C74 /* CalendarViewModel.swift */,
EA7AE54E2C74EB3700107C74 /* CalendarView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -2546,8 +2495,6 @@
D2BEFEF5248A954C00FAB3A9 /* FormFields */ = {
isa = PBXGroup;
children = (
EA7AE5542C74F20600107C74 /* DatePickerEntryFieldModel.swift */,
EA7AE5522C74F1F600107C74 /* DatePickerEntryField.swift */,
EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */,
D2BEFEF6248A957A00FAB3A9 /* Tags */,
D29DF22B21E6A0FA003B2FB9 /* TextFields */,
@ -2652,15 +2599,6 @@
path = Alerts;
sourceTree = "<group>";
};
EA7AE55A2C7D188900107C74 /* Breadcrumbs */ = {
isa = PBXGroup;
children = (
EA7AE55B2C7D18A100107C74 /* BreadcrumbsModel.swift */,
EA7AE55D2C7D234500107C74 /* Breadcrumbs.swift */,
);
path = Breadcrumbs;
sourceTree = "<group>";
};
EAA0CFAD275E7D5A00D65EB0 /* FormFieldEffect */ = {
isa = PBXGroup;
children = (
@ -2892,13 +2830,11 @@
AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */,
EAA482CE2B45F2F300978105 /* MFLoadingSpinner+VDS.swift in Sources */,
D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */,
EA7AE54D2C74CAD700107C74 /* RadioButtonsModel.swift in Sources */,
D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */,
0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */,
D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */,
01004F3022721C3800991ECC /* RadioButton.swift in Sources */,
D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */,
AFB633702C65175800791221 /* ActionUpdateVisibility.swift in Sources */,
D236E5B7242007C500C38625 /* MVMControllerModelProtocol.swift in Sources */,
AA71AD4024A32FE700ACA76F /* HeadersH2Link.swift in Sources */,
D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */,
@ -2907,7 +2843,6 @@
EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */,
011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */,
EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */,
EA7AE5532C74F1F600107C74 /* DatePickerEntryField.swift in Sources */,
EA985C892981AB7100F2FF2E /* VDS-TextStyle.swift in Sources */,
BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */,
D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */,
@ -2949,7 +2884,6 @@
D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */,
01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */,
D2A92884241ACB25004E01C6 /* ProgrammaticScrollViewController.swift in Sources */,
AFB633722C653C0900791221 /* ActionUpdateVisibilityModel.swift in Sources */,
EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */,
D23A90002612347A007E14CE /* PageBehaviorContainerModelProtocol.swift in Sources */,
EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */,
@ -2963,7 +2897,6 @@
D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */,
D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */,
D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */,
EA7AE5602C7E554700107C74 /* PaginationModel.swift in Sources */,
608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */,
014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */,
0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */,
@ -2989,10 +2922,8 @@
525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */,
D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */,
D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */,
EA7AE55C2C7D18A100107C74 /* BreadcrumbsModel.swift in Sources */,
D20F3B44252E00E4004B3F56 /* PageProtocol.swift in Sources */,
AA37CBD3251907200027344C /* StarsModel.swift in Sources */,
EA7AE5622C7E555D00107C74 /* Pagination.swift in Sources */,
8D084AD22410BF7600951227 /* ListOneColumnFullWidthTextBodyText.swift in Sources */,
94C0150C2421564A005811A9 /* ActionCollapseNotificationModel.swift in Sources */,
D2CAC7CB251104E100C75681 /* NotificationXButtonModel.swift in Sources */,
@ -3019,7 +2950,6 @@
AAE96FA525341F7D0037A989 /* ListStoreLocator.swift in Sources */,
D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */,
944589232385DA9600DE9FD4 /* ImageViewModel.swift in Sources */,
EA7AE55E2C7D234500107C74 /* Breadcrumbs.swift in Sources */,
D213347723843825008E41B3 /* Line.swift in Sources */,
D2E2A99C23D8D975000B42E6 /* ImageHeadlineBodyModel.swift in Sources */,
BB3BC1302550094500297977 /* ListLeftVariableIconWithRightCaretAllTextLinksModel.swift in Sources */,
@ -3085,7 +3015,6 @@
D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */,
D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */,
D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */,
4B3408A42C3873E8003BFABF /* CircularProgressBar.swift in Sources */,
0A9D09222433796500D2E6C0 /* CarouselIndicator.swift in Sources */,
EA17584E2BC9895A00A5C0D9 /* ButtonIcon.swift in Sources */,
D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */,
@ -3106,7 +3035,6 @@
AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */,
AA37CBD52519072F0027344C /* Stars.swift in Sources */,
942C378E2412F5B60066E45E /* ModalMoleculeStackTemplate.swift in Sources */,
4B3408A22C3873B0003BFABF /* CircularProgressBarModel.swift in Sources */,
8D8067D32444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift in Sources */,
8D4687E4242E2DF300802879 /* ListFourColumnDataUsageListItem.swift in Sources */,
D2874024249BA6F300BE950A /* MVMCoreUISplitViewController+Extension.swift in Sources */,
@ -3158,9 +3086,7 @@
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */,
EA7AE54B2C74CACA00107C74 /* RadioButtons.swift in Sources */,
D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */,
EA7AE5552C74F20600107C74 /* DatePickerEntryFieldModel.swift in Sources */,
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */,
27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */,
EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */,
@ -3185,7 +3111,6 @@
D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */,
D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */,
C7F8012123E8303200396FBD /* ListRVWheel.swift in Sources */,
4B53AF7B2C45BBBA00274685 /* GraphSizeProtocol.swift in Sources */,
BB2C968F24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift in Sources */,
D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */,
EA7D81622B2B6E7F00D29F9E /* IconModel.swift in Sources */,
@ -3239,7 +3164,6 @@
D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */,
0AE14F64238315D2005417F8 /* TextField.swift in Sources */,
0A51F3E22475CB73002E08B6 /* LoadingSpinnerModel.swift in Sources */,
AFB6336E2C65166E00791221 /* GoneableProtocol.swift in Sources */,
D2169303251E53D9002A6324 /* SectionListTemplateModel.swift in Sources */,
EA6642932BCDA97D00D81DC4 /* TileContainerModel.swift in Sources */,
AF7E509929E477C1009DC2AD /* AlertController.swift in Sources */,
@ -3317,7 +3241,6 @@
01F2C20327C81F9700DC3D36 /* SubNavManagerNavigationController.swift in Sources */,
EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */,
AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */,
EA7AE5492C7403DC00107C74 /* Checkboxes.swift in Sources */,
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,
D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */,
D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */,
@ -3348,7 +3271,6 @@
BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */,
0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */,
D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */,
EA7AE5512C74EB4500107C74 /* CalendarViewModel.swift in Sources */,
AA104B1A24474A66004D2810 /* HeadersH2Buttons.swift in Sources */,
C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */,
D2D2FCF0252B72AF0033EAAA /* MoleculeSectionFooterModel.swift in Sources */,
@ -3369,7 +3291,6 @@
D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */,
D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */,
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */,
EA7AE5472C73C01A00107C74 /* CheckboxesModel.swift in Sources */,
8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */,
D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */,
EA6642912BCDA97300D81DC4 /* TileContainer.swift in Sources */,
@ -3410,7 +3331,6 @@
D2169301251E51E7002A6324 /* SectionListTemplate.swift in Sources */,
0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */,
D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */,
EA7AE54F2C74EB3700107C74 /* CalendarView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3625,7 +3545,7 @@
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks";
INFOPLIST_FILE = MVMCoreUI/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -3655,7 +3575,7 @@
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks";
INFOPLIST_FILE = MVMCoreUI/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View File

@ -1,47 +0,0 @@
//
// ActionToggleGone.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 8/8/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import MVMCore
public struct ActionUpdateVisibility: MVMCoreActionHandlerProtocol {
public init() {}
public func execute(with model: any MVMCore.ActionModelProtocol, delegateObject: MVMCore.DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
guard let model = model as? ActionUpdateVisibilityModel,
let delegate = (delegateObject as? MVMCoreUIDelegateObject)?.moleculeDelegate else { return }
let stateMap = model.targets.reduce(into: [String: ActionUpdateVisibilityModel.VisibilityTarget.VisibilityState]()) {
$0[$1.id] = $1.state
}
let updatedModels: [MoleculeModelProtocol] = delegate.getRootMolecules().reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: [], nextPartialResult: { accumulator, model, depth in
guard var model = model as? (GoneableProtocol & MoleculeModelProtocol),
let state = stateMap[model.id] else { return accumulator }
model.updateVisibility(state)
return accumulator + [model]
})
guard updatedModels.count > 0 else { return }
await delegate.updateUI(for: updatedModels)
}
}
extension GoneableProtocol {
mutating func updateVisibility(_ state: ActionUpdateVisibilityModel.VisibilityTarget.VisibilityState) {
switch state {
case .true:
gone = false
case .false:
gone = true
case .inverted:
gone = !gone
}
}
}

View File

@ -1,42 +0,0 @@
//
// ActionToggleGoneModel.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 8/8/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import MVMCore
public struct ActionUpdateVisibilityModel: ActionModelProtocol {
public static var identifier: String = "updateVisibility"
public var actionType: String = ActionUpdateVisibilityModel.identifier
public var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary?
public var targets: [VisibilityTarget]
public struct VisibilityTarget: Codable {
public enum VisibilityState: String, Codable {
case `true`
case `false`
case inverted
}
public var id: String
public var state: VisibilityState
public init(id: String, state: VisibilityState) {
self.id = id
self.state = state
}
}
public init(targets: [VisibilityTarget], extraParameters: JSONValueDictionary? = nil, analyticsData: JSONValueDictionary? = nil) {
self.targets = targets
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
}

View File

@ -38,10 +38,7 @@ open class ButtonGroup: VDS.ButtonGroup, VDSMoleculeViewProtocol {
return PillButton.estimatedHeight(with: buttonModel, delegateObject)
}
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
public func viewModelDidUpdate() {
surface = viewModel.surface
isEnabled = viewModel.enabled
alignment = viewModel.alignment

View File

@ -7,7 +7,6 @@
//
import Foundation
import MVMCore
import VDS
public class ButtonGroupModel: ParentMoleculeModelProtocol {
@ -18,7 +17,6 @@ public class ButtonGroupModel: ParentMoleculeModelProtocol {
public static var identifier: String = "buttonGroup"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var children: [MoleculeModelProtocol] { buttons }
@ -40,7 +38,6 @@ public class ButtonGroupModel: ParentMoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case backgroundColor
case buttons
case alignment
@ -59,7 +56,6 @@ public class ButtonGroupModel: ParentMoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
surface = try typeContainer.decodeIfPresent(Surface.self, forKey: .surface) ?? .light
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
buttons = try typeContainer.decodeModels(codingKey: .buttons)
@ -74,7 +70,6 @@ public class ButtonGroupModel: ParentMoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(surface, forKey: .surface)
try container.encode(enabled, forKey: .enabled)
try container.encodeModels(buttons, forKey: .buttons)

View File

@ -15,8 +15,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
public static var identifier: String = "imageButton"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var image: ImageViewModel?
@ -46,7 +45,6 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case image
case backgroundColor
case accessibilityText
@ -66,7 +64,6 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
image = try typeContainer.decodeIfPresent(ImageViewModel.self, forKey: .image)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
@ -94,7 +91,6 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(image, forKey: .image)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)

View File

@ -1,51 +0,0 @@
//
// DatePickerEntryField.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class DatePickerEntryField: VDS.DatePicker, VDSMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var viewModel: DatePickerEntryFieldModel!
public var delegateObject: MVMCoreUIDelegateObject?
public var additionalData: [AnyHashable : Any]?
//--------------------------------------------------
// MARK: - Public Methods
//--------------------------------------------------
open override func setup() {
super.setup()
//turn off internal required rule
useRequiredRule = false
publisher(for: .valueChanged)
.sink { [weak self] control in
guard let self, let viewModel, isEnabled else { return }
viewModel.selectedDate = control.selectedDate
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}.store(in: &subscribers)
}
public func viewModelDidUpdate() {
surface = viewModel.surface
labelText = viewModel.title
helperText = viewModel.feedback
helperTextPlacement = viewModel.feedbackTextPlacement
tooltipModel = viewModel.tooltip?.convertToVDSTooltipModel()
transparentBackground = viewModel.transparentBackground
width = viewModel.width
selectedDate = viewModel.selectedDate
calendarModel = viewModel.calendar.convertToVDSCalendarModel()
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
}
public func updateView(_ size: CGFloat) {}
}

View File

@ -1,121 +0,0 @@
//
// DatePickerEntryFieldModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class DatePickerEntryFieldModel: FormFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override static var identifier: String { "datePicker" }
public var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeZone = NSTimeZone.system
formatter.locale = .current
formatter.formatterBehavior = .default
return formatter
}()
/// Update the property value to alter the format of how the date is presented.
public var dateFormat: String = "MMM d, y" {
didSet { dateFormatter.dateFormat = dateFormat }
}
public var selectedDate: Date?
public var calendar: CalendarViewModel = .init()
public var title: String?
public var feedback: String?
public var feedbackTextPlacement: VDS.DatePicker.HelperTextPlacement = .bottom
public var tooltip: TooltipModel?
public var transparentBackground: Bool = false
public var width: CGFloat?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case dateFormat
case selectedDate
case calendar
case title
case feedback
case feedbackTextPlacement
case tooltip
case transparentBackground
case width
}
//--------------------------------------------------
// MARK: - Form Validation
//--------------------------------------------------
/// Returns the fieldValue of the selectedDate.
public override func formFieldValue() -> AnyHashable? {
guard let selectedDate, enabled else { return nil }
return dateFormatter.string(from: selectedDate)
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let dateFormat = try container.decodeIfPresent(String.self, forKey: .dateFormat) {
self.dateFormat = dateFormat
dateFormatter.dateFormat = dateFormat
}
if let date = try container.decodeIfPresent(String.self, forKey: .selectedDate) {
selectedDate = calendar.dateFormatter.date(from: date)
}
if let calendar = try container.decodeIfPresent(CalendarViewModel.self, forKey: .calendar) {
self.calendar = calendar
}
title = try container.decodeIfPresent(String.self, forKey: .title)
feedback = try container.decodeIfPresent(String.self, forKey: .feedback)
feedbackTextPlacement = try container.decodeIfPresent(VDS.EntryFieldBase.HelperTextPlacement.self, forKey: .feedbackTextPlacement) ?? .bottom
tooltip = try container.decodeIfPresent(TooltipModel.self, forKey: .tooltip)
transparentBackground = try container.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false
width = try container.decodeIfPresent(CGFloat.self, forKey: .width)
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(selectedDate, forKey: .selectedDate)
try container.encode(calendar, forKey: .calendar)
try container.encodeIfPresent(title, forKey: .title)
try container.encodeIfPresent(feedback, forKey: .feedback)
try container.encode(feedbackTextPlacement, forKey: .feedbackTextPlacement)
try container.encodeIfPresent(tooltip, forKey: .tooltip)
try container.encode(transparentBackground, forKey: .transparentBackground)
try container.encodeIfPresent(width, forKey: .width)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return dateFormat == model.dateFormat
&& selectedDate == model.selectedDate
&& calendar == model.calendar
&& title == model.title
&& feedback == model.feedback
&& feedbackTextPlacement == model.feedbackTextPlacement
&& tooltip == model.tooltip
&& transparentBackground == model.transparentBackground
&& width == model.width
}
}

View File

@ -15,8 +15,6 @@ import VDS
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String { "" }
public var moleculeName: String { Self.identifier }
public var id: String = UUID().uuidString
public var backgroundColor: Color?
@ -25,7 +23,7 @@ import VDS
public var enabled: Bool = true
public var required: Bool = true
public var readOnly: Bool = false
public var showError: Bool = false
public var showError: Bool?
public var errorMessage: String?
public var initialErrorMessage: String?
@ -68,7 +66,6 @@ import VDS
case moleculeName
case accessibilityIdentifier
case errorMessage
case showError
case enabled
case readOnly
case required
@ -110,7 +107,6 @@ import VDS
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage)
initialErrorMessage = errorMessage
showError = try typeContainer.decodeIfPresent(Bool.self, forKey: .showError) ?? false
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
@ -130,30 +126,9 @@ import VDS
try container.encodeIfPresent(errorMessage, forKey: .errorMessage)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(showError, forKey: .showError)
try container.encode(readOnly, forKey: .readOnly)
try container.encode(enabled, forKey: .enabled)
try container.encode(required, forKey: .required)
try container.encode(inverted, forKey: .inverted)
}
open func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return moleculeName == model.moleculeName
&& enabled == model.enabled
&& showError == model.showError
&& errorMessage == model.errorMessage
&& readOnly == model.readOnly
&& required == model.required
&& inverted == model.inverted
&& fieldKey == model.fieldKey
&& groupName == model.groupName
&& accessibilityText == model.accessibilityText
&& accessibilityIdentifier == model.accessibilityIdentifier
&& accessibilityTraits == model.accessibilityTraits
}
}
extension FormFieldModel {
public var isEnabled: Bool { enabled && !readOnly }
}

View File

@ -12,15 +12,13 @@ import MVMCore
@objcMembers public class TagModel: MoleculeModelProtocol {
public static var identifier: String = "tag"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var label: LabelModel
public var action: ActionModelProtocol?
public var backgroundColor: Color?
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case label
case action
@ -44,7 +42,6 @@ import MVMCore
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
label = try typeContainer.decode(LabelModel.self, forKey: .label)
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
@ -53,7 +50,6 @@ import MVMCore
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(label, forKey: .label)
try container.encodeModelIfPresent(action, forKey: .action)

View File

@ -12,14 +12,12 @@ import MVMCore
@objcMembers public class TagsModel: MoleculeModelProtocol {
public static var identifier: String = "tags"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var tags: [TagModel]
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case tags
@ -36,7 +34,6 @@ import MVMCore
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
tags = try typeContainer.decode([TagModel].self, forKey: .tags)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
}
@ -44,7 +41,6 @@ import MVMCore
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(tags, forKey: .tags)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)

View File

@ -7,79 +7,19 @@
//
import UIKit
import VDS
open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol, ObservingTextFieldDelegate {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: ItemDropdownEntryFieldModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
open var pickerData: [String] = [] {
didSet {
options = pickerData.compactMap({ DropdownOptionModel(text: $0) })
}
}
private var isEditting: Bool = false
//override for to deal with getting the
//old selectedItem to pass down to the observeDropdownChange
open override var selectId: Int? {
didSet {
guard let observeDropdownChange, let selectedItem else { return }
var oldSelectedItem: DropdownOptionModel?
if let oldValue {
oldSelectedItem = options[oldValue]
}
observeDropdownChange(oldSelectedItem?.text, selectedItem.text)
}
}
open class ItemDropdownEntryField: BaseItemPickerEntryField {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var isValid: Bool = true
/// Closure passed here will run as picker changes items.
public var observeDropdownChange: ((_ oldValue: String?, _ newValue: String) -> ())?
/// Closure passed here will run upon dismissing the selection picker.
public var observeDropdownSelection: ((_ newValue: String) -> ())?
open var pickerData: [String] = []
/// When selecting for first responder, allow initial selected value to appear in empty text field.
public var setInitialValueInTextField = true
open override var errorText: String? {
get {
viewModel.dynamicErrorMessage ?? viewModel.errorMessage
}
set {}
}
//--------------------------------------------------
// MARK: - Delegate Properties
//--------------------------------------------------
/// The delegate and block for validation. Validates if the text that the user has entered.
public weak var observingTextFieldDelegate: ObservingTextFieldDelegate?
/// If you're using a ViewController, you must set this to it
open weak var uiTextFieldDelegate: UITextFieldDelegate? {
get { dropdownField.delegate }
set { dropdownField.delegate = newValue }
public var itemDropdownEntryFieldModel: ItemDropdownEntryFieldModel? {
model as? ItemDropdownEntryFieldModel
}
@objc public func dismissFieldInput(_ sender: Any?) {
_ = resignFirstResponder()
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -88,7 +28,7 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol,
super.init(frame: frame)
}
@objc public convenience required init() {
@objc public convenience init() {
self.init(frame: .zero)
}
@ -100,133 +40,76 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol,
@objc required public init?(coder: NSCoder) {
fatalError("ItemDropdownEntryField init(coder:) has not been implemented")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
open override func setup() {
super.setup()
useRequiredRule = false
publisher(for: .valueChanged)
.sink { [weak self] control in
guard let self, let selectedItem, let viewModel else { return }
viewModel.selectedIndex = control.selectId
observeDropdownSelection?(selectedItem.text)
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}.store(in: &subscribers)
dropdownField
.publisher(for: .editingDidBegin)
.sink { [weak self] textField in
guard let self else { return }
isEditting = true
setInitialValueFromPicker()
}.store(in: &subscribers)
dropdownField
.publisher(for: .editingDidEnd)
.sink { [weak self] textField in
guard let self else { return }
isEditting = false
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
if let viewModel, let valid = viewModel.isValid {
updateValidation(valid)
}
performDropdownAction()
}.store(in: &subscribers)
}
public func viewModelDidUpdate() {
pickerData = viewModel.options
showInlineLabel = viewModel.showInlineLabel
helperTextPlacement = viewModel.feedbackTextPlacement
labelText = viewModel.title
helperText = viewModel.feedback
isEnabled = viewModel.enabled
isReadOnly = viewModel.readOnly
isRequired = viewModel.required
tooltipModel = viewModel.tooltip?.convertToVDSTooltipModel()
width = viewModel.width
transparentBackground = viewModel.transparentBackground
if let index = viewModel.selectedIndex {
selectId = index
optionsPicker.selectRow(index, inComponent: 0, animated: false)
pickerView(optionsPicker, didSelectRow: index, inComponent: 0)
}
if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected {
viewModel.wasInitiallySelected = true
isEditting = true
}
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
if isEditting {
DispatchQueue.main.async {
_ = self.becomeFirstResponder()
}
}
viewModel.updateUI = {
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
guard let self = self else { return }
if isEditting {
updateValidation(viewModel.isValid ?? true)
} else if viewModel.isValid ?? true && showError {
showError = false
}
isEnabled = viewModel.enabled
})
}
viewModel.updateUIDynamicError = {
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
guard let self = self else { return }
let validState = viewModel.isValid ?? false
if !validState && viewModel.shouldClearText {
selectId = nil
viewModel.shouldClearText = false
}
updateValidation(validState)
})
}
}
public func updateView(_ size: CGFloat) { }
/// Sets the textField with the first value of the available picker data.
private func setInitialValueFromPicker() {
@objc private func setInitialValueFromPicker() {
guard !pickerData.isEmpty else { return }
if setInitialValueInTextField {
let pickerIndex = optionsPicker.selectedRow(inComponent: 0)
viewModel?.selectedIndex = pickerIndex
selectId = pickerIndex
let pickerIndex = pickerView.selectedRow(inComponent: 0)
itemDropdownEntryFieldModel?.selectedIndex = pickerIndex
observeDropdownChange?(text, pickerData[pickerIndex])
text = pickerData[pickerIndex]
}
}
private func performDropdownAction() {
guard let actionModel = viewModel.action,
!dropdownField.isFirstResponder
else { return }
MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel, additionalData: additionalData, delegateObject: delegateObject)
}
private func updateValidation(_ isValid: Bool) {
let previousValidity = self.isValid
self.isValid = isValid
@objc override func startEditing() {
super.startEditing()
if previousValidity && !isValid {
showError = true
} else if (!previousValidity && isValid) {
showError = false
setInitialValueFromPicker()
}
@objc override func endInputing() {
super.endInputing()
guard !pickerData.isEmpty else { return }
observeDropdownSelection?(pickerData[pickerView.selectedRow(inComponent: 0)])
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? ItemDropdownEntryFieldModel else { return }
pickerData = model.options
if let index = model.selectedIndex {
self.pickerView.selectRow(index, inComponent: 0, animated: false)
self.pickerView(pickerView, didSelectRow: index, inComponent: 0)
}
}
//--------------------------------------------------
// MARK: - Picker Delegate
//--------------------------------------------------
@objc public override func numberOfComponents(in pickerView: UIPickerView) -> Int { 1 }
@objc public override func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
pickerData.count
}
@objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
guard !pickerData.isEmpty else { return nil }
return pickerData[row]
}
@objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
guard !pickerData.isEmpty else { return }
itemDropdownEntryFieldModel?.selectedIndex = row
observeDropdownChange?(text, pickerData[row])
text = pickerData[row]
}
}

View File

@ -5,19 +5,16 @@
// Created by Kevin Christiano on 1/22/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
@objcMembers open class ItemDropdownEntryFieldModel: TextEntryFieldModel {
@objcMembers open class ItemDropdownEntryFieldModel: BaseItemPickerEntryFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override class var identifier: String { "dropDown" }
public var action: ActionModelProtocol?
public var options: [String] = []
public var selectedIndex: Int?
public var showInlineLabel: Bool = false
public var feedbackTextPlacement: VDS.DropdownSelect.HelperTextPlacement = .bottom
public init(with options: [String], selectedIndex: Int? = nil) {
self.options = options
@ -45,9 +42,6 @@ import VDS
private enum CodingKeys: String, CodingKey {
case options
case selectedIndex
case action
case showInlineLabel
case feedbackTextPlacement
}
//--------------------------------------------------
@ -64,9 +58,6 @@ import VDS
self.selectedIndex = selectedIndex
baseValue = options.indices.contains(selectedIndex) ? options[selectedIndex] : nil
}
showInlineLabel = try typeContainer.decodeIfPresent(Bool.self, forKey: .showInlineLabel) ?? false
feedbackTextPlacement = try typeContainer.decodeIfPresent(VDS.EntryFieldBase.HelperTextPlacement.self, forKey: .feedbackTextPlacement) ?? .bottom
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
}
public override func encode(to encoder: Encoder) throws {
@ -74,17 +65,5 @@ import VDS
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(options, forKey: .options)
try container.encodeIfPresent(selectedIndex, forKey: .selectedIndex)
try container.encode(showInlineLabel, forKey: .showInlineLabel)
try container.encode(feedbackTextPlacement, forKey: .feedbackTextPlacement)
try container.encodeModelIfPresent(action, forKey: .action)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return options == model.options
&& selectedIndex == model.selectedIndex
&& showInlineLabel == model.showInlineLabel
&& feedbackTextPlacement == model.feedbackTextPlacement
&& action.isEqual(to: model.action)
}
}

View File

@ -315,9 +315,7 @@ import UIKit
self.showError = false
}
self.isEnabled = model.enabled
if let text = model.text, !text.isEmpty {
self.text = model.text
}
self.text = model.text
})
}

View File

@ -34,6 +34,7 @@ import Foundation
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case backgroundColor
case title
case feedback
case errorTextColor
@ -85,6 +86,7 @@ import Foundation
required public init(from decoder: Decoder) throws {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
title = try typeContainer.decodeIfPresent(String.self, forKey: .title)
feedback = try typeContainer.decodeIfPresent(String.self, forKey: .feedback)
errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor)
@ -104,6 +106,7 @@ import Foundation
open override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(title, forKey: .title)
try container.encodeIfPresent(feedback, forKey: .feedback)
try container.encodeIfPresent(text, forKey: .text)
@ -113,16 +116,4 @@ import Foundation
try container.encode(hideBorders, forKey: .hideBorders)
try container.encode(shouldMaskRecordedView, forKey: .shouldMaskRecordedView)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return selected == model.selected
&& title == model.title
&& feedback == model.feedback
&& errorTextColor == model.errorTextColor
&& locked == model.locked
&& hideBorders == model.hideBorders
&& text == model.text
&& shouldMaskRecordedView == model.shouldMaskRecordedView
}
}

View File

@ -33,7 +33,7 @@ import VDS
private var isEditting: Bool = false {
didSet {
viewModel?.selected = isEditting
viewModel.selected = isEditting
}
}
@ -47,7 +47,7 @@ import VDS
open var validateWhenDoneEditing: Bool = true
open var shouldMaskWhileRecording: Bool {
return viewModel?.shouldMaskRecordedView ?? false
return viewModel.shouldMaskRecordedView ?? false
}
//--------------------------------------------------
// MARK: - Computed Properties
@ -99,7 +99,7 @@ import VDS
publisher(for: .valueChanged)
.sink { [weak self] control in
guard let self, let viewModel else { return }
guard let self else { return }
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
if (viewModel.type == .email) {
// remove spaces (either user entered Or auto-correct suggestion) for the email field
@ -112,7 +112,7 @@ import VDS
.sink { [weak self] textView in
guard let self else { return }
isEditting = true
if let viewModel, viewModel.clearTextOnTap {
if viewModel.clearTextOnTap {
text = ""
}
}.store(in: &subscribers)
@ -122,7 +122,7 @@ import VDS
.sink { [weak self] textView in
guard let self else { return }
isEditting = false
if let viewModel, validateWhenDoneEditing, let valid = viewModel.isValid {
if validateWhenDoneEditing, let valid = viewModel.isValid {
updateValidation(valid)
}
regexTextFieldOutputIfAvailable()
@ -173,7 +173,7 @@ import VDS
isEnabled = viewModel.enabled
isReadOnly = viewModel.readOnly
isRequired = viewModel.required
tooltipModel = viewModel.tooltip?.convertToVDSTooltipModel()
tooltipModel = viewModel.tooltip?.toVDSTooltipModel()
width = viewModel.width
transparentBackground = viewModel.transparentBackground

View File

@ -218,21 +218,4 @@ import VDS
try container.encode(transparentBackground, forKey: .transparentBackground)
try container.encodeIfPresent(width, forKey: .width)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return placeholder == model.placeholder
&& textAlignment == model.textAlignment
&& enabledTextColor == model.enabledTextColor
&& disabledTextColor == model.disabledTextColor
&& keyboardOverride == model.keyboardOverride
&& type == model.type
&& clearTextOnTap == model.clearTextOnTap
&& displayFormat == model.displayFormat
&& displayMask == model.displayMask
&& enableClipboardActions == model.enableClipboardActions
&& tooltip == model.tooltip
&& transparentBackground == model.transparentBackground
&& width == model.width
}
}

View File

@ -7,60 +7,100 @@
//
import UIKit
import VDS
open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingTextFieldDelegate, ViewMaskingProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: TextViewEntryFieldModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
open var fieldKey: String?
open var fieldValue: JSONValue?
open var groupName: String?
class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDelegate {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
open private(set) var textView: TextView = {
let textView = TextView()
textView.setContentCompressionResistancePriority(.required, for: .vertical)
return textView
}()
//--------------------------------------------------
// MARK: - Stored Properties
// MARK: - Properties
//--------------------------------------------------
public var isValid: Bool = true
private var isEditting: Bool = false {
didSet {
viewModel?.selected = isEditting
}
}
private var observingForChange: Bool = false
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
open var shouldMaskWhileRecording: Bool {
return viewModel?.shouldMaskRecordedView ?? false
public var textViewEntryFieldModel: TextViewEntryFieldModel? {
model as? TextViewEntryFieldModel
}
/// Placeholder access for the textView.
open var placeholder: String? {
get { viewModel?.placeholder }
set {
textView.placeholder = newValue ?? ""
viewModel?.placeholder = newValue
public override var isEnabled: Bool {
get { super.isEnabled }
set (enabled) {
super.isEnabled = enabled
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.textView.isEnabled = enabled
if self.textView.isShowingPlaceholder {
self.textView.textColor = self.textView.placeholderTextColor
} else {
self.textView.textColor = (self.textView.isEnabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor
}
}
}
}
public override var showError: Bool {
get { super.showError }
set (error) {
if error {
textView.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textView_error_message") ?? "", textView.text ?? "", entryFieldModel?.errorMessage ?? "")
} else {
textView.accessibilityValue = nil
}
super.showError = error
}
}
/// The text of this textView.
open override var text: String? {
didSet {
viewModel?.text = text
get { textViewEntryFieldModel?.text }
set {
textView.text = newValue
textViewEntryFieldModel?.text = newValue
}
}
open override var errorText: String? {
get {
viewModel.dynamicErrorMessage ?? viewModel.errorMessage
/// Placeholder access for the textView.
public var placeholder: String? {
get { textViewEntryFieldModel?.placeholder }
set {
textView.placeholder = newValue ?? ""
textViewEntryFieldModel?.placeholder = newValue
textView.setPlaceholderIfAvailable()
}
set {}
}
//--------------------------------------------------
// MARK: - Constraint
//--------------------------------------------------
public var heightConstraint: NSLayoutConstraint?
private var topConstraint: NSLayoutConstraint?
private var leadingConstraint: NSLayoutConstraint?
private var trailingConstraint: NSLayoutConstraint?
private var bottomConstraint: NSLayoutConstraint?
private func adjustMarginConstraints(constant: CGFloat) {
topConstraint?.constant = constant
leadingConstraint?.constant = constant
trailingConstraint?.constant = constant
bottomConstraint?.constant = constant
}
//--------------------------------------------------
@ -68,178 +108,198 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT
//--------------------------------------------------
/// The delegate and block for validation. Validates if the text that the user has entered.
open weak var observingTextViewDelegate: ObservingTextFieldDelegate?
public weak var observingTextViewDelegate: ObservingTextFieldDelegate? {
didSet {
if observingTextViewDelegate != nil && !observingForChange {
observingForChange = true
NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextView.textDidChangeNotification, object: textView)
NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextView.textDidEndEditingNotification, object: textView)
NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextView.textDidBeginEditingNotification, object: textView)
} else if observingTextViewDelegate == nil && observingForChange {
observingForChange = false
NotificationCenter.default.removeObserver(self, name: UITextView.textDidChangeNotification, object: textView)
NotificationCenter.default.removeObserver(self, name: UITextView.textDidEndEditingNotification, object: textView)
NotificationCenter.default.removeObserver(self, name: UITextView.textDidBeginEditingNotification, object: textView)
}
}
}
/// If you're using a ViewController, you must set this to it
open weak var uiTextViewDelegate: UITextViewDelegate? {
public weak var uiTextViewDelegate: UITextViewDelegate? {
get { textView.delegate }
set { textView.delegate = newValue }
}
@objc open func setBothTextDelegates(to delegate: (UITextViewDelegate & ObservingTextFieldDelegate)?) {
@objc public func setBothTextDelegates(to delegate: (UITextViewDelegate & ObservingTextFieldDelegate)?) {
observingTextViewDelegate = delegate
uiTextViewDelegate = delegate
}
open func setupTextViewToolbar() {
let observingDelegate = observingTextViewDelegate ?? self
textView.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate,
action: #selector(observingDelegate.dismissFieldInput))
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func setup() {
super.setup()
//turn off internal required rule
useRequiredRule = false
publisher(for: .valueChanged)
.sink { [weak self] control in
guard let self, let viewModel else { return }
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}.store(in: &subscribers)
textView
.publisher(for: .editingDidBegin)
.sink { [weak self] textView in
guard let self else { return }
isEditting = true
}.store(in: &subscribers)
textView
.publisher(for: .editingDidEnd)
.sink { [weak self] textView in
guard let self else { return }
isEditting = false
if let viewModel, let valid = viewModel.isValid {
updateValidation(valid)
}
}.store(in: &subscribers)
}
open func viewModelDidUpdate() {
text = viewModel.text
minHeight = viewModel.minHeight
maxLength = viewModel.maxLength
@objc open override func setupFieldContainerContent(_ container: UIView) {
labelText = viewModel.title
helperText = viewModel.feedback
isEnabled = viewModel.enabled
isReadOnly = viewModel.readOnly
isRequired = viewModel.required
tooltipModel = viewModel.tooltip?.convertToVDSTooltipModel()
width = viewModel.width
transparentBackground = viewModel.transparentBackground
container.addSubview(textView)
topConstraint = textView.topAnchor.constraint(equalTo: container.topAnchor, constant: Padding.Three)
leadingConstraint = textView.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: Padding.Three)
trailingConstraint = container.trailingAnchor.constraint(equalTo: textView.trailingAnchor, constant: Padding.Three)
bottomConstraint = container.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: Padding.Three)
topConstraint?.isActive = true
leadingConstraint?.isActive = true
trailingConstraint?.isActive = true
bottomConstraint?.isActive = true
heightConstraint = textView.heightAnchor.constraint(equalToConstant: 0)
accessibilityElements = [textView]
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
textView.updateView(size)
}
open override func reset() {
super.reset()
textView.reset()
adjustMarginConstraints(constant: Padding.Three)
heightConstraint?.constant = 0
heightConstraint?.isActive = false
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Validates the text of the entry field.
@objc public override func validateText() {
text = textView.text
super.validateText()
}
/// Executes on UITextView.textDidBeginEditingNotification
@objc override func startEditing() {
super.startEditing()
_ = textView.becomeFirstResponder()
}
/// Executes on UITextView.textDidChangeNotification (each character entry)
@objc override func valueChanged() {
super.valueChanged()
validateText()
}
/// Executes on UITextView.textDidEndEditingNotification
@objc override func endInputing() {
super.endInputing()
// Don't show error till user starts typing.
guard text?.count ?? 0 != 0 else {
showError = false
return
}
if let isValid = textViewEntryFieldModel?.isValid {
self.isValid = isValid
}
showError = !isValid
}
//--------------------------------------------------
// 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? TextViewEntryFieldModel else { return }
if let height = model.height {
heightConstraint?.constant = height
heightConstraint?.isActive = true
}
text = model.text
uiTextViewDelegate = delegateObject?.uiTextViewDelegate
observingTextViewDelegate = delegateObject?.observingTextFieldDelegate
if let accessibilityText = viewModel.accessibilityText {
if let accessibilityText = model.accessibilityText {
accessibilityLabel = accessibilityText
}
containerView.accessibilityIdentifier = viewModel.accessibilityIdentifier
textView.isEditable = viewModel.editable
textView.textAlignment = viewModel.textAlignment
textView.placeholder = viewModel.placeholder ?? ""
if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected {
viewModel.wasInitiallySelected = true
isEditting = true
}
switch viewModel.type {
case .secure:
textView.isEditable = model.editable
textView.textAlignment = model.textAlignment
textView.accessibilityIdentifier = model.accessibilityIdentifier
textView.textColor = model.enabled ? model.enabledTextColor.uiColor : model.disabledTextColor.uiColor
textView.font = model.fontStyle.getFont()
textView.placeholder = model.placeholder ?? ""
textView.placeholderFontStyle = model.placeholderFontStyle
textView.placeholderTextColor = model.placeholderTextColor.uiColor
textView.setPlaceholderIfAvailable()
switch model.type {
case .secure, .password:
textView.isSecureTextEntry = true
textView.shouldMaskWhileRecording = true
case .numberSecure:
textView.isSecureTextEntry = true
textView.shouldMaskWhileRecording = true
textView.keyboardType = .numberPad
case .number:
textView.keyboardType = .numberPad
case .email:
textView.keyboardType = .emailAddress
case .securityCode, .creditCard, .password:
textView.shouldMaskWhileRecording = true
default:
break;
default: break
}
// Override the preset keyboard set in type.
if let keyboardType = viewModel.assignKeyboardType() {
textView.keyboardType = keyboardType
}
/// append any internal rules:
viewModel.rules = rules
/// No point in configuring if the TextView is Read-only.
if textView.isEditable {
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
setupTextViewToolbar()
if isEditting {
if isSelected {
DispatchQueue.main.async {
_ = self.becomeFirstResponder()
_ = self.textView.becomeFirstResponder()
}
}
}
viewModel.updateUI = {
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
guard let self = self else { return }
if isEditting {
updateValidation(viewModel.isValid ?? true)
} else if viewModel.isValid ?? true && showError {
showError = false
}
isEnabled = viewModel.enabled
})
if model.hideBorders {
adjustMarginConstraints(constant: 0)
}
viewModel.updateUIDynamicError = {
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
guard let self = self else { return }
let validState = viewModel.isValid ?? false
if !validState && viewModel.shouldClearText {
text = ""
viewModel.shouldClearText = false
}
updateValidation(validState)
})
}
updateAccessibility(model: model)
}
private func updateValidation(_ isValid: Bool) {
let previousValidity = self.isValid
self.isValid = isValid
func updateAccessibility(model: TextViewEntryFieldModel) {
if previousValidity && !isValid {
showError = true
} else if (!previousValidity && isValid) {
showError = false
var message = ""
if let titleText = model.accessibilityText ?? model.title {
message += "\(titleText) \( model.enabled ? String(format: (MVMCoreUIUtility.hardcodedString(withKey: "textfield_optional")) ?? "") : "" ) \(self.textView.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
}
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open func updateView(_ size: CGFloat) {}
}
extension VDS.TextView: ViewMaskingProtocol {
public var shouldMaskWhileRecording: Bool {
get {
return (objc_getAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording) as? Bool) ?? false
if let feedback = model.feedback {
message += ", " + feedback
}
set {
objc_setAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
if let errorMessage = errorLabel.text {
message += ", " + errorMessage
}
textView.accessibilityLabel = message
}
}

View File

@ -7,9 +7,9 @@
//
import UIKit
import VDS
public class TextViewEntryFieldModel: TextEntryFieldModel {
class TextViewEntryFieldModel: TextEntryFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -17,10 +17,12 @@ public class TextViewEntryFieldModel: TextEntryFieldModel {
public override class var identifier: String { "textView" }
public var accessibilityText: String?
public var fontStyle: Styler.Font = Styler.Font.RegularBodyLarge
public var height: CGFloat?
public var placeholderTextColor: Color = Color(uiColor: .mvmCoolGray3)
public var placeholderFontStyle: Styler.Font = Styler.Font.RegularMicro
public var editable: Bool = true
public var showsPlaceholder: Bool = false
public var minHeight: VDS.TextArea.Height = .twoX
public var maxLength: Int?
//--------------------------------------------------
// MARK: - Keys
@ -28,9 +30,11 @@ public class TextViewEntryFieldModel: TextEntryFieldModel {
private enum CodingKeys: String, CodingKey {
case accessibilityText
case fontStyle
case height
case placeholderFontStyle
case placeholderTextColor
case editable
case minHeight
case maxLength
}
//--------------------------------------------------
@ -41,27 +45,34 @@ public class TextViewEntryFieldModel: TextEntryFieldModel {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) ?? true
minHeight = try typeContainer.decodeIfPresent(VDS.TextArea.Height.self, forKey: .minHeight) ?? .twoX
maxLength = try typeContainer.decodeIfPresent(Int.self, forKey: .maxLength)
if let placeholderFontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .placeholderFontStyle) {
self.placeholderFontStyle = placeholderFontStyle
}
if let placeholderTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .placeholderTextColor) {
self.placeholderTextColor = placeholderTextColor
}
if let fontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .fontStyle) {
self.fontStyle = fontStyle
}
if let editable = try typeContainer.decodeIfPresent(Bool.self, forKey: .editable) {
self.editable = editable
}
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(height, forKey: .height)
try container.encode(fontStyle, forKey: .fontStyle)
try container.encode(editable, forKey: .editable)
try container.encode(minHeight, forKey: .minHeight)
try container.encodeIfPresent(maxLength, forKey: .maxLength)
try container.encode(placeholderFontStyle, forKey: .placeholderFontStyle)
try container.encode(placeholderTextColor, forKey: .placeholderTextColor)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return accessibilityText == model.accessibilityText
&& editable == model.editable
&& minHeight == model.minHeight
&& maxLength == model.maxLength
}
}

View File

@ -7,41 +7,144 @@
//
import MVMCore
import VDS
/**
This class expects its height and width to be equal.
*/
@objcMembers open class Checkbox: VDS.Checkbox, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
@objcMembers open class Checkbox: Control, MVMCoreUIViewConstrainingProtocol {
//--------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: CheckboxModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
//--------------------------------------------------
public var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0)
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
/// Action Block called when the switch is selected.
open var actionBlock: ActionBlock? {
var delegateObject: MVMCoreUIDelegateObject?
public var checkboxModel: CheckboxModel? {
model as? CheckboxModel
}
public static let defaultHeightWidth: CGFloat = 18.0
/// If true the border of this checkbox will be circular.
public var isRound: Bool = false
/// Determined if the checkbox's UI should animated when selected.
public var isAnimated: Bool = true
/// Disables all selection logic when setting the value of isSelected, reducing it to a stored property.
public var updateSelectionOnly: Bool = false
/// The color of the background when checked.
public var checkedBackgroundColor: UIColor = .clear {
didSet {
if let actionBlock {
onChange = { _ in
actionBlock()
}
} else {
onChange = nil
if isSelected {
backgroundColor = checkedBackgroundColor
}
}
}
/// The color of the background when unChecked.
public var unCheckedBackgroundColor: UIColor = .clear {
didSet {
if !isSelected {
backgroundColor = unCheckedBackgroundColor
}
}
}
/// Retrieves ideeal radius value to curve square into a circle.
public var cornerRadiusValue: CGFloat {
bounds.size.height / 2
}
/// Action Block called when the switch is selected.
public var actionBlock: ActionBlock?
/// Manages the appearance of the checkbox.
private var shapeLayer: CAShapeLayer?
/// Width of the check mark.
public var checkWidth: CGFloat = 2 {
didSet {
if let shapeLayer = shapeLayer {
CATransaction.withDisabledAnimations {
shapeLayer.lineWidth = checkWidth
}
}
}
}
open override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
if isEnabled {
layer.borderColor = borderColor.cgColor
backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor
setShapeLayerStrokeColor(checkColor)
} else {
layer.borderColor = disabledBorderColor.cgColor
backgroundColor = disabledBackgroundColor
setShapeLayerStrokeColor(disabledCheckColor)
}
}
}
public var disabledBackgroundColor: UIColor = .clear
public var disabledBorderColor: UIColor = .mvmCoolGray3
public var disabledCheckColor: UIColor = .mvmCoolGray3
/// Color of the check mark.
public var checkColor: UIColor = .mvmBlack {
didSet { setShapeLayerStrokeColor(checkColor) }
}
/// Border width of the checkbox
public var borderWidth: CGFloat = 1 {
didSet { layer.borderWidth = borderWidth }
}
/// border color of the Checkbox
public var borderColor: UIColor = .mvmBlack {
didSet { layer.borderColor = borderColor.cgColor }
}
/**
The represented state of the Checkbox.
Setting updateSelectionOnly to true bypasses the animation logic inherent with setting this property.
*/
override open var isSelected: Bool {
didSet {
viewModel?.selected = isSelected
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
if !updateSelectionOnly {
layoutIfNeeded()
(model as? CheckboxModel)?.selected = isSelected
shapeLayer?.removeAllAnimations()
updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated)
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
updateAccessibilityLabel()
}
}
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var heightConstraint: NSLayoutConstraint?
public var widthConstraint: NSLayoutConstraint?
/// Updates the height and width anchors of the Checkbox with the assigned value.
public var heigthWidthConstant: CGFloat = Checkbox.defaultHeightWidth {
didSet {
heightConstraint?.constant = heigthWidthConstant
widthConstraint?.constant = heigthWidthConstant
}
}
@ -51,6 +154,11 @@ import VDS
override public init(frame: CGRect) {
super.init(frame: frame)
isAccessibilityElement = true
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint")
accessibilityTraits = .button
updateAccessibilityLabel()
}
/// There is currently no intention on using xib files.
@ -59,110 +167,278 @@ import VDS
fatalError("xib file is not implemented for Checkbox.")
}
public convenience required init() {
public convenience override init() {
self.init(frame:.zero)
}
public convenience init(isChecked: Bool) {
self.init(frame: .zero)
isSelected = isChecked
checkAndBypassAnimations(selected: isChecked)
}
public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) {
self.init(frame: .zero)
isSelected = isChecked
checkAndBypassAnimations(selected: isChecked)
self.checkedBackgroundColor = checkedBackgroundColor
self.unCheckedBackgroundColor = unCheckedBackgroundColor
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
override open func layoutSubviews() {
super.layoutSubviews()
drawShapeLayer()
layer.cornerRadius = isRound ? cornerRadiusValue : 0
}
open override func setupView() {
super.setupView()
isUserInteractionEnabled = true
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .clear
widthConstraint = widthAnchor.constraint(equalToConstant: Checkbox.defaultHeightWidth)
heightConstraint = heightAnchor.constraint(equalToConstant: Checkbox.defaultHeightWidth)
heightWidthIsActive(true)
}
//--------------------------------------------------
// MARK: - Actions
//--------------------------------------------------
open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
super.sendAction(action, to: target, for: event)
toggleAndAction()
}
open override func sendActions(for controlEvents: UIControl.Event) {
super.sendActions(for: controlEvents)
toggleAndAction()
}
/// This will toggle the state of the Checkbox and execute the actionBlock if provided.
open func toggleAndAction() {
toggle()
public func toggleAndAction() {
isSelected.toggle()
actionBlock?()
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Creates the check mark layer.
private func drawShapeLayer() {
if shapeLayer == nil {
let shapeLayer = CAShapeLayer()
self.shapeLayer = shapeLayer
shapeLayer.frame = bounds
layer.addSublayer(shapeLayer)
shapeLayer.strokeColor = isEnabled ? checkColor.cgColor : disabledCheckColor.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.path = checkMarkPath()
shapeLayer.lineJoin = .miter
shapeLayer.lineWidth = checkWidth
CATransaction.withDisabledAnimations {
shapeLayer.strokeEnd = isSelected ? 1 : 0
}
}
}
/// - returns: The CGPath of a UIBezierPath detailing the path of a checkmark
func checkMarkPath() -> CGPath {
let length = max(bounds.size.height, bounds.size.width)
let xInsetLeft = length * 0.25
let yInsetTop = length * 0.3
let innerWidth = length - (xInsetLeft + length * 0.25) // + Right X Inset
let innerHeight = length - (yInsetTop + length * 0.35) // + Bottom Y Inset
let startPoint = CGPoint(x: xInsetLeft, y: yInsetTop + (innerHeight / 2))
let pivotOffSet = CGPoint(x: xInsetLeft + (innerWidth * 0.33), y: yInsetTop + innerHeight)
let endOffset = CGPoint(x: xInsetLeft + innerWidth, y: yInsetTop)
let bezierPath = UIBezierPath()
bezierPath.move(to: startPoint)
bezierPath.addLine(to: pivotOffSet)
bezierPath.addLine(to: endOffset)
return bezierPath.cgPath
}
/// Programmatic means to check/uncheck the box.
/// - parameter selected: state of the check box: true = checked OR false = unchecked.
/// - parameter animated: allows the state of the checkbox to change with or without animation.
open func updateSelection(to selected: Bool, animated: Bool) {
public func updateSelection(to selected: Bool, animated: Bool) {
DispatchQueue.main.async {
self.isAnimated = animated
self.isSelected = selected
self.checkAndBypassAnimations(selected: selected)
self.drawShapeLayer()
self.shapeLayer?.removeAllAnimations()
self.updateCheckboxUI(isSelected: selected, isAnimated: animated)
}
}
/// updates the visuals of the check mark and background.
/// - parameter isSelected: the check state of the checkbox.
/// - parameter isAnimated: determines of the changes should animate or immediately refelect.
open func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) {
public func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) {
DispatchQueue.main.async {
self.isAnimated = isAnimated
self.isSelected = isSelected
if isAnimated {
let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear)
animateStrokeEnd.duration = 0.3
animateStrokeEnd.fillMode = .both
animateStrokeEnd.isRemovedOnCompletion = false
animateStrokeEnd.fromValue = !isSelected ? 1 : 0
animateStrokeEnd.toValue = isSelected ? 1 : 0
self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd")
UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: {
self.backgroundColor = isSelected ? self.checkedBackgroundColor : self.unCheckedBackgroundColor
})
} else {
CATransaction.withDisabledAnimations {
self.shapeLayer?.strokeEnd = isSelected ? 1 : 0
}
backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor
}
}
/// Adjust accessibility label based on state of Checkbox.
public func updateAccessibilityLabel() {
// Attention: This needs to be addressed with the accessibility team.
// NOTE: Currently emptying description part of MVMCoreUICheckBox accessibility label to avoid crashing!
if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") {
accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@%@", "", state)
}
}
private func setShapeLayerStrokeColor(_ color: UIColor) {
if let shapeLayer = shapeLayer {
CATransaction.withDisabledAnimations {
shapeLayer.strokeColor = color.cgColor
}
}
}
public func heightWidthIsActive(_ isActive: Bool) {
heightConstraint?.isActive = isActive
widthConstraint?.isActive = isActive
}
private func checkAndBypassAnimations(selected: Bool) {
updateSelectionOnly = true
isSelected = selected
updateSelectionOnly = false
}
//--------------------------------------------------
// MARK: - UITouch
//--------------------------------------------------
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
sendActions(for: .touchUpInside)
}
override open func accessibilityActivate() -> Bool {
guard isEnabled else { return false }
sendActions(for: .touchUpInside)
return true
}
//--------------------------------------------------
// MARK: - Molecular
//--------------------------------------------------
open func needsToBeConstrained() -> Bool { true }
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
open func updateView(_ size: CGFloat) {}
private func performCheckboxAction(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel, additionalData: additionalData, delegateObject: delegateObject)
open override func reset() {
super.reset()
isEnabled = true
shapeLayer?.removeAllAnimations()
shapeLayer?.removeFromSuperlayer()
shapeLayer = nil
backgroundColor = .clear
borderColor = .mvmBlack
borderWidth = 1
checkColor = .mvmBlack
checkWidth = 2
checkAndBypassAnimations(selected: false)
}
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
public override func updateView(_ size: CGFloat) {
super.updateView(size)
if let dimension = sizeObject?.getValueBased(onSize: size) {
widthConstraint?.constant = dimension
heightConstraint?.constant = dimension
}
//forms
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
groupName = viewModel.groupName
if let fieldKey = viewModel.fieldKey {
}
private func performCheckboxAction(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: checkboxModel, additionalData: additionalData, delegateObject: delegateObject)
}
public 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? CheckboxModel else { return }
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
if let fieldKey = model.fieldKey {
self.fieldKey = fieldKey
}
//properties
isEnabled = viewModel.isEnabled
isAnimated = viewModel.animated
//call super here to go around the didSet
//in this class
super.isSelected = viewModel.selected
//events
viewModel.updateUI = {
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
borderColor = (model.inverted ? model.invertedColor : model.borderColor).uiColor
borderWidth = model.borderWidth
checkColor = (model.inverted ? model.invertedColor : model.checkColor).uiColor
unCheckedBackgroundColor = (model.inverted ? model.invertedBackgroundColor : model.unCheckedBackgroundColor).uiColor
checkedBackgroundColor = (model.inverted ? model.invertedBackgroundColor : model.checkedBackgroundColor).uiColor
disabledCheckColor = (model.inverted ? model.invertedColor : model.disabledCheckColor).uiColor
disabledBorderColor = (model.inverted ? model.invertedColor : model.disabledBorderColor).uiColor
disabledBackgroundColor = (model.inverted ? model.invertedColor : model.disabledBackgroundColor).uiColor
isAnimated = model.animated
isRound = model.round
if model.selected {
checkAndBypassAnimations(selected: model.selected)
}
model.updateUI = { [weak self] in
MVMCoreDispatchUtility.performBlock(onMainThread: {
guard let self = self else { return }
//let isValid = viewModel.isValid ?? true
//TODO: Fix issue with default state
//showError = !isValid
isEnabled = viewModel.enabled
self.isEnabled = model.enabled
})
}
//onChange
if (viewModel.action != nil || viewModel.offAction != nil) {
isEnabled = model.enabled && !model.readOnly
if (model.action != nil || model.offAction != nil) {
actionBlock = { [weak self] in
guard let self = self else { return }
if let offAction = viewModel.offAction, !isSelected {
performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData)
if let offAction = model.offAction, !self.isSelected {
self.performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData)
} else if let action = viewModel.action {
performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData)
} else if let action = model.action {
self.performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData)
}
}
}

View File

@ -5,7 +5,6 @@
// Created by Chintakrinda, Arun Kumar (Arun) on 21/01/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
/// Protocol to apply to any model of a UI Control with a binary on/off nature.
///
@ -14,25 +13,67 @@ import VDS
var selected: Bool { get set }
}
@objcMembers public class CheckboxModel: FormFieldModel, SelectableMoleculeModelProtocol{
@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol, UIUpdatableModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static override var identifier: String { "checkbox" }
public static var identifier: String = "checkbox"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
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
public var borderWidth: CGFloat = 1
public var borderColor: Color = Color(uiColor: .mvmBlack)
public var checkColor: Color = Color(uiColor: .mvmBlack)
public var unCheckedBackgroundColor: Color = Color(uiColor: .clear)
public var checkedBackgroundColor: Color = Color(uiColor: .clear)
public var disabledBackgroundColor: Color = Color(uiColor: .clear)
public var disabledBorderColor: Color = Color(uiColor: .mvmCoolGray3)
public var disabledCheckColor: Color = Color(uiColor: .mvmCoolGray3)
public var invertedColor: Color = Color(uiColor: .mvmWhite)
public var invertedBackgroundColor: Color = Color(uiColor: .mvmBlack)
public var action: ActionModelProtocol?
public var offAction: ActionModelProtocol?
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var updateUI: ActionBlock?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case checked
case enabled
case readOnly
case inverted
case animated
case round
case borderWidth
case borderColor
case checkColor
case invertedColor
case invertedBackgroundColor
case unCheckedBackgroundColor
case checkedBackgroundColor
case disabledBackgroundColor
case disabledCheckColor
case disabledBorderColor
case action
case fieldKey
case groupName
case offAction
}
@ -40,17 +81,16 @@ import VDS
// MARK: - Form Validation
//--------------------------------------------------
open override func formFieldValue() -> AnyHashable? {
public func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
return selected
}
open override func setValidity(_ valid: Bool, errorMessage: String?) {
if let ruleErrorMessage = errorMessage, fieldKey != nil {
self.errorMessage = ruleErrorMessage
}
isValid = valid
updateUI?()
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
//--------------------------------------------------
@ -58,8 +98,7 @@ import VDS
//--------------------------------------------------
public init(isChecked: Bool = false) {
super.init()
selected = isChecked
self.selected = isChecked
baseValue = isChecked
}
@ -68,9 +107,52 @@ import VDS
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let borderWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .borderWidth) {
self.borderWidth = borderWidth
}
if let borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) {
self.borderColor = borderColor
}
if let checkColor = try typeContainer.decodeIfPresent(Color.self, forKey: .checkColor) {
self.checkColor = checkColor
}
if let unCheckedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .unCheckedBackgroundColor) {
self.unCheckedBackgroundColor = unCheckedBackgroundColor
}
if let checkedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .checkedBackgroundColor) {
self.checkedBackgroundColor = checkedBackgroundColor
}
if let disabledBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBackgroundColor) {
self.disabledBackgroundColor = disabledBackgroundColor
}
if let disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) {
self.disabledBorderColor = disabledBorderColor
}
if let disabledCheckColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledCheckColor) {
self.disabledCheckColor = disabledCheckColor
}
if let invertedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .invertedColor) {
self.invertedColor = invertedColor
}
if let invertedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .invertedBackgroundColor) {
self.invertedBackgroundColor = invertedBackgroundColor
}
if let checked = try typeContainer.decodeIfPresent(Bool.self, forKey: .checked) {
self.selected = checked
}
@ -80,26 +162,51 @@ import VDS
if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) {
self.animated = animated
}
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
offAction = try typeContainer.decodeModelIfPresent(codingKey: .offAction)
if let round = try typeContainer.decodeIfPresent(Bool.self, forKey: .round) {
self.round = round
}
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
self.inverted = inverted
}
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)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
offAction = try typeContainer.decodeModelIfPresent(codingKey: .offAction)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(borderColor, forKey: .borderColor)
try container.encode(borderWidth, forKey: .borderWidth)
try container.encode(selected, forKey: .checked)
try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(checkColor, forKey: .checkColor)
try container.encodeIfPresent(invertedColor, forKey: .invertedColor)
try container.encodeIfPresent(invertedBackgroundColor, forKey: .invertedBackgroundColor)
try container.encodeIfPresent(unCheckedBackgroundColor, forKey: .unCheckedBackgroundColor)
try container.encodeIfPresent(checkedBackgroundColor, forKey: .checkedBackgroundColor)
try container.encodeIfPresent(disabledBorderColor, forKey: .disabledBorderColor)
try container.encodeIfPresent(disabledBackgroundColor, forKey: .disabledBackgroundColor)
try container.encodeIfPresent(disabledCheckColor, forKey: .disabledCheckColor)
try container.encodeIfPresent(animated, forKey: .animated)
try container.encodeIfPresent(round, forKey: .round)
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)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return selected == model.selected
&& animated == model.animated
&& offAction.isEqual(to: model.offAction)
&& action.isEqual(to: model.action)
}
}

View File

@ -1,58 +0,0 @@
//
// Checkboxes.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/19/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class Checkboxes: VDS.CheckboxGroup, VDSMoleculeViewProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: CheckboxesModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
/// The models for the molecules.
public var checkboxes: [CheckboxLabelModel]?
// MARK: - MoleculeViewProtocol
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
showError = viewModel.showError
isEnabled = viewModel.enabled && !viewModel.readOnly
checkboxes = viewModel.checkboxes
checkboxes?.forEach {
FormValidator.setupValidation(for: $0.checkbox, delegate: delegateObject?.formHolderDelegate)
}
selectorModels = viewModel.checkboxes.convertToVDSCheckboxItemModel(surface: surface,
delegateObject: delegateObject,
additionalData: additionalData)
}
open func updateView(_ size: CGFloat) {}
open override func didSelect(_ selectedControl: CheckboxItem) {
super.didSelect(selectedControl)
// since the checkboxes has the state being tracked, we need to update the values here.
if let index = items.firstIndex(where: {$0 === selectedControl}), let selected = checkboxes?[index] {
selected.checkbox.selected = true
}
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}
}

View File

@ -1,101 +0,0 @@
//
// CheckboxesModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/19/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import MVMCore
import VDS
public class CheckboxesModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String { "checkboxes" }
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var enabled: Bool = true
public var required: Bool = true
public var readOnly: Bool = false
public var showError: Bool = false
public var inverted: Bool = false
public var surface: Surface { inverted ? .dark : .light }
public var checkboxes: [CheckboxLabelModel]
public var children: [any MoleculeModelProtocol] { checkboxes }
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case inverted
case enabled
case readOnly
case showError
case checkboxes
}
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with checkboxes: [CheckboxLabelModel]){
self.checkboxes = checkboxes
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
showError = try typeContainer.decodeIfPresent(Bool.self, forKey: .showError) ?? false
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
self.inverted = inverted
}
checkboxes = try typeContainer.decode([CheckboxLabelModel].self, forKey: .checkboxes)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(readOnly, forKey: .readOnly)
try container.encode(enabled, forKey: .enabled)
try container.encode(inverted, forKey: .inverted)
try container.encode(checkboxes, forKey: .checkboxes)
}
open func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return moleculeName == model.moleculeName
&& enabled == model.enabled
&& showError == model.showError
&& readOnly == model.readOnly
&& required == model.required
&& inverted == model.inverted
&& accessibilityText == model.accessibilityText
&& accessibilityIdentifier == model.accessibilityIdentifier
&& accessibilityTraits == model.accessibilityTraits
}
}

View File

@ -5,66 +5,243 @@
// Created by Scott Pfeil on 4/9/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
@objcMembers open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol {
//------------------------------------------------------
open class RadioBox: Control, MFButtonProtocol {
//--------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: RadioBoxModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
//--------------------------------------------------
public let label = Label(fontStyle: .RegularBodySmall)
public let subTextLabel = Label(fontStyle: .RegularMicro)
public var isOutOfStock = false
public var accentColor = UIColor.mvmRed
open var isOutOfStock: Bool {
get { strikethrough }
set {
strikethrough = newValue
viewModel?.strikethrough = newValue
}
public let innerPadding: CGFloat = 12.0
private var borderLayer: CALayer?
private var strikeLayer: CALayer?
private var maskLayer: CALayer?
public var subTextLabelHeightConstraint: NSLayoutConstraint?
private var delegateObject: MVMCoreUIDelegateObject?
var additionalData: [AnyHashable: Any]?
public var radioBoxModel: RadioBoxModel? {
model as? RadioBoxModel
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public override var isSelected: Bool {
didSet { updateAccessibility() }
}
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
text = viewModel.text
subText = viewModel.subText
subTextRight = viewModel.subTextRight
strikethrough = viewModel.strikethrough
isSelected = viewModel.selected
isEnabled = viewModel.enabled && !viewModel.readOnly
public override var isEnabled: Bool {
didSet { updateAccessibility() }
}
onChange = { [weak self] _ in
if let radioBoxModel = self?.viewModel, let actionModel = radioBoxModel.action {
Task(priority: .userInitiated) { [weak self] in
guard let self else { return }
try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioBoxModel)
}
}
}
}
//--------------------------------------------------
// MARK: - Functions
//--------------------------------------------------
@objc open func selectBox() {
toggle()
}
@objc open func deselectBox() {
toggle()
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open func updateView(_ size: CGFloat) {}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
label.updateView(size)
subTextLabel.updateView(size)
layer.setNeedsDisplay()
}
open override func setupView() {
super.setupView()
layer.delegate = self
layer.borderColor = UIColor.mvmCoolGray6.cgColor
layer.borderWidth = 1
label.numberOfLines = 1
addSubview(label)
NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: innerPadding, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding)
subTextLabel.textColor = .mvmCoolGray6
subTextLabel.numberOfLines = 1
addSubview(subTextLabel)
NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding)
bottomAnchor.constraint(greaterThanOrEqualTo: subTextLabel.bottomAnchor, constant: innerPadding).isActive = true
subTextLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2).isActive = true
subTextLabelHeightConstraint = subTextLabel.heightAnchor.constraint(equalToConstant: 0)
subTextLabelHeightConstraint?.isActive = true
addTarget(self, action: #selector(selectBox), for: .touchUpInside)
isAccessibilityElement = true
}
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? RadioBoxModel else { return }
self.delegateObject = delegateObject
self.additionalData = additionalData
label.text = model.text
subTextLabel.text = model.subText
isOutOfStock = model.strikethrough
subTextLabelHeightConstraint?.isActive = (subTextLabel.text?.count ?? 0) == 0
if let color = model.selectedAccentColor?.uiColor {
accentColor = color
}
isSelected = model.selected
isEnabled = model.enabled && !model.readOnly
}
open override func reset() {
super.reset()
backgroundColor = .white
accentColor = .mvmRed
}
//--------------------------------------------------
// MARK: - State Handling
//--------------------------------------------------
open override func draw(_ layer: CALayer, in ctx: CGContext) {
// Draw the strikethrough
strikeLayer?.removeFromSuperlayer()
if isOutOfStock {
let line = getStrikeThrough(color: isSelected ? .black : .mvmCoolGray6, thickness: 1)
layer.addSublayer(line)
strikeLayer = line
}
// Draw the border
borderLayer?.removeFromSuperlayer()
if isSelected {
layer.borderWidth = 0
let border = getSelectedBorder()
layer.addSublayer(border)
borderLayer = border
} else {
layer.borderWidth = 1
}
// Handle Mask
maskLayer?.removeFromSuperlayer()
if !isEnabled {
let mask = getMaskLayer()
layer.mask = mask
maskLayer = mask
}
}
open override func layoutSubviews() {
super.layoutSubviews()
// Accounts for any size changes
layer.setNeedsDisplay()
}
@objc open func selectBox() {
guard isEnabled, !isSelected else { return }
isSelected = true
radioBoxModel?.selected = isSelected
if let radioBoxModel = radioBoxModel, let actionModel = radioBoxModel.action {
Task(priority: .userInitiated) {
try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioBoxModel)
}
}
layer.setNeedsDisplay()
}
@objc open func deselectBox() {
isSelected = false
radioBoxModel?.selected = isSelected
layer.setNeedsDisplay()
}
/// Gets the selected state border
func getSelectedBorder() -> CAShapeLayer {
let layer = CAShapeLayer()
let topLineWidth: CGFloat = 4
let topLinePath = UIBezierPath()
topLinePath.lineWidth = topLineWidth
topLinePath.move(to: CGPoint(x: 0, y: topLineWidth / 2.0))
topLinePath.addLine(to: CGPoint(x: bounds.width, y: topLineWidth / 2.0))
let topLineLayer = CAShapeLayer()
topLineLayer.fillColor = nil
topLineLayer.strokeColor = accentColor.cgColor
topLineLayer.lineWidth = 4
topLineLayer.path = topLinePath.cgPath
layer.addSublayer(topLineLayer)
let lineWidth: CGFloat = 1
let halfLineWidth: CGFloat = 0.5
let linePath = UIBezierPath()
linePath.move(to: CGPoint(x: halfLineWidth, y: topLineWidth))
linePath.addLine(to: CGPoint(x: halfLineWidth, y: bounds.height))
linePath.move(to: CGPoint(x: 0, y: bounds.height - halfLineWidth))
linePath.addLine(to: CGPoint(x: bounds.width, y: bounds.height - halfLineWidth))
linePath.move(to: CGPoint(x: bounds.width - halfLineWidth, y: bounds.height))
linePath.addLine(to: CGPoint(x: bounds.width - halfLineWidth, y: topLineWidth))
let borderLayer = CAShapeLayer()
borderLayer.fillColor = nil
borderLayer.strokeColor = UIColor.black.cgColor
borderLayer.lineWidth = lineWidth
borderLayer.path = linePath.cgPath
layer.addSublayer(borderLayer)
return layer
}
/// Adds a border to edge
func getStrikeThrough(color: UIColor, thickness: CGFloat) -> CAShapeLayer {
let border = CAShapeLayer()
border.name = "strikethrough"
border.fillColor = nil
border.opacity = 1.0
border.lineWidth = thickness
border.strokeColor = color.cgColor
let linePath = UIBezierPath()
linePath.move(to: CGPoint(x: 0, y: bounds.height))
linePath.addLine(to: CGPoint(x: bounds.width, y: 0))
border.path = linePath.cgPath
return border
}
func getMaskLayer() -> CALayer {
let mask = CALayer()
mask.backgroundColor = UIColor.white.cgColor
mask.opacity = 0.3
mask.frame = bounds
return mask
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
public func updateAccessibility() {
var message = ""
if let labelText = label.text, label.hasText {
message += labelText + ", "
}
if let subLabelText = subTextLabel.text, subTextLabel.hasText {
message += subLabelText + ", "
}
accessibilityLabel = message
accessibilityTraits = .button
if isSelected {
accessibilityTraits.insert(.selected)
}
if !isEnabled {
accessibilityTraits.insert(.notEnabled)
}
}
}

View File

@ -6,35 +6,45 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import MVMCore
import VDS
public class RadioBoxModel: FormFieldModel {
@objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override static var identifier: String { "radioBox" }
public static var identifier: String = "radioBox"
public var id: String = UUID().uuidString
public var text: String
public var subText: String?
public var subTextRight: String?
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
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 action: ActionModelProtocol?
public var fieldValue: String?
public var action: ActionModelProtocol?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case text
case subText
case subTextRight
case selectedAccentColor
case backgroundColor
case accessibilityIdentifier
case selected
case enabled
case strikethrough
case action
case fieldValue
case action
case readOnly
}
//--------------------------------------------------
@ -43,19 +53,8 @@ public class RadioBoxModel: FormFieldModel {
public init(text: String) {
self.text = text
super.init()
}
//--------------------------------------------------
// MARK: - Form Validation
//--------------------------------------------------
/// Returns the fieldValue of the selected box, otherwise the text of the selected box.
public override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
return fieldValue
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
@ -63,42 +62,42 @@ public class RadioBoxModel: FormFieldModel {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
text = try typeContainer.decode(String.self, forKey: .text)
subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText)
subTextRight = try typeContainer.decodeIfPresent(String.self, forKey: .subTextRight)
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
selected = isSelected
}
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
}
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(text, forKey: .text)
try container.encodeIfPresent(subText, forKey: .subText)
try container.encodeIfPresent(subTextRight, forKey: .subTextRight)
try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
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)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return text == model.text
&& subText == model.subText
&& subTextRight == model.subTextRight
&& selected == model.selected
&& strikethrough == model.strikethrough
&& fieldValue == model.fieldValue
&& action.isEqual(to: model.action)
}
}

View File

@ -7,49 +7,172 @@
//
import Foundation
import VDS
open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol {
public protocol RadioBoxSelectionDelegate: AnyObject {
func selected(radioBox: RadioBoxModel)
}
open class RadioBoxes: View {
public var collectionView: CollectionView!
public var collectionViewHeight: NSLayoutConstraint!
private let boxWidth: CGFloat = 151.0
private let boxHeight: CGFloat = 64.0
private var itemSpacing: CGFloat = 12.0
private var numberOfColumns: CGFloat = 2.0
private var radioBoxesModel: RadioBoxesModel? {
return model as? RadioBoxesModel
}
public weak var radioDelegate: RadioBoxSelectionDelegate?
private var delegateObject: MVMCoreUIDelegateObject?
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: RadioBoxesModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
/// The models for the molecules.
public var boxes: [RadioBoxModel]?
// MARK: - MoleculeViewProtocol
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
private var size: CGFloat?
open override func layoutSubviews() {
super.layoutSubviews()
// Accounts for any collection size changes
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
boxes = viewModel.boxes
surface = viewModel.surface
selectorModels = viewModel.boxes.convertToVDSRadioBoxModel(surface: surface)
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
}
open func updateView(_ size: CGFloat) {}
open func updateAccessibilityValue(collectionView: UICollectionView, cell: RadioBoxCollectionViewCell, indexPath: IndexPath) {
guard let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: indexPath.row + 1)) else { return }
let total = self.collectionView(collectionView, numberOfItemsInSection: indexPath.section)
cell.accessibilityValue = String(format: format, indexString, total)
}
open override func didSelect(_ selectedControl: RadioBoxItem) {
super.didSelect(selectedControl)
// MARK: - MVMCoreViewProtocol
open override func setupView() {
super.setupView()
collectionView = createCollectionView()
addSubview(collectionView)
NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
collectionViewHeight?.isActive = true
}
// MARK: - MoleculeViewProtocol
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
// since the boxes has the state being tracked, we need to update the values here.
if let index = items.firstIndex(where: {$0 === selectedControl}), let selectedBox = boxes?[index] {
boxes?.forEach { $0.selected = false }
selectedBox.selected = true
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
guard let model = model as? RadioBoxesModel else { return }
boxes = model.boxes
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
backgroundColor = model.backgroundColor?.uiColor
registerCells()
setHeight()
collectionView.reloadData()
}
@objc override open func updateView(_ size: CGFloat) {
super.updateView(size)
self.size = size
itemSpacing = Padding.Component.gutterFor(size: size)
collectionView.updateView(size)
}
// MARK: - Creation
/// Creates the layout for the collection.
open func createCollectionViewLayout() -> UICollectionViewLayout {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = itemSpacing
layout.minimumInteritemSpacing = itemSpacing
return layout
}
/// Creates the collection view.
open func createCollectionView() -> CollectionView {
let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout())
collection.dataSource = self
collection.delegate = self
return collection
}
/// Registers the cells with the collection view
open func registerCells() {
collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell")
}
// MARK: - JSON Setters
open func setHeight() {
guard let boxes = boxes, boxes.count > 0 else {
collectionViewHeight.constant = 0
return
}
// Calculate the height
let rows = ceil(CGFloat(boxes.count) / numberOfColumns)
let height = (rows * boxHeight) + ((rows - 1) * itemSpacing)
collectionViewHeight?.constant = height
}
}
extension RadioBoxes: UICollectionViewDelegateFlowLayout {
open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemWidth: CGFloat = (collectionView.bounds.width - itemSpacing) / numberOfColumns
return CGSize(width: itemWidth, height: boxHeight)
}
}
extension RadioBoxes: UICollectionViewDataSource {
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return boxes?.count ?? 0
}
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let molecule = boxes?[indexPath.row],
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else {
fatalError()
}
cell.reset()
cell.radioBox.isUserInteractionEnabled = false
if let color = radioBoxesModel?.boxesColor {
cell.radioBox.backgroundColor = color.uiColor
}
if let color = radioBoxesModel?.selectedAccentColor {
cell.radioBox.accentColor = color.uiColor
}
cell.set(with: molecule, delegateObject, nil)
cell.updateView(size ?? collectionView.bounds.width)
if molecule.selected {
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
}
updateAccessibilityValue(collectionView: collectionView, cell: cell, indexPath: indexPath)
cell.layoutIfNeeded()
return cell
}
}
extension RadioBoxes: UICollectionViewDelegate {
open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
guard let molecule = boxes?[indexPath.row] else { return false }
return molecule.enabled
}
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return }
cell.radioBox.selectBox()
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
cell.updateAccessibility()
guard let radioBox = boxes?[indexPath.row] else { return }
radioDelegate?.selected(radioBox: radioBox)
}
open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return }
cell.radioBox.deselectBox()
cell.updateAccessibility()
}
}

View File

@ -6,37 +6,43 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import MVMCore
import VDS
public class RadioBoxesModel: FormFieldModel, ParentMoleculeModelProtocol {
@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override static var identifier: String { "radioBoxes" }
public static var identifier: String = "radioBoxes"
public var id: String = UUID().uuidString
public var boxes: [RadioBoxModel]
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var selectedAccentColor: Color?
public var boxesColor: Color?
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var enabled: Bool = true
public var readOnly: Bool = false
public var children: [any MoleculeModelProtocol] { boxes }
//--------------------------------------------------
// MARK: - Form Validation
//--------------------------------------------------
/// Returns the fieldValue of the selected box, otherwise the text of the selected box.
public override func formFieldValue() -> AnyHashable? {
public func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
let selectedBox = boxes.first { (box) -> Bool in
return box.selected
}
return selectedBox?.formFieldValue() ?? selectedBox?.text
return selectedBox?.fieldValue ?? selectedBox?.text
}
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open override func formFieldServerValue() -> AnyHashable? {
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
@ -45,7 +51,17 @@ public class RadioBoxesModel: FormFieldModel, ParentMoleculeModelProtocol {
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case enabled
case readOnly
case selectedAccentColor
case backgroundColor
case accessibilityIdentifier
case boxesColor
case boxes
case fieldKey
case groupName
}
//--------------------------------------------------
@ -53,8 +69,7 @@ public class RadioBoxesModel: FormFieldModel, ParentMoleculeModelProtocol {
//--------------------------------------------------
public init(with boxes: [RadioBoxModel]){
self.boxes = boxes
super.init()
self.boxes = boxes
}
//--------------------------------------------------
@ -63,29 +78,32 @@ public class RadioBoxesModel: FormFieldModel, ParentMoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
boxesColor = try typeContainer.decodeIfPresent(Color.self, forKey: .boxesColor)
boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes)
try super.init(from: decoder)
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
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()
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(boxes, forKey: .boxes)
}
}
extension Array where Element == RadioBoxModel {
internal func convertToVDSRadioBoxModel(surface: Surface) -> [RadioBoxGroup.RadioBoxItemModel] {
compactMap({ item in
var radioBox = RadioBoxGroup.RadioBoxItemModel()
radioBox.text = item.text
radioBox.subText = item.subText
radioBox.subTextRight = item.subTextRight
radioBox.surface = surface
radioBox.selected = item.selected
radioBox.strikethrough = item.strikethrough
radioBox.enabled = item.isEnabled
return radioBox
})
try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
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)
}
}

View File

@ -7,26 +7,37 @@
//
import UIKit
import VDS
import VDSCoreTokens
@objcMembers open class RadioButton: VDS.RadioButton, RadioButtonSelectionHelperProtocol, VDSMoleculeViewProtocol, MFButtonProtocol, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
@objcMembers open class RadioButton: Control, MFButtonProtocol {
//--------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: RadioButtonModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
open var radioButtonModel: RadioButtonModel {
viewModel
//--------------------------------------------------
public var diameter: CGFloat = 20 {
didSet { widthConstraint?.constant = diameter }
}
// Form Validation
open var fieldKey: String?
open var fieldValue: JSONValue?
open var groupName: String?
lazy public var radioGroupName: String? = { viewModel.fieldKey }()
@objc public override var isSelected: Bool {
didSet {
radioModel?.state = isSelected
updateAccessibilityLabel()
}
}
public var enabledColor: UIColor {
return radioModel?.inverted ?? false ? VDSColor.elementsPrimaryOndark : VDSColor.elementsPrimaryOnlight
}
public var disabledColor: UIColor {
return radioModel?.inverted ?? false ? VDSColor.interactiveDisabledOndark : VDSColor.interactiveDisabledOnlight
}
public var delegateObject: MVMCoreUIDelegateObject?
var additionalData: [AnyHashable: Any]?
public var radioModel: RadioButtonModel? {
model as? RadioButtonModel
}
lazy public var radioGroupName: String? = { radioModel?.fieldKey }()
lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = {
@ -37,120 +48,132 @@ import VDS
return radioButtonModel
}()
public override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
setNeedsDisplay()
}
}
//--------------------------------------------------
// MARK: - Initializers
// MARK: - Constraints
//--------------------------------------------------
override public init(frame: CGRect) {
super.init(frame: frame)
public var widthConstraint: NSLayoutConstraint?
public var heightConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let color = isEnabled == false ? disabledColor.cgColor : enabledColor.cgColor
layer.cornerRadius = bounds.width * 0.5
layer.borderColor = color
layer.borderWidth = bounds.width * 0.0333
if isSelected {
// Space around inner circle is 1/5 the size
context.addEllipse(in: CGRect(x: bounds.width * 0.2,
y: bounds.height * 0.2,
width: bounds.width * 0.6,
height: bounds.height * 0.6))
context.setFillColor(color)
context.fillPath()
}
}
/// There is currently no intention on using xib files.
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("xib file is not implemented for Checkbox.")
}
public convenience required init() {
self.init(frame:.zero)
}
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
/// The action performed when tapped.
func tapAction() {
if !isEnabled {
return
}
let wasPreviouslySelected = isSelected
if let radioButtonModel = radioButtonSelectionHelper {
radioButtonModel.selected(self)
} else {
isSelected = !isSelected
}
if let radioModel = radioModel, let actionModel = radioModel.action, isSelected, !wasPreviouslySelected {
Task(priority: .userInitiated) {
try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioModel)
}
}
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
setNeedsDisplay()
}
public func isValidField() -> Bool { isSelected }
public func formFieldName() -> String? {
viewModel.fieldKey
radioModel?.fieldKey
}
public func formFieldGroupName() -> String? {
viewModel.fieldKey
radioModel?.fieldKey
}
public func formFieldValue() -> AnyHashable? {
guard let radioModel = viewModel, radioModel.enabled else { return nil }
guard let radioModel = radioModel, radioModel.enabled else { return nil }
return radioModel.fieldValue
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func setup() {
super.setup()
// Radio button should never be smaller that its content size.
setContentCompressionResistancePriority(.required, for: .vertical)
setContentCompressionResistancePriority(.required, for: .horizontal)
publisher(for: .valueChanged)
.sink { [weak self] control in
guard let self, isEnabled else { return }
viewModel?.state = isSelected
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}.store(in: &subscribers)
}
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
//events
viewModel.updateUI = {
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
guard let self = self else { return }
let isValid = viewModel.isValid ?? true
showError = !isValid
isEnabled = viewModel.enabled
})
}
isSelected = viewModel.state
isEnabled = viewModel.isEnabled
RadioButtonSelectionHelper.setupForRadioButtonGroup(viewModel, self, delegateObject: delegateObject)
}
// MARK: - Methods
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func toggle() {
guard !isSelected, isEnabled else { return }
//removed error
if showError && isSelected == false {
showError.toggle()
/// Adjust accessibility label based on state of RadioButton.
func updateAccessibilityLabel() {
if let message = MVMCoreUIUtility.hardcodedString(withKey: "radio_button"),
let selectedState = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "radio_selected_state" : "radio_not_selected_state") {
accessibilityLabel = message + selectedState
}
let wasPreviouslySelected = isSelected
if let radioButtonSelectionHelper {
radioButtonSelectionHelper.selected(self)
} else {
isSelected.toggle()
}
if let actionModel = viewModel.action, isSelected, !wasPreviouslySelected {
Task(priority: .userInitiated) {
try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: viewModel)
}
}
sendActions(for: .valueChanged)
setNeedsUpdate()
}
//--------------------------------------------------
// MARK: - Actions
// MARK: - MVMViewProtocol
//--------------------------------------------------
/// This will toggle the state of the Checkbox and execute the actionBlock if provided.
public func tapAction() {
toggle()
open override func setupView() {
super.setupView()
backgroundColor = .clear
clipsToBounds = true
widthConstraint = widthAnchor.constraint(equalToConstant: 20)
widthConstraint?.isActive = true
heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1)
heightConstraint?.isActive = true
addTarget(self, action: #selector(tapAction), for: .touchUpInside)
isAccessibilityElement = true
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint")
accessibilityTraits = .button
updateAccessibilityLabel()
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public func updateView(_ size: CGFloat) {}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
self.additionalData = additionalData
guard let model = model as? RadioButtonModel else { return }
isSelected = model.state
isEnabled = model.enabled && !model.readOnly
RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject)
}
public override func reset() {
super.reset()
backgroundColor = .clear
}
}

View File

@ -7,29 +7,48 @@
//
import MVMCore
import VDS
open class RadioButtonModel: FormFieldModel {
open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static override var identifier: String { "radioButton" }
public static var identifier: String = "radioButton"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
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?
public var baseValue: AnyHashable?
public var groupName: String = FormValidator.defaultGroupName
public var fieldKey: String?
public var action: ActionModelProtocol?
public var inverted: Bool = false
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case backgroundColor
case accessibilityIdentifier
case state
case enabled
case fieldValue
case fieldKey
case groupName
case action
case readOnly
case inverted
}
//--------------------------------------------------
@ -37,57 +56,69 @@ open class RadioButtonModel: FormFieldModel {
//--------------------------------------------------
public init(_ state: Bool) {
super.init()
self.state = state
self.baseValue = state
baseValue = state
}
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
public override func formFieldValue() -> AnyHashable? {
public func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
return fieldValue
}
open override func setValidity(_ valid: Bool, errorMessage: String?) {
if let ruleErrorMessage = errorMessage, fieldKey != nil {
self.errorMessage = ruleErrorMessage
}
isValid = valid
updateUI?()
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
self.state = state
}
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)
baseValue = state
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
self.inverted = inverted
}
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(id, forKey: .id)
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)
try container.encodeModelIfPresent(action, forKey: .action)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return state == model.state
&& fieldValue == model.fieldValue
&& action.isEqual(to: model.action)
try container.encodeIfPresent(inverted, forKey: .inverted)
}
}

View File

@ -6,10 +6,6 @@
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
public protocol RadioButtonSelectionHelperProtocol: AnyObject {
var isSelected: Bool { get set }
var radioButtonModel: RadioButtonModel { get }
}
@objcMembers public class RadioButtonSelectionHelper: FormFieldProtocol {
//--------------------------------------------------
@ -18,7 +14,7 @@ public protocol RadioButtonSelectionHelperProtocol: AnyObject {
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
private var selectedRadioButton: RadioButtonSelectionHelperProtocol?
private var selectedRadioButton: RadioButton?
private var selectedRadioButtonModel: RadioButtonModel?
public var baseValue: AnyHashable?
public var enabled: Bool = true
@ -28,7 +24,7 @@ public protocol RadioButtonSelectionHelperProtocol: AnyObject {
// MARK: - Initializer
//--------------------------------------------------
public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButtonSelectionHelperProtocol) {
public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) {
self.fieldKey = radioButtonModel.fieldKey
self.groupName = radioButtonModel.groupName
@ -53,7 +49,7 @@ public protocol RadioButtonSelectionHelperProtocol: AnyObject {
// MARK: - Functions
//--------------------------------------------------
public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButtonSelectionHelperProtocol, delegateObject: MVMCoreUIDelegateObject?) {
public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton, delegateObject: MVMCoreUIDelegateObject?) {
guard let groupName = radioButtonModel.fieldKey,
let formValidator = delegateObject?.formHolderDelegate?.formValidator
@ -65,10 +61,10 @@ public protocol RadioButtonSelectionHelperProtocol: AnyObject {
FormValidator.setupValidation(for: radioButtonSelectionHelper, delegate: delegateObject?.formHolderDelegate)
}
public func selected(_ radioButton: RadioButtonSelectionHelperProtocol) {
public func selected(_ radioButton: RadioButton) {
// Checks because the view could be reused
if selectedRadioButton?.radioButtonModel === selectedRadioButtonModel {
if selectedRadioButton?.radioModel === selectedRadioButtonModel {
selectedRadioButton?.isSelected = false
} else {
selectedRadioButtonModel?.state = false
@ -76,7 +72,7 @@ public protocol RadioButtonSelectionHelperProtocol: AnyObject {
selectedRadioButton = radioButton
selectedRadioButton?.isSelected = true
selectedRadioButtonModel = selectedRadioButton?.radioButtonModel
selectedRadioButtonModel = selectedRadioButton?.radioModel
}
}

View File

@ -1,57 +0,0 @@
//
// RadioButtons.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class RadioButtons: VDS.RadioButtonGroup, VDSMoleculeViewProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: RadioButtonsModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
/// The models for the molecules.
public var radioButtons: [RadioButtonLabelModel]?
// MARK: - MoleculeViewProtocol
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
showError = viewModel.showError
isEnabled = viewModel.isEnabled
surface = viewModel.surface
radioButtons = viewModel.radioButtons
selectorModels = viewModel.radioButtons.convertToVDSRadioButtonItemModel(surface: surface,
delegateObject: delegateObject,
additionalData: additionalData)
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
}
open func updateView(_ size: CGFloat) {}
open override func didSelect(_ selectedControl: RadioButtonItem) {
super.didSelect(selectedControl)
// since the radiobutton has the state being tracked, we need to update the values here.
if let index = items.firstIndex(where: {$0 === selectedControl}), let selected = radioButtons?[index] {
radioButtons?.forEach { $0.radioButton.state = false }
selected.radioButton.state = true
}
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}
}

View File

@ -1,74 +0,0 @@
//
// RadioButtonsModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import MVMCore
import VDS
public class RadioButtonsModel: FormFieldModel, ParentMoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override static var identifier: String { "radioButtons" }
public var radioButtons: [RadioButtonLabelModel]
public var children: [any MoleculeModelProtocol] { radioButtons }
//--------------------------------------------------
// MARK: - Form Validation
//--------------------------------------------------
/// Returns the fieldValue of the selected RadioButton.
public override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
let selected = radioButtons.first { $0.radioButton.state }
return selected?.radioButton.formFieldValue()
}
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open override func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case radioButtons
}
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(with radioButtons: [RadioButtonLabelModel]){
self.radioButtons = radioButtons
super.init()
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
radioButtons = try typeContainer.decode([RadioButtonLabelModel].self, forKey: .radioButtons)
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(radioButtons, forKey: .radioButtons)
}
}

View File

@ -8,7 +8,6 @@
import MVMCore
import UIKit
import VDS
public typealias ActionBlockConfirmation = () -> (Bool)
@ -20,40 +19,137 @@ public typealias ActionBlockConfirmation = () -> (Bool)
Container: The background of the toggle control.
Knob: The circular indicator that slides on the container.
*/
@objcMembers open class Toggle: VDS.Toggle, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
@objcMembers open class Toggle: Control, MVMCoreUIViewConstrainingProtocol {
//--------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: ToggleModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
//--------------------------------------------------
public var didToggleAction: ActionBlock? {
didSet {
if let didToggleAction {
onChange = { _ in
didToggleAction()
}
} else {
onChange = nil
}
}
}
/// Holds the on and off colors for the container.
public var containerTintColor: (on: UIColor, off: UIColor) = (on: .mvmGreen, off: .mvmBlack)
/// Holds the on and off colors for the knob.
public var knobTintColor: (on: UIColor, off: UIColor) = (on: .mvmWhite, off: .mvmWhite)
/// Holds the on and off colors for the disabled state..
public var disabledTintColor: (container: UIColor, knob: UIColor) = (container: .mvmCoolGray3, knob: .mvmWhite)
/// Set this flag to false if you do not want to animate state changes.
public var isAnimated = true
public var didToggleAction: ActionBlock?
/// Executes logic before state change. If false, then toggle state will not change and the didToggleAction will not execute.
public var shouldToggleAction: ActionBlockConfirmation? = {
return { true }
}()
// Sizes are from InVision design specs.
static let containerSize = CGSize(width: 51, height: 31)
static let knobSize = CGSize(width: 28, height: 28)
private var knobView: View = {
let view = View()
view.backgroundColor = .white
view.layer.cornerRadius = Toggle.getKnobHeight() / 2.0
return view
}()
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
open override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
changeStateNoAnimation(isEnabled ? isOn : false)
setToggleAppearanceFromState()
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: isEnabled ? "AccToggleHint" : "AccDisabled")
}
}
/// Simple means to prevent user interaction with the toggle.
public var isLocked: Bool = false {
didSet { isUserInteractionEnabled = !isLocked }
}
/// The state on the toggle. Default value: false.
open var isOn: Bool = false {
didSet {
if isAnimated {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: {
if self.isOn {
self.knobView.backgroundColor = self.knobTintColor.on
self.backgroundColor = self.containerTintColor.on
} else {
self.knobView.backgroundColor = self.knobTintColor.off
self.backgroundColor = self.containerTintColor.off
}
}, completion: nil)
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: {
self.constrainKnob()
self.knobWidthConstraint?.constant = Self.getKnobWidth()
self.layoutIfNeeded()
}, completion: nil)
} else {
setToggleAppearanceFromState()
self.constrainKnob()
}
toggleModel?.selected = isOn
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
accessibilityValue = isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff")
setNeedsLayout()
layoutIfNeeded()
}
}
public var toggleModel: ToggleModel? {
model as? ToggleModel
}
//--------------------------------------------------
// MARK: - Delegate
//--------------------------------------------------
private var delegateObject: MVMCoreUIDelegateObject?
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
private var knobLeadingConstraint: NSLayoutConstraint?
private var knobTrailingConstraint: NSLayoutConstraint?
private var knobHeightConstraint: NSLayoutConstraint?
private var knobWidthConstraint: NSLayoutConstraint?
private var heightConstraint: NSLayoutConstraint?
private var widthConstraint: NSLayoutConstraint?
private func constrainKnob() {
knobLeadingConstraint?.isActive = false
knobTrailingConstraint?.isActive = false
_ = isOn ? constrainKnobOn() : constrainKnobOff()
knobTrailingConstraint?.isActive = true
knobLeadingConstraint?.isActive = true
}
private func constrainKnobOn() {
knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 2)
knobLeadingConstraint = knobView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor)
}
private func constrainKnobOff() {
knobTrailingConstraint = trailingAnchor.constraint(greaterThanOrEqualTo: knobView.trailingAnchor)
knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2)
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -62,7 +158,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
super.init(frame: frame)
}
public convenience required init() {
public convenience override init() {
self.init(frame: .zero)
}
@ -75,7 +171,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
/// - parameter didToggleAction: A closure which is executed after the toggle changes states.
public convenience init(isOn: Bool = false, didToggleAction: ActionBlock?) {
self.init(frame: .zero)
self.isOn = isOn
changeStateNoAnimation(isOn)
self.didToggleAction = didToggleAction
}
@ -95,78 +191,223 @@ public typealias ActionBlockConfirmation = () -> (Bool)
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public override func updateView(_ size: CGFloat) {
super.updateView(size)
heightConstraint?.constant = Self.getContainerHeight()
widthConstraint?.constant = Self.getContainerWidth()
knobHeightConstraint?.constant = Self.getKnobHeight()
knobWidthConstraint?.constant = Self.getKnobWidth()
layer.cornerRadius = Self.getContainerHeight() / 2.0
knobView.layer.cornerRadius = Self.getKnobHeight() / 2.0
changeStateNoAnimation(isOn)
}
public override func setupView() {
super.setupView()
isAccessibilityElement = true
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccToggleHint")
accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel")
accessibilityTraits = .button
heightConstraint = heightAnchor.constraint(equalToConstant: Self.containerSize.height)
heightConstraint?.isActive = true
widthConstraint = widthAnchor.constraint(equalToConstant: Self.containerSize.width)
widthConstraint?.isActive = true
layer.cornerRadius = Self.getContainerHeight() / 2.0
backgroundColor = containerTintColor.off
addSubview(knobView)
knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: Self.knobSize.height)
knobHeightConstraint?.isActive = true
knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: Self.knobSize.width)
knobWidthConstraint?.isActive = true
knobView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
knobView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true
bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true
constrainKnobOff()
}
public override func reset() {
super.reset()
backgroundColor = containerTintColor.off
knobView.backgroundColor = knobTintColor.off
accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel")
isAnimated = true
didToggleAction = nil
shouldToggleAction = { return true }
}
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
class func getContainerWidth() -> CGFloat {
let containerWidth = Self.containerSize.width
return (MFSizeObject(standardSize: containerWidth, standardiPadPortraitSize: CGFloat(Self.containerSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerWidth
}
class func getContainerHeight() -> CGFloat {
let containerHeight = Self.containerSize.height
return (MFSizeObject(standardSize: containerHeight, standardiPadPortraitSize: CGFloat(Self.containerSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerHeight
}
class func getKnobWidth() -> CGFloat {
let knobWidth = Self.knobSize.width
return (MFSizeObject(standardSize: knobWidth, standardiPadPortraitSize: CGFloat(Self.knobSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobWidth
}
class func getKnobHeight() -> CGFloat {
let knobHeight = Self.knobSize.width
return (MFSizeObject(standardSize: knobHeight, standardiPadPortraitSize: CGFloat(Self.knobSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobHeight
}
//--------------------------------------------------
// MARK: - Actions
//--------------------------------------------------
open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
super.sendAction(action, to: target, for: event)
toggleAndAction()
}
open override func sendActions(for controlEvents: UIControl.Event) {
super.sendActions(for: controlEvents)
toggleAndAction()
}
/// This will toggle the state of the Toggle and execute the actionBlock if provided.
public func toggleAndAction() {
isOn = viewModel.selected
surface = viewModel.surface
isAnimated = viewModel.animated
isEnabled = viewModel.isEnabled
showText = viewModel.showText
if let onText = viewModel.onText {
self.onText = onText
if let result = shouldToggleAction?(), result {
isOn.toggle()
didToggleAction?()
}
if let offText = viewModel.offText {
self.offText = offText
}
textSize = viewModel.textSize
textWeight = viewModel.textWeight
textPosition = viewModel.textPosition
}
private func changeStateNoAnimation(_ state: Bool) {
if let accessibileString = viewModel.accessibilityText {
// Hold state in case User wanted isAnimated to remain off.
let isAnimatedState = isAnimated
isAnimated = false
isOn = state
isAnimated = isAnimatedState
}
override open func accessibilityActivate() -> Bool {
// Hold state in case User wanted isAnimated to remain off.
guard isUserInteractionEnabled else { return false }
let isAnimatedState = isAnimated
isAnimated = false
sendActions(for: .touchUpInside)
isAnimated = isAnimatedState
return true
}
//--------------------------------------------------
// MARK: - UIResponder
//--------------------------------------------------
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
UIView.animate(withDuration: 0.1, animations: {
self.knobWidthConstraint?.constant += PaddingOne
self.layoutIfNeeded()
})
}
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
knobReformAnimation()
// Action only occurs of the user lifts up from withing acceptable region of the toggle.
guard let coordinates = touches.first?.location(in: self),
coordinates.x > -20,
coordinates.x < bounds.width + 20,
coordinates.y > -20,
coordinates.y < bounds.height + 20
else { return }
sendActions(for: .touchUpInside)
}
public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
knobReformAnimation()
sendActions(for: .touchCancel)
}
//--------------------------------------------------
// MARK: - Animations
//--------------------------------------------------
public func setToggleAppearanceFromState() {
backgroundColor = isEnabled ? isOn ? containerTintColor.on : containerTintColor.off : disabledTintColor.container
knobView.backgroundColor = isEnabled ? isOn ? knobTintColor.on : knobTintColor.off : disabledTintColor.knob
}
public func knobReformAnimation() {
if isAnimated {
UIView.animate(withDuration: 0.1, animations: {
self.knobWidthConstraint?.constant = Self.getKnobWidth()
self.layoutIfNeeded()
}, completion: nil)
} else {
knobWidthConstraint?.constant = Self.getKnobWidth()
layoutIfNeeded()
}
}
// MARK:- MoleculeViewProtocol
public 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? ToggleModel else { return }
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
containerTintColor.on = model.onTintColor.uiColor
containerTintColor.off = model.offTintColor.uiColor
knobTintColor.on = model.onKnobTintColor.uiColor
knobTintColor.off = model.offKnobTintColor.uiColor
isOn = model.selected
changeStateNoAnimation(isOn)
isAnimated = model.animated
isEnabled = model.enabled && !model.readOnly
if let accessibileString = model.accessibilityText {
accessibilityLabel = accessibileString
}
if viewModel.action != nil || viewModel.alternateAction != nil {
if model.action != nil || model.alternateAction != nil {
didToggleAction = { [weak self] in
guard let self = self else { return }
if self.isOn {
if let action = viewModel.action {
if let action = model.action {
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: model, additionalData: additionalData, delegateObject: delegateObject)
}
} else {
if let action = viewModel.alternateAction ?? viewModel.action {
if let action = model.alternateAction ?? model.action {
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: model, additionalData: additionalData, delegateObject: delegateObject)
}
}
}
}
}
//--------------------------------------------------
// MARK: - Actions
//--------------------------------------------------
/// This will toggle the state of the Toggle and execute the actionBlock if provided.
public func toggleAndAction() {
toggle()
}
open override func toggle() {
if let result = shouldToggleAction?(), result {
super.toggle()
viewModel?.selected = isOn
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}
}
//--------------------------------------------------
// MARK:- MoleculeViewProtocol
//--------------------------------------------------
public func updateView(_ size: CGFloat) {}
public class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return Self().intrinsicContentSize.height
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
Self.getContainerHeight()
}
}

View File

@ -5,62 +5,80 @@
// Created by Scott Pfeil on 1/14/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
public class ToggleModel: FormFieldModel {
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public override static var identifier: String { "toggle" }
public static var identifier: String = "toggle"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
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?
public var onTintColor: Color = Color(uiColor: .mvmGreen)
public var offTintColor: Color = Color(uiColor: .mvmBlack)
public var onKnobTintColor: Color = Color(uiColor: .mvmWhite)
public var offKnobTintColor: Color = Color(uiColor: .mvmWhite)
public var showText: Bool = false
public var onText: String?
public var offText: String?
public var textSize: VDS.Toggle.TextSize = .small
public var textWeight: VDS.Toggle.TextWeight = .regular
public var textPosition: VDS.Toggle.TextPosition = .left
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case state
case animated
case enabled
case readOnly
case action
case backgroundColor
case accessibilityIdentifier
case alternateAction
case accessibilityText
case showText
case onText
case offText
case textSize
case textWeight
case textPosition
case onTintColor
case offTintColor
case onKnobTintColor
case offKnobTintColor
case fieldKey
case groupName
}
//--------------------------------------------------
// MARK: - Form Valdiation
//--------------------------------------------------
public override func formFieldValue() -> AnyHashable? {
public func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
return selected
}
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(_ state: Bool, id: String = UUID().uuidString) {
selected = state
super.init()
self.selected = state
baseValue = state
self.id = id
}
@ -72,6 +90,8 @@ public class ToggleModel: FormFieldModel {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
self.selected = state
}
@ -82,48 +102,54 @@ public class ToggleModel: FormFieldModel {
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let onTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onTintColor) {
self.onTintColor = onTintColor
}
if let offTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offTintColor) {
self.offTintColor = offTintColor
}
if let onKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onKnobTintColor) {
self.onKnobTintColor = onKnobTintColor
}
if let offKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offKnobTintColor) {
self.offKnobTintColor = offKnobTintColor
}
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
showText = try typeContainer.decodeIfPresent(Bool.self, forKey: .showText) ?? false
onText = try typeContainer.decodeIfPresent(String.self, forKey: .onText)
offText = try typeContainer.decodeIfPresent(String.self, forKey: .offText)
textSize = try typeContainer.decodeIfPresent(VDS.Toggle.TextSize.self, forKey: .textSize) ?? .small
textWeight = try typeContainer.decodeIfPresent(VDS.Toggle.TextWeight.self, forKey: .textWeight) ?? .regular
textPosition = try typeContainer.decodeIfPresent(VDS.Toggle.TextPosition.self, forKey: .textPosition) ?? .left
try super.init(from: decoder)
baseValue = selected
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
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 override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeModelIfPresent(action, forKey: .action)
try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(selected, forKey: .state)
try container.encode(animated, forKey: .animated)
try container.encode(enabled, forKey: .enabled)
try container.encode(onTintColor, forKey: .onTintColor)
try container.encode(onKnobTintColor, forKey: .onKnobTintColor)
try container.encode(onKnobTintColor, forKey: .onKnobTintColor)
try container.encode(offKnobTintColor, forKey: .offKnobTintColor)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encode(showText, forKey: .showText)
try container.encodeIfPresent(onText, forKey: .onText)
try container.encodeIfPresent(offText, forKey: .offText)
try container.encode(textSize, forKey: .textSize)
try container.encode(textWeight, forKey: .textWeight)
try container.encode(textPosition, forKey: .textPosition)
}
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return selected == model.selected
&& animated == model.animated
&& action.isEqual(to: model.action)
&& alternateAction.isEqual(to: model.alternateAction)
&& accessibilityText == model.accessibilityText
&& showText == model.showText
&& onText == model.onText
&& offText == model.offText
&& textSize == model.textSize
&& textWeight == model.textWeight
&& textPosition == model.textPosition
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -19,8 +19,7 @@ open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
}
public var moleculeName: String?
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var disabledColor: Color = Color(uiColor: .mvmCoolGray3)
public var color: Color = Color(uiColor: .mvmBlack)
@ -60,7 +59,6 @@ open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case disabledColor
@ -81,8 +79,7 @@ open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let disabledColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor) {
self.disabledColor = disabledColor
}
@ -119,7 +116,6 @@ open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(disabledColor, forKey: .disabledColor)

View File

@ -27,14 +27,10 @@ open class Badge: VDS.Badge, VDSMoleculeViewProtocol {
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
text = viewModel.text
textColor = viewModel.textColorStyle
maxWidth = viewModel.maxWidth
numberOfLines = viewModel.numberOfLines
fillColor = viewModel.fillColorStyle
fillColor = viewModel.fillColor
surface = viewModel.surface
}

View File

@ -25,9 +25,6 @@ open class BadgeIndicator: VDS.BadgeIndicator, VDSMoleculeViewProtocol {
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
number = viewModel.number
fillColor = viewModel.fillColor

View File

@ -17,8 +17,7 @@ open class BadgeIndicatorModel: MoleculeModelProtocol {
public static var identifier: String { "badgeIndicator" }
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
//--------------------------------------------------
// MARK: - VDS Properties
//--------------------------------------------------
@ -44,7 +43,6 @@ open class BadgeIndicatorModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case inverted
case accessibilityText
case number
@ -69,7 +67,6 @@ open class BadgeIndicatorModel: MoleculeModelProtocol {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.init()
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
accessibilityText = try container.decodeIfPresent(String.self, forKey: .accessibilityText)
number = try container.decodeIfPresent(Int.self, forKey: .number)
@ -94,7 +91,6 @@ open class BadgeIndicatorModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(number, forKey: .number)
try container.encodeIfPresent(fillColor, forKey: .fillColor)

View File

@ -16,46 +16,28 @@ open class BadgeModel: MoleculeModelProtocol {
public static var identifier: String = "badge"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
//--------------------------------------------------
// MARK: - VDS Properties
//--------------------------------------------------
public var text: String = ""
public var textColorStyle: Badge.TextColor? = nil
public var accessibilityText: String?
public var maxWidth: CGFloat?
public var numberOfLines: Int = 1
public var fillColorStyle = Badge.FillColor.red
public var fillColor = Badge.FillColor.red
public var surface: Surface = .light
private enum CodingKeys: String, CodingKey {
case id, accessibilityIdentifier, accessibilityText
case surface, numberOfLines, maxWidth
case text, textColor
case fillColor, fillColorStyle
case id, text, accessibilityText, fillColor, surface, numberOfLines, maxWidth
}
required public convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
text = try container.decode(String.self, forKey: .text)
accessibilityText = try container.decodeIfPresent(String.self, forKey: .accessibilityText)
//look for a textColor
if let textColor = try container.decodeIfPresent(Color.self, forKey: .textColor) {
textColorStyle = .custom(textColor.uiColor)
}
//look for a style
fillColorStyle = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColorStyle) ?? .red
//look for a color and set the style
if let fillColor = try container.decodeIfPresent(Color.self, forKey: .fillColor) {
fillColorStyle = .custom(fillColor.uiColor)
}
fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red
surface = try container.decodeIfPresent(Surface.self, forKey: .surface) ?? .light
numberOfLines = try container.decodeIfPresent(Int.self, forKey: .numberOfLines) ?? 1
maxWidth = try container.decodeIfPresent(CGFloat.self, forKey: .maxWidth)
@ -66,28 +48,9 @@ open class BadgeModel: MoleculeModelProtocol {
try container.encode(id, forKey: .id)
try container.encode(text, forKey: .text)
try container.encode(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(fillColor, forKey: .fillColor)
try container.encode(surface, forKey: .surface)
try container.encode(numberOfLines, forKey: .numberOfLines)
try container.encodeIfPresent(maxWidth, forKey: .maxWidth)
try container.encode(fillColorStyle, forKey: .fillColorStyle)
switch textColorStyle {
case .custom(let color):
try container.encode(Color(uiColor: color), forKey: .textColor)
default:
break
}
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? BadgeModel else { return false }
return self.backgroundColor == model.backgroundColor
&& self.fillColorStyle == model.fillColorStyle
&& self.textColorStyle == model.textColorStyle
&& self.numberOfLines == model.numberOfLines
&& self.text == model.text
&& self.surface == model.surface
&& self.accessibilityText == model.accessibilityText
&& self.maxWidth == model.maxWidth
}
}

View File

@ -25,9 +25,6 @@ open class ButtonIcon: VDS.ButtonIcon, VDSMoleculeViewProtocol {
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
onClick = { [weak self] control in

View File

@ -16,8 +16,7 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
public static var identifier: String = "buttonIcon"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
//--------------------------------------------------
// MARK: - VDS Properties
//--------------------------------------------------
@ -78,7 +77,6 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case inverted
case accessibilityText
case action
@ -107,7 +105,6 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
let container = try decoder.container(keyedBy: CodingKeys.self)
action = try container.decodeModel(codingKey: .action)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
accessibilityText = try container.decodeIfPresent(String.self, forKey: .accessibilityText)
badgeIndicator = try container.decodeIfPresent(BadgeIndicatorModel.self, forKey: .badgeIndicator)
@ -131,7 +128,6 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(badgeIndicator, forKey: .badgeIndicator)

View File

@ -1,12 +0,0 @@
//
// Calendar.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class CalendarView: VDS.CalendarBase, VDSMoleculeViewProtocol

View File

@ -1,65 +0,0 @@
//
// Calendar.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class CalendarView: VDS.CalendarBase, VDSMoleculeViewProtocol {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var viewModel: CalendarViewModel!
public var delegateObject: MVMCoreUIDelegateObject?
public var additionalData: [AnyHashable : Any]?
//--------------------------------------------------
// MARK: - Public Methods
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
if let _selectedDate = viewModel.selectedDate {
selectedDate = _selectedDate
}
if let _activeDates = viewModel.activeDates {
activeDates = _activeDates
}
if let _hideContainerBorder = viewModel.hideContainerBorder {
hideContainerBorder = _hideContainerBorder
}
if let _hideCurrentDateIndicator = viewModel.hideCurrentDateIndicator {
hideCurrentDateIndicator = _hideCurrentDateIndicator
}
if let _inactiveDates = viewModel.inactiveDates {
inactiveDates = _inactiveDates
}
if let _indicators = viewModel.indicators {
indicators = _indicators
}
if let _maxDate = viewModel.maxDate {
maxDate = _maxDate
}
if let _minDate = viewModel.minDate {
minDate = _minDate
}
surface = viewModel.surface
}
public func updateView(_ size: CGFloat) {}
}

View File

@ -1,159 +0,0 @@
//
// CalendarModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
open class CalendarViewModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "calendar"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeZone = NSTimeZone.system
formatter.locale = .current
formatter.formatterBehavior = .default
return formatter
}()
/// Update the property value to alter the format of how the date is presented.
public var dateFormat: String = "MMM d, y" {
didSet { dateFormatter.dateFormat = dateFormat }
}
public var hideContainerBorder: Bool?
public var hideCurrentDateIndicator: Bool?
public var activeDates: [Date]?
public var inactiveDates: [Date]?
public var selectedDate: Date?
public var minDate: Date?
public var maxDate: Date?
public var indicators: [CalendarBase.CalendarIndicatorModel]?
//--------------------------------------------------
// MARK: - VDS Properties
//--------------------------------------------------
public var surface: Surface { inverted ? .dark : .light }
public var inverted: Bool = false
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case inverted
case dateFormat
case hideContainerBorder
case hideCurrentDateIndicator
case activeDates
case inactiveDates
case selectedDate
case minDate
case maxDate
case indicators
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init() {}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
hideContainerBorder = try container.decodeIfPresent(Bool.self, forKey: .hideContainerBorder)
hideCurrentDateIndicator = try container.decodeIfPresent(Bool.self, forKey: .hideCurrentDateIndicator)
if let dateFormat = try container.decodeIfPresent(String.self, forKey: .dateFormat) {
self.dateFormat = dateFormat
dateFormatter.dateFormat = dateFormat
}
if let dates = try container.decodeIfPresent([String].self, forKey: .activeDates) {
activeDates = dates.compactMap { dateFormatter.date(from: $0) }
}
if let dates = try container.decodeIfPresent([String].self, forKey: .inactiveDates) {
inactiveDates = dates.compactMap { dateFormatter.date(from: $0) }
}
if let date = try container.decodeIfPresent(String.self, forKey: .selectedDate) {
selectedDate = dateFormatter.date(from: date)
}
if let date = try container.decodeIfPresent(String.self, forKey: .minDate) {
minDate = dateFormatter.date(from: date)
}
if let date = try container.decodeIfPresent(String.self, forKey: .maxDate) {
maxDate = dateFormatter.date(from: date)
}
indicators = try container.decodeIfPresent([CalendarBase.CalendarIndicatorModel].self, forKey: .indicators)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(inverted, forKey: .inverted)
try container.encode(dateFormat, forKey: .dateFormat)
try container.encode(hideContainerBorder, forKey: .hideContainerBorder)
try container.encode(hideCurrentDateIndicator, forKey: .hideCurrentDateIndicator)
try container.encode(activeDates, forKey: .activeDates)
try container.encode(selectedDate, forKey: .selectedDate)
try container.encode(minDate, forKey: .minDate)
try container.encode(maxDate, forKey: .maxDate)
try container.encode(indicators, forKey: .indicators)
}
open func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return inverted == model.inverted
&& dateFormat == model.dateFormat
&& hideContainerBorder == model.hideContainerBorder
&& hideCurrentDateIndicator == model.hideCurrentDateIndicator
&& activeDates == model.activeDates
&& inactiveDates == model.inactiveDates
&& selectedDate == model.selectedDate
&& minDate == model.minDate
&& maxDate == model.maxDate
&& indicators == model.indicators
}
}
extension CalendarViewModel {
public func convertToVDSCalendarModel() -> DatePicker.CalendarModel {
let defaults = DatePicker.CalendarModel()
return .init(hideContainerBorder: hideContainerBorder ?? defaults.hideContainerBorder ,
hideCurrentDateIndicator: hideCurrentDateIndicator ?? defaults.hideCurrentDateIndicator,
activeDates: activeDates ?? defaults.activeDates,
inactiveDates: inactiveDates ?? defaults.inactiveDates,
selectedDate: selectedDate ?? defaults.selectedDate,
minDate: minDate ?? defaults.minDate,
maxDate: maxDate ?? defaults.maxDate,
indicators: indicators ?? defaults.indicators)
}
}

View File

@ -16,7 +16,6 @@ import MVMCore
public static var identifier: String = "caretView"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var strokeColor: Color = Color(uiColor: .mvmBlack)
public var strokeColor_inverted: Color = Color(uiColor: .mvmWhite)
@ -31,7 +30,6 @@ import MVMCore
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case strokeColor
@ -56,8 +54,7 @@ import MVMCore
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let strokeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .strokeColor) {
self.strokeColor = strokeColor
}
@ -87,7 +84,6 @@ import MVMCore
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(strokeColor, forKey: .strokeColor)
try container.encode(strokeColor_inverted, forKey: .strokeColor_inverted)
try container.encode(inverted, forKey: .inverted)

View File

@ -19,7 +19,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
}
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var moleculeName: String?
@ -49,7 +48,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case currentIndex
@ -72,7 +70,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
@ -121,7 +118,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(currentIndex, forKey: .currentIndex)
try container.encode(alwaysSendAction, forKey: .alwaysSendAction)

View File

@ -5,99 +5,141 @@
// Created by Kevin Christiano on 9/13/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import VDS
@objcMembers open class CheckboxLabel: VDS.CheckboxItem, VDSMoleculeViewProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: CheckboxLabelModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
@objcMembers open class CheckboxLabel: View {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
override open var isSelected: Bool {
didSet {
viewModel?.checkbox.selected = isSelected
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
public let checkbox = Checkbox()
public let label = Label(fontStyle: .RegularBodySmall)
private var observation: NSKeyValueObservation? = nil
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var checkboxPosition: CheckboxPosition = .center
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var checkboxTopConstraint: NSLayoutConstraint?
public var checkboxBottomConstraint: NSLayoutConstraint?
public var checkboxCenterYConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Life Cycle
//--------------------------------------------------
override open func setupView() {
super.setupView()
guard subviews.isEmpty else { return }
addSubview(checkbox)
addSubview(label)
label.text = ""
checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor)
checkboxBottomConstraint?.isActive = true
checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor)
checkboxTopConstraint?.isActive = true
checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor)
label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true
layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true
let bottomLabelConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor)
bottomLabelConstraint.priority = .defaultLow
bottomLabelConstraint.isActive = true
alignCheckbox(.center)
isAccessibilityElement = false
accessibilityElements = [checkbox, label]
observation = observe(\.checkbox.isSelected, options: [.new]) { [weak self] _, _ in
self?.updateAccessibilityLabel()
}
}
@objc override open func updateView(_ size: CGFloat) {
super.updateView(size)
label.updateView(size)
checkbox.updateView(size)
layoutIfNeeded()
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// Aligns Checkbox and Label relative to the desired position of the Checkbox.
private func alignCheckbox(_ position: CheckboxPosition) {
checkboxPosition = position
switch position {
case .center:
checkboxBottomConstraint?.isActive = false
checkboxTopConstraint?.isActive = false
checkboxCenterYConstraint?.isActive = true
case .top:
checkboxBottomConstraint?.isActive = false
checkboxTopConstraint?.isActive = true
checkboxCenterYConstraint?.isActive = false
case .bottom:
checkboxBottomConstraint?.isActive = true
checkboxTopConstraint?.isActive = false
checkboxCenterYConstraint?.isActive = false
}
}
//--------------------------------------------------
// MARK: - Atomic
//--------------------------------------------------
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let checkBoxWithLabelModel = model as? CheckboxLabelModel else { return }
if let checkboxAlignment = checkBoxWithLabelModel.checkboxAlignment {
alignCheckbox(checkboxAlignment)
}
surface = viewModel.surface
updateCheckbox()
//primary label
labelText = viewModel.label?.text
labelTextAttributes = viewModel.label?.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
//secondary label
childText = viewModel.subTitle?.text
childTextAttributes = viewModel.subTitle?.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
checkbox.set(with: checkBoxWithLabelModel.checkbox, delegateObject, additionalData)
label.set(with: checkBoxWithLabelModel.label, delegateObject, additionalData)
updateAccessibilityLabel()
}
private func performCheckboxAction(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel.checkbox, additionalData: additionalData, delegateObject: delegateObject)
}
open func updateCheckbox() {
//forms
FormValidator.setupValidation(for: viewModel.checkbox, delegate: delegateObject?.formHolderDelegate)
groupName = viewModel.checkbox.groupName
if let fieldKey = viewModel.checkbox.fieldKey {
self.fieldKey = fieldKey
}
//properties
isAnimated = viewModel.checkbox.animated
isEnabled = viewModel.checkbox.isEnabled
//call super here to go around the didSet
//in this class
super.isSelected = viewModel.checkbox.selected
//events
viewModel.checkbox.updateUI = {
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
guard let self = self else { return }
//let isValid = viewModel.checkbox.isValid ?? true
//TODO: Fix issue with default state
//showError = !isValid
errorText = viewModel.checkbox.errorMessage
isEnabled = viewModel.checkbox.isEnabled
})
}
//onChange
if (viewModel.checkbox.action != nil || viewModel.checkbox.offAction != nil) {
onChange = { [weak self] control in
guard let self = self else { return }
if let offAction = viewModel.checkbox.offAction, !isSelected {
performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData)
} else if let action = viewModel.checkbox.action {
performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData)
}
}
}
}
@objc open func updateView(_ size: CGFloat) {}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 200
}
open override func reset() {
super.reset()
label.text = ""
checkbox.reset()
alignCheckbox(.center)
}
override open func accessibilityActivate() -> Bool {
checkbox.accessibilityActivate()
}
open func updateAccessibilityLabel() {
checkbox.updateAccessibilityLabel()
if let text = label.text {
checkbox.accessibilityLabel?.append(", \(text)")
}
}
}

View File

@ -8,67 +8,31 @@
import Foundation
import MVMCore
import VDS
public enum CheckboxPosition: String, Codable {
case center
case top
case bottom
}
@objcMembers open class CheckboxLabelModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
open class var identifier: String { "checkboxLabel" }
public var moleculeName: String = CheckboxLabelModel.identifier
@DecodableDefault.UUIDString public var id: String
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var checkboxAlignment: CheckboxPosition?
public var checkbox: CheckboxModel
public var label: LabelModel?
public var subTitle: LabelModel?
public var inverted: Bool? = false
public var surface: Surface { inverted ?? false ? .dark : .light }
public var children: [MoleculeModelProtocol] {
var values: [MoleculeModelProtocol] = [checkbox]
if let label { values.append(label) }
if let subTitle { values.append(subTitle) }
return values
}
public var label: LabelModel
public var children: [MoleculeModelProtocol] { [checkbox, label] }
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(checkbox: CheckboxModel, label: LabelModel, subTitle: LabelModel?) {
public init(checkbox: CheckboxModel, label: LabelModel) {
self.checkbox = checkbox
self.label = label
self.subTitle = subTitle
}
open func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return moleculeName == model.moleculeName
&& inverted == model.inverted
&& accessibilityText == model.accessibilityText
&& accessibilityIdentifier == model.accessibilityIdentifier
&& accessibilityTraits == model.accessibilityTraits
}
}
extension Array where Element == CheckboxLabelModel {
internal func convertToVDSCheckboxItemModel(surface: Surface,
delegateObject: MVMCoreUIDelegateObject?,
additionalData: [AnyHashable: Any]?) -> [CheckboxGroup.CheckboxItemModel] {
return compactMap({ model in
var item = CheckboxGroup.CheckboxItemModel()
item.inputId = model.checkbox.fieldKey
item.labelText = model.label?.text
if let attributes = model.label?.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) {
item.labelTextAttributes = attributes
}
item.childText = model.subTitle?.text
if let attributes = model.subTitle?.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) {
item.childTextAttributes = attributes
}
item.surface = surface
item.selected = model.checkbox.selected
item.enabled = model.checkbox.isEnabled
return item
})
}
}

View File

@ -1,128 +0,0 @@
//
// CircularProgressBar.swift
// MVMCoreUI
//
// Created by Xi Zhang on 7/5/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import UIKit
@objcMembers open class CircularProgressBar: View, MVMCoreUIViewConstrainingProtocol {
var heightConstraint: NSLayoutConstraint?
var graphModel: CircularProgressBarModel? {
return model as? CircularProgressBarModel
}
var viewWidth: CGFloat {
graphModel?.diameter ?? CGFloat(64)
}
private var progressLayer = CAShapeLayer()
private var tracklayer = CAShapeLayer()
private var labelLayer = CATextLayer()
var progressColor: UIColor = UIColor.red
var trackColor: UIColor = UIColor.lightGray
// A path with which CAShapeLayer will be drawn on the screen
private var viewCGPath: CGPath? {
let width = viewWidth
let height = width
return UIBezierPath(arcCenter: CGPoint(x: width / 2.0, y: height / 2.0),
radius: (width - 1.5)/2,
startAngle: CGFloat(-0.5 * Double.pi),
endAngle: CGFloat(1.5 * Double.pi), clockwise: true).cgPath
}
// MARK: setup
override open func setupView() {
super.setupView()
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
heightConstraint?.isActive = true
widthAnchor.constraint(equalTo: heightAnchor).isActive = true
}
override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? CircularProgressBarModel else { return }
// set background color
backgroundColor = model.backgroundColor?.uiColor ?? UIColor.clear
configureProgressViewToBeCircular()
// set progress color
progressColor = model.color?.uiColor ?? .red
progressLayer.strokeColor = progressColor.cgColor
// set track color
trackColor = model.trackColor?.uiColor ?? .lightGray
tracklayer.strokeColor = trackColor.cgColor
// show circular progress view with animation.
showProgressWithAnimation(duration: graphModel?.duration ?? 0, value: Float(graphModel?.percent ?? 0) / 100)
// show progress percentage label.
if let drawText = model.drawText, drawText {
showProgressPercentage()
}
}
private func configureProgressViewToBeCircular() {
let lineWidth = graphModel?.lineWidth ?? 4.0
self.drawShape(using: tracklayer, lineWidth: lineWidth)
self.drawShape(using: progressLayer, lineWidth: lineWidth)
}
private func drawShape(using shape: CAShapeLayer, lineWidth: CGFloat) {
shape.path = self.viewCGPath
shape.fillColor = UIColor.clear.cgColor
shape.lineWidth = lineWidth
self.layer.addSublayer(shape)
}
// value range is [0,1]
private func showProgressWithAnimation(duration: TimeInterval, value: Float) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0 //start animation at point 0
animation.toValue = value //end animation at point specified
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
progressLayer.strokeEnd = CGFloat(value)
progressLayer.add(animation, forKey: "animateCircle")
}
private func showProgressPercentage() {
let percent = graphModel?.percent ?? 0
let percentLen = String(percent).count
// configure attributed string for progress percentage.
let attributedString = NSMutableAttributedString(string: String(percent) + "%")
// percent value
attributedString.setAttributes([NSAttributedString.Key.font: Styler.Font.BoldTitleXLarge], range: NSMakeRange(0, percentLen))
// % symbol
attributedString.setAttributes([NSAttributedString.Key.font: Styler.Font.RegularMicro], range: NSMakeRange(percentLen, 1))
// show progress percentage in a text layer
let width = viewWidth
let height = width
labelLayer.string = attributedString
labelLayer.frame = CGRectMake((width - CGFloat(percentLen * 20))/2, (height - 40)/2, 80, 40)
self.layer.addSublayer(labelLayer)
}
//MARK: MVMCoreUIViewConstrainingProtocol
public func needsToBeConstrained() -> Bool {
return true
}
}

View File

@ -1,123 +0,0 @@
//
// CircularProgressBarModel.swift
// MVMCoreUI
//
// https://oneconfluence.verizon.com/display/MFD/Circular+Progress+Tracker
//
// Created by Xi Zhang on 7/5/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
public class CircularProgressBarModel: GraphSizeBase, MoleculeModelProtocol {
public static var identifier: String = "circularProgress"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var percent: Int = 0
public var diameter: CGFloat? = 64
public var lineWidth: CGFloat? = 4
public var duration : Double? = 0
public var color: Color? = Color(uiColor: UIColor.mfGet(forHex: "#007AB8"))
public var trackColor: Color? = Color(uiColor: .mvmCoolGray3)
public var drawText: Bool? = true
public var backgroundColor: Color? = Color(uiColor: UIColor.clear)
public override init() {
super.init()
updateSize()
}
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case percent
case size
case diameter
case lineWidth
case duration
case color
case trackColor
case drawText
case backgroundColor
}
required public init(from decoder: Decoder) throws {
super.init()
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
percent = try typeContainer.decode(Int.self, forKey: .percent)
if let size = try typeContainer.decodeIfPresent(GraphSize.self, forKey: .size) {
self.size = size
}
updateSize()
if let diameter = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .diameter) {
self.diameter = diameter
}
if let lineWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .lineWidth) {
self.lineWidth = lineWidth
}
if let duration = try typeContainer.decodeIfPresent(Double.self, forKey: .duration) {
self.duration = duration
}
if let drawText = try typeContainer.decodeIfPresent(Bool.self, forKey: .drawText) {
self.drawText = drawText
}
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) {
self.color = color
}
if let trackColor = try typeContainer.decodeIfPresent(Color.self, forKey: .trackColor) {
self.trackColor = trackColor
}
if let backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) {
self.backgroundColor = backgroundColor
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(percent, forKey: .percent)
try container.encodeIfPresent(size, forKey: .size)
try container.encodeIfPresent(diameter, forKey: .diameter)
try container.encodeIfPresent(lineWidth, forKey: .lineWidth)
try container.encodeIfPresent(duration, forKey: .duration)
try container.encodeIfPresent(drawText, forKey: .drawText)
try container.encodeIfPresent(trackColor, forKey: .trackColor)
try container.encodeIfPresent(color, forKey: .color)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
}
public override func updateSize() {
switch size {
case .small:
diameter = MFSizeObject(standardSize: 64)?.getValueBasedOnApplicationWidth() ?? 64
lineWidth = MFSizeObject(standardSize: 4)?.getValueBasedOnApplicationWidth() ?? 4
break
case .medium:
diameter = MFSizeObject(standardSize: 84)?.getValueBasedOnApplicationWidth() ?? 84
lineWidth = MFSizeObject(standardSize: 4)?.getValueBasedOnApplicationWidth() ?? 4
break
case .large:
diameter = MFSizeObject(standardSize: 124)?.getValueBasedOnApplicationWidth() ?? 124
lineWidth = MFSizeObject(standardSize: 4)?.getValueBasedOnApplicationWidth() ?? 4
break
}
}
}

View File

@ -1,29 +0,0 @@
//
// GraphSizeProtocol.swift
// MVMCoreUI
//
// Created by Xi Zhang on 7/15/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
public enum GraphSize: String, Codable {
case small, medium, large
}
public protocol GraphSizeProtocol {
var size: GraphSize { get set }
func updateSize()
}
public class GraphSizeBase: GraphSizeProtocol {
public var size: GraphSize = .small {
didSet {
updateSize()
}
}
public func updateSize() {
}
}

View File

@ -31,15 +31,11 @@ open class Icon: VDS.Icon, VDSMoleculeViewProtocol{
// MARK: - Public
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
color = viewModel.color.uiColor
size = viewModel.size
customSize = viewModel.customSize
name = viewModel.name
isAccessibilityElement = viewModel.isAccessibilityElement ?? true
}
//--------------------------------------------------

View File

@ -21,8 +21,6 @@ open class IconModel: MoleculeModelProtocol {
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
/// A representation that will be used to render the icon with corresponding name.
public var name: Icon.Name
@ -37,6 +35,4 @@ open class IconModel: MoleculeModelProtocol {
/// A custom size of the icon.
public var customSize: Int?
public var isAccessibilityElement: Bool?
}

View File

@ -18,7 +18,6 @@
public var backgroundColor: Color?
public var moleculeName: String = ImageViewModel.identifier
public var image: String
public var accessibilityIdentifier: String?
public var accessibilityText: String?
public var fallbackImage: String?
public var imageFormat: String?
@ -48,7 +47,6 @@
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case image

View File

@ -16,15 +16,13 @@ public class FormLabel: Label {
//public properties
public override var isEnabled: Bool {
didSet{
guard let formModel else { return }
formModel.enabled = isEnabled
self.formModel.enabled = isEnabled
self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData)
}
}
public var isRequired: Bool = true {
didSet{
guard let formModel else { return }
self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData)
}
}
@ -52,7 +50,6 @@ public class FormLabel: Label {
/// Text change that will update both enabledModel and disabledModel text values
/// - Parameter text: text you want to see
public func set(text: String?){
guard let formModel else { return }
formModel.set(text: text ?? "")
self.formModel.set(text: text ?? "")
}
}

View File

@ -34,7 +34,11 @@ public typealias ActionBlock = () -> ()
/// A specific text index to use as a unique marker.
public var hero: Int?
public var getRange: NSRange {
NSRange(location: 0, length: text?.count ?? 0)
}
public var shouldMaskWhileRecording: Bool = false
public var hasText: Bool {
@ -182,9 +186,6 @@ public typealias ActionBlock = () -> ()
}
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
shouldMaskWhileRecording = viewModel.shouldMaskRecordedView ?? false
text = viewModel.text
hero = viewModel.hero
@ -377,24 +378,19 @@ extension Label {
// MARK: - Atomization
extension Label {
public func needsToBeConstrained() -> Bool { true }
public func horizontalAlignment() -> UIStackView.Alignment { .leading }
public func copyBackgroundColor() -> Bool { true }
}
// MARK: - Multi-Link Functionality
extension VDS.Label {
public var getRange: NSRange {
NSRange(location: 0, length: text?.count ?? 0)
}
extension Label {
/// Underlines the tappable region and stores the tap logic for interation.
internal func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
guard let text, text.isValid(range: range) else { return }
var textLink = ActionLabelAttribute(location: range.location, length: range.length)
@ -421,16 +417,8 @@ extension VDS.Label {
return { [weak self] in
guard let self = self else { return }
if let button = self as? MFButtonProtocol {
if (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(button, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap,
additionalData: additionalData,
delegateObject: delegateObject)
}
} else {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap,
additionalData: additionalData,
delegateObject: delegateObject)
if (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
}
}
}

View File

@ -14,8 +14,7 @@ import VDS
open class var identifier: String { "label" }
public var id: String
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var text: String
public var accessibilityText: String?
@ -39,7 +38,6 @@ import VDS
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case text
case accessibilityText
@ -90,7 +88,6 @@ import VDS
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
text = try typeContainer.decode(String.self, forKey: .text)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor)
@ -118,7 +115,6 @@ import VDS
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(moleculeName, forKey: .moleculeName)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(text, forKey: .text)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(textColor, forKey: .textColor)

View File

@ -16,8 +16,7 @@ import UIKit
public static var identifier: String = "leftRightLabelView"
public var moleculeName: String = LeftRightLabelModel.identifier
@DecodableDefault.UUIDString public var id: String
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var leftText: LabelModel
public var rightText: LabelModel?

View File

@ -66,7 +66,7 @@ import VDS
}
open func setStyle(_ style: LineModel.Style) {
viewModel?.type = style
viewModel.type = style
update(viewModel: viewModel)
}
@ -85,7 +85,7 @@ import VDS
}
open override func draw(_ rect: CGRect) {
guard let viewModel, viewModel.type != .none else { return }
guard viewModel.type != .none else { return }
super.draw(rect)
}
@ -93,9 +93,6 @@ import VDS
// MARK: - VDSMoleculeViewProtocol
//--------------------------------------------------
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
style = VDS.Line.Style(rawValue: viewModel.type.rawValue) ?? .primary
orientation = viewModel.orientation

View File

@ -55,7 +55,6 @@ public class LineModel: MoleculeModelProtocol, Invertable {
public static var identifier: String = "line"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var type: Style = .secondary
@ -84,7 +83,6 @@ public class LineModel: MoleculeModelProtocol, Invertable {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case type
case frequency
@ -101,8 +99,7 @@ public class LineModel: MoleculeModelProtocol, Invertable {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let type = try typeContainer.decodeIfPresent(Style.self, forKey: .type) {
self.type = type
}
@ -127,7 +124,6 @@ public class LineModel: MoleculeModelProtocol, Invertable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(type, forKey: .type)
try container.encode(inverted, forKey: .inverted)
try container.encodeIfPresent(frequency, forKey: .frequency)

View File

@ -22,9 +22,6 @@ open class LoadingSpinner: VDS.Loader, VDSMoleculeViewProtocol {
// MARK: - Public Functions
//--------------------------------------------------
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
size = Int(viewModel.diameter)
surface = viewModel.surface
}

View File

@ -16,8 +16,7 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
//--------------------------------------------------
public static var identifier: String = "loadingSpinner"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var inverted: Bool = false
public var diameter: CGFloat = 40
@ -28,7 +27,6 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case strokeColor
case diameter
@ -49,8 +47,7 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let diameter = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .diameter) {
self.diameter = diameter
}
@ -68,7 +65,6 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(diameter, forKey: .diameter)
try container.encodeIfPresent(inverted, forKey: .inverted)
}

View File

@ -22,8 +22,7 @@ import Foundation
public static var identifier: String = "multiProgressBar"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var progressList: [SingleProgressBarModel]
public var backgroundColor: Color?
public var thickness: CGFloat?
@ -31,7 +30,6 @@ import Foundation
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case progressList
case thickness
@ -46,7 +44,6 @@ import Foundation
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
progressList = try typeContainer.decode([SingleProgressBarModel].self, forKey: .progressList)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness)
@ -57,7 +54,6 @@ import Foundation
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(progressList, forKey: .progressList)
try container.encodeIfPresent(thickness, forKey: .thickness)
try container.encodeIfPresent(roundedCorners, forKey: .roundedCorners)

View File

@ -1,60 +0,0 @@
//
// Pagination.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/27/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
@objcMembers open class Pagination: VDS.Pagination, VDSMoleculeViewProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: PaginationModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
open var fieldKey: String?
open var fieldValue: JSONValue?
open var groupName: String?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public convenience required init() {
self.init(frame:.zero)
}
open override func setup() {
super.setup()
pageChangedPublisher
.sink { [weak self] control in
guard let self else { return }
viewModel?.selectedPage = control.selectedPage
}.store(in: &subscribers)
}
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
isEnabled = viewModel.enabled
surface = viewModel.surface
total = viewModel.totalPages
selectedPage = viewModel.selectedPage
}
//--------------------------------------------------
// MARK: - Actions
//--------------------------------------------------
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public func updateView(_ size: CGFloat) {}
}

View File

@ -1,76 +0,0 @@
//
// PaginationModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/27/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
import MVMCore
open class PaginationModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open class var identifier: String { "pagination" }
open var moleculeName: String { Self.identifier }
open var backgroundColor: Color?
open var id: String = UUID().uuidString
open var accessibilityIdentifier: String?
open var totalPages: Int = 0
open var selectedPage: Int = 0
open var enabled: Bool = true
open var inverted: Bool = false
open var surface: Surface { inverted ? .dark : .light }
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case totalPages
case selectedPage
case enabled
case inverted
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
totalPages = try container.decode(Int.self, forKey: .totalPages)
selectedPage = try container.decodeIfPresent(Int.self, forKey: .selectedPage) ?? 0
enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled) ?? false
inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(totalPages, forKey: .totalPages)
try container.encode(selectedPage, forKey: .selectedPage)
try container.encode(enabled, forKey: .enabled)
try container.encode(inverted, forKey: .inverted)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return inverted == model.inverted
&& enabled == model.enabled
&& totalPages == model.totalPages
&& selectedPage == model.selectedPage
}
}

View File

@ -75,9 +75,6 @@ import Foundation
guard let progressBarModel = model as? ProgressBarModel else { return }
self.progressBarModel = progressBarModel
if let accessibilityIdentifier = model.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
thickness = progressBarModel.thickness ?? 8
progress = Float((progressBarModel.percent) / 100.0)
progressTintColor = progressBarModel.color.uiColor

View File

@ -11,8 +11,7 @@ import Foundation
@objcMembers open class ProgressBarModel: MoleculeModelProtocol {
open class var identifier: String { "progressBar" }
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
@Percent public var percent: CGFloat
public var color: Color = Color(uiColor: .mfCerulean())
public var backgroundColor: Color? = Color(uiColor: .mfLightSilver())
@ -21,7 +20,6 @@ import Foundation
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case roundedCorners
case thickness
@ -37,7 +35,6 @@ import Foundation
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
percent = try typeContainer.decode(CGFloat.self, forKey: .percent)
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) {
self.color = color
@ -53,7 +50,6 @@ import Foundation
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(percent, forKey: .percent)
try container.encode(color, forKey: .color)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)

View File

@ -14,8 +14,7 @@ open class StarModel: MoleculeModelProtocol {
//--------------------------------------------------
public static var identifier: String = "star"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
@Percent public var percent: CGFloat = 0
public var borderColor: Color?
@ -27,7 +26,6 @@ open class StarModel: MoleculeModelProtocol {
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case percent
@ -49,7 +47,6 @@ open class StarModel: MoleculeModelProtocol {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let percent = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .percent) {
self.percent = percent
}
@ -66,7 +63,6 @@ open class StarModel: MoleculeModelProtocol {
try container.encode(id, forKey: .id)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(percent, forKey: .percent)
try container.encodeIfPresent(borderColor, forKey: .borderColor)
try container.encodeIfPresent(fillColor, forKey: .fillColor)

View File

@ -14,8 +14,7 @@ import MVMCore
//--------------------------------------------------
public static var identifier: String = "stars"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var starBackgroundColor: Color?
public var stars: [StarModel]
@ -29,7 +28,6 @@ import MVMCore
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case starBackgroundColor
@ -55,7 +53,6 @@ import MVMCore
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
stars = try typeContainer.decode([StarModel].self, forKey: .stars)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
starBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .starBackgroundColor)
@ -73,7 +70,6 @@ import MVMCore
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(stars, forKey: .stars)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(starBackgroundColor, forKey: .starBackgroundColor)

View File

@ -40,9 +40,7 @@ open class TileContainer: VDS.TileContainer, VDSMoleculeViewProtocol{
// MARK: - Public
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
if let moleculeModel = viewModel.molecule {
if let molecule,
moleculeModel.moleculeName == molecule.model?.moleculeName {
@ -101,9 +99,7 @@ open class TileContainer: VDS.TileContainer, VDSMoleculeViewProtocol{
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open func updateView(_ size: CGFloat) {
(molecule as? MVMCoreViewProtocol)?.updateView(size)
}
open func updateView(_ size: CGFloat) {}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol

View File

@ -70,10 +70,8 @@ open class TileContainerBaseModel<PaddingType: DefaultValuing & Codable, TileCon
public var aspectRatio: TileContainerType.AspectRatio = .none
public var backgroundEffect: TileContainerType.BackgroundEffect = .none
public var surface: Surface { inverted ? .dark : .light }
public var accessibilityIdentifier: String?
private enum CodingKeys: String, CodingKey {
case accessibilityIdentifier
case inverted
case backgroundImage
case action
@ -90,7 +88,6 @@ open class TileContainerBaseModel<PaddingType: DefaultValuing & Codable, TileCon
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
backgroundImage = try container.decodeIfPresent(String.self, forKey: .backgroundImage)
action = try container.decodeModelIfPresent(codingKey: .action)
@ -107,7 +104,6 @@ open class TileContainerBaseModel<PaddingType: DefaultValuing & Codable, TileCon
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(backgroundImage, forKey: .backgroundImage)
try container.encodeModelIfPresent(action, forKey: .action)
try container.encodeIfPresent(imageFallbackColor, forKey: .imageFallbackColor)

View File

@ -39,9 +39,6 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{
// MARK: - Public
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
//tilelet specific properties
if let value = viewModel.textWidth {
textWidth = .value(value)
@ -51,7 +48,7 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{
eyebrowModel = viewModel.eyebrowModel(delegateObject: delegateObject, additionalData: additionalData)
titleModel = viewModel.titleModel(delegateObject: delegateObject, additionalData: additionalData)
subTitleModel = viewModel.subTitleModel(delegateObject: delegateObject, additionalData: additionalData)
badgeModel = viewModel.badgeModel()
badgeModel = viewModel.badge
descriptiveIconModel = viewModel.descriptiveIcon
directionalIconModel = viewModel.directionalIcon
//setup action

View File

@ -18,7 +18,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var badge: BadgeModel?
public var badge: Tilelet.BadgeModel?
public var eyebrow: LabelModel?
public var eyebrowColor: TitleLockup.TextColor = .primary
public var title: LabelModel?
@ -49,7 +49,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
badge = try container.decodeIfPresent(BadgeModel.self, forKey: .badge)
badge = try container.decodeIfPresent(Tilelet.BadgeModel.self, forKey: .badge)
eyebrow = try container.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
title = try container.decodeIfPresent(LabelModel.self, forKey: .title)
subTitle = try container.decodeIfPresent(LabelModel.self, forKey: .subTitle)
@ -91,48 +91,32 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
try super.init(from: decoder)
}
public func badgeModel() -> Tilelet.BadgeModel? {
guard let badge else { return nil }
return .init(text: badge.text,
textColor: badge.textColorStyle,
fillColor: badge.fillColorStyle,
surface: badge.surface,
numberOfLines: badge.numberOfLines,
maxWidth: badge.maxWidth
)
}
public func eyebrowModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.EyebrowModel? {
guard let eyebrow else { return nil }
let attrs = eyebrow.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
var isBold: Bool = true
do {
if let style = eyebrow.fontStyle {
isBold = style.isBold()
return .init(text: eyebrow.text,
textColor: eyebrowColor,
textAttributes: attrs,
isBold: isBold,
textAttributes: attrs, isBold: style.isBold(),
standardStyle: try style.vdsSubsetStyle())
}
} catch MVMCoreError.errorObject(let object) {
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: eyebrow.text, textColor: eyebrowColor, textAttributes: attrs, isBold: isBold)
return .init(text: eyebrow.text, textColor: eyebrowColor, textAttributes: attrs)
}
public func titleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.TitleModel? {
guard let title else { return nil }
let attrs = title.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData)
var isBold: Bool = true
do {
if let style = title.fontStyle {
isBold = style.isBold()
return .init(text: title.text,
textColor: titleColor,
textAttributes: attrs,
isBold: isBold,
standardStyle: try style.vdsSubsetStyle())
}
@ -140,7 +124,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: title.text, textColor: titleColor, textAttributes: attrs, isBold: isBold)
return .init(text: title.text, textColor: titleColor, textAttributes: attrs)
}
public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? {

View File

@ -28,9 +28,6 @@ open class Tooltip: VDS.Tooltip, VDSMoleculeViewProtocol{
// MARK: - Public
//--------------------------------------------------
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
fillColor = viewModel.fillColor
size = viewModel.size

View File

@ -22,8 +22,6 @@ open class TooltipModel: MoleculeModelProtocol {
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var size: VDS.Tooltip.Size = .medium
public var fillColor: VDS.Tooltip.FillColor = .primary
@ -41,7 +39,6 @@ open class TooltipModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case closeButtonText
@ -55,14 +52,13 @@ open class TooltipModel: MoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
backgroundColor = try container.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
self.id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
self.backgroundColor = try container.decodeIfPresent(Color.self, forKey: .backgroundColor)
title = try container.decodeIfPresent(String.self, forKey: .title)
content = try container.decodeIfPresent(String.self, forKey: .content)
self.title = try container.decodeIfPresent(String.self, forKey: .title)
self.content = try container.decodeIfPresent(String.self, forKey: .content)
molecule = try container.decodeModelIfPresent(codingKey: .contentView)
self.molecule = try container.decodeModelIfPresent(codingKey: .contentView)
if let closeButtonText = try container.decodeIfPresent(String.self, forKey: .closeButtonText) {
self.closeButtonText = closeButtonText
@ -85,7 +81,6 @@ open class TooltipModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(surface, forKey: .surface)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
@ -103,7 +98,7 @@ open class TooltipModel: MoleculeModelProtocol {
}
extension TooltipModel {
public func convertToVDSTooltipModel() -> Tooltip.TooltipModel {
public func toVDSTooltipModel() -> Tooltip.TooltipModel {
var moleculeView: MoleculeViewProtocol?
if let molecule, let view = ModelRegistry.createMolecule(molecule) {
moleculeView = view

View File

@ -11,7 +11,6 @@ import Foundation
open class VideoModel: MoleculeModelProtocol, PageBehaviorProtocolRequirer {
public static var identifier = "video"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var video: String
public var showControls = false
@ -46,7 +45,6 @@ open class VideoModel: MoleculeModelProtocol, PageBehaviorProtocolRequirer {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case video
case showControls
@ -62,7 +60,6 @@ open class VideoModel: MoleculeModelProtocol, PageBehaviorProtocolRequirer {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
video = try typeContainer.decode(String.self, forKey:.video)
if let showControls = try typeContainer.decodeIfPresent(Bool.self, forKey: .showControls) {
self.showControls = showControls
@ -79,7 +76,6 @@ open class VideoModel: MoleculeModelProtocol, PageBehaviorProtocolRequirer {
open func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(video, forKey: .video)
try container.encode(showControls, forKey: .showControls)

View File

@ -13,8 +13,7 @@ import MVMCore
public static var identifier: String = "webview"
public var moleculeName: String = WebViewModel.identifier
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var url: URL?
public var htmlString: String?
@ -27,7 +26,6 @@ import MVMCore
private enum CodingKeys: String, CodingKey{
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case url
@ -45,7 +43,6 @@ import MVMCore
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
url = try typeContainer.decodeIfPresent(URL.self, forKey: .url)
htmlString = try typeContainer.decodeIfPresent(String.self, forKey: .htmlString)
@ -60,7 +57,6 @@ import MVMCore
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(url, forKey: .url)
try container.encodeIfPresent(htmlString, forKey: .htmlString)

View File

@ -8,22 +8,30 @@
import UIKit
public enum GraphSize: String, Codable {
case small, medium, large
}
public enum GraphStyle: String, Codable {
case unlimited, safetyMode
}
public class WheelModel: GraphSizeBase, MoleculeModelProtocol {
public class WheelModel: MoleculeModelProtocol {
public static var identifier: String = "wheel"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var style: GraphStyle = .unlimited {
didSet {
updateStyle()
}
}
public var size: GraphSize = .small {
didSet {
updateSize()
}
}
public var diameter: CGFloat = 24
public var lineWidth: CGFloat = 5
public var clockwise: Bool = true
@ -31,15 +39,13 @@ public class WheelModel: GraphSizeBase, MoleculeModelProtocol {
public var colors = [Color]()
public var backgroundColor: Color?
public override init() {
super.init()
public init() {
updateStyle()
updateSize()
}
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case style
case size
case diameter
@ -52,11 +58,9 @@ public class WheelModel: GraphSizeBase, MoleculeModelProtocol {
}
required public init(from decoder: Decoder) throws {
super.init()
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let style = try typeContainer.decodeIfPresent(GraphStyle.self, forKey: .style) {
self.style = style
}
@ -87,7 +91,6 @@ public class WheelModel: GraphSizeBase, MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(style, forKey: .style)
try container.encode(size, forKey: .size)
try container.encode(diameter, forKey: .diameter)
@ -120,7 +123,7 @@ public class WheelModel: GraphSizeBase, MoleculeModelProtocol {
}
}
public override func updateSize() {
func updateSize() {
switch size {
case .small:
diameter = MFSizeObject(standardSize: 20)?.getValueBasedOnApplicationWidth() ?? 20

View File

@ -9,13 +9,13 @@
import Foundation
import VDS
import VDSCoreTokens
import MVMCore
//--------------------------------------------------
// MARK: - Codable Extensions
//--------------------------------------------------
extension VDS.Surface: Codable {}
extension VDS.Badge.FillColor: Codable {}
extension VDS.BadgeIndicator.FillColor: Codable {}
extension VDS.BadgeIndicator.Kind: Codable {}
extension VDS.BadgeIndicator.MaximumDigits: Codable {}
@ -57,52 +57,6 @@ extension VDS.Button.Size: RawRepresentableCodable {
public static var defaultValue: VDS.Button.Size? { nil }
}
extension VDS.CalendarBase.CalendarIndicatorModel: Codable, ModelComparisonProtocol, MoleculeModelComparisonProtocol {
enum CodingKeys: String, CodingKey {
case label
case date
case dateFormat
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let label = try container.decode(String.self, forKey: .label)
let formatter = Self.formatter()
formatter.dateFormat = try container.decodeIfPresent(String.self, forKey: .dateFormat) ?? "MMM d, y"
let foundDate = try container.decode(String.self, forKey: .date)
let date = formatter.date(from: foundDate)!
self = .init(label: label, date: date)
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(label, forKey: .label)
try container.encode(Self.formatter().string(from: date), forKey: .date)
}
static func formatter() -> DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeZone = NSTimeZone.system
formatter.locale = .current
formatter.formatterBehavior = .default
return formatter
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return label == model.label
&& date == model.date
}
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return label == model.label
&& date == model.date
}
}
//--------------------------------------------------
// MARK: - Decodable Defaults
//--------------------------------------------------
@ -293,45 +247,6 @@ extension VDS.TitleLockup.TextColor: Codable {
}
}
extension VDS.Badge.FillColor: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let type = try container.decode(String.self)
switch type {
case "red":
self = .red
case "yellow":
self = .yellow
case "green":
self = .green
case "orange":
self = .orange
case "blue":
self = .blue
case "black":
self = .black
case "white":
self = .white
default:
if let color = try? Color(from: decoder) {
self = .custom(color.uiColor)
} else {
self = .custom(UIColor(hexString: type))
}
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .custom(let value):
try container.encode(Color(uiColor: value))
default:
try container.encode(String(reflecting: self))
}
}
}
extension VDS.TitleLockup.TitleTextColor: Codable {
enum CodingKeys: String, CodingKey {

View File

@ -16,7 +16,7 @@ extension Array where Element: MVMCoreUI.LabelAttributeModel {
var attributes: [any VDS.LabelAttributeModel] = []
forEach { atomicLabelAttribute in
if let attr = atomicLabelAttribute as? (any VDSLabelAttributeConvertable),
let vds = attr.convertToVDSLabelAttribute(delegateObject: delegateObject,
let vds = attr.convertToVDSLabelAttirbute(delegateObject: delegateObject,
additionalData: additionalData){
attributes.append(vds)
}
@ -28,12 +28,12 @@ extension Array where Element: MVMCoreUI.LabelAttributeModel {
//VDS Convertable Protocol and Extensions
public protocol VDSLabelAttributeConvertable<LabelAttributeType> {
associatedtype LabelAttributeType: VDS.LabelAttributeModel
func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> LabelAttributeType?
func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> LabelAttributeType?
}
extension LabelAttributeUnderlineModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> UnderlineLabelAttribute? {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> UnderlineLabelAttribute? {
guard let style = UnderlineLabelAttribute.Style(rawValue: style.rawValue) else { return nil }
var pattern: UnderlineLabelAttribute.Pattern?
@ -50,7 +50,7 @@ extension LabelAttributeUnderlineModel: VDSLabelAttributeConvertable {
}
extension LabelAttributeActionModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> ActionLabelAttribute? {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> ActionLabelAttribute? {
var vdsAttribute = VDS.ActionLabelAttribute(location: location, length: length)
vdsAttribute.subscriber = vdsAttribute.action.sink { [weak self] in
guard let self else { return }
@ -64,13 +64,11 @@ extension LabelAttributeActionModel: VDSLabelAttributeConvertable {
}
extension LabelAttributeFontModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> TextStyleLabelAttribute? {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> TextStyleLabelAttribute? {
var textStyle: TextStyle?
if let found = style?.vdsTextStyle() {
textStyle = found
} else if let name, let size, let font = UIFont(name: name, size: size) {
textStyle = TextStyle.convert(font: font)
} else if let name, let found = TextStyle(rawValue: name) {
textStyle = found
}
@ -84,7 +82,7 @@ extension LabelAttributeFontModel: VDSLabelAttributeConvertable {
}
extension LabelAttributeColorModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> ColorLabelAttribute? {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> ColorLabelAttribute? {
guard let textColor else { return nil }
return ColorLabelAttribute(location: location,
length: length,
@ -93,14 +91,14 @@ extension LabelAttributeColorModel: VDSLabelAttributeConvertable {
}
extension LabelAttributeStrikeThroughModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> StrikeThroughLabelAttribute? {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> StrikeThroughLabelAttribute? {
return StrikeThroughLabelAttribute(location: location,
length: length)
}
}
extension LabelAttributeImageModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> AtomicImageLabelAttribute? {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> AtomicImageLabelAttribute? {
var frame: CGRect?
if let size {
frame = CGRect(x: 0, y: 0, width: size, height: size)

View File

@ -1,59 +0,0 @@
//
// Breadcrumbs.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/26/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
@objcMembers open class Breadcrumbs: VDS.Breadcrumbs, VDSMoleculeViewProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: BreadcrumbsModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
open var fieldKey: String?
open var fieldValue: JSONValue?
open var groupName: String?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public convenience required init() {
self.init(frame:.zero)
}
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
isEnabled = viewModel.enabled
surface = viewModel.surface
breadcrumbModels = viewModel.breadcrumbs.compactMap { [unowned self] breadcrumb in
return .init(text: breadcrumb.text,
selected: breadcrumb.selected,
onClick: { _ in
MVMCoreUIActionHandler.performActionUnstructured(with: breadcrumb.action,
sourceModel: breadcrumb,
additionalData: self.additionalData,
delegateObject: self.delegateObject)
})
}
}
//--------------------------------------------------
// MARK: - Actions
//--------------------------------------------------
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public func updateView(_ size: CGFloat) {}
}

View File

@ -1,124 +0,0 @@
//
// BreadCrumbs.swift
// MVMCoreUI
//
// Created by Matt Bruce on 8/26/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
import MVMCore
open class BreadcrumbsModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open class var identifier: String { "breadcrumbs" }
open var moleculeName: String { Self.identifier }
open var backgroundColor: Color?
open var id: String = UUID().uuidString
open var accessibilityIdentifier: String?
open var children: [any MoleculeModelProtocol] { breadcrumbs }
open var breadcrumbs: [BreadcrumbModel] = []
open var enabled: Bool = true
open var inverted: Bool = false
open var surface: Surface { inverted ? .dark : .light }
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case accessibilityIdentifier
case moleculeName
case breadcrumbs
case enabled
case inverted
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
breadcrumbs = try container.decode([BreadcrumbModel].self, forKey: .breadcrumbs)
enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled) ?? false
inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(breadcrumbs, forKey: .breadcrumbs)
try container.encode(enabled, forKey: .enabled)
try container.encode(inverted, forKey: .inverted)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return inverted == model.inverted
&& enabled == model.enabled
}
}
open class BreadcrumbModel: MoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open class var identifier: String { "breadcrumb" }
open var moleculeName: String { Self.identifier }
open var backgroundColor: Color?
open var id: String = UUID().uuidString
open var accessibilityIdentifier: String?
open var text: String = ""
open var selected: Bool = false
open var action: ActionModelProtocol
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case accessibilityIdentifier
case text
case selected
case action
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
accessibilityIdentifier = try container.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
text = try container.decode(String.self, forKey: .text)
selected = try container.decodeIfPresent(Bool.self, forKey: .selected) ?? false
action = try container.decodeModel(codingKey: .action)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(text, forKey: .text)
try container.encode(selected, forKey: .selected)
try container.encodeModelIfPresent(action, forKey: .action)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return text == model.text
&& selected == model.selected
&& action.isEqual(to: model.action)
}
}

View File

@ -52,9 +52,6 @@ public class HeadersH1ButtonModel: HeaderModel, MoleculeModelProtocol, ParentMol
if titleLockup.subTitle?.fontStyle == nil {
titleLockup.subTitle?.fontStyle = Styler.Font.RegularTitleMedium
}
if titleLockup.title.accessibilityTraits == nil {
titleLockup.title.accessibilityTraits = [.header]
}
}
//--------------------------------------------------

View File

@ -31,9 +31,6 @@ public class HeadersH1NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
if titleLockup.subTitle?.fontStyle == nil {
titleLockup.subTitle?.fontStyle = Styler.Font.RegularTitleMedium
}
if titleLockup.title.accessibilityTraits == nil {
titleLockup.title.accessibilityTraits = [.header]
}
}
//--------------------------------------------------

View File

@ -47,15 +47,18 @@ public class HeadersH2ButtonsModel: HeaderModel, MoleculeModelProtocol, ParentMo
//--------------------------------------------------
public override func setDefaults() {
if topPadding == nil {
topPadding = Padding.Component.VerticalMarginSpacing
}
if bottomPadding == nil {
bottomPadding = Padding.Component.VerticalMarginSpacing
}
if titleLockup.title.fontStyle == nil {
titleLockup.title.fontStyle = Styler.Font.RegularTitleXLarge
}
if titleLockup.subTitle?.fontStyle == nil {
titleLockup.subTitle?.fontStyle = Styler.Font.RegularTitleMedium
}
if titleLockup.title.accessibilityTraits == nil {
titleLockup.title.accessibilityTraits = [.header]
}
super.setDefaults()
}

View File

@ -42,15 +42,18 @@ public class HeadersH2CaretLinkModel: HeaderModel, MoleculeModelProtocol, Parent
// MARK: - Methods
//--------------------------------------------------
public override func setDefaults() {
if topPadding == nil {
topPadding = Padding.Component.VerticalMarginSpacing
}
if bottomPadding == nil {
bottomPadding = Padding.Component.VerticalMarginSpacing
}
if titleLockup.title.fontStyle == nil {
titleLockup.title.fontStyle = Styler.Font.RegularTitleXLarge
}
if titleLockup.subTitle?.fontStyle == nil {
titleLockup.subTitle?.fontStyle = Styler.Font.RegularTitleMedium
}
if titleLockup.title.accessibilityTraits == nil {
titleLockup.title.accessibilityTraits = [.header]
}
super.setDefaults()
}

View File

@ -46,15 +46,18 @@ public class HeadersH2LinkModel: HeaderModel, ParentMoleculeModelProtocol {
//--------------------------------------------------
public override func setDefaults() {
if titleLockup.title.fontStyle == nil {
if topPadding == nil {
topPadding = Padding.Component.VerticalMarginSpacing
}
if bottomPadding == nil {
bottomPadding = Padding.Component.VerticalMarginSpacing
}
if titleLockup.title.fontStyle == nil {
titleLockup.title.fontStyle = Styler.Font.RegularTitleXLarge
}
if titleLockup.subTitle?.fontStyle == nil {
titleLockup.subTitle?.fontStyle = Styler.Font.RegularTitleMedium
}
if titleLockup.title.accessibilityTraits == nil {
titleLockup.title.accessibilityTraits = [.header]
}
super.setDefaults()
}

View File

@ -36,15 +36,18 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
}
public override func setDefaults() {
if topPadding == nil {
topPadding = Padding.Component.VerticalMarginSpacing
}
if bottomPadding == nil {
bottomPadding = Padding.Component.VerticalMarginSpacing
}
if titleLockup.title.fontStyle == nil {
titleLockup.title.fontStyle = Styler.Font.RegularTitleXLarge
}
if titleLockup.subTitle?.fontStyle == nil {
titleLockup.subTitle?.fontStyle = Styler.Font.RegularTitleMedium
}
if titleLockup.title.accessibilityTraits == nil {
titleLockup.title.accessibilityTraits = [.header]
}
super.setDefaults()
}

View File

@ -57,6 +57,12 @@ public class HeadersH2PricingTwoRowsModel: HeaderModel, MoleculeModelProtocol, P
// MARK: - Methods
//--------------------------------------------------
public override func setDefaults() {
if topPadding == nil {
topPadding = Padding.Component.VerticalMarginSpacing
}
if bottomPadding == nil {
bottomPadding = Padding.Component.VerticalMarginSpacing
}
if headline.accessibilityTraits == nil {
headline.accessibilityTraits = .header
}

View File

@ -47,15 +47,18 @@ public class HeadersH2TinyButtonModel: HeaderModel, MoleculeModelProtocol, Paren
//--------------------------------------------------
public override func setDefaults() {
if topPadding == nil {
topPadding = Padding.Component.VerticalMarginSpacing
}
if bottomPadding == nil {
bottomPadding = Padding.Component.VerticalMarginSpacing
}
if titleLockup.title.fontStyle == nil {
titleLockup.title.fontStyle = Styler.Font.RegularTitleXLarge
}
if titleLockup.subTitle?.fontStyle == nil {
titleLockup.subTitle?.fontStyle = Styler.Font.RegularTitleMedium
}
if titleLockup.title.accessibilityTraits == nil {
titleLockup.title.accessibilityTraits = [.header]
}
super.setDefaults()
button.style = .secondary
button.size = .small

View File

@ -83,7 +83,9 @@
func updateAccessibilityLabel() {
var message = ""
checkbox.updateAccessibilityLabel()
if let checkboxLabel = checkbox.accessibilityLabel, !checkboxLabel.isEmpty {
message += checkboxLabel + ", "
}

View File

@ -88,7 +88,9 @@
func updateAccessibilityLabel() {
var message = ""
checkbox.updateAccessibilityLabel()
if let checkboxLabel = checkbox.accessibilityLabel {
message += checkboxLabel + ", "
}

View File

@ -81,7 +81,9 @@
func updateAccessibilityLabel() {
var message = ""
radioButton.updateAccessibilityLabel()
if let radioButtonLabel = radioButton.accessibilityLabel {
message += radioButtonLabel + ", "
}

View File

@ -98,7 +98,9 @@ import UIKit
func updateAccessibilityLabel() {
var message = ""
radioButton.updateAccessibilityLabel()
if let radioButtonLabel = radioButton.accessibilityLabel {
message += radioButtonLabel + ", "
}

View File

@ -85,6 +85,7 @@ open class ListLeftVariableRadioButtonBodyText: TableViewCell {
var message = ""
radioButton.updateAccessibilityLabel()
if let radioButtonLabel = radioButton.accessibilityLabel {
message += radioButtonLabel + ", "
}

View File

@ -14,8 +14,7 @@ public class LockUpsPlanNamesModel: MoleculeModelProtocol {
//--------------------------------------------------
public static var identifier: String = "planNamesLockup"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var headline: LabelModel
public var subHeadline: LabelModel
@ -36,7 +35,6 @@ public class LockUpsPlanNamesModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case backgroundColor
case headline
case subHeadline
@ -49,7 +47,6 @@ public class LockUpsPlanNamesModel: MoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
headline = try typeContainer.decode(LabelModel.self, forKey: .headline)
subHeadline = try typeContainer.decode(LabelModel.self, forKey: .subHeadline)
@ -60,7 +57,6 @@ public class LockUpsPlanNamesModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(headline, forKey: .headline)
try container.encode(subHeadline, forKey: .subHeadline)

View File

@ -15,8 +15,7 @@ public class LockupsPlanSMLXLModel: MoleculeModelProtocol {
public static var identifier: String = "planLockup"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var planLabel : LabelModel
public var headline : LabelModel
@ -48,7 +47,6 @@ public class LockupsPlanSMLXLModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case backgroundColor
case planLabel
case headline
@ -63,7 +61,6 @@ public class LockupsPlanSMLXLModel: MoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
planLabel = try typeContainer.decode(LabelModel.self, forKey: .planLabel)
headline = try typeContainer.decode(LabelModel.self, forKey: .headline)
@ -76,7 +73,6 @@ public class LockupsPlanSMLXLModel: MoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(planLabel, forKey: .planLabel)
try container.encode(headline, forKey: .headline)

View File

@ -20,9 +20,6 @@ import VDS
// MARK: - Public Functions
//--------------------------------------------------
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
textAlignment = viewModel.textAlignment
eyebrowModel = viewModel.eyebrowModel(delegateObject: delegateObject, additionalData: additionalData)

View File

@ -18,8 +18,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
public static var identifier: String = "titleLockup"
public var moleculeName: String = TitleLockupModel.identifier
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var textAlignment: TitleLockup.TextAlignment = .left
public var eyebrow: LabelModel?
public var eyebrowColor: TitleLockup.TextColor = .primary
@ -27,6 +26,8 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
public var titleColor: TitleLockup.TitleTextColor = .primary
public var subTitle: LabelModel?
public var subTitleColor: TitleLockup.TextColor = .primary
public var alignment: VDS.TitleLockup.TextAlignment = .left
public var inverted: Bool = false
public var backgroundColor: Color?
@ -48,6 +49,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return textAlignment == model.textAlignment
&& alignment == model.alignment
&& titleColor == model.titleColor
&& subTitleColor == model.subTitleColor
&& inverted == model.inverted
@ -73,7 +75,6 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case textAlignment
case eyebrow
case eyebrowColor
@ -82,6 +83,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
case subTitle
case subTitleColor
case inverted
case alignment
}
//--------------------------------------------------
@ -91,7 +93,6 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
textAlignment = try typeContainer.decodeIfPresent(TitleLockup.TextAlignment.self, forKey: .textAlignment) ?? .left
title = try typeContainer.decodeMolecule(codingKey: .title)
eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow)
@ -127,6 +128,10 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
subTitleColor = .primary
}
if let newAlignment = try typeContainer.decodeIfPresent(VDS.TitleLockup.TextAlignment.self, forKey: .alignment) {
alignment = newAlignment
}
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
self.inverted = inverted
} else {
@ -139,7 +144,6 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(textAlignment, forKey: .textAlignment)
try container.encodeIfPresent(eyebrow, forKey: .eyebrow)
try container.encode(eyebrowColor, forKey: .eyebrowColor)
@ -147,6 +151,7 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
try container.encode(titleColor, forKey: .titleColor)
try container.encodeIfPresent(subTitle, forKey: .subTitle)
try container.encode(subTitleColor, forKey: .subTitleColor)
try container.encode(alignment, forKey: .alignment)
try container.encode(inverted, forKey: .inverted)
}

View File

@ -15,7 +15,7 @@ import Foundation
//-----------------------------------------------------
public var stack: Stack<StackModel>
public let headline = Label(fontStyle: .BoldTitleLarge)
public let headline = Label(fontStyle: .BoldTitleMedium)
public let body = Label(fontStyle: .RegularBodySmall)
//--------------------------------------------------
@ -24,7 +24,7 @@ import Foundation
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
stack = Stack<StackModel>.createStack(with: [(view: headline, model: StackItemModel(horizontalAlignment: .leading)),
(view: body, model: StackItemModel(spacing: 8, horizontalAlignment: .leading))],
(view: body, model: StackItemModel(spacing: 0, horizontalAlignment: .leading))],
axis: .vertical)
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
@ -63,7 +63,7 @@ import Foundation
open override func reset() {
super.reset()
headline.setFontStyle(.BoldTitleLarge)
headline.setFontStyle(.BoldTitleMedium)
body.setFontStyle(.RegularBodySmall)
}

Some files were not shown because too many files have changed in this diff Show More