Compare commits

...

290 Commits

Author SHA1 Message Date
Pfeil, Scott Robert
8212d7146c Merge branch 'bugfix/CXTDT-626224' into 'develop'
CXTDT-626224

### Summary
CXTDT-626224
MVA/iOS: Progress section has visual defects that need to be corrected.
Change :-  Number size should be: (font-size: 32px / line-height: 36px and “%” symbol size should be: font-size: 11px / line-height: 16px

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-626224![Screenshot_2024-10-17_at_7.15.02_PM](/uploads/e89f367a09b23c230ab4f461320fc669/Screenshot_2024-10-17_at_7.15.02_PM.png)

![Screenshot_2024-10-17_at_7.15.33_PM](/uploads/a5055585223f4ab2697d1ce79f52d43b/Screenshot_2024-10-17_at_7.15.33_PM.png)

Co-authored-by: rajani kumari  Gupta <rajani.kumari.gupta@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1204
2024-10-21 13:10:53 +00:00
rajani kumari Gupta
5c280b7922 Fix comment 2024-10-21 14:28:34 +05:30
Pfeil, Scott Robert
1eed9ebb76 Merge branch 'feature/safe_area_insets' into 'develop'
Digital ACT192 story ONEAPP-11297 - Updating insets to allow for more...

### Summary
Updating insets to allow flexibility with the safe area

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-6673

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1203
2024-10-17 15:30:33 +00:00
Scott Pfeil
a210b8da9b Remove Spacer 2024-10-17 09:34:50 -04:00
Scott Pfeil
44adc76dcb Digital ACT192 story ONEAPP-11297 - Updating insets to allow for more flexibility and edge using. Removing the status bar view because it is no longer used. 2024-10-17 09:04:31 -04:00
rajani kumari Gupta
eb4f741f1a CXTDT-626224
MVA/iOS: Progress section has visual defects that need to be corrected.
issue fixed :- Number size should be: (font-size: 32px / line-height: 36px and “%” symbol size should be: font-size: 11px / line-height: 16px
2024-10-17 17:47:24 +05:30
Bruce, Matt R
928c8c5b54 Merge branch 'bugfix/CXTDT-626309' into 'develop'
Digital ACT191 defect CXTDT-626309 - Updating fonts and spacing

### Summary
Fixes to the header component defaults.
https://docs.google.com/spreadsheets/d/1DqG_ZDxIseuVsEtRaQTVRC5mHME79Ksmhdj067jLNr4/edit?pli=1&gid=0#gid=0

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-626309
https://onejira.verizon.com/browse/CXTDT-628092

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1202
2024-10-16 16:34:13 +00:00
Scott Pfeil
2dd3a7dca8 Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui into bugfix/CXTDT-626309 2024-10-16 12:31:28 -04:00
Scott Pfeil
7bed914dca Digital ACT191 defect CXTDT-626309 - Updating fonts and spacing
Digital ACT191 defect CXTDT-628092 - Fixing accessibility trait header default.
2024-10-16 10:53:16 -04:00
Pfeil, Scott Robert
fe5bd4a9ac Merge branch 'feature/CXTDT-624895-Badge-Color-Updates' into 'develop'
refactored badge text/fill color

### Summary
Fixed bug in how fillColor is set.

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1200
2024-10-16 01:25:12 +00:00
Matt Bruce
7eb1a4ff4b redid the values
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-15 16:57:37 -05:00
Matt Bruce
76b58ec88a using type now and not the old way.
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-14 15:57:25 -05:00
Matt Bruce
4487f9b03b refactored badge text/fill color
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-14 15:53:31 -05:00
Scott Pfeil
3a06f7b43e Digital ACT191 defect CXTDT-626309 - Update to default header h2 padding 2024-10-14 11:03:24 -04:00
Hedden, Kyle Matthew
f3e97ecfd4 Merge branch 'feature/CXTDT-624895-Badge-Color-Updates' into 'develop'
VDS - iOS Badge - Include ability to select custom color for Label and Background

### Summary
CXT - Defect TrackerCXTDT-624895
VDS - iOS Badge - Include ability to select custom color for Label and Background
### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-624895

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1199
2024-10-09 13:04:36 +00:00
Matt Bruce
f66dc66818 undid comment
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-08 09:18:23 -05:00
Matt Bruce
bf2a3e7e5b removed isDark
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-08 09:10:04 -05:00
Matt Bruce
54b50bffd3 added fillColor codable
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-08 09:08:30 -05:00
Matt Bruce
3b2dc2d447 updated to use badgeModel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-08 09:08:20 -05:00
Matt Bruce
c737ca1344 added textColor
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-08 09:08:05 -05:00
Pfeil, Scott Robert
d2e841c533 Merge branch 'bugfix/FontLabelAttribute-conversion' into 'develop'
added in model conversion to textStyle

### Summary
The size was not being used in the conversion along with the name. 

So now you either pass in a Style or a Name & Size.

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1197
2024-10-04 17:57:09 +00:00
Matt Bruce
69d057c45a added in model conversion to textStyle
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-10-04 12:03:18 -05:00
Pfeil, Scott Robert
0510cded89 Merge branch 'feature/ONEAPP-11359' into 'develop'
Digital ACT192 story ONEAPP-11359: Lift the minimum supported iOS version number to 15.

### Summary
Lift the minimum supported iOS version number to 15.

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-11359

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1196
2024-10-03 21:07:26 +00:00
Hedden, Kyle Matthew
03a8a2d925 Merge branch 'bugfix/missing_accessibility_identifiers' into 'develop'
Added missing accessibility identifier

### Summary
Added missing accessibilityIdentifiers

### JIRA Ticket
Awaiting

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1195
2024-10-02 21:38:41 +00:00
Hedden, Kyle Matthew
65bf81830e Digital ACT192 story ONEAPP-11359: Lift the minimum supported iOS version number to 15. 2024-10-01 20:24:32 -04:00
Scott Pfeil
bb2fbe4bdd Added missing accessibility identifier 2024-09-27 12:13:09 -04:00
Pfeil, Scott Robert
ce038458dc Merge branch 'bugfix/atomic-vds-titleLockup-model-issue' into 'develop'
TitleLockup Model issue

### Summary
removed duplicate alignment property and updated the confluence to reference the correct textAlignment.

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1194
2024-09-27 14:40:34 +00:00
Matt Bruce
de33f8ffa1 fixed migration issue in checkboxLabel to integrate the VDS.CheckboxItem
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-09-27 09:38:36 -05:00
Matt Bruce
4d4765ccec removed duplicate alignment property and updated the confluence to reference the correct textAlignment.
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-09-27 09:03:12 -05:00
Bruce, Matt R
464e36606e Merge branch 'bugfix/MVAPCT-271' into 'develop'
Digital PCT265 defect MVAPCT-271 - Ensure the app doesn't crash on invalid color hex.

### Summary
Ensure the app doesn't crash on invalid color hex. 

### JIRA Ticket
https://onejira.verizon.com/browse/MVAPCT-271

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1192
2024-09-20 18:05:55 +00:00
Scott Pfeil
8a7c07ecf2 Digital PCT265 defect MVAPCT-271 - greater than or equal to length fix 2024-09-20 14:04:44 -04:00
Scott Pfeil
740c3eeb31 Digital PCT265 defect MVAPCT-271 - Ensure the app doesn't crash on invalid color hex. 2024-09-20 12:45:11 -04:00
Hedden, Kyle Matthew
00a11cd5dc Merge branch 'feature/atomic-vds-sept2024' into 'develop'
September Atomic/VDS Release

### Summary
Added Breadcrumbs and Pagination 

### JIRA Ticket
- https://onejira.verizon.com/browse/ONEAPP-6827
- https://onejira.verizon.com/browse/ONEAPP-6978

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1185
2024-09-20 16:29:11 +00:00
Matt Bruce
03a1bd0920 made parentalmodelmolecule
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-09-20 08:30:51 -05:00
Matt Bruce
4f6b848e72 removed inits
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-09-20 08:30:37 -05:00
Matt Bruce
5043ff35a0 removed inits
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-09-20 08:30:27 -05:00
Bruce, Matt R
806ac9821a Merge branch 'bugfix/featured_products_defects' into 'develop'
Bugfix/featured products defects

### Summary
Fix for bold when the font isn't supported, and for bg image forcing full image size.

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1191
2024-09-19 18:41:29 +00:00
Scott Pfeil
47e63e0c8b Digital ACT191 defect FeaturedProducts - Fix for bold when the font isn't supported, and for bg image forcing full image size. 2024-09-19 14:35:33 -04:00
Hedden, Kyle Matthew
2d7f8ae2ca Merge branch 'feature/MVAPCT-273' into 'develop'
MVAPCT-273

### Summary
Template Decoding Failed Error logs

### MVAPCT-273
https://onejira.verizon.com/browse/MVAPCT-273

Co-authored-by: Danish Phiroz <danish.phiroz@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1188
2024-09-19 12:27:47 +00:00
Phiroz, Danish
e708b97581 MVAPCT-273 2024-09-19 12:27:47 +00:00
Hedden, Kyle Matthew
1b5ca94988 Merge branch 'release/20_2_0' into 'develop'
release/20_2_0 hotfix merge

Co-authored-by: Pfeil, Scott Robert <scott.pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1182
2024-09-06 19:59:31 +00:00
Pfeil, Scott Robert
d7756b66b0 Merge branch 'bugfix/CXTDT-608227-2' into 'release/20_2_0'
Digital PCT032 defect CXTDT-608227: Set content compression resistance for RadioButton.

### Summary
Prevent the radio button from being crushed.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-608227

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1187
2024-08-29 17:59:42 +00:00
Hedden, Kyle Matthew
89842ee443 Digital PCT032 defect CXTDT-608227: Set content compression resistance for RadioButton. 2024-08-29 13:39:41 -04:00
Matt Bruce
61f9fb9946 enforce an action
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-27 16:38:17 -05:00
Matt Bruce
484b578568 added Pagination
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-27 14:30:39 -05:00
Matt Bruce
1c723c31de registered in core
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-26 16:13:16 -05:00
Matt Bruce
e79c1d7637 added breadcrumbs/model
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-26 16:13:05 -05:00
Bruce, Matt R
02a2da7f0a Merge branch 'bugfix/CXTDT-601365' into 'develop'
Fix for CXTDT-601365, enabling option for Icon to be accessible from JSON.

### Summary
Enabling option for Icon to be accessible from JSON.

### JIRA Ticket
[CXTDT-601365](https://onejira.verizon.com/browse/CXTDT-601365)

Co-authored-by: Sumanth Nadigadda <sumanth.nadigadda@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1183
2024-08-26 18:49:05 +00:00
Sumanth Nadigadda
22c30de62f Fix for CXTDT-601365, enabling option for Icon to be accessible from JSON. 2024-08-26 23:40:29 +05:30
Hedden, Kyle Matthew
f6f3cd53fe Merge branch 'feature/atomic-vds-new-forms-atoms' into 'develop'
Revert code

### Summary
Removed code since this was a fix in VDS

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1180
2024-08-23 17:29:19 +00:00
Matt Bruce
378c46facd removed this since it is no longer needed.
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-23 08:35:10 -05:00
Hedden, Kyle Matthew
c2e10582f8 Merge branch 'feature/atomic-vds-new-forms-atoms' into 'develop'
Last of the VDS FormFields

### Summary
Added in 
1. Checkboxes (VDS CheckboxGroup)
2. RadioButtons (VDS RadioButtonGroup)
3. CalendarView (VDS CalendarBase)
4. DatePickerEntryField (VDS DatePicker)

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7001
https://onejira.verizon.com/browse/ONEAPP-7004
https://onejira.verizon.com/browse/ONEAPP-7016
https://onejira.verizon.com/browse/ONEAPP-7958

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1176
2024-08-22 21:46:28 +00:00
Matt Bruce
8670fa3193 removed visualEquivalent
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 14:25:48 -05:00
Matt Bruce
9bbf325640 removed since this is not needed.
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 14:11:14 -05:00
Matt Bruce
59e762f52e added == and != for equality
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 14:10:44 -05:00
Matt Bruce
62c1aa6a8a removed isVisuallyEqual since this was the same as isEqual
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 14:10:26 -05:00
Matt Bruce
df1846c295 added parentMoleculeModelProtocol
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 14:09:48 -05:00
Matt Bruce
533bcd2dea added isEqual to CalendarView
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:30:13 -05:00
Matt Bruce
e211d4dc22 added isEqual to TextViewEntryFieldModel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:21:52 -05:00
Matt Bruce
f9915dfacc update func
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:20:22 -05:00
Matt Bruce
68a72f6b2e added isEqual to ItemDropDown
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:20:10 -05:00
Matt Bruce
2a497082a0 added isEqual to TextEntry
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:19:50 -05:00
Matt Bruce
961a0546a8 added isEqual to base model
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:19:37 -05:00
Matt Bruce
86d49053a6 add isEqual to DatePicker
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:12:58 -05:00
Matt Bruce
db8ba63892 added isEqual to Toggle
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:12:00 -05:00
Matt Bruce
878fd11c8a added isEqual to RadioButton variations
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:11:48 -05:00
Matt Bruce
c40dc5e396 added isEqual to RadioBox variations
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:11:35 -05:00
Matt Bruce
f94ddbf866 added isEqual to Checkbox variations
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:11:14 -05:00
Matt Bruce
b70d2f8add added isEqual
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 13:10:49 -05:00
Matt Bruce
398bf94811 removed obj-c compatibility
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 11:41:28 -05:00
Bruce, Matt R
7f9ee10c5e Merge branch 'feature/tile_cont_upd_view' into 'develop'
added updateView for molecule in tile container

Added this change as updateView for the molecule was not getting called resulting in the molecule and its children always pinned to their respective superviews.

Co-authored-by: Arun Kumar Chintakrinda <arun.kumar.chintakrinda@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1179
2024-08-22 14:25:37 +00:00
Arun Kumar Chintakrinda
6354174857 Merge remote-tracking branch 'refs/remotes/origin/develop' 2024-08-22 19:46:40 +05:30
Arun Kumar Chintakrinda
3be3b11e39 added updateView for molecule in tile container 2024-08-22 19:41:43 +05:30
Bruce, Matt R
de30c5d286 Merge branch 'bugfix/molecule_replacement_module_id_validation' into 'develop'
Digital PCT265 defect: Missing moecule.id to moduleName validation for the replacement behavior.

### Summary
Data validation bug on notification_count_fios module return.

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1178
2024-08-22 12:56:13 +00:00
Hedden, Kyle Matthew
472323f303 Digital PCT265 defect: Missing moecule.id to moduleName validation for the replacement behavior. 2024-08-21 19:59:46 -04:00
Matt Bruce
3b306a299a using enabled now
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 16:28:14 -05:00
Matt Bruce
de67fd7fb6 refactored
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 16:07:10 -05:00
Matt Bruce
60cefc57a6 pushed word change
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 15:47:27 -05:00
Matt Bruce
9185e185fd removed duplicate .
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 15:17:27 -05:00
Matt Bruce
3d5c188898 changed comment
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 15:16:26 -05:00
Matt Bruce
458f303972 fixed per comments
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:21:17 -05:00
Matt Bruce
ef08aa043e added other properties to encoder :D
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:14:01 -05:00
Matt Bruce
698f7b5520 subclass to formFieldModel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:10:23 -05:00
Matt Bruce
3270f21951 used new isEnabled in model
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:10:09 -05:00
Matt Bruce
315eac1dac used new isEnabled in model
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:09:57 -05:00
Matt Bruce
c3290f83ba updated for change in RadioBoxModel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:09:24 -05:00
Matt Bruce
c41cf3f18e converted to use FormFieldModel subclass
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:08:57 -05:00
Matt Bruce
56b4142c14 added isEnabled
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 14:07:59 -05:00
Matt Bruce
e7bcd32588 put the moleculeName in the base
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-21 13:07:51 -05:00
Bruce, Matt R
6c841c078a Merge branch 'release/20_1_1' into 'develop'
Digital PCT265 defect: VDS TabsTableViewCell delegate reset issue. Impacts UAD...

Co-authored-by: Subramaniam, Ramya <ramya.subramaniam@one.verizon.com>
Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1177
2024-08-21 13:17:32 +00:00
Matt Bruce
29b56bf63b updated field
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 15:41:53 -05:00
Matt Bruce
e030b460ce added final properties 2024-08-20 14:19:22 -05:00
Matt Bruce
25dce9e88c refactored naming of method
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 14:19:15 -05:00
Matt Bruce
7b0f4e4328 update only after the model sets the properties
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 13:59:52 -05:00
Matt Bruce
8829bd7457 added new mapping
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 13:59:27 -05:00
Matt Bruce
478f2b6083 added codable for VDS
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 13:59:19 -05:00
Matt Bruce
94ace3d49a added new models/views
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 13:59:07 -05:00
Matt Bruce
e3f22efb12 updated to convertTo
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 08:49:51 -05:00
Matt Bruce
b71a4c5b25 refactored to convertTo
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 08:49:36 -05:00
Matt Bruce
3fd10bf199 added registration
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 08:49:20 -05:00
Matt Bruce
aeabee2666 refactored to convertToXXXModel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 08:49:12 -05:00
Matt Bruce
d8c83f9230 removed delegate that isn't used anywhere.
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 08:45:38 -05:00
Matt Bruce
6b608b4a8a added showError
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 08:45:23 -05:00
Matt Bruce
13f9776a4f new Checkboxes/RadioButtons
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-20 08:45:04 -05:00
Pfeil, Scott Robert
ffd5242cde Merge branch 'bugfix/atomic-buttonGroup' into 'develop'
Fix for the reset() issue with default properties being reset for buttonGroup

### Summary
There were buttongroup properties added in the setup() method, however they were getting removed in reset(). 

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-602228

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1175
2024-08-15 17:32:31 +00:00
Matt Bruce
e89272a3cd comments on the order of operations in reset()
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-15 12:31:50 -05:00
Matt Bruce
33dc411743 Fix for the reset() issue with default properties being reset for buttonGroup
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-15 11:08:25 -05:00
Pfeil, Scott Robert
558a3d7c55 Merge branch 'feature/negate_gone' into 'develop'
Digital PCT265 story VZWYZDG-1866 - Added action for negating the gone property on molecules.

### Summary
Action for negating the gone property on goneable molecules.
https://oneconfluence.verizon.com/x/AZT2UQ

### JIRA Ticket
https://onejira.verizon.com/browse/VZWYZDG-1866

![SuccessfulTest](/uploads/ac54fd745447ef52497843af641f24e4/SuccessfulTest.mp4)

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1166
2024-08-14 18:56:12 +00:00
Scott Pfeil
17a94e2f04 Digital PCT265 story VZWYZDG-1866 - Ensure the footer stays at the bottom. 2024-08-14 12:32:50 -04:00
Scott Pfeil
4921d7d8db Digital PCT265 story VZWYZDG-1866 - Updating format per code review. 2024-08-14 11:33:40 -04:00
Subramaniam, Ramya
8c5a8776ab Merge branch 'bugfix/tabstableviewcell' into 'release/20_1_1'
Digital PCT265 defect: VDS TabsTableViewCell delegate reset issue. Impacts UAD.

### Summary
This issue is due to a change in the VDS tabs reset handling. The onTabSelect and onTabShouldSelect gets reset to nil. In the case of table cell reuse this reset gets called and our setup connection in the initializer is lost. Adding the delegate back in the model setter seems to resolve this immediate issue but we should expand out and see if there are other cases.

### JIRA Ticket
[CXTDT-601399](https://onejira.verizon.com/browse/CXTDT-601399)

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1174
2024-08-14 05:34:05 +00:00
Hedden, Kyle Matthew
3867384307 Digital PCT265 defect: VDS TabsTableViewCell delegate reset issue. Impacts UAD account tab switcher. 2024-08-13 23:25:18 -04:00
Pfeil, Scott Robert
efccb312e8 Merge branch 'release/20_1_0' into 'develop'
ensure viewModel?. is used outside of the viewModelDidUpdate method

### Summary
Hotfix

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>
Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1173
2024-08-13 20:43:30 +00:00
Hedden, Kyle Matthew
f437fbaea9 Merge branch 'hotfix/20_1-vds-update' into 'release/20_1_0'
ensure viewModel?. is used outside of the viewModelDidUpdate method

### Summary
Crash from not using optional ? for the viewModels.

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1172
2024-08-12 14:47:10 +00:00
Matt Bruce
441525a610 ensure formModel exists
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-12 09:28:40 -05:00
Matt Bruce
07351c1a22 ensure viewModel?. is used outside of the viewModelDidUpdate method
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-12 09:28:31 -05:00
Hedden, Kyle Matthew
236a1969e9 Merge branch 'bugfix/vds-update' into 'develop'
Merge of release/20_1 and bugfix

### Summary
VDS Bugfix for new Generics plus all of the release/20_1 code that hasn't been merged down into develop.

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>
Co-authored-by: Pfeil, Scott Robert <scott.pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1170
2024-08-12 13:21:53 +00:00
Matt Bruce
f96b750302 Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into test 2024-08-10 11:31:02 -05:00
Matt Bruce
715a911ee4 fixed issue with vds refactor 2024-08-10 11:30:47 -05:00
Hedden, Kyle Matthew
696b6b99d0 Merge branch 'hotfix/checkbox-showError' into 'release/20_1_0'
CXTDT-599891 - taking out showError for now

### Summary
Checkboxes have never had an error state, so until we figure out the default state, we need to turn it off. 

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-599891

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1169
2024-08-09 22:52:31 +00:00
Matt Bruce
b41d2189bf taking out showError for now
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-09 17:48:05 -05:00
Matt Bruce
8c1577bb7f Merge branch 'release/20_1_0' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui into release/20_1_0 2024-08-09 09:17:13 -05:00
Pfeil, Scott Robert
21d1e957d0 Merge branch 'bugfix/proddef_26055_2' into 'release/20_1_0'
bugfix PRODDEF-26055 Issue #2

### Summary
added missing view to getAcessibilityElements func

### JIRA Ticket
https://onejira.verizon.com/browse/PRODDEF-26055

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>
Co-authored-by: Arun Kumar Chintakrinda <arun.kumar.chintakrinda@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1168
2024-08-08 20:56:49 +00:00
Hedden, Kyle Matthew
c34a5c7661 Merge branch 'hotfix/radioButton' into 'release/20_1_0'
CXTDT-598142 - RadioButton fix

### Summary
Updated VDS RadioButton had more atomic setting than required. 

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-598142

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1167
2024-08-08 20:45:16 +00:00
Matt Bruce
d308bfc957 CXTDT-598142 - RadioButton fix
QA3||MVA||Prepay||IOS - 22053||Devices||From Add device screen

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-08 15:38:53 -05:00
Matt Bruce
92b28d767b Merge branch 'release/20_1_0' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui into release/20_1_0 2024-08-08 14:32:35 -05:00
Scott Pfeil
a691f8d713 Digital PCT265 story VZWYZDG-1866 - Update name to proper jargon. 2024-08-08 14:14:03 -04:00
Scott Pfeil
f412fd70e1 Digital PCT265 story VZWYZDG-1866 - Added action for negating the gone property on molecules. 2024-08-08 14:02:21 -04:00
Pfeil, Scott Robert
50b0280ba4 Merge branch 'feature/MVAPCT-213' into 'develop'
Digital PCT265 story MVAPCT-213 - Allow for panels to override the supported...

### Summary
Allow for panels to override the supported orientation. Force chatbot panel to be full screen width on non tablet devices.

### JIRA Ticket
https://onejira.verizon.com/browse/MVAPCT-213

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1165
2024-08-06 15:06:09 +00:00
Scott Pfeil
dfbb53f545 Digital PCT265 story MVAPCT-213 - Function name update 2024-08-06 10:31:18 -04:00
Scott Pfeil
1d2defeabb Digital PCT265 story MVAPCT-213 - Add Helper to update the orientation. 2024-08-06 09:53:59 -04:00
Hedden, Kyle Matthew
3d6d7c0cad Merge branch 'bugfix/proddef_26055_2' into 'develop'
bugfix PRODDEF-26055 Issue #2

### Summary
added missing view to getAcessibilityElements func

### JIRA Ticket
https://onejira.verizon.com/browse/PRODDEF-26055

Co-authored-by: Arun Kumar Chintakrinda <arun.kumar.chintakrinda@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1164
2024-08-06 13:38:57 +00:00
Arun Kumar Chintakrinda
2ce1b671bd added comment 2024-08-06 17:18:56 +05:30
Scott Pfeil
e13e8ca5ab Digital PCT265 story MVAPCT-213 - Allow for panels to override the supported orientation. Force chatbot panel to be full screen width on non tablet devices. 2024-08-05 13:59:59 -04:00
Arun Kumar Chintakrinda
100ede48fa added condition to check if view is available 2024-08-05 20:32:09 +05:30
Arun Kumar Chintakrinda
0d6fc3d15d bugfix PRODDEF-26055 Issue #2, added missing view to getAcessibilityElements func 2024-08-05 20:25:24 +05:30
Hedden, Kyle Matthew
5eca67fe0f Merge branch 'hotfix/checkbox-isSelected' into 'release/20_1_0'
HotFix Checkbox/CheckboxLabel

### Summary
reverted to old code so that if isSelected is set manually, the form will validate.

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1163
2024-08-02 19:49:04 +00:00
Matt Bruce
1a48113b0d Merge branch 'hotfix/checkbox-isSelected' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into release/20_1_0 2024-08-02 13:14:35 -05:00
Matt Bruce
37c6dfd6b3 optional viewModel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-02 13:13:57 -05:00
Matt Bruce
0938328ea2 Merge branch 'hotfix/checkbox-isSelected' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into release/20_1_0 2024-08-02 12:42:10 -05:00
Matt Bruce
5a3f04aa5f use super instead of var
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-02 08:53:15 -05:00
Matt Bruce
5e116e3c91 reverted code in case the isSelected is set directly
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-01 17:14:04 -05:00
Hedden, Kyle Matthew
941312dd08 Merge branch 'bugfix/dropDownSelect' into 'release/20_1_0'
DropDownSelect update

### Summary
fixed issue with EstimatedHeight and delegate callback.

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7135

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1162
2024-08-01 20:35:04 +00:00
Matt Bruce
c2cb8781d6 ensured all vds components have the estimated height implementation
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-01 15:18:33 -05:00
Matt Bruce
ab5a5a1e55 added placeholder in delegates so you can understand what is passed back
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-01 10:55:48 -05:00
Matt Bruce
3732f98d7c added override property to deal with oldvalue/newValue for the selected Item.
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-01 10:53:14 -05:00
Pfeil, Scott Robert
d69722c431 Merge branch 'bugfix/atomic-vds-selectorUpdate' into 'develop'
Updated Merged Selectors

### Summary
updated selectors for similar logic in valuechanged event 

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7001

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1159
2024-07-31 00:52:13 +00:00
Matt Bruce
9606b914cc removed code
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 17:02:17 -05:00
Matt Bruce
57bec1ecec moved formvalidation to publisher
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 17:02:09 -05:00
Matt Bruce
998ad97372 Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-checkbox 2024-07-30 16:39:59 -05:00
Matt Bruce
d199389ff3 ensured isEnabled
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 16:38:30 -05:00
Pfeil, Scott Robert
b24094ea5b Merge branch 'feature/atomic-vds-checkbox' into 'develop'
VDS Brand 3.0 Checkbox

### Summary
VDS Brand 3.0 Checkbox, CheckboxItem for IOS
- CheckboxGroup will be a later integration since this is not in the app at this time.
 
### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7001

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1152
2024-07-30 21:36:51 +00:00
Matt Bruce
ac23b0dc41 refactored checkbox action to didSet
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 16:35:46 -05:00
Matt Bruce
5a47b32789 Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-checkbox 2024-07-30 16:34:17 -05:00
Pfeil, Scott Robert
f1087527d8 Merge branch 'feature/atomic-vds-toggle' into 'develop'
VDS Brand 3.0 Toggle Integration

### Summary
VDS Components - (iOS) VDS Brand 3.0 Toggle into Atomic

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-4827

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1151
2024-07-30 21:18:56 +00:00
Matt Bruce
eb9ad769e5 Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-toggle 2024-07-30 16:14:02 -05:00
Matt Bruce
fe66380d72 refactored setting of isON on viewModelDidUpdate
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 16:13:35 -05:00
Matt Bruce
eab6fb5f3b refactored model to remove default text
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 16:13:17 -05:00
Pfeil, Scott Robert
fa650bafb5 Merge branch 'feature/atomic-vds-radioButton' into 'develop'
VDS Brand 3.0 Radiobutton

### Summary
VDS Brand 3.0 Radiobutton, RadiobuttonItem for IOS
* RadiobuttonGroup will be a later integration since this is not in the app at this time.

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7004

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1153
2024-07-30 20:07:46 +00:00
Pfeil, Scott Robert
aca65b13d3 Merge branch 'feature/atomic-vds-radiobox' into 'develop'
VDS Brand 3.0 Radiobox

### Summary
VDS Brand 3.0 Radiobox and group for IOS

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7007

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1158
2024-07-30 19:52:42 +00:00
Matt Bruce
4b06a5ba1f ensured inverted is set
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 14:48:36 -05:00
Matt Bruce
ccf520f49e Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-radiobox 2024-07-30 14:25:52 -05:00
Matt Bruce
2771808674 added inverted encoding
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 14:10:24 -05:00
Pfeil, Scott Robert
bf491e2341 Merge branch 'feature/atomic-vds-textArea' into 'develop'
VDS Brand 3.0 Text Area

### Summary
VDS Brand 3.0 Text Area for IOS

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-6682

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1155
2024-07-30 18:51:43 +00:00
Matt Bruce
3e702ec0f5 removed properties that were pushed down
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 13:44:19 -05:00
Matt Bruce
c794add84d removed duplicate struct for view masking protocol
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 13:44:03 -05:00
Matt Bruce
344b2f18aa Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-textArea 2024-07-30 13:36:09 -05:00
Pfeil, Scott Robert
d0a6bdda3d Merge branch 'feature/atomic-vds-inputField' into 'develop'
VDS Brand 3.0 Input Field

### Summary
VDS Brand 3.0 Input Field for IOS

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7963

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1156
2024-07-30 18:19:59 +00:00
Matt Bruce
68083819be change protocol to any
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 13:16:16 -05:00
Pfeil, Scott Robert
fbecc82d20 Merge branch 'feature/atomic-vds-dropDownSelect' into 'develop'
VDS Brand 3.0 Dropdown Select

### Summary
VDS Brand 3.0 Dropdown Select for IOS

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7135

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1157
2024-07-30 17:47:22 +00:00
Matt Bruce
8ec3f01e2c Merge branch 'feature/vds-form-controls' into feature/atomic-vds-textArea 2024-07-30 11:10:07 -05:00
Matt Bruce
bdaa4b5ea7 added extra check to ensure you aren't duplicating rules
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 11:09:40 -05:00
Matt Bruce
66464cebc3 comment
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 10:39:04 -05:00
Matt Bruce
b754b476a6 reverted code
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:29:07 -05:00
Matt Bruce
8215542ee7 removed method not needed
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:25:21 -05:00
Matt Bruce
192509feef remvoed inverted since this is now in subclass
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:24:05 -05:00
Matt Bruce
8a89b2b2a9 updated methods and comments
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:22:24 -05:00
Matt Bruce
35ab4c94d2 removed accessibility and rearranged methods/comments
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:17:51 -05:00
Matt Bruce
5ebdbd24f1 rearranged methods for commenting
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:15:17 -05:00
Matt Bruce
3d86b8fcf7 Merge branch 'feature/atomic-vds-toggle' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-toggle 2024-07-30 08:10:01 -05:00
Matt Bruce
62a74e8c34 removed accessibility settings to use VDS and rearranged
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:09:44 -05:00
Matt Bruce
796215f64d rearranged methods and comments
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:07:34 -05:00
Matt Bruce
694f074d08 Merge branch 'feature/atomic-vds-checkbox' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-checkbox 2024-07-30 08:03:34 -05:00
Matt Bruce
2675488ab3 removed accessibility so we can use the VDS version
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-30 08:03:26 -05:00
Matt Bruce
4ad3f7c078 Merge branch 'feature/atomic-vds-dropDownSelect' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-dropDownSelect 2024-07-29 13:21:08 -05:00
Matt Bruce
e1755a3b32 Merge branch 'feature/vds-form-controls' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-dropDownSelect 2024-07-29 13:09:31 -05:00
Bruce, Matt R
fa3c97cabe Merge branch 'bugfix/CXTDT-590886' into 'develop'
CXTDT-590886 Fix to make placeholder text visible.

### Summary
Placeholder text is getting hidden when text is empty for the first launch. Added a fix

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-590886

Co-authored-by: Nandhini Rajendran <nandhini.rajendran@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1150
2024-07-26 19:05:42 +00:00
Matt Bruce
b8d6376e86 Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-dropDownSelect 2024-07-25 19:26:29 -05:00
Nandhini Rajendran
6db3a58782 CXTDT-590886 Fix to make placeholder text visible. 2024-07-26 01:55:36 +05:30
Matt Bruce
226c23abae removed inverted
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-25 10:39:40 -05:00
Matt Bruce
3689b16339 refactored into VDS.Label extension
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-25 10:19:55 -05:00
Matt Bruce
c47d21bdf1 updated with open
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-19 16:32:46 -05:00
Matt Bruce
f82ef2e51c add open
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-19 16:12:12 -05:00
Matt Bruce
efd98cc887 removed un-needed properties
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-19 16:00:43 -05:00
Matt Bruce
c7d2dcd655 Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-checkbox 2024-07-19 14:23:22 -05:00
Matt Bruce
2888eebb69 updated validity
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-19 14:16:55 -05:00
Hedden, Kyle Matthew
38ae12e91b Merge branch 'feature/VZWYZDG-1810-UpdatedProgressUI' into 'develop'
Set the default value of duration to 0 to disable the animation by default...

Set the default value of animation duration to 0 to disable the animation by default following confluence page.

JIRA Ticket
https://onejira.verizon.com/browse/VZWYZDG-2236

Co-authored-by: Xi Zhang <xi.zhang@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1149
2024-07-17 18:41:16 +00:00
Xi Zhang
0f4a97b49c Set the default value of duration to 0 to disable the animation by default following confluence page. 2024-07-17 14:35:55 -04:00
Hedden, Kyle Matthew
7f32af62cc Merge branch 'feature/VZWYZDG-1810-UpdatedProgressUI' into 'develop'
Fix the circular progress showing issue when percent is 100%.

Fix the circular progress showing issue when percent is 100%.

Jira Ticket:
https://onejira.verizon.com/browse/VZWYZDG-2236

Co-authored-by: Xi Zhang <xi.zhang@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1148
2024-07-17 15:49:12 +00:00
Xi Zhang
3b5b03a368 Fix the showing issue when percent is 100%. 2024-07-17 10:23:50 -04:00
Bruce, Matt R
a845f70b55 Merge branch 'feature/DE307-834' into 'develop'
Digital PCT265 story DE307-834: Prevent scroll reset on reconfigure.

### Summary
Prevent carousel reset on same data.

### JIRA Ticket
https://onejira.verizon.com/browse/DE307-834

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1147
2024-07-17 12:32:44 +00:00
Hedden, Kyle Matthew
16c20507ce Digital PCT265 story DE307-834: Crash prevention on registered cells check for gone change. 2024-07-16 19:58:26 -04:00
Matt Bruce
018fe9a25e updated MDN Field to use new inputfield
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-16 16:16:39 -05:00
Matt Bruce
239af70710 fix initial updates
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-16 15:27:54 -05:00
Matt Bruce
36669b61cb used new input in mapping for now
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-16 15:27:42 -05:00
Matt Bruce
65be46c767 added inputentryfield
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-16 15:27:27 -05:00
Matt Bruce
273f45def0 updated model
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-16 15:27:14 -05:00
Hedden, Kyle Matthew
bc72d81396 Digital PCT265 story DE307-834: Prevent scroll reset on reconfigure. 2024-07-16 15:16:56 -04:00
Bruce, Matt R
c77f0c6837 Merge branch 'feature/DE307-834' into 'develop'
Digital PCT265 story DE307-834: Increase throttle time for collision safety....

### Summary
Badge isEqual implementation.

### JIRA Ticket
https://onejira.verizon.com/browse/DE307-834

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1146
2024-07-16 18:38:37 +00:00
Hedden, Kyle Matthew
46d57733e4 Digital PCT265 story DE307-834: Increase throttle time for collision safety. As a throttle this is to space the updates at regular intervals. This should also help reduce some of the page stuttering as there will be bigger update batches. 2024-07-16 14:32:48 -04:00
Hedden, Kyle Matthew
f001f98d3a Digital PCT265 story DE307-834: Add missing BadgeModel isEqual(to:). 2024-07-16 14:20:37 -04:00
Hedden, Kyle Matthew
8e5e713c72 Merge branch 'feature/VZWYZDG-1810-UpdatedProgressUI' into 'develop'
Support for Circular Progress UI Component

add support for Circular progress UI Component

Jira Ticket:
https://onejira.verizon.com/browse/VZWYZDG-2236

Co-authored-by: Xi Zhang <xi.zhang@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1145
2024-07-16 13:17:13 +00:00
Xi Zhang
29466e4e33 Move GraphSize to a higher level for common use. 2024-07-15 17:02:28 -04:00
Xi Zhang
5cbd472a2d Make diameter default value to 64 consistent with the default small size diameter value. 2024-07-15 16:04:34 -04:00
Xi Zhang
53c4bd6c46 Revise codes following MR comments 2024-07-12 16:12:54 -04:00
Matt Bruce
022355a49f Merge branch 'feature/vds-form-controls' into feature/atomic-vds-textArea 2024-07-12 15:04:59 -05:00
Matt Bruce
c8496194f0 updated FormField
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 15:04:17 -05:00
Matt Bruce
d2eae79f47 bug in rule
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 15:04:12 -05:00
Matt Bruce
1a23c97a73 updated FormField
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 15:03:46 -05:00
Matt Bruce
b5f61953df updated textViewEntry
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 14:58:05 -05:00
Matt Bruce
c8d817948b bug in rule
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 14:57:56 -05:00
Matt Bruce
80edb943b2 Merge branch 'feature/vds-form-controls' into feature/atomic-vds-textArea 2024-07-12 14:49:47 -05:00
Matt Bruce
05e2967131 add to validator
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 14:48:56 -05:00
Matt Bruce
c925323347 implemented new protocol
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 14:48:48 -05:00
Matt Bruce
716550bf23 added new rule
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 14:48:36 -05:00
Matt Bruce
1b391272c5 updated model
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 14:45:20 -05:00
Matt Bruce
fedf1760ec made public/open
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-12 09:33:21 -05:00
Xi Zhang
de60fdfaf9 Make animation duration configurable. 2024-07-11 17:22:11 -04:00
Matt Bruce
73893d36d7 Merge branch 'feature/vds-form-controls' into feature/vds-radiobox 2024-07-11 12:53:59 -05:00
Matt Bruce
cb30a58db6 Merge branch 'feature/vds-form-controls' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/vds-radioButton 2024-07-11 12:53:16 -05:00
Matt Bruce
0abc903793 turn off required
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 12:52:53 -05:00
Matt Bruce
675ba3eaff Merge branch 'feature/vds-form-controls' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/vds-checkbox 2024-07-11 12:51:55 -05:00
Matt Bruce
c7d5cac1af Merge branch 'feature/vds-form-controls' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/vds-toggle
# Conflicts:
#	MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 12:51:01 -05:00
Matt Bruce
4c28312191 updated helper text placement
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 12:47:31 -05:00
Matt Bruce
d76d7c0974 Merge branch 'feature/vds-form-controls' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/atomic-vds-dropDownSelect 2024-07-11 12:42:34 -05:00
Matt Bruce
be5a691365 added more properties
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 12:41:45 -05:00
Matt Bruce
7ec685136b added more properties
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 12:22:28 -05:00
Xi Zhang
021f4184a6 Merge branch 'develop' into feature/VZWYZDG-1810-UpdatedProgressUI 2024-07-11 13:14:08 -04:00
Xi Zhang
20d818b2c2 Refactor codes for circular progress UI. 2024-07-11 13:12:29 -04:00
Matt Bruce
bd4c7780b0 Merge branch 'feature/vds-form-controls' into feature/vds-textArea 2024-07-11 12:11:45 -05:00
Matt Bruce
cadded3217 added more enums
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 12:09:52 -05:00
Matt Bruce
c7a4bf72c3 added more propeties
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 12:00:54 -05:00
Matt Bruce
9e9a1ab853 added properties from model
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 11:23:14 -05:00
Matt Bruce
b1c59124a7 fixed bugs in validation to match textViewentryfield
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 11:20:17 -05:00
Matt Bruce
a4b550cf03 first cut of textEntry
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-11 10:19:01 -05:00
Matt Bruce
0ab9ad37a8 added extension
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-10 14:51:49 -05:00
Matt Bruce
797d044913 added inverted
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-10 14:51:36 -05:00
Matt Bruce
79d5529991 added delegates
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-10 14:51:10 -05:00
Matt Bruce
981bdac292 refactored to use VDS Dropdown Select
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-10 14:37:04 -05:00
Matt Bruce
8bf4a28b0f added extension
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-10 14:36:47 -05:00
Matt Bruce
144efea342 added inverted
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-10 12:54:50 -05:00
Matt Bruce
4eba94f413 refactored to use formfieldmodel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-10 12:05:07 -05:00
Xi Zhang
1b0197ed2c Add support for circular progress bar part 2 2024-07-09 18:49:10 -04:00
Pfeil, Scott Robert
82fe0e13de Merge branch 'bugfix/CXTDT-579050' into 'develop'
Digital PCT265 defect CXTDT-579049: Fix indexing safety to mocules to add....

### Summary
A new attempt at synchronizing data + UI updates.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-579050

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1142
2024-07-09 18:40:06 +00:00
Matt Bruce
8c5840fba3 views updated for model changes
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-09 10:23:05 -05:00
Matt Bruce
8a12bf12c2 refactored models
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-09 10:21:55 -05:00
Hedden, Kyle Matthew
6e88ae0b62 Merge branch 'release/20_0_3' into 'develop'
release/20_0_3 hotfix merge


See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1139
2024-07-09 14:53:22 +00:00
Xi Zhang
7fec6f540e Add support for circular progress bar. 2024-07-05 15:10:34 -04:00
Hedden, Kyle Matthew
cae92360a2 Digital PCT265 defect CXTDT-579050: Carousel cell registration check break early. 2024-07-03 18:45:29 -04:00
Hedden, Kyle Matthew
b2ad684f00 Digital PCT265 defect CXTDT-579050: Carousel cell registration check. 2024-07-03 18:40:43 -04:00
Hedden, Kyle Matthew
90f9a0bcf5 Digital PCT265 defect CXTDT-579050: Code simplification and fixes of combine pipeline for page updates. 2024-07-03 18:39:28 -04:00
Matt Bruce
04f228ea48 refactored to remove old properties
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-03 15:58:35 -05:00
Matt Bruce
724130a827 converted to vds
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-03 15:58:22 -05:00
Matt Bruce
10c55b12be converted to VDS
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-03 15:58:15 -05:00
Matt Bruce
fa52fa8c12 added protocol/refactored to deal with RadiobuttonLabel
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-03 15:58:01 -05:00
Matt Bruce
fcd4f4e0ec refactored name to subTitle
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-03 15:57:22 -05:00
Hedden, Kyle Matthew
4cbf15a3a7 Digital PCT265 defect CXTDT-579050: Change batching to the UI. 2024-07-03 15:42:12 -04:00
Matt Bruce
1d048f9389 Merge branch 'feature/vds-form-controls' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui.git into feature/vds-checkbox 2024-07-03 11:43:42 -05:00
Matt Bruce
aa773dc8f5 added base FormField model class
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-03 11:42:08 -05:00
Pfeil, Scott Robert
fdab57200d Merge branch 'bugfix/tilecontainer-refactor' into 'develop'
fixes for VDS TileContainer update

### Summary
TileContainer/Tilelet for VDS still hasn't been fully VDS Tested, however the automatic fill is in fact a part of the requirement. This was resolved as well as the height/width issue.

This change in Atomic is to default to leading for Tilelet so it won't automatically be pinned to all edges. These components shouldn't be pinned to the trailing/right or bottom if they are to be resized by ratio or having width/height values.

Also by default, the Tilelet's aspectRatio should be set to .none

### JIRA
https://onejira.verizon.com/browse/VZWYZDG-2117

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1141
2024-07-03 14:44:37 +00:00
Matt Bruce
a84f502a0e updated checkbox/label
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-03 09:37:00 -05:00
Hedden, Kyle Matthew
3cdd3a95a0 Merge branch 'bugfix/CXTDT-578423' into 'develop'
Digital ACT191 defect CXTDT-578423 - Fix to missing navigation line when the...

### Summary
The hide navigation bar line logic has been updated to include if the sub navigation is manually hidden.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-578423

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1143
2024-07-03 13:46:04 +00:00
Scott Pfeil
9e712aa213 Digital ACT191 defect CXTDT-578423 - Fix to missing navigation line when the subnav tabs are manually hidden. 2024-07-03 09:33:57 -04:00
Hedden, Kyle Matthew
d8d4b37d1d Digital PCT265 defect CXTDT-579050: Rewire asynchronous response JSON parsing to filter, queue, and delay drop UI changes. 2024-07-02 19:05:53 -04:00
Matt Bruce
568ab2e5e5 added surface
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-02 10:56:40 -05:00
Matt Bruce
847daee32f - refactored more code
- CheckboxLabel to VDS.CheckboxItem

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-01 14:55:25 -05:00
Matt Bruce
dee11d7732 aspectRatio set to .none
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-01 11:13:18 -05:00
Matt Bruce
f3efb91fee remove horizontalAlignment
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-07-01 11:12:57 -05:00
Matt Bruce
7357a199d2 fixes for VDS TileContainer update
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-06-29 09:48:18 -05:00
Hedden, Kyle Matthew
12bbe5d9a2 Merge branch 'bugfix/CXTDT-579050' into 'release/20_0_3'
Digital PCT265 defect CXTDT-579050: Prevent updateViews from triggering...

### Summary
UAD thread state crash fix.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-579050

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1140
2024-06-28 20:02:36 +00:00
Hedden, Kyle Matthew
3092804f7c Merge branch 'bugfix/CXTDT-579049' into 'release/20_0_3'
Digital PCT265 defect CXTDT-579049: Index out of bounds crash fixes.

### Summary
Array indexing safety for mid property updates.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-579049

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1138
2024-06-28 19:58:59 +00:00
Matt Bruce
11a023d92a initial cut for the checkbox
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-06-28 14:57:15 -05:00
Matt Bruce
04e11ee1fb a few updates
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-06-28 14:54:47 -05:00
Hedden, Kyle Matthew
91eb4fa87a Digital PCT265 defect CXTDT-579050: Prevent updateViews from triggering tableView(_:cellForRowAt:) through visibleCells. 2024-06-28 15:23:17 -04:00
Hedden, Kyle Matthew
080f77581f Digital PCT265 defect CXTDT-579049: Fix indexing safety to mocules to add. (Another crash in App Store reports.) 2024-06-28 09:18:13 -04:00
Hedden, Kyle Matthew
525f0f8f0a Digital PCT265 defect CXTDT-579049: Add indexing safety to track action call. 2024-06-28 09:13:49 -04:00
Matt Bruce
66255d9dc5 implemented initial vds toggle
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-06-27 15:49:06 -05:00
Pfeil, Scott Robert
eb78d507d6 Merge branch 'release/20_0_0' into 'develop'
release/20_0_0 hotfix merge

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>
Co-authored-by: Matt Bruce <matt.bruce@verizon.com>
Co-authored-by: Subramaniam, Ramya <ramya.subramaniam@one.verizon.com>
Co-authored-by: Bruce, Matt R <matt.bruce@one.verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1128
2024-06-26 14:42:50 +00:00
Hedden, Kyle Matthew
3cdd74f097 Merge branch 'hotfix/vds-label-actions' into 'release/20_0_0'
refactored out code that is now in VDS.Label

### Summary
Code refactored down in to VDS Label, therefor this needed to be removed. 

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-576006

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1136
2024-06-21 19:18:31 +00:00
Matt Bruce
3108dcdafc refactored out code that is now in VDS.Label
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-06-21 14:11:20 -05:00
174 changed files with 4343 additions and 2603 deletions

View File

@ -153,6 +153,9 @@
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 */; };
@ -304,6 +307,9 @@
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 */; };
@ -578,13 +584,28 @@
EA17584A2BC97EF100A5C0D9 /* BadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1758492BC97EF100A5C0D9 /* BadgeIndicatorModel.swift */; };
EA17584C2BC9894800A5C0D9 /* ButtonIconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */; };
EA17584E2BC9895A00A5C0D9 /* ButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */; };
EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */; };
EA1B02E02C470AFD00F0758B /* InputEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */; };
EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; };
EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; };
EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; };
EA5DBDAB2C35B6C500290DF8 /* FormFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */; };
EA6642912BCDA97300D81DC4 /* TileContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642902BCDA97300D81DC4 /* TileContainer.swift */; };
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 */; };
@ -770,6 +791,9 @@
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>"; };
@ -922,6 +946,9 @@
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>"; };
@ -1198,13 +1225,28 @@
EA1758492BC97EF100A5C0D9 /* BadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeIndicatorModel.swift; sourceTree = "<group>"; };
EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconModel.swift; sourceTree = "<group>"; };
EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIcon.swift; sourceTree = "<group>"; };
EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleVDSModel.swift; sourceTree = "<group>"; };
EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputEntryField.swift; sourceTree = "<group>"; };
EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = "<group>"; };
EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = "<group>"; };
EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = "<group>"; };
EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormFieldModel.swift; sourceTree = "<group>"; };
EA6642902BCDA97300D81DC4 /* TileContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainer.swift; sourceTree = "<group>"; };
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>"; };
@ -1275,6 +1317,7 @@
27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */,
27577DCC286CA959001EC47E /* MoleculeMaskingProtocol.swift */,
58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */,
AFB6336D2C65166E00791221 /* GoneableProtocol.swift */,
);
path = ModelProtocols;
sourceTree = "<group>";
@ -1294,6 +1337,7 @@
011D95A0240453D0000E3791 /* RuleEqualsModel.swift */,
0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */,
FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */,
EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */,
);
name = Rules;
path = Rules/Rules;
@ -1585,6 +1629,8 @@
AF1C33722885D481006B1001 /* MVMCoreUIActionOpenPageHandler.swift */,
AF60A7F52892D2E300919EEB /* ActionDismissNotificationModel.swift */,
AF60A7F72892D34D00919EEB /* ActionDismissNotificationHandler.swift */,
AFB633712C653C0900791221 /* ActionUpdateVisibilityModel.swift */,
AFB6336F2C65175800791221 /* ActionUpdateVisibility.swift */,
);
path = Actions;
sourceTree = "<group>";
@ -2009,8 +2055,12 @@
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 */,
@ -2161,6 +2211,7 @@
D29DF10E21E67A77003B2FB9 /* Molecules */ = {
isa = PBXGroup;
children = (
EA7AE55A2C7D188900107C74 /* Breadcrumbs */,
D2EC7BD22527A1E400F540AF /* HeadersAndFooters */,
D2CAC7C9251104CB00C75681 /* TopNotification */,
D2509ED42472EE0B001BFB9D /* NavigationBar */,
@ -2309,8 +2360,11 @@
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 */,
@ -2319,6 +2373,8 @@
D20492A524329CE200A5EED6 /* LoadImageView.swift */,
0A51F3E02475CB73002E08B6 /* LoadingSpinnerModel.swift */,
0A51F3E12475CB73002E08B6 /* LoadingSpinner.swift */,
EA7AE55F2C7E554700107C74 /* PaginationModel.swift */,
EA7AE5612C7E555D00107C74 /* Pagination.swift */,
AA37CBD2251907200027344C /* StarsModel.swift */,
AA37CBD42519072F0027344C /* Stars.swift */,
AA07EA902510A442009A2AE3 /* StarModel.swift */,
@ -2337,6 +2393,8 @@
EA7D815F2B2B6E6800D29F9E /* Icon.swift */,
EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */,
EA7D81652B2BABD200D29F9E /* Tooltip.swift */,
EA7AE5502C74EB4500107C74 /* CalendarViewModel.swift */,
EA7AE54E2C74EB3700107C74 /* CalendarView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -2346,6 +2404,7 @@
children = (
0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */,
0A21DB7E235DECC500C160A2 /* EntryField.swift */,
EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */,
0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */,
0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */,
0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */,
@ -2487,6 +2546,9 @@
D2BEFEF5248A954C00FAB3A9 /* FormFields */ = {
isa = PBXGroup;
children = (
EA7AE5542C74F20600107C74 /* DatePickerEntryFieldModel.swift */,
EA7AE5522C74F1F600107C74 /* DatePickerEntryField.swift */,
EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */,
D2BEFEF6248A957A00FAB3A9 /* Tags */,
D29DF22B21E6A0FA003B2FB9 /* TextFields */,
);
@ -2590,6 +2652,15 @@
path = Alerts;
sourceTree = "<group>";
};
EA7AE55A2C7D188900107C74 /* Breadcrumbs */ = {
isa = PBXGroup;
children = (
EA7AE55B2C7D18A100107C74 /* BreadcrumbsModel.swift */,
EA7AE55D2C7D234500107C74 /* Breadcrumbs.swift */,
);
path = Breadcrumbs;
sourceTree = "<group>";
};
EAA0CFAD275E7D5A00D65EB0 /* FormFieldEffect */ = {
isa = PBXGroup;
children = (
@ -2821,11 +2892,13 @@
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 */,
@ -2833,6 +2906,8 @@
D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */,
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 */,
@ -2874,6 +2949,7 @@
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 */,
@ -2887,6 +2963,7 @@
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 */,
@ -2912,8 +2989,10 @@
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 */,
@ -2940,6 +3019,7 @@
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 */,
@ -3005,6 +3085,7 @@
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 */,
@ -3025,6 +3106,7 @@
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 */,
@ -3076,7 +3158,9 @@
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 */,
@ -3101,6 +3185,7 @@
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 */,
@ -3133,6 +3218,7 @@
323AC96A24C837F000F8E4C4 /* ListThreeColumnBillChangesModel.swift in Sources */,
D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */,
525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */,
EA1B02E02C470AFD00F0758B /* InputEntryField.swift in Sources */,
D28BA730247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift in Sources */,
0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */,
D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */,
@ -3153,6 +3239,7 @@
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 */,
@ -3230,6 +3317,7 @@
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 */,
@ -3260,6 +3348,7 @@
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 */,
@ -3280,6 +3369,7 @@
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 */,
@ -3292,6 +3382,7 @@
D28BA7432480284E00B75CB8 /* TabBar.swift in Sources */,
AA3561AE24C96B9000452EB1 /* ListRightVariableRightCaretAllTextAndLinks.swift in Sources */,
AA26850C244840AE00CE34CC /* HeadersH2TinyButton.swift in Sources */,
EA5DBDAB2C35B6C500290DF8 /* FormFieldModel.swift in Sources */,
011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */,
D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */,
0A25209824645B76000FA9F6 /* TextViewEntryFieldModel.swift in Sources */,
@ -3319,6 +3410,7 @@
D2169301251E51E7002A6324 /* SectionListTemplate.swift in Sources */,
0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */,
D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */,
EA7AE54F2C74EB3700107C74 /* CalendarView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3533,7 +3625,7 @@
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks";
INFOPLIST_FILE = MVMCoreUI/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -3563,7 +3655,7 @@
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks";
INFOPLIST_FILE = MVMCoreUI/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

View File

@ -0,0 +1,47 @@
//
// 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

@ -0,0 +1,42 @@
//
// 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,7 +38,10 @@ open class ButtonGroup: VDS.ButtonGroup, VDSMoleculeViewProtocol {
return PillButton.estimatedHeight(with: buttonModel, delegateObject)
}
public func viewModelDidUpdate() {
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
isEnabled = viewModel.enabled
alignment = viewModel.alignment

View File

@ -7,6 +7,7 @@
//
import Foundation
import MVMCore
import VDS
public class ButtonGroupModel: ParentMoleculeModelProtocol {
@ -17,6 +18,7 @@ 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 }
@ -38,6 +40,7 @@ public class ButtonGroupModel: ParentMoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case backgroundColor
case buttons
case alignment
@ -56,6 +59,7 @@ 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)
@ -70,6 +74,7 @@ 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,7 +15,8 @@ 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?
@ -45,6 +46,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case image
case backgroundColor
case accessibilityText
@ -64,6 +66,7 @@ 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)
@ -91,6 +94,7 @@ 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

@ -0,0 +1,51 @@
//
// 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

@ -0,0 +1,121 @@
//
// 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

@ -0,0 +1,159 @@
//
// FormFieldModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 7/3/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
@objcMembers open class FormFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String { "" }
public var moleculeName: String { Self.identifier }
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 errorMessage: String?
public var initialErrorMessage: String?
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var inverted: Bool = false
public var surface: Surface { inverted ? .dark : .light }
public var dynamicErrorMessage: String? {
didSet {
isValid = dynamicErrorMessage?.isEmpty ?? true
updateUIDynamicError?()
}
}
public var isValid: Bool? = true {
didSet { updateUI?() }
}
/// Temporary binding mechanism for the view to update on enable changes.
public var updateUI: ActionBlock?
// TODO: Remove once updateUI is fixed with isSelected
public var updateUIDynamicError: ActionBlock?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init() {}
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case errorMessage
case showError
case enabled
case readOnly
case required
case fieldKey
case groupName
case inverted
}
//--------------------------------------------------
// MARK: - Validation Methods
//--------------------------------------------------
open func formFieldValue() -> AnyHashable? {
fatalError("developer must implement")
}
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
public func setValidity(_ valid: Bool, errorMessage: String?) {
if let ruleErrorMessage = errorMessage, fieldKey != nil {
self.errorMessage = ruleErrorMessage
} else {
self.errorMessage = initialErrorMessage
}
isValid = valid
updateUI?()
}
//--------------------------------------------------
// MARK: - Codable
//--------------------------------------------------
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)
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
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) ?? FormValidator.defaultGroupName
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
self.inverted = inverted
}
}
open 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.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,13 +12,15 @@ 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
@ -42,6 +44,7 @@ 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)
@ -50,6 +53,7 @@ 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,12 +12,14 @@ 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
@ -34,6 +36,7 @@ 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)
}
@ -41,6 +44,7 @@ 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

@ -345,9 +345,8 @@ import UIKit
numberOfDigits = model.digits
if let entryType = model.type {
setAsSecureTextEntry(entryType == .secure || entryType == .password)
}
let entryType = model.type
setAsSecureTextEntry(entryType == .secure || entryType == .password)
let observingDelegate = delegateObject?.observingTextFieldDelegate ?? self

View File

@ -7,19 +7,79 @@
//
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]?
open class ItemDropdownEntryField: BaseItemPickerEntryField {
// 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)
}
}
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var isValid: Bool = true
/// Closure passed here will run as picker changes items.
public var observeDropdownChange: ((_ oldValue: String?, _ newValue: String) -> ())?
open var pickerData: [String] = []
/// Closure passed here will run upon dismissing the selection picker.
public var observeDropdownSelection: ((_ newValue: String) -> ())?
public var itemDropdownEntryFieldModel: ItemDropdownEntryFieldModel? {
model as? ItemDropdownEntryFieldModel
/// 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 }
}
@objc public func dismissFieldInput(_ sender: Any?) {
_ = resignFirstResponder()
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -28,7 +88,7 @@ open class ItemDropdownEntryField: BaseItemPickerEntryField {
super.init(frame: frame)
}
@objc public convenience init() {
@objc public convenience required init() {
self.init(frame: .zero)
}
@ -40,76 +100,133 @@ open class ItemDropdownEntryField: BaseItemPickerEntryField {
@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.
@objc private func setInitialValueFromPicker() {
private func setInitialValueFromPicker() {
guard !pickerData.isEmpty else { return }
if setInitialValueInTextField {
let pickerIndex = pickerView.selectedRow(inComponent: 0)
itemDropdownEntryFieldModel?.selectedIndex = pickerIndex
observeDropdownChange?(text, pickerData[pickerIndex])
text = pickerData[pickerIndex]
let pickerIndex = optionsPicker.selectedRow(inComponent: 0)
viewModel?.selectedIndex = pickerIndex
selectId = pickerIndex
}
}
@objc override func startEditing() {
super.startEditing()
setInitialValueFromPicker()
private func performDropdownAction() {
guard let actionModel = viewModel.action,
!dropdownField.isFirstResponder
else { return }
MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel, additionalData: additionalData, delegateObject: delegateObject)
}
@objc override func endInputing() {
super.endInputing()
private func updateValidation(_ isValid: Bool) {
let previousValidity = self.isValid
self.isValid = isValid
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)
if previousValidity && !isValid {
showError = true
} else if (!previousValidity && isValid) {
showError = false
}
}
//--------------------------------------------------
// 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,16 +5,19 @@
// Created by Kevin Christiano on 1/22/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
@objcMembers open class ItemDropdownEntryFieldModel: BaseItemPickerEntryFieldModel {
@objcMembers open class ItemDropdownEntryFieldModel: TextEntryFieldModel {
//--------------------------------------------------
// 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
@ -42,6 +45,9 @@
private enum CodingKeys: String, CodingKey {
case options
case selectedIndex
case action
case showInlineLabel
case feedbackTextPlacement
}
//--------------------------------------------------
@ -58,6 +64,9 @@
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 {
@ -65,5 +74,17 @@
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,7 +315,9 @@ import UIKit
self.showError = false
}
self.isEnabled = model.enabled
self.text = model.text
if let text = model.text, !text.isEmpty {
self.text = model.text
}
})
}

View File

@ -9,79 +9,38 @@
import Foundation
@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol, ClearableModelProtocol {
@objcMembers open class EntryFieldModel: FormFieldModel, ClearableModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public class var identifier: String { "" }
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var shouldClearText: Bool = false
public var dynamicErrorMessage: String? {
didSet {
isValid = dynamicErrorMessage?.isEmpty ?? true
updateUIDynamicError?()
}
}
public var errorMessage: String?
public var errorTextColor: Color?
public var enabled: Bool = true
public var required: Bool = true
public var readOnly: Bool = false
public var showError: Bool?
public var hideBorders = false
public var locked: Bool?
public var selected: Bool?
public var text: String?
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var wasInitiallySelected: Bool = false
public var text: String?
public var title: String?
public var feedback: String?
public var shouldMaskRecordedView: Bool? = true
//used to drive the EntryFieldView UI
public var titleStateLabel: FormLabelModel
public var feedbackStateLabel: FormLabelModel
public var isValid: Bool? = true {
didSet { updateUI?() }
}
/// Temporary binding mechanism for the view to update on enable changes.
public var updateUI: ActionBlock?
// TODO: Remove once updateUI is fixed with isSelected
public var updateUIDynamicError: ActionBlock?
public var titleStateLabel = FormLabelModel(text: "")
public var feedbackStateLabel = FormLabelModel(text: "")
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case backgroundColor
case accessibilityIdentifier
case title
case enabled
case readOnly
case feedback
case errorMessage
case errorTextColor
case locked
case selected
case showError
case hideBorders
case text
case fieldKey
case groupName
case required
case shouldMaskRecordedView
}
@ -92,7 +51,7 @@ import Foundation
// MARK: - Validation Methods
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
open override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
if dynamicErrorMessage != nil {
@ -100,30 +59,15 @@ import Foundation
}
return text
}
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
public func setValidity(_ valid: Bool, errorMessage: String?) {
if let ruleErrorMessage = errorMessage, fieldKey != nil {
self.errorMessage = ruleErrorMessage
}
self.isValid = valid
updateUI?()
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public init(with text: String) {
super.init()
self.text = text
baseValue = text
self.titleStateLabel = FormLabelModel(text: "")
self.feedbackStateLabel = FormLabelModel(text: "")
setDefaults()
}
@ -131,7 +75,7 @@ import Foundation
// MARK: - Initializers
//--------------------------------------------------
public func clear() {
self.text = ""
text = ""
}
//--------------------------------------------------
@ -139,55 +83,46 @@ import Foundation
//--------------------------------------------------
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
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
title = try typeContainer.decodeIfPresent(String.self, forKey: .title)
feedback = try typeContainer.decodeIfPresent(String.self, forKey: .feedback)
errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage)
errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor)
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
locked = try typeContainer.decodeIfPresent(Bool.self, forKey: .locked)
selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected)
text = try typeContainer.decodeIfPresent(String.self, forKey: .text)
hideBorders = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideBorders) ?? false
baseValue = text
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? shouldMaskRecordedView
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
self.titleStateLabel = FormLabelModel(text: title ?? "")
self.feedbackStateLabel = FormLabelModel(model: LabelModel(text: feedback ?? "",
titleStateLabel = FormLabelModel(text: title ?? "")
feedbackStateLabel = FormLabelModel(model: LabelModel(text: feedback ?? "",
fontStyle: FormLabelModel.defaultFontStyle,
textColor: Color(uiColor: .mvmCoolGray6)))
setDefaults()
}
public func encode(to encoder: Encoder) throws {
open override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(title, forKey: .title)
try container.encodeIfPresent(feedback, forKey: .feedback)
try container.encodeIfPresent(text, forKey: .text)
try container.encodeIfPresent(locked, forKey: .locked)
try container.encodeIfPresent(showError, forKey: .showError)
try container.encodeIfPresent(selected, forKey: .selected)
try container.encodeIfPresent(errorTextColor, forKey: .errorTextColor)
try container.encodeIfPresent(errorMessage, forKey: .errorMessage)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(readOnly, forKey: .readOnly)
try container.encode(enabled, forKey: .enabled)
try container.encode(required, forKey: .required)
try container.encode(hideBorders, forKey: .hideBorders)
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

@ -0,0 +1,347 @@
//
// InputEntryField.swift
// MVMCoreUI
//
// Created by Matt Bruce on 7/16/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import VDS
@objcMembers open class InputEntryField: VDS.InputField, VDSMoleculeViewProtocol, ObservingTextFieldDelegate, ViewMaskingProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: TextEntryFieldModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
//--------------------------------------------------
// MARK: - Stored Properties
//--------------------------------------------------
public var isValid: Bool = true
/// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well.
private weak var proprietorTextDelegate: UITextFieldDelegate?
private var isEditting: Bool = false {
didSet {
viewModel?.selected = isEditting
}
}
//--------------------------------------------------
// MARK: - Stored Properties
//--------------------------------------------------
private var observingForChange: Bool = false
/// Validate when user resigns editing. Default: true
open var validateWhenDoneEditing: Bool = true
open var shouldMaskWhileRecording: Bool {
return viewModel?.shouldMaskRecordedView ?? false
}
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
/// The text of this TextField.
open override var text: String? {
didSet {
viewModel?.text = text
}
}
open override var errorText: String? {
get {
viewModel.dynamicErrorMessage ?? viewModel.errorMessage
}
set {}
}
/// Placeholder access for the TextField.
public var placeholder: String? {
get { textField.placeholder }
set { textField.placeholder = newValue }
}
//--------------------------------------------------
// 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 { textField.delegate }
set {
textField.delegate = self
proprietorTextDelegate = newValue
}
}
//--------------------------------------------------
// 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)
if (viewModel.type == .email) {
// remove spaces (either user entered Or auto-correct suggestion) for the email field
text = textField.text?.replacingOccurrences(of: " ", with: "")
}
}.store(in: &subscribers)
textField
.publisher(for: .editingDidBegin)
.sink { [weak self] textView in
guard let self else { return }
isEditting = true
if let viewModel, viewModel.clearTextOnTap {
text = ""
}
}.store(in: &subscribers)
textField
.publisher(for: .editingDidEnd)
.sink { [weak self] textView in
guard let self else { return }
isEditting = false
if let viewModel, validateWhenDoneEditing, let valid = viewModel.isValid {
updateValidation(valid)
}
regexTextFieldOutputIfAvailable()
}.store(in: &subscribers)
}
open override func updateView() {
super.updateView()
if let viewModel {
switch viewModel.type {
case .secure:
textField.isSecureTextEntry = true
textField.shouldMaskWhileRecording = true
case .numberSecure:
textField.isSecureTextEntry = true
textField.shouldMaskWhileRecording = true
textField.keyboardType = .numberPad
case .email:
textField.keyboardType = .emailAddress
case .securityCode, .creditCard, .password:
textField.shouldMaskWhileRecording = true
default:
break;
}
// Override the preset keyboard set in type.
if let keyboardType = viewModel.assignKeyboardType() {
textField.keyboardType = keyboardType
}
}
}
open func viewModelDidUpdate() {
fieldType = viewModel.type.toVDSFieldType()
text = viewModel.text
placeholder = viewModel.placeholder
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
containerView.accessibilityIdentifier = model.accessibilityIdentifier
textField.textAlignment = viewModel.textAlignment
textField.enableClipboardActions = viewModel.enableClipboardActions
textField.placeholder = viewModel.placeholder ?? ""
uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected {
viewModel.wasInitiallySelected = true
isEditting = true
}
viewModel.rules = rules
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 {
text = ""
viewModel.shouldClearText = false
}
updateValidation(validState)
})
}
//Added to override text when view is reloaded.
if let text = viewModel.text, !text.isEmpty {
regexTextFieldOutputIfAvailable()
}
}
//--------------------------------------------------
// MARK: - Observing for Change (TextFieldDelegate)
//--------------------------------------------------
@objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) {
observingTextFieldDelegate = delegate
uiTextFieldDelegate = delegate
}
func regexTextFieldOutputIfAvailable() {
if let regex = viewModel?.displayFormat,
let mask = viewModel?.displayMask,
let finalText = text {
let range = NSRange(finalText.startIndex..., in: finalText)
if let regex = try? NSRegularExpression(pattern: regex) {
let maskedText = regex.stringByReplacingMatches(in: finalText,
range: range,
withTemplate: mask)
textField.text = maskedText
}
}
}
@objc public func dismissFieldInput(_ sender: Any?) {
_ = resignFirstResponder()
}
private func updateValidation(_ isValid: Bool) {
let previousValidity = self.isValid
self.isValid = isValid
if previousValidity && !isValid {
showError = true
observingTextFieldDelegate?.isValid?(textfield: self)
} else if (!previousValidity && isValid) {
showError = false
observingTextFieldDelegate?.isInvalid?(textfield: self)
}
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
@objc open func updateView(_ size: CGFloat) {}
}
extension InputEntryField {
//--------------------------------------------------
// MARK: - Implemented TextField Delegate
//--------------------------------------------------
@discardableResult
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true
}
@objc public override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string)
??
super.textField(textField, shouldChangeCharactersIn: range, replacementString: string)
}
@objc public override func textFieldDidBeginEditing(_ textField: UITextField) {
proprietorTextDelegate?.textFieldDidBeginEditing?(textField) ?? super.textFieldDidBeginEditing(textField)
}
@objc public override func textFieldDidEndEditing(_ textField: UITextField) {
proprietorTextDelegate?.textFieldDidEndEditing?(textField) ?? super.textFieldDidEndEditing(textField)
}
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
}
@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
}
@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
}
}
// MARK: - Accessibility
extension InputEntryField {
@objc open func pushAccessibilityNotification() {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
UIAccessibility.post(notification: .layoutChanged, argument: containerView)
}
}
}
internal struct ViewMasking {
static var shouldMaskWhileRecording: UInt8 = 0
}
extension VDS.TextField: ViewMaskingProtocol {
public var shouldMaskWhileRecording: Bool {
get {
return (objc_getAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording) as? Bool) ?? false
}
set {
objc_setAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

View File

@ -14,7 +14,7 @@ import MVMCore
/**
This class provides the convenience of formatting the MDN entered/displayer for the user.
*/
@objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate {
@objcMembers open class MdnEntryField: InputEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate {
//--------------------------------------------------
// MARK: - Stored Properties
//--------------------------------------------------
@ -47,52 +47,17 @@ import MVMCore
get { MVMCoreUIUtility.removeMdnFormat(text) }
set { text = MVMCoreUIUtility.formatMdn(newValue) }
}
/// Toggles selected or original (unselected) UI.
public override var isSelected: Bool {
get { return entryFieldContainer.isSelected }
set (selected) {
if selected && showError {
showError = false
}
super.isSelected = selected
}
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@objc public override init(frame: CGRect) {
super.init(frame: .zero)
}
@objc public convenience init() {
self.init(frame: .zero)
}
@objc required public init?(coder: NSCoder) {
super.init(coder: coder)
fatalError("MdnEntryField xib not supported.")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
@objc public override func setupFieldContainerContent(_ container: UIView) {
super.setupFieldContainerContent(container)
textField.keyboardType = .numberPad
open override func setup() {
super.setup()
setupTextFieldToolbar()
}
open override func setupTextFieldToolbar() {
open func setupTextFieldToolbar() {
let toolbar = UIToolbar.createEmptyToolbar()
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let contacts = UIBarButtonItem(title: MVMCoreUIUtility.hardcodedString(withKey: "textfield_contacts_barbutton"), style: .plain, target: self, action: #selector(getContacts))
@ -103,40 +68,7 @@ import MVMCore
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
@objc public func hasValidMDN() -> Bool {
guard let MDN = mdn, !MDN.isEmpty else { return false }
if isNationalMDN {
return MVMCoreUIUtility.validateMDNString(MDN)
}
return MVMCoreUIUtility.validateInternationalMDNString(MDN)
}
@objc public func validateMDNTextField() -> Bool {
guard !shouldValidateMDN, let MDN = mdn, !MDN.isEmpty else {
isValid = true
return true
}
isValid = hasValidMDN()
if self.isValid {
showError = false
} else {
entryFieldModel?.errorMessage = entryFieldModel?.errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message")
showError = true
UIAccessibility.post(notification: .layoutChanged, argument: textField)
}
return isValid
}
//--------------------------------------------------
@objc public func getContacts(_ sender: Any?) {
let picker = CNContactPickerViewController()
@ -152,11 +84,12 @@ import MVMCore
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
textField.keyboardType = .phonePad
public override func viewModelDidUpdate() {
viewModel.type = .phone
super.viewModelDidUpdate()
if let phoneNumber = viewModel.text {
text = phoneNumber.formatUSNumber()
}
}
//--------------------------------------------------
@ -179,62 +112,47 @@ import MVMCore
let startIndex = unformedMDN.index(unformedMDN.startIndex, offsetBy: 1)
unformattedMDN = String(unformedMDN[startIndex...])
}
text = unformattedMDN
textFieldShouldReturn(textField)
textFieldDidEndEditing(textField)
}
}
//--------------------------------------------------
// MARK: - Implemented TextField Delegate
//--------------------------------------------------
@discardableResult
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true
@objc public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
_ = resignFirstResponder()
let superValue = super.textFieldShouldReturn(textField)
return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? superValue
}
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) {
return false
}
return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true
@objc public override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let superValue = super.textField(textField, shouldChangeCharactersIn: range, replacementString: string)
return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? superValue
}
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text)
@objc public override func textFieldDidBeginEditing(_ textField: UITextField) {
super.textFieldDidBeginEditing(textField)
proprietorTextDelegate?.textFieldDidBeginEditing?(textField)
}
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
@objc public override func textFieldDidEndEditing(_ textField: UITextField) {
proprietorTextDelegate?.textFieldDidEndEditing?(textField)
if validateMDNTextField() {
if isNationalMDN {
textField.text = MVMCoreUIUtility.formatMdn(textField.text)
}
// Validate the base input field along with triggering form field validation rules.
validateText()
}
super.textFieldDidEndEditing(textField)
}
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
@objc public override func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
}
@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
@objc public override func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
}
@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
@objc public override func textFieldShouldClear(_ textField: UITextField) -> Bool {
proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
}
}

View File

@ -12,4 +12,9 @@
//--------------------------------------------------
public override class var identifier: String { "mdnEntryField" }
open override func formFieldServerValue() -> AnyHashable? {
guard let value = formFieldValue() as? String else { return nil }
return value.filter { $0.isNumber }
}
}

View File

@ -11,9 +11,9 @@ import UIKit
@objc public protocol ObservingTextFieldDelegate {
/// Called when the entered text becomes valid based on the validation block
@objc optional func isValid(textfield: TextEntryField?)
@objc optional func isValid(textfield: Any?)
/// Called when the entered text becomes invalid based on the validation block
@objc optional func isInvalid(textfield: TextEntryField?)
@objc optional func isInvalid(textfield: Any?)
/// Dismisses the keyboard.
@objc optional func dismissFieldInput(_ sender: Any?)
}
@ -317,9 +317,9 @@ import UIKit
super.shouldShowError(showError)
if showError {
observingTextFieldDelegate?.isValid?(textfield: self)
} else {
observingTextFieldDelegate?.isInvalid?(textfield: self)
} else {
observingTextFieldDelegate?.isValid?(textfield: self)
}
}

View File

@ -5,9 +5,10 @@
// Created by Kevin Christiano on 1/22/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
@objcMembers open class TextEntryFieldModel: EntryFieldModel {
@objcMembers open class TextEntryFieldModel: EntryFieldModel, FormFieldInternalValidatableProtocol {
//--------------------------------------------------
// MARK: - Types
//--------------------------------------------------
@ -20,6 +21,39 @@
case email
case text
case phone
//additional
case inlineAction
case creditCard
case date
case securityCode
public func toVDSFieldType() -> VDS.InputField.FieldType {
switch self {
case .password:
.password
case .secure:
.text
case .number:
.number
case .numberSecure:
.number
case .email:
.text
case .text:
.text
case .phone:
.telephone
case .inlineAction:
.inlineAction
case .creditCard:
.creditCard
case .date:
.date
case .securityCode:
.securityCode
}
}
}
//--------------------------------------------------
@ -33,12 +67,21 @@
public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3)
public var textAlignment: NSTextAlignment = .left
public var keyboardOverride: String?
public var type: EntryType?
public var type: EntryType = .text
public var clearTextOnTap: Bool = false
public var displayFormat: String?
public var displayMask: String?
public var enableClipboardActions: Bool = true
public var tooltip: TooltipModel?
public var transparentBackground: Bool = false
public var width: CGFloat?
//--------------------------------------------------
// MARK: - FormFieldInternalValidatableProtocol
//--------------------------------------------------
open var rules: [AnyRule<String>]?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -114,6 +157,9 @@
case displayFormat
case displayMask
case enableClipboardActions
case tooltip
case transparentBackground
case width
}
//--------------------------------------------------
@ -128,7 +174,7 @@
displayFormat = try typeContainer.decodeIfPresent(String.self, forKey: .displayFormat)
keyboardOverride = try typeContainer.decodeIfPresent(String.self, forKey: .keyboardOverride)
displayMask = try typeContainer.decodeIfPresent(String.self, forKey: .displayMask)
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type)
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type) ?? .text
if let clearTextOnTap = try typeContainer.decodeIfPresent(Bool.self, forKey: .clearTextOnTap) {
self.clearTextOnTap = clearTextOnTap
@ -149,6 +195,10 @@
if let enableClipboardActions = try typeContainer.decodeIfPresent(Bool.self, forKey: .enableClipboardActions) {
self.enableClipboardActions = enableClipboardActions
}
tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip)
transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false
width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width)
}
open override func encode(to encoder: Encoder) throws {
@ -164,5 +214,25 @@
try container.encode(disabledTextColor, forKey: .disabledTextColor)
try container.encode(clearTextOnTap, forKey: .clearTextOnTap)
try container.encode(enableClipboardActions, forKey: .enableClipboardActions)
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 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,100 +7,60 @@
//
import UIKit
import VDS
class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDelegate {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
open private(set) var textView: TextView = {
let textView = TextView()
textView.setContentCompressionResistancePriority(.required, for: .vertical)
return textView
}()
//--------------------------------------------------
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?
private var observingForChange: Bool = false
//--------------------------------------------------
// MARK: - Stored Properties
//--------------------------------------------------
public var isValid: Bool = true
private var isEditting: Bool = false {
didSet {
viewModel?.selected = isEditting
}
}
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
public var textViewEntryFieldModel: TextViewEntryFieldModel? {
model as? TextViewEntryFieldModel
open var shouldMaskWhileRecording: Bool {
return viewModel?.shouldMaskRecordedView ?? false
}
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
/// Placeholder access for the textView.
open var placeholder: String? {
get { viewModel?.placeholder }
set {
textView.placeholder = newValue ?? ""
viewModel?.placeholder = newValue
}
}
/// The text of this textView.
open override var text: String? {
get { textViewEntryFieldModel?.text }
set {
textView.text = newValue
textViewEntryFieldModel?.text = newValue
didSet {
viewModel?.text = text
}
}
/// Placeholder access for the textView.
public var placeholder: String? {
get { textViewEntryFieldModel?.placeholder }
set {
textView.placeholder = newValue ?? ""
textViewEntryFieldModel?.placeholder = newValue
textView.setPlaceholderIfAvailable()
open override var errorText: String? {
get {
viewModel.dynamicErrorMessage ?? viewModel.errorMessage
}
}
//--------------------------------------------------
// 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
set {}
}
//--------------------------------------------------
@ -108,198 +68,178 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
//--------------------------------------------------
/// The delegate and block for validation. Validates if the text that the user has entered.
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)
}
}
}
open weak var observingTextViewDelegate: ObservingTextFieldDelegate?
/// If you're using a ViewController, you must set this to it
public weak var uiTextViewDelegate: UITextViewDelegate? {
open weak var uiTextViewDelegate: UITextViewDelegate? {
get { textView.delegate }
set { textView.delegate = newValue }
}
@objc public func setBothTextDelegates(to delegate: (UITextViewDelegate & ObservingTextFieldDelegate)?) {
@objc open 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
//--------------------------------------------------
@objc open override func setupFieldContainerContent(_ container: UIView) {
open override func setup() {
super.setup()
//turn off internal required rule
useRequiredRule = false
container.addSubview(textView)
publisher(for: .valueChanged)
.sink { [weak self] control in
guard let self, let viewModel else { return }
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}.store(in: &subscribers)
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)
textView
.publisher(for: .editingDidBegin)
.sink { [weak self] textView in
guard let self else { return }
isEditting = true
}.store(in: &subscribers)
topConstraint?.isActive = true
leadingConstraint?.isActive = true
trailingConstraint?.isActive = true
bottomConstraint?.isActive = true
heightConstraint = textView.heightAnchor.constraint(equalToConstant: 0)
accessibilityElements = [textView]
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 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
}
open func viewModelDidUpdate() {
//--------------------------------------------------
// 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()
text = viewModel.text
minHeight = viewModel.minHeight
maxLength = viewModel.maxLength
// 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
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
uiTextViewDelegate = delegateObject?.uiTextViewDelegate
observingTextViewDelegate = delegateObject?.observingTextFieldDelegate
if let accessibilityText = model.accessibilityText {
if let accessibilityText = viewModel.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
}
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:
switch viewModel.type {
case .secure:
textView.isSecureTextEntry = true
textView.shouldMaskWhileRecording = true
case .numberSecure:
textView.isSecureTextEntry = true
textView.keyboardType = .numberPad
case .number:
textView.shouldMaskWhileRecording = true
textView.keyboardType = .numberPad
case .email:
textView.keyboardType = .emailAddress
default: break
case .securityCode, .creditCard, .password:
textView.shouldMaskWhileRecording = true
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: model, delegate: delegateObject?.formHolderDelegate)
setupTextViewToolbar()
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
if isSelected {
if isEditting {
DispatchQueue.main.async {
_ = self.textView.becomeFirstResponder()
_ = self.becomeFirstResponder()
}
}
}
if model.hideBorders {
adjustMarginConstraints(constant: 0)
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
})
}
updateAccessibility(model: model)
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)
})
}
}
func updateAccessibility(model: TextViewEntryFieldModel) {
private func updateValidation(_ isValid: Bool) {
let previousValidity = self.isValid
self.isValid = isValid
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") ?? "")"
if previousValidity && !isValid {
showError = true
} else if (!previousValidity && isValid) {
showError = false
}
if let feedback = model.feedback {
message += ", " + feedback
}
//--------------------------------------------------
// 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 errorMessage = errorLabel.text {
message += ", " + errorMessage
set {
objc_setAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
textView.accessibilityLabel = message
}
}

View File

@ -7,9 +7,9 @@
//
import UIKit
import VDS
class TextViewEntryFieldModel: TextEntryFieldModel {
public class TextViewEntryFieldModel: TextEntryFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -17,12 +17,10 @@ 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
@ -30,11 +28,9 @@ class TextViewEntryFieldModel: TextEntryFieldModel {
private enum CodingKeys: String, CodingKey {
case accessibilityText
case fontStyle
case height
case placeholderFontStyle
case placeholderTextColor
case editable
case minHeight
case maxLength
}
//--------------------------------------------------
@ -45,34 +41,27 @@ class TextViewEntryFieldModel: TextEntryFieldModel {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
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
}
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)
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(placeholderFontStyle, forKey: .placeholderFontStyle)
try container.encode(placeholderTextColor, forKey: .placeholderTextColor)
try container.encode(minHeight, forKey: .minHeight)
try container.encodeIfPresent(maxLength, forKey: .maxLength)
}
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,144 +7,41 @@
//
import MVMCore
import VDS
/**
This class expects its height and width to be equal.
*/
@objcMembers open class Checkbox: Control, MVMCoreUIViewConstrainingProtocol {
//--------------------------------------------------
@objcMembers open class Checkbox: VDS.Checkbox, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0)
//------------------------------------------------------
open var viewModel: CheckboxModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
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 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 {
open var actionBlock: ActionBlock? {
didSet {
if let shapeLayer = shapeLayer {
CATransaction.withDisabledAnimations {
shapeLayer.lineWidth = checkWidth
if let actionBlock {
onChange = { _ in
actionBlock()
}
}
}
}
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)
onChange = nil
}
}
}
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 {
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
viewModel?.selected = isSelected
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}
}
@ -154,11 +51,6 @@ import MVMCore
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.
@ -167,278 +59,110 @@ import MVMCore
fatalError("xib file is not implemented for Checkbox.")
}
public convenience override init() {
public convenience required init() {
self.init(frame:.zero)
}
public convenience init(isChecked: Bool) {
self.init(frame: .zero)
checkAndBypassAnimations(selected: isChecked)
isSelected = isChecked
}
public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) {
self.init(frame: .zero)
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)
isSelected = isChecked
}
//--------------------------------------------------
// 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.
public func toggleAndAction() {
isSelected.toggle()
actionBlock?()
open func toggleAndAction() {
toggle()
}
//--------------------------------------------------
// 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.
public func updateSelection(to selected: Bool, animated: Bool) {
open func updateSelection(to selected: Bool, animated: Bool) {
DispatchQueue.main.async {
self.checkAndBypassAnimations(selected: selected)
self.drawShapeLayer()
self.shapeLayer?.removeAllAnimations()
self.updateCheckboxUI(isSelected: selected, isAnimated: animated)
self.isAnimated = animated
self.isSelected = selected
}
}
/// 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.
public func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) {
open func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) {
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
DispatchQueue.main.async {
self.isAnimated = isAnimated
self.isSelected = isSelected
}
}
/// 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 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 override func updateView(_ size: CGFloat) {
super.updateView(size)
if let dimension = sizeObject?.getValueBased(onSize: size) {
widthConstraint?.constant = dimension
heightConstraint?.constant = dimension
}
}
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: checkboxModel, additionalData: additionalData, delegateObject: delegateObject)
MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel, 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 {
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
//forms
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
groupName = viewModel.groupName
if let fieldKey = viewModel.fieldKey {
self.fieldKey = fieldKey
}
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: {
//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
guard let self = self else { return }
self.isEnabled = model.enabled
//let isValid = viewModel.isValid ?? true
//TODO: Fix issue with default state
//showError = !isValid
isEnabled = viewModel.enabled
})
}
isEnabled = model.enabled && !model.readOnly
if (model.action != nil || model.offAction != nil) {
//onChange
if (viewModel.action != nil || viewModel.offAction != nil) {
actionBlock = { [weak self] in
guard let self = self else { return }
if let offAction = model.offAction, !self.isSelected {
self.performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData)
if let offAction = viewModel.offAction, !isSelected {
performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData)
} else if let action = model.action {
self.performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData)
} else if let action = viewModel.action {
performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData)
}
}
}

View File

@ -5,6 +5,7 @@
// 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.
///
@ -13,67 +14,25 @@
var selected: Bool { get set }
}
@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol, UIUpdatableModelProtocol {
@objcMembers public class CheckboxModel: FormFieldModel, SelectableMoleculeModelProtocol{
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "checkbox"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public static override var identifier: String { "checkbox" }
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
}
@ -81,16 +40,17 @@
// MARK: - Form Validation
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
open override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
return selected
}
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
open override func setValidity(_ valid: Bool, errorMessage: String?) {
if let ruleErrorMessage = errorMessage, fieldKey != nil {
self.errorMessage = ruleErrorMessage
}
isValid = valid
updateUI?()
}
//--------------------------------------------------
@ -98,7 +58,8 @@
//--------------------------------------------------
public init(isChecked: Bool = false) {
self.selected = isChecked
super.init()
selected = isChecked
baseValue = isChecked
}
@ -107,52 +68,9 @@
//--------------------------------------------------
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
}
@ -162,51 +80,26 @@
if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) {
self.animated = animated
}
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 func encode(to encoder: Encoder) throws {
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
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

@ -0,0 +1,58 @@
//
// 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

@ -0,0 +1,101 @@
//
// 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,243 +5,66 @@
// Created by Scott Pfeil on 4/9/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
open class RadioBox: Control, MFButtonProtocol {
//--------------------------------------------------
@objcMembers open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol {
//------------------------------------------------------
// MARK: - Properties
//------------------------------------------------------
open var viewModel: RadioBoxModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
open var isOutOfStock: Bool {
get { strikethrough }
set {
strikethrough = newValue
viewModel?.strikethrough = newValue
}
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
public let label = Label(fontStyle: .RegularBodySmall)
public let subTextLabel = Label(fontStyle: .RegularMicro)
public var isOutOfStock = false
public var accentColor = UIColor.mvmRed
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
}
public override var isSelected: Bool {
didSet { updateAccessibility() }
}
public override var isEnabled: 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
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 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
open func updateView(_ size: CGFloat) {}
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,45 +6,35 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import MVMCore
import VDS
@objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol {
public class RadioBoxModel: FormFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioBox"
public var id: String = UUID().uuidString
public override static var identifier: String { "radioBox" }
public var text: String
public var subText: String?
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var selectedAccentColor: Color?
public var subTextRight: String?
public var selected: Bool = false
public var enabled: Bool = true
public var readOnly: Bool = false
public var strikethrough: Bool = false
public var fieldValue: String?
public var action: ActionModelProtocol?
public var fieldValue: String?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case text
case subText
case selectedAccentColor
case backgroundColor
case accessibilityIdentifier
case subTextRight
case selected
case enabled
case strikethrough
case fieldValue
case action
case readOnly
case fieldValue
}
//--------------------------------------------------
@ -53,8 +43,19 @@ import MVMCore
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
//--------------------------------------------------
@ -62,42 +63,42 @@ 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
text = try typeContainer.decode(String.self, forKey: .text)
subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText)
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
subTextRight = try typeContainer.decodeIfPresent(String.self, forKey: .subTextRight)
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 func encode(to encoder: Encoder) throws {
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
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(selectedAccentColor, forKey: .selectedAccentColor)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(subTextRight, forKey: .subTextRight)
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,172 +7,49 @@
//
import Foundation
import VDS
public protocol RadioBoxSelectionDelegate: AnyObject {
func selected(radioBox: RadioBoxModel)
}
open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol {
//------------------------------------------------------
// 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?
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?
/// The models for the molecules.
public var boxes: [RadioBoxModel]?
private var size: CGFloat?
open override func layoutSubviews() {
super.layoutSubviews()
// Accounts for any collection size changes
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
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)
}
// 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
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
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
boxes = viewModel.boxes
surface = viewModel.surface
selectorModels = viewModel.boxes.convertToVDSRadioBoxModel(surface: surface)
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
// Calculate the height
let rows = ceil(CGFloat(boxes.count) / numberOfColumns)
let height = (rows * boxHeight) + ((rows - 1) * itemSpacing)
collectionViewHeight?.constant = height
}
open func updateView(_ size: CGFloat) {}
open override func didSelect(_ selectedControl: RadioBoxItem) {
super.didSelect(selectedControl)
// 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)
}
}
}
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,43 +6,37 @@
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import MVMCore
import VDS
@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol {
public class RadioBoxesModel: FormFieldModel, ParentMoleculeModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioBoxes"
public var id: String = UUID().uuidString
public override static var identifier: String { "radioBoxes" }
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 func formFieldValue() -> AnyHashable? {
public override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
let selectedBox = boxes.first { (box) -> Bool in
return box.selected
}
return selectedBox?.fieldValue ?? selectedBox?.text
return selectedBox?.formFieldValue() ?? selectedBox?.text
}
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open func formFieldServerValue() -> AnyHashable? {
open override func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
@ -51,17 +45,7 @@ import MVMCore
//--------------------------------------------------
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
}
//--------------------------------------------------
@ -69,7 +53,8 @@ import MVMCore
//--------------------------------------------------
public init(with boxes: [RadioBoxModel]){
self.boxes = boxes
self.boxes = boxes
super.init()
}
//--------------------------------------------------
@ -78,32 +63,29 @@ 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
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)
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()
try super.init(from: decoder)
}
public func encode(to encoder: Encoder) throws {
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
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)
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)
}
}
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
})
}
}

View File

@ -7,37 +7,26 @@
//
import UIKit
import VDSCoreTokens
import VDS
@objcMembers open class RadioButton: Control, MFButtonProtocol {
//--------------------------------------------------
@objcMembers open class RadioButton: VDS.RadioButton, RadioButtonSelectionHelperProtocol, VDSMoleculeViewProtocol, MFButtonProtocol, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var diameter: CGFloat = 20 {
didSet { widthConstraint?.constant = diameter }
//------------------------------------------------------
open var viewModel: RadioButtonModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
open var radioButtonModel: RadioButtonModel {
viewModel
}
@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 }()
// Form Validation
open var fieldKey: String?
open var fieldValue: JSONValue?
open var groupName: String?
lazy public var radioGroupName: String? = { viewModel.fieldKey }()
lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = {
@ -48,132 +37,120 @@ import VDSCoreTokens
return radioButtonModel
}()
public override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
setNeedsDisplay()
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
override public init(frame: CGRect) {
super.init(frame: frame)
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
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? {
radioModel?.fieldKey
viewModel.fieldKey
}
public func formFieldGroupName() -> String? {
radioModel?.fieldKey
viewModel.fieldKey
}
public func formFieldValue() -> AnyHashable? {
guard let radioModel = radioModel, radioModel.enabled else { return nil }
guard let radioModel = viewModel, radioModel.enabled else { return nil }
return radioModel.fieldValue
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
/// 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
// 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: - Overrides
//--------------------------------------------------
open override func toggle() {
guard !isSelected, isEnabled else { return }
//removed error
if showError && isSelected == false {
showError.toggle()
}
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: - MVMViewProtocol
// MARK: - Actions
//--------------------------------------------------
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()
/// This will toggle the state of the Checkbox and execute the actionBlock if provided.
public func tapAction() {
toggle()
}
//--------------------------------------------------
// 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,48 +7,29 @@
//
import MVMCore
import VDS
open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
open class RadioButtonModel: FormFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioButton"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public static override var identifier: String { "radioButton" }
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
}
//--------------------------------------------------
@ -56,69 +37,57 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
public init(_ state: Bool) {
super.init()
self.state = state
baseValue = state
self.baseValue = state
}
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
public override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil }
return fieldValue
}
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
open override func setValidity(_ valid: Bool, errorMessage: String?) {
if let ruleErrorMessage = errorMessage, fieldKey != nil {
self.errorMessage = ruleErrorMessage
}
isValid = valid
updateUI?()
}
//--------------------------------------------------
// 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 func encode(to encoder: Encoder) throws {
public 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(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)
try container.encodeIfPresent(inverted, forKey: .inverted)
}
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)
}
}

View File

@ -6,6 +6,10 @@
// 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 {
//--------------------------------------------------
@ -14,7 +18,7 @@
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
private var selectedRadioButton: RadioButton?
private var selectedRadioButton: RadioButtonSelectionHelperProtocol?
private var selectedRadioButtonModel: RadioButtonModel?
public var baseValue: AnyHashable?
public var enabled: Bool = true
@ -24,7 +28,7 @@
// MARK: - Initializer
//--------------------------------------------------
public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) {
public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButtonSelectionHelperProtocol) {
self.fieldKey = radioButtonModel.fieldKey
self.groupName = radioButtonModel.groupName
@ -49,7 +53,7 @@
// MARK: - Functions
//--------------------------------------------------
public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton, delegateObject: MVMCoreUIDelegateObject?) {
public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButtonSelectionHelperProtocol, delegateObject: MVMCoreUIDelegateObject?) {
guard let groupName = radioButtonModel.fieldKey,
let formValidator = delegateObject?.formHolderDelegate?.formValidator
@ -61,10 +65,10 @@
FormValidator.setupValidation(for: radioButtonSelectionHelper, delegate: delegateObject?.formHolderDelegate)
}
public func selected(_ radioButton: RadioButton) {
public func selected(_ radioButton: RadioButtonSelectionHelperProtocol) {
// Checks because the view could be reused
if selectedRadioButton?.radioModel === selectedRadioButtonModel {
if selectedRadioButton?.radioButtonModel === selectedRadioButtonModel {
selectedRadioButton?.isSelected = false
} else {
selectedRadioButtonModel?.state = false
@ -72,7 +76,7 @@
selectedRadioButton = radioButton
selectedRadioButton?.isSelected = true
selectedRadioButtonModel = selectedRadioButton?.radioModel
selectedRadioButtonModel = selectedRadioButton?.radioButtonModel
}
}

View File

@ -0,0 +1,57 @@
//
// 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

@ -0,0 +1,74 @@
//
// 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,6 +8,7 @@
import MVMCore
import UIKit
import VDS
public typealias ActionBlockConfirmation = () -> (Bool)
@ -19,137 +20,40 @@ public typealias ActionBlockConfirmation = () -> (Bool)
Container: The background of the toggle control.
Knob: The circular indicator that slides on the container.
*/
@objcMembers open class Toggle: Control, MVMCoreUIViewConstrainingProtocol {
//--------------------------------------------------
@objcMembers open class Toggle: VDS.Toggle, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
//------------------------------------------------------
open var viewModel: ToggleModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
/// 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?
public var didToggleAction: ActionBlock? {
didSet {
if let didToggleAction {
onChange = { _ in
didToggleAction()
}
} else {
onChange = nil
}
}
}
/// 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
//--------------------------------------------------
@ -158,7 +62,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
super.init(frame: frame)
}
public convenience override init() {
public convenience required init() {
self.init(frame: .zero)
}
@ -171,7 +75,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)
changeStateNoAnimation(isOn)
self.isOn = isOn
self.didToggleAction = didToggleAction
}
@ -191,223 +95,78 @@ 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 }
}
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() {
if let result = shouldToggleAction?(), result {
isOn.toggle()
didToggleAction?()
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
}
private func changeStateNoAnimation(_ state: Bool) {
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
// 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()
isOn = viewModel.selected
surface = viewModel.surface
isAnimated = viewModel.animated
isEnabled = viewModel.isEnabled
showText = viewModel.showText
if let onText = viewModel.onText {
self.onText = onText
}
}
// MARK:- MoleculeViewProtocol
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
if let offText = viewModel.offText {
self.offText = offText
}
textSize = viewModel.textSize
textWeight = viewModel.textWeight
textPosition = viewModel.textPosition
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 {
if let accessibileString = viewModel.accessibilityText {
accessibilityLabel = accessibileString
}
if model.action != nil || model.alternateAction != nil {
if viewModel.action != nil || viewModel.alternateAction != nil {
didToggleAction = { [weak self] in
guard let self = self else { return }
if self.isOn {
if let action = model.action {
if let action = viewModel.action {
MVMCoreUIActionHandler.performActionUnstructured(with: action, sourceModel: model, additionalData: additionalData, delegateObject: delegateObject)
}
} else {
if let action = model.alternateAction ?? model.action {
if let action = viewModel.alternateAction ?? viewModel.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()
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
Self.getContainerHeight()
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
}
}

View File

@ -5,80 +5,62 @@
// Created by Scott Pfeil on 1/14/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import VDS
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
public class ToggleModel: FormFieldModel {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "toggle"
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public override static var identifier: String { "toggle" }
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 fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
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
//--------------------------------------------------
// 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 onTintColor
case offTintColor
case onKnobTintColor
case offKnobTintColor
case fieldKey
case groupName
case showText
case onText
case offText
case textSize
case textWeight
case textPosition
}
//--------------------------------------------------
// MARK: - Form Valdiation
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
public override 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) {
self.selected = state
selected = state
super.init()
baseValue = state
self.id = id
}
@ -90,8 +72,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
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
}
@ -102,54 +82,48 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
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 func encode(to encoder: Encoder) throws {
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
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.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(readOnly, forKey: .readOnly)
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
}
}

View File

@ -19,7 +19,8 @@ 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)
@ -59,6 +60,7 @@ open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case disabledColor
@ -79,7 +81,8 @@ 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
}
@ -116,6 +119,7 @@ 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,10 +27,14 @@ 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.fillColor
fillColor = viewModel.fillColorStyle
surface = viewModel.surface
}

View File

@ -25,6 +25,9 @@ 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,7 +17,8 @@ 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
//--------------------------------------------------
@ -43,6 +44,7 @@ open class BadgeIndicatorModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case inverted
case accessibilityText
case number
@ -67,6 +69,7 @@ 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)
@ -91,6 +94,7 @@ 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,28 +16,46 @@ 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 fillColor = Badge.FillColor.red
public var fillColorStyle = Badge.FillColor.red
public var surface: Surface = .light
private enum CodingKeys: String, CodingKey {
case id, text, accessibilityText, fillColor, surface, numberOfLines, maxWidth
case id, accessibilityIdentifier, accessibilityText
case surface, numberOfLines, maxWidth
case text, textColor
case fillColor, fillColorStyle
}
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)
fillColor = try container.decodeIfPresent(Badge.FillColor.self, forKey: .fillColor) ?? .red
//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)
}
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)
@ -48,9 +66,28 @@ open class BadgeModel: MoleculeModelProtocol {
try container.encode(id, forKey: .id)
try container.encode(text, forKey: .text)
try container.encode(accessibilityText, forKey: .accessibilityText)
try container.encode(fillColor, forKey: .fillColor)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
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,6 +25,9 @@ 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,7 +16,8 @@ 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
//--------------------------------------------------
@ -77,6 +78,7 @@ open class ButtonIconModel: ButtonModelProtocol, MoleculeModelProtocol {
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case inverted
case accessibilityText
case action
@ -105,6 +107,7 @@ 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)
@ -128,6 +131,7 @@ 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

@ -0,0 +1,12 @@
//
// 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

@ -0,0 +1,65 @@
//
// 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

@ -0,0 +1,159 @@
//
// 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,6 +16,7 @@ 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)
@ -30,6 +31,7 @@ import MVMCore
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case strokeColor
@ -54,7 +56,8 @@ 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
}
@ -84,6 +87,7 @@ 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,6 +19,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
}
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var moleculeName: String?
@ -48,6 +49,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case currentIndex
@ -70,6 +72,7 @@ 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)
@ -118,6 +121,7 @@ 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,141 +5,99 @@
// Created by Kevin Christiano on 9/13/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import VDS
@objcMembers open class CheckboxLabel: View {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
public let checkbox = Checkbox()
public let label = Label(fontStyle: .RegularBodySmall)
private var observation: NSKeyValueObservation? = nil
//--------------------------------------------------
@objcMembers open class CheckboxLabel: VDS.CheckboxItem, VDSMoleculeViewProtocol {
//------------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var checkboxPosition: CheckboxPosition = .center
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
//------------------------------------------------------
open var viewModel: CheckboxLabelModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
public var checkboxTopConstraint: NSLayoutConstraint?
public var checkboxBottomConstraint: NSLayoutConstraint?
public var checkboxCenterYConstraint: NSLayoutConstraint?
// Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
//--------------------------------------------------
// 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()
override open var isSelected: Bool {
didSet {
viewModel?.checkbox.selected = isSelected
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}
}
@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 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)
open func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
surface = viewModel.surface
checkbox.set(with: checkBoxWithLabelModel.checkbox, delegateObject, additionalData)
label.set(with: checkBoxWithLabelModel.label, delegateObject, additionalData)
updateAccessibilityLabel()
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)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
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? {
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,31 +8,67 @@
import Foundation
import MVMCore
public enum CheckboxPosition: String, Codable {
case center
case top
case bottom
}
import VDS
@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 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 children: [MoleculeModelProtocol] { [checkbox, label] }
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(checkbox: CheckboxModel, label: LabelModel) {
public init(checkbox: CheckboxModel, label: LabelModel, subTitle: 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

@ -0,0 +1,128 @@
//
// 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

@ -0,0 +1,123 @@
//
// 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

@ -0,0 +1,29 @@
//
// 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,11 +31,15 @@ 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,6 +21,8 @@ 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
@ -35,4 +37,6 @@ open class IconModel: MoleculeModelProtocol {
/// A custom size of the icon.
public var customSize: Int?
public var isAccessibilityElement: Bool?
}

View File

@ -18,6 +18,7 @@
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?
@ -47,6 +48,7 @@
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case image

View File

@ -16,13 +16,15 @@ public class FormLabel: Label {
//public properties
public override var isEnabled: Bool {
didSet{
self.formModel.enabled = isEnabled
guard let formModel else { return }
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)
}
}
@ -50,6 +52,7 @@ 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?){
self.formModel.set(text: text ?? "")
guard let formModel else { return }
formModel.set(text: text ?? "")
}
}

View File

@ -34,11 +34,7 @@ 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 {
@ -186,6 +182,9 @@ public typealias ActionBlock = () -> ()
}
public func viewModelDidUpdate() {
if let accessibilityIdentifier = viewModel.accessibilityIdentifier {
self.accessibilityIdentifier = accessibilityIdentifier
}
shouldMaskWhileRecording = viewModel.shouldMaskRecordedView ?? false
text = viewModel.text
hero = viewModel.hero
@ -364,8 +363,8 @@ extension Label {
public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect {
guard let abstractContainer = label.abstractTextContainer() else { return CGRect() }
let textContainer = abstractContainer.0
let layoutManager = abstractContainer.1
let textContainer = abstractContainer.textContainer
let layoutManager = abstractContainer.layoutManager
var glyphRange = NSRange()
@ -374,53 +373,28 @@ extension Label {
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
/**
Provides a text container and layout manager of how the text would appear on screen.
They are used in tandem to derive low-level TextKit results of the label.
*/
public func abstractTextContainer() -> (NSTextContainer, NSLayoutManager, NSTextStorage)? {
// Must configure the attributed string to translate what would appear on screen to accurately analyze.
guard let attributedText = attributedText else { return nil }
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = textAlignment
let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText)
stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count))
let textStorage = NSTextStorage(attributedString: stagedAttributedString)
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
textContainer.size = bounds.size
return (textContainer, layoutManager, textStorage)
}
}
// 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 Label {
extension VDS.Label {
public var getRange: NSRange {
NSRange(location: 0, length: text?.count ?? 0)
}
/// Underlines the tappable region and stores the tap logic for interation.
private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) {
internal 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)
@ -447,8 +421,16 @@ extension Label {
return { [weak self] in
guard let self = self else { return }
if (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
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)
}
}
}

View File

@ -14,7 +14,8 @@ 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?
@ -38,6 +39,7 @@ import VDS
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case text
case accessibilityText
@ -88,6 +90,7 @@ 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)
@ -115,6 +118,7 @@ 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,7 +16,8 @@ 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 viewModel.type != .none else { return }
guard let viewModel, viewModel.type != .none else { return }
super.draw(rect)
}
@ -93,6 +93,9 @@ 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,6 +55,7 @@ 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
@ -83,6 +84,7 @@ public class LineModel: MoleculeModelProtocol, Invertable {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case type
case frequency
@ -99,7 +101,8 @@ 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
}
@ -124,6 +127,7 @@ 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,6 +22,9 @@ 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,7 +16,8 @@ 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
@ -27,6 +28,7 @@ open class LoadingSpinnerModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case strokeColor
case diameter
@ -47,7 +49,8 @@ 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
}
@ -65,6 +68,7 @@ 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,7 +22,8 @@ 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?
@ -30,6 +31,7 @@ import Foundation
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case progressList
case thickness
@ -44,6 +46,7 @@ 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)
@ -54,6 +57,7 @@ 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

@ -0,0 +1,60 @@
//
// 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

@ -0,0 +1,76 @@
//
// 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,6 +75,9 @@ 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,7 +11,8 @@ 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())
@ -20,6 +21,7 @@ import Foundation
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case roundedCorners
case thickness
@ -35,6 +37,7 @@ 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
@ -50,6 +53,7 @@ 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,7 +14,8 @@ 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?
@ -26,6 +27,7 @@ open class StarModel: MoleculeModelProtocol {
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case percent
@ -47,6 +49,7 @@ 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
}
@ -63,6 +66,7 @@ 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,7 +14,8 @@ 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]
@ -28,6 +29,7 @@ import MVMCore
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case starBackgroundColor
@ -53,6 +55,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)
stars = try typeContainer.decode([StarModel].self, forKey: .stars)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
starBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .starBackgroundColor)
@ -70,6 +73,7 @@ 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,7 +40,9 @@ 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 {
@ -99,7 +101,9 @@ open class TileContainer: VDS.TileContainer, VDSMoleculeViewProtocol{
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
open func updateView(_ size: CGFloat) {}
open func updateView(_ size: CGFloat) {
(molecule as? MVMCoreViewProtocol)?.updateView(size)
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
@ -135,8 +139,6 @@ open class TileContainer: VDS.TileContainer, VDSMoleculeViewProtocol{
}
extension TileContainer: MVMCoreUIViewConstrainingProtocol {
public func horizontalAlignment() -> UIStackView.Alignment { .leading }
public func isClippable() -> Bool {
return false
}

View File

@ -67,11 +67,13 @@ open class TileContainerBaseModel<PaddingType: DefaultValuing & Codable, TileCon
public var showDropShadow: Bool = false
public var padding = PaddingType.defaultValue
public var color: TileContainerType.BackgroundColor = .black
public var aspectRatio: TileContainerType.AspectRatio = .ratio1x1
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
@ -88,6 +90,7 @@ 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)
@ -98,12 +101,13 @@ open class TileContainerBaseModel<PaddingType: DefaultValuing & Codable, TileCon
showDropShadow = try container.decodeIfPresent(Bool.self, forKey: .showDropShadow) ?? false
padding = try container.decodeIfPresent(PaddingType.self, forKey: .padding) ?? PaddingType.defaultValue
color = try container.decodeIfPresent(TileContainerType.BackgroundColor.self, forKey: .color) ?? .black
aspectRatio = try container.decodeIfPresent(TileContainerType.AspectRatio.self, forKey: .aspectRatio) ?? .ratio1x1
aspectRatio = try container.decodeIfPresent(TileContainerType.AspectRatio.self, forKey: .aspectRatio) ?? .none
backgroundEffect = try container.decodeIfPresent(TileContainerType.BackgroundEffect.self, forKey: .backgroundEffect) ?? .none
}
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,6 +39,9 @@ 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)
@ -48,7 +51,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.badge
badgeModel = viewModel.badgeModel()
descriptiveIconModel = viewModel.descriptiveIcon
directionalIconModel = viewModel.directionalIcon
//setup action
@ -136,10 +139,6 @@ open class Tilelet: VDS.Tilelet, VDSMoleculeViewProtocol{
}
extension Tilelet: MVMCoreUIViewConstrainingProtocol {
// Investigate later.
//public func horizontalAlignment() -> UIStackView.Alignment { .leading }
public func isClippable() -> Bool {
return false
}

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: Tilelet.BadgeModel?
public var badge: BadgeModel?
public var eyebrow: LabelModel?
public var eyebrowColor: TitleLockup.TextColor = .primary
public var title: LabelModel?
@ -45,10 +45,11 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
case textWidth
case textPercentage
}
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(Tilelet.BadgeModel.self, forKey: .badge)
badge = try container.decodeIfPresent(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)
@ -86,36 +87,52 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
} else {
subTitleColor = .primary
}
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: style.isBold(),
textAttributes: attrs,
isBold: 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)
return .init(text: eyebrow.text, textColor: eyebrowColor, textAttributes: attrs, isBold: isBold)
}
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())
}
@ -123,7 +140,7 @@ open class TileletModel: TileContainerBaseModel<Tilelet.Padding, Tilelet>, Molec
MVMCoreLoggingHandler.shared()?.addError(toLog: object)
} catch { }
return .init(text: title.text, textColor: titleColor, textAttributes: attrs)
return .init(text: title.text, textColor: titleColor, textAttributes: attrs, isBold: isBold)
}
public func subTitleModel(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Tilelet.SubTitleModel? {

View File

@ -28,6 +28,9 @@ 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,6 +22,8 @@ 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
@ -39,6 +41,7 @@ open class TooltipModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case closeButtonText
@ -52,13 +55,14 @@ open class TooltipModel: MoleculeModelProtocol {
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
self.backgroundColor = try container.decodeIfPresent(Color.self, forKey: .backgroundColor)
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.title = try container.decodeIfPresent(String.self, forKey: .title)
self.content = try container.decodeIfPresent(String.self, forKey: .content)
title = try container.decodeIfPresent(String.self, forKey: .title)
content = try container.decodeIfPresent(String.self, forKey: .content)
self.molecule = try container.decodeModelIfPresent(codingKey: .contentView)
molecule = try container.decodeModelIfPresent(codingKey: .contentView)
if let closeButtonText = try container.decodeIfPresent(String.self, forKey: .closeButtonText) {
self.closeButtonText = closeButtonText
@ -81,6 +85,7 @@ 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)
@ -96,3 +101,17 @@ open class TooltipModel: MoleculeModelProtocol {
}
}
extension TooltipModel {
public func convertToVDSTooltipModel() -> Tooltip.TooltipModel {
var moleculeView: MoleculeViewProtocol?
if let molecule, let view = ModelRegistry.createMolecule(molecule) {
moleculeView = view
}
return .init(closeButtonText: closeButtonText,
title: title,
content: content,
contentView: moleculeView
)
}
}

View File

@ -11,6 +11,7 @@ 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
@ -45,6 +46,7 @@ open class VideoModel: MoleculeModelProtocol, PageBehaviorProtocolRequirer {
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case moleculeName
case video
case showControls
@ -60,6 +62,7 @@ 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
@ -76,6 +79,7 @@ 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,7 +13,8 @@ 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?
@ -26,6 +27,7 @@ import MVMCore
private enum CodingKeys: String, CodingKey{
case id
case accessibilityIdentifier
case moleculeName
case backgroundColor
case url
@ -43,6 +45,7 @@ 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)
@ -57,6 +60,7 @@ 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,30 +8,22 @@
import UIKit
public enum GraphSize: String, Codable {
case small, medium, large
}
public enum GraphStyle: String, Codable {
case unlimited, safetyMode
}
public class WheelModel: MoleculeModelProtocol {
public class WheelModel: GraphSizeBase, 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
@ -39,13 +31,15 @@ public class WheelModel: MoleculeModelProtocol {
public var colors = [Color]()
public var backgroundColor: Color?
public init() {
public override init() {
super.init()
updateStyle()
updateSize()
}
private enum CodingKeys: String, CodingKey {
case id
case accessibilityIdentifier
case style
case size
case diameter
@ -58,9 +52,11 @@ public class WheelModel: 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
}
@ -91,6 +87,7 @@ public class WheelModel: 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)
@ -123,7 +120,7 @@ public class WheelModel: MoleculeModelProtocol {
}
}
func updateSize() {
public override 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 {}
@ -25,21 +25,31 @@ extension VDS.ButtonIcon.Size: Codable {}
extension VDS.ButtonIcon.BadgeIndicatorModel.ExpandDirection: Codable {}
extension VDS.ButtonIcon.SurfaceType: Codable {}
extension VDS.ButtonGroup.Alignment: Codable {}
extension VDS.CarouselScrollbar.Layout: Codable {}
extension VDS.DatePicker.DateFormat: Codable {}
extension VDS.EntryFieldBase.HelperTextPlacement: Codable {}
extension VDS.Icon.Name: Codable {}
extension VDS.Icon.Size: Codable {}
extension VDS.InputField.CreditCardType: Codable {}
extension VDS.InputField.DateFormat: Codable {}
extension VDS.InputField.FieldType: Codable {}
extension VDS.Line.Style: Codable {}
extension VDS.Line.Orientation: Codable {}
extension VDS.Tabs.Orientation: Codable {}
extension VDS.Tabs.IndicatorPosition: Codable {}
extension VDS.Tabs.Overflow: Codable {}
extension VDS.Tabs.Size: Codable {}
extension VDS.TextArea.Height: Codable {}
extension VDS.TextLink.Size: Codable {}
extension VDS.TextLinkCaret.IconPosition: Codable {}
extension VDS.TileContainerBase.AspectRatio: Codable {}
extension VDS.Tilelet.Padding: Codable {}
extension VDS.TitleLockup.TextAlignment: Codable {}
extension VDS.Toggle.TextSize: Codable {}
extension VDS.Toggle.TextPosition: Codable {}
extension VDS.Toggle.TextWeight: Codable {}
extension VDS.Tooltip.FillColor: Codable {}
extension VDS.Tooltip.Size: Codable {}
extension VDS.Line.Style: Codable {}
extension VDS.Line.Orientation: Codable {}
extension VDS.Use: Codable {}
extension VDS.Button.Size: RawRepresentableCodable {
@ -47,6 +57,52 @@ 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
//--------------------------------------------------
@ -237,6 +293,45 @@ 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.convertToVDSLabelAttirbute(delegateObject: delegateObject,
let vds = attr.convertToVDSLabelAttribute(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 convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> LabelAttributeType?
func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> LabelAttributeType?
}
extension LabelAttributeUnderlineModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> UnderlineLabelAttribute? {
public func convertToVDSLabelAttribute(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 convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> ActionLabelAttribute? {
public func convertToVDSLabelAttribute(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,11 +64,13 @@ extension LabelAttributeActionModel: VDSLabelAttributeConvertable {
}
extension LabelAttributeFontModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> TextStyleLabelAttribute? {
public func convertToVDSLabelAttribute(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
}
@ -82,7 +84,7 @@ extension LabelAttributeFontModel: VDSLabelAttributeConvertable {
}
extension LabelAttributeColorModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> ColorLabelAttribute? {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> ColorLabelAttribute? {
guard let textColor else { return nil }
return ColorLabelAttribute(location: location,
length: length,
@ -91,14 +93,14 @@ extension LabelAttributeColorModel: VDSLabelAttributeConvertable {
}
extension LabelAttributeStrikeThroughModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> StrikeThroughLabelAttribute? {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> StrikeThroughLabelAttribute? {
return StrikeThroughLabelAttribute(location: location,
length: length)
}
}
extension LabelAttributeImageModel: VDSLabelAttributeConvertable {
public func convertToVDSLabelAttirbute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> AtomicImageLabelAttribute? {
public func convertToVDSLabelAttribute(delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> AtomicImageLabelAttribute? {
var frame: CGRect?
if let size {
frame = CGRect(x: 0, y: 0, width: size, height: size)

View File

@ -0,0 +1,59 @@
//
// 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

@ -0,0 +1,124 @@
//
// 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,6 +52,9 @@ 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,6 +31,9 @@ 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,18 +47,15 @@ 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,18 +42,15 @@ 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,18 +46,15 @@ public class HeadersH2LinkModel: HeaderModel, ParentMoleculeModelProtocol {
//--------------------------------------------------
public override func setDefaults() {
if topPadding == nil {
topPadding = Padding.Component.VerticalMarginSpacing
}
if bottomPadding == nil {
bottomPadding = Padding.Component.VerticalMarginSpacing
}
if titleLockup.title.fontStyle == nil {
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,18 +36,15 @@ 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,12 +57,6 @@ 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,18 +47,15 @@ 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,9 +83,7 @@
func updateAccessibilityLabel() {
var message = ""
checkbox.updateAccessibilityLabel()
if let checkboxLabel = checkbox.accessibilityLabel, !checkboxLabel.isEmpty {
message += checkboxLabel + ", "
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,8 @@ 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
@ -35,6 +36,7 @@ public class LockUpsPlanNamesModel: MoleculeModelProtocol {
private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case backgroundColor
case headline
case subHeadline
@ -47,6 +49,7 @@ 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)
@ -57,6 +60,7 @@ 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)

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