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
This commit is contained in:
Hedden, Kyle Matthew 2024-08-22 21:46:28 +00:00
commit c2e10582f8
37 changed files with 1062 additions and 140 deletions

View File

@ -594,6 +594,14 @@
EA6642932BCDA97D00D81DC4 /* TileContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */; }; EA6642932BCDA97D00D81DC4 /* TileContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */; };
EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B942B504A43000139B4 /* ButtonGroup.swift */; }; EA6E8B952B504A43000139B4 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B942B504A43000139B4 /* ButtonGroup.swift */; };
EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6E8B962B504A4D000139B4 /* ButtonGroupModel.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 */; };
EA7D81602B2B6E6800D29F9E /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D815F2B2B6E6800D29F9E /* Icon.swift */; }; EA7D81602B2B6E6800D29F9E /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D815F2B2B6E6800D29F9E /* Icon.swift */; };
EA7D81622B2B6E7F00D29F9E /* IconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81612B2B6E7F00D29F9E /* IconModel.swift */; }; EA7D81622B2B6E7F00D29F9E /* IconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81612B2B6E7F00D29F9E /* IconModel.swift */; };
EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */; }; EA7D81642B2BABCB00D29F9E /* TooltipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */; };
@ -1223,6 +1231,14 @@
EA6642922BCDA97D00D81DC4 /* TileContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainerModel.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>"; }; 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>"; }; 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>"; };
EA7D815F2B2B6E6800D29F9E /* Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icon.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>"; }; 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>"; }; EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipModel.swift; sourceTree = "<group>"; };
@ -2031,8 +2047,12 @@
BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */, BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */,
D264FAA6243FE13B00D98315 /* RadioBox.swift */, D264FAA6243FE13B00D98315 /* RadioBox.swift */,
0116A4E4228B19640094F3ED /* RadioButtonSelectionHelper.swift */, 0116A4E4228B19640094F3ED /* RadioButtonSelectionHelper.swift */,
EA7AE54C2C74CAD700107C74 /* RadioButtonsModel.swift */,
EA7AE54A2C74CACA00107C74 /* RadioButtons.swift */,
011D95AE2407266E000E3791 /* RadioButtonModel.swift */, 011D95AE2407266E000E3791 /* RadioButtonModel.swift */,
01004F2F22721C3800991ECC /* RadioButton.swift */, 01004F2F22721C3800991ECC /* RadioButton.swift */,
EA7AE5462C73C01A00107C74 /* CheckboxesModel.swift */,
EA7AE5482C7403DC00107C74 /* Checkboxes.swift */,
31BE15CA23D8924C00452370 /* CheckboxModel.swift */, 31BE15CA23D8924C00452370 /* CheckboxModel.swift */,
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */,
AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */, AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */,
@ -2362,6 +2382,8 @@
EA7D815F2B2B6E6800D29F9E /* Icon.swift */, EA7D815F2B2B6E6800D29F9E /* Icon.swift */,
EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */, EA7D81632B2BABCB00D29F9E /* TooltipModel.swift */,
EA7D81652B2BABD200D29F9E /* Tooltip.swift */, EA7D81652B2BABD200D29F9E /* Tooltip.swift */,
EA7AE5502C74EB4500107C74 /* CalendarViewModel.swift */,
EA7AE54E2C74EB3700107C74 /* CalendarView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2513,6 +2535,8 @@
D2BEFEF5248A954C00FAB3A9 /* FormFields */ = { D2BEFEF5248A954C00FAB3A9 /* FormFields */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EA7AE5542C74F20600107C74 /* DatePickerEntryFieldModel.swift */,
EA7AE5522C74F1F600107C74 /* DatePickerEntryField.swift */,
EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */, EA5DBDAA2C35B6C500290DF8 /* FormFieldModel.swift */,
D2BEFEF6248A957A00FAB3A9 /* Tags */, D2BEFEF6248A957A00FAB3A9 /* Tags */,
D29DF22B21E6A0FA003B2FB9 /* TextFields */, D29DF22B21E6A0FA003B2FB9 /* TextFields */,
@ -2848,6 +2872,7 @@
AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */, AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */,
EAA482CE2B45F2F300978105 /* MFLoadingSpinner+VDS.swift in Sources */, EAA482CE2B45F2F300978105 /* MFLoadingSpinner+VDS.swift in Sources */,
D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */, D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */,
EA7AE54D2C74CAD700107C74 /* RadioButtonsModel.swift in Sources */,
D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */, D2D2FCF3252B72CF0033EAAA /* MoleculeSectionFooter.swift in Sources */,
0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */, 0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */,
D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */, D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */,
@ -2862,6 +2887,7 @@
EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */, EAB14BC127D935F00012AB2C /* RuleCompareModelProtocol.swift in Sources */,
011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */, 011D95A924057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift in Sources */,
EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */, EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */,
EA7AE5532C74F1F600107C74 /* DatePickerEntryField.swift in Sources */,
EA985C892981AB7100F2FF2E /* VDS-TextStyle.swift in Sources */, EA985C892981AB7100F2FF2E /* VDS-TextStyle.swift in Sources */,
BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */, BB2BF0EA2452A9BB001D0FC2 /* ListDeviceComplexButtonSmall.swift in Sources */,
D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */, D20C700B250BFDE40095B21C /* NotificationContainerView.swift in Sources */,
@ -3108,7 +3134,9 @@
AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */,
AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */, AFA4935729EE3DCC001A9663 /* AlertDelegateProtocol.swift in Sources */,
58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */, 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */,
EA7AE54B2C74CACA00107C74 /* RadioButtons.swift in Sources */,
D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */,
EA7AE5552C74F20600107C74 /* DatePickerEntryFieldModel.swift in Sources */,
D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */,
27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */, 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */,
EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */, EA6E8B972B504A4D000139B4 /* ButtonGroupModel.swift in Sources */,
@ -3265,6 +3293,7 @@
01F2C20327C81F9700DC3D36 /* SubNavManagerNavigationController.swift in Sources */, 01F2C20327C81F9700DC3D36 /* SubNavManagerNavigationController.swift in Sources */,
EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */, EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */,
AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */, AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */,
EA7AE5492C7403DC00107C74 /* Checkboxes.swift in Sources */,
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,
D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */, D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */,
D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */, D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */,
@ -3295,6 +3324,7 @@
BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */, BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */,
0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */,
D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */, D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */,
EA7AE5512C74EB4500107C74 /* CalendarViewModel.swift in Sources */,
AA104B1A24474A66004D2810 /* HeadersH2Buttons.swift in Sources */, AA104B1A24474A66004D2810 /* HeadersH2Buttons.swift in Sources */,
C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */,
D2D2FCF0252B72AF0033EAAA /* MoleculeSectionFooterModel.swift in Sources */, D2D2FCF0252B72AF0033EAAA /* MoleculeSectionFooterModel.swift in Sources */,
@ -3315,6 +3345,7 @@
D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */, D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */,
D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */, D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */,
D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */, D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */,
EA7AE5472C73C01A00107C74 /* CheckboxesModel.swift in Sources */,
8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */, 8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */,
D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */, D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */,
EA6642912BCDA97300D81DC4 /* TileContainer.swift in Sources */, EA6642912BCDA97300D81DC4 /* TileContainer.swift in Sources */,
@ -3355,6 +3386,7 @@
D2169301251E51E7002A6324 /* SectionListTemplate.swift in Sources */, D2169301251E51E7002A6324 /* SectionListTemplate.swift in Sources */,
0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */, 0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */,
D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */, D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */,
EA7AE54F2C74EB3700107C74 /* CalendarView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

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

@ -15,6 +15,8 @@ import VDS
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public class var identifier: String { "" } public class var identifier: String { "" }
public var moleculeName: String { Self.identifier }
public var id: String = UUID().uuidString public var id: String = UUID().uuidString
public var backgroundColor: Color? public var backgroundColor: Color?
@ -23,7 +25,7 @@ import VDS
public var enabled: Bool = true public var enabled: Bool = true
public var required: Bool = true public var required: Bool = true
public var readOnly: Bool = false public var readOnly: Bool = false
public var showError: Bool? public var showError: Bool = false
public var errorMessage: String? public var errorMessage: String?
public var initialErrorMessage: String? public var initialErrorMessage: String?
@ -66,6 +68,7 @@ import VDS
case moleculeName case moleculeName
case accessibilityIdentifier case accessibilityIdentifier
case errorMessage case errorMessage
case showError
case enabled case enabled
case readOnly case readOnly
case required case required
@ -107,6 +110,7 @@ import VDS
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage)
initialErrorMessage = errorMessage initialErrorMessage = errorMessage
showError = try typeContainer.decodeIfPresent(Bool.self, forKey: .showError) ?? false
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? true required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
@ -126,9 +130,30 @@ import VDS
try container.encodeIfPresent(errorMessage, forKey: .errorMessage) try container.encodeIfPresent(errorMessage, forKey: .errorMessage)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(showError, forKey: .showError)
try container.encode(readOnly, forKey: .readOnly) try container.encode(readOnly, forKey: .readOnly)
try container.encode(enabled, forKey: .enabled) try container.encode(enabled, forKey: .enabled)
try container.encode(required, forKey: .required) try container.encode(required, forKey: .required)
try container.encode(inverted, forKey: .inverted) 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

@ -146,7 +146,7 @@ open class ItemDropdownEntryField: VDS.DropdownSelect, VDSMoleculeViewProtocol,
isEnabled = viewModel.enabled isEnabled = viewModel.enabled
isReadOnly = viewModel.readOnly isReadOnly = viewModel.readOnly
isRequired = viewModel.required isRequired = viewModel.required
tooltipModel = viewModel.tooltip?.toVDSTooltipModel() tooltipModel = viewModel.tooltip?.convertToVDSTooltipModel()
width = viewModel.width width = viewModel.width
transparentBackground = viewModel.transparentBackground transparentBackground = viewModel.transparentBackground

View File

@ -78,4 +78,13 @@ import VDS
try container.encode(feedbackTextPlacement, forKey: .feedbackTextPlacement) try container.encode(feedbackTextPlacement, forKey: .feedbackTextPlacement)
try container.encodeModelIfPresent(action, forKey: .action) 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

@ -34,7 +34,6 @@ import Foundation
//-------------------------------------------------- //--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case backgroundColor
case title case title
case feedback case feedback
case errorTextColor case errorTextColor
@ -86,7 +85,6 @@ import Foundation
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
try super.init(from: decoder) try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
title = try typeContainer.decodeIfPresent(String.self, forKey: .title) title = try typeContainer.decodeIfPresent(String.self, forKey: .title)
feedback = try typeContainer.decodeIfPresent(String.self, forKey: .feedback) feedback = try typeContainer.decodeIfPresent(String.self, forKey: .feedback)
errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor) errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor)
@ -106,7 +104,6 @@ import Foundation
open override func encode(to encoder: Encoder) throws { open override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder) try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(title, forKey: .title) try container.encodeIfPresent(title, forKey: .title)
try container.encodeIfPresent(feedback, forKey: .feedback) try container.encodeIfPresent(feedback, forKey: .feedback)
try container.encodeIfPresent(text, forKey: .text) try container.encodeIfPresent(text, forKey: .text)
@ -116,4 +113,16 @@ import Foundation
try container.encode(hideBorders, forKey: .hideBorders) try container.encode(hideBorders, forKey: .hideBorders)
try container.encode(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) 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

@ -173,7 +173,7 @@ import VDS
isEnabled = viewModel.enabled isEnabled = viewModel.enabled
isReadOnly = viewModel.readOnly isReadOnly = viewModel.readOnly
isRequired = viewModel.required isRequired = viewModel.required
tooltipModel = viewModel.tooltip?.toVDSTooltipModel() tooltipModel = viewModel.tooltip?.convertToVDSTooltipModel()
width = viewModel.width width = viewModel.width
transparentBackground = viewModel.transparentBackground transparentBackground = viewModel.transparentBackground

View File

@ -218,4 +218,21 @@ import VDS
try container.encode(transparentBackground, forKey: .transparentBackground) try container.encode(transparentBackground, forKey: .transparentBackground)
try container.encodeIfPresent(width, forKey: .width) 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

@ -126,7 +126,7 @@ open class TextViewEntryField: VDS.TextArea, VDSMoleculeViewProtocol, ObservingT
isEnabled = viewModel.enabled isEnabled = viewModel.enabled
isReadOnly = viewModel.readOnly isReadOnly = viewModel.readOnly
isRequired = viewModel.required isRequired = viewModel.required
tooltipModel = viewModel.tooltip?.toVDSTooltipModel() tooltipModel = viewModel.tooltip?.convertToVDSTooltipModel()
width = viewModel.width width = viewModel.width
transparentBackground = viewModel.transparentBackground transparentBackground = viewModel.transparentBackground

View File

@ -21,7 +21,7 @@ public class TextViewEntryFieldModel: TextEntryFieldModel {
public var showsPlaceholder: Bool = false public var showsPlaceholder: Bool = false
public var minHeight: VDS.TextArea.Height = .twoX public var minHeight: VDS.TextArea.Height = .twoX
public var maxLength: Int? public var maxLength: Int?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Keys // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------
@ -55,4 +55,13 @@ public class TextViewEntryFieldModel: TextEntryFieldModel {
try container.encode(minHeight, forKey: .minHeight) try container.encode(minHeight, forKey: .minHeight)
try container.encodeIfPresent(maxLength, forKey: .maxLength) 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

@ -131,7 +131,7 @@ import VDS
} }
//properties //properties
isEnabled = viewModel.enabled && !viewModel.readOnly isEnabled = viewModel.isEnabled
isAnimated = viewModel.animated isAnimated = viewModel.animated
//call super here to go around the didSet //call super here to go around the didSet

View File

@ -94,4 +94,12 @@ import VDS
try container.encodeModelIfPresent(action, forKey: .action) try container.encodeModelIfPresent(action, forKey: .action)
try container.encodeModelIfPresent(offAction, forKey: .offAction) 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,55 @@
//
// 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() {
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

@ -8,47 +8,33 @@
import MVMCore import MVMCore
import VDS import VDS
@objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol { public class RadioBoxModel: FormFieldModel {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public static var identifier: String = "radioBox" public override static var identifier: String { "radioBox" }
public var id: String = UUID().uuidString
public var text: String public var text: String
public var subText: String? public var subText: String?
public var subTextRight: String? public var subTextRight: String?
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var selected: Bool = false public var selected: Bool = false
public var enabled: Bool = true
public var readOnly: Bool = false
public var strikethrough: Bool = false public var strikethrough: Bool = false
public var fieldValue: String?
public var action: ActionModelProtocol? public var action: ActionModelProtocol?
public var inverted: Bool = false public var fieldValue: String?
public var surface: Surface { inverted ? .dark : .light }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Keys // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case text case text
case subText case subText
case subTextRight case subTextRight
case backgroundColor
case accessibilityIdentifier
case selected case selected
case enabled
case strikethrough case strikethrough
case fieldValue
case action case action
case readOnly case fieldValue
case inverted
} }
//-------------------------------------------------- //--------------------------------------------------
@ -57,8 +43,19 @@ import VDS
public init(text: String) { public init(text: String) {
self.text = text 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 // MARK: - Codec
//-------------------------------------------------- //--------------------------------------------------
@ -66,45 +63,42 @@ import VDS
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
text = try typeContainer.decode(String.self, forKey: .text) text = try typeContainer.decode(String.self, forKey: .text)
subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText) subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText)
subTextRight = try typeContainer.decodeIfPresent(String.self, forKey: .subTextRight) subTextRight = try typeContainer.decodeIfPresent(String.self, forKey: .subTextRight)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
selected = isSelected 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) { if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) {
strikethrough = isStrikeTrough strikethrough = isStrikeTrough
} }
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
self.inverted = inverted
}
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
action = try typeContainer.decodeModelIfPresent(codingKey: .action) 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) 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.encode(text, forKey: .text)
try container.encodeIfPresent(subText, forKey: .subText) try container.encodeIfPresent(subText, forKey: .subText)
try container.encodeIfPresent(subTextRight, forKey: .subTextRight) try container.encodeIfPresent(subTextRight, forKey: .subTextRight)
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(selected, forKey: .selected) 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.encode(strikethrough, forKey: .strikethrough)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
try container.encodeModelIfPresent(action, forKey: .action) try container.encodeModelIfPresent(action, forKey: .action)
try container.encode(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 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

@ -9,10 +9,6 @@
import Foundation import Foundation
import VDS import VDS
public protocol RadioBoxSelectionDelegate: AnyObject {
func selected(radioBox: RadioBoxModel)
}
open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol { open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol {
//------------------------------------------------------ //------------------------------------------------------
@ -29,13 +25,12 @@ open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol {
/// The models for the molecules. /// The models for the molecules.
public var boxes: [RadioBoxModel]? public var boxes: [RadioBoxModel]?
public weak var radioDelegate: RadioBoxSelectionDelegate?
// MARK: - MoleculeViewProtocol // MARK: - MoleculeViewProtocol
public func viewModelDidUpdate() { public func viewModelDidUpdate() {
boxes = viewModel.boxes boxes = viewModel.boxes
surface = viewModel.surface surface = viewModel.surface
selectorModels = viewModel.selectorModels selectorModels = viewModel.boxes.convertToVDSRadioBoxModel(surface: surface)
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
} }
@ -50,7 +45,8 @@ open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol {
boxes?.forEach { $0.selected = false } boxes?.forEach { $0.selected = false }
selectedBox.selected = true selectedBox.selected = true
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
radioDelegate?.selected(radioBox: selectedBox)
} }
} }
} }

View File

@ -8,7 +8,8 @@
import MVMCore import MVMCore
import VDS import VDS
@objcMembers public class RadioBoxesModel: FormFieldModel { public class RadioBoxesModel: FormFieldModel, ParentMoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
@ -16,20 +17,9 @@ import VDS
public override static var identifier: String { "radioBoxes" } public override static var identifier: String { "radioBoxes" }
public var boxes: [RadioBoxModel] public var boxes: [RadioBoxModel]
public var children: [any MoleculeModelProtocol] { boxes }
public var selectorModels: [VDS.RadioBoxGroup.RadioBoxItemModel] {
boxes.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.disabled = !(item.enabled && !item.readOnly)
return radioBox
})
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Form Validation // MARK: - Form Validation
//-------------------------------------------------- //--------------------------------------------------
@ -40,7 +30,7 @@ import VDS
let selectedBox = boxes.first { (box) -> Bool in let selectedBox = boxes.first { (box) -> Bool in
return box.selected return box.selected
} }
return selectedBox?.fieldValue ?? selectedBox?.text return selectedBox?.formFieldValue() ?? selectedBox?.text
} }
//-------------------------------------------------- //--------------------------------------------------
@ -83,3 +73,19 @@ import VDS
try container.encode(boxes, forKey: .boxes) try container.encode(boxes, forKey: .boxes)
} }
} }
extension Array where Element == RadioBoxModel {
internal func convertToVDSRadioBoxModel(surface: Surface) -> [RadioBoxGroup.RadioBoxItemModel] {
compactMap({ item in
var radioBox = RadioBoxGroup.RadioBoxItemModel()
radioBox.text = item.text
radioBox.subText = item.subText
radioBox.subTextRight = item.subTextRight
radioBox.surface = surface
radioBox.selected = item.selected
radioBox.strikethrough = item.strikethrough
radioBox.enabled = item.isEnabled
return radioBox
})
}
}

View File

@ -101,7 +101,7 @@ import VDS
} }
isSelected = viewModel.state isSelected = viewModel.state
isEnabled = viewModel.enabled && !viewModel.readOnly isEnabled = viewModel.isEnabled
RadioButtonSelectionHelper.setupForRadioButtonGroup(viewModel, self, delegateObject: delegateObject) RadioButtonSelectionHelper.setupForRadioButtonGroup(viewModel, self, delegateObject: delegateObject)
} }

View File

@ -83,4 +83,11 @@ open class RadioButtonModel: FormFieldModel {
try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
try container.encodeModelIfPresent(action, forKey: .action) try container.encodeModelIfPresent(action, forKey: .action)
} }
open override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return state == model.state
&& fieldValue == model.fieldValue
&& action.isEqual(to: model.action)
}
} }

View File

@ -0,0 +1,54 @@
//
// 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() {
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

@ -108,7 +108,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
isOn = viewModel.selected isOn = viewModel.selected
surface = viewModel.surface surface = viewModel.surface
isAnimated = viewModel.animated isAnimated = viewModel.animated
isEnabled = viewModel.enabled && !viewModel.readOnly isEnabled = viewModel.isEnabled
showText = viewModel.showText showText = viewModel.showText
if let onText = viewModel.onText { if let onText = viewModel.onText {
self.onText = onText self.onText = onText

View File

@ -7,26 +7,18 @@
// //
import VDS import VDS
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol { public class ToggleModel: FormFieldModel {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public static var identifier: String = "toggle" public override static var identifier: String { "toggle" }
public var id: String = UUID().uuidString
public var accessibilityIdentifier: String?
public var backgroundColor: Color?
public var selected: Bool = false public var selected: Bool = false
public var animated: Bool = true public var animated: Bool = true
public var enabled: Bool = true
public var readOnly: Bool = false
public var action: ActionModelProtocol? public var action: ActionModelProtocol?
public var alternateAction: ActionModelProtocol? public var alternateAction: ActionModelProtocol?
public var accessibilityText: String? public var accessibilityText: String?
public var surface: Surface { inverted ? .dark : .light }
public var inverted: Bool = false
public var showText: Bool = false public var showText: Bool = false
public var onText: String? public var onText: String?
public var offText: String? public var offText: String?
@ -34,61 +26,41 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
public var textWeight: VDS.Toggle.TextWeight = .regular public var textWeight: VDS.Toggle.TextWeight = .regular
public var textPosition: VDS.Toggle.TextPosition = .left public var textPosition: VDS.Toggle.TextPosition = .left
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Keys // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case state case state
case animated case animated
case enabled
case readOnly
case action case action
case backgroundColor
case accessibilityIdentifier
case alternateAction case alternateAction
case accessibilityText case accessibilityText
case inverted
case showText case showText
case onText case onText
case offText case offText
case textSize case textSize
case textWeight case textWeight
case textPosition case textPosition
case fieldKey
case groupName
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Form Valdiation // MARK: - Form Valdiation
//-------------------------------------------------- //--------------------------------------------------
public func formFieldValue() -> AnyHashable? { public override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil } guard enabled else { return nil }
return selected return selected
} }
//--------------------------------------------------
// MARK: - Server Value
//--------------------------------------------------
open func formFieldServerValue() -> AnyHashable? {
return formFieldValue()
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initializer // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------
public init(_ state: Bool, id: String = UUID().uuidString) { public init(_ state: Bool, id: String = UUID().uuidString) {
self.selected = state selected = state
super.init()
baseValue = state baseValue = state
self.id = id self.id = id
} }
@ -100,8 +72,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
self.selected = state self.selected = state
} }
@ -112,44 +82,28 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
action = try typeContainer.decodeModelIfPresent(codingKey: .action) action = try typeContainer.decodeModelIfPresent(codingKey: .action)
alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction) alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
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
inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) ?? false
showText = try typeContainer.decodeIfPresent(Bool.self, forKey: .showText) ?? false showText = try typeContainer.decodeIfPresent(Bool.self, forKey: .showText) ?? false
onText = try typeContainer.decodeIfPresent(String.self, forKey: .onText) onText = try typeContainer.decodeIfPresent(String.self, forKey: .onText)
offText = try typeContainer.decodeIfPresent(String.self, forKey: .offText) offText = try typeContainer.decodeIfPresent(String.self, forKey: .offText)
textSize = try typeContainer.decodeIfPresent(VDS.Toggle.TextSize.self, forKey: .textSize) ?? .small textSize = try typeContainer.decodeIfPresent(VDS.Toggle.TextSize.self, forKey: .textSize) ?? .small
textWeight = try typeContainer.decodeIfPresent(VDS.Toggle.TextWeight.self, forKey: .textWeight) ?? .regular textWeight = try typeContainer.decodeIfPresent(VDS.Toggle.TextWeight.self, forKey: .textWeight) ?? .regular
textPosition = try typeContainer.decodeIfPresent(VDS.Toggle.TextPosition.self, forKey: .textPosition) ?? .left textPosition = try typeContainer.decodeIfPresent(VDS.Toggle.TextPosition.self, forKey: .textPosition) ?? .left
try super.init(from: decoder)
baseValue = selected
} }
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) 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(action, forKey: .action)
try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction) try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(selected, forKey: .state) try container.encode(selected, forKey: .state)
try container.encode(animated, forKey: .animated) try container.encode(animated, forKey: .animated)
try container.encode(enabled, forKey: .enabled)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) 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(inverted, forKey: .inverted)
try container.encode(showText, forKey: .showText) try container.encode(showText, forKey: .showText)
try container.encodeIfPresent(onText, forKey: .onText) try container.encodeIfPresent(onText, forKey: .onText)
try container.encodeIfPresent(offText, forKey: .offText) try container.encodeIfPresent(offText, forKey: .offText)
@ -157,4 +111,19 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
try container.encode(textWeight, forKey: .textWeight) try container.encode(textWeight, forKey: .textWeight)
try container.encode(textPosition, forKey: .textPosition) 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

@ -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,62 @@
//
// 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 _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,153 @@
//
// 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 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 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
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(id, forKey: .id)
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

@ -64,7 +64,7 @@ import VDS
//properties //properties
isAnimated = viewModel.checkbox.animated isAnimated = viewModel.checkbox.animated
isEnabled = viewModel.checkbox.enabled && !viewModel.checkbox.readOnly isEnabled = viewModel.checkbox.isEnabled
//call super here to go around the didSet //call super here to go around the didSet
//in this class //in this class
@ -78,7 +78,7 @@ import VDS
//TODO: Fix issue with default state //TODO: Fix issue with default state
//showError = !isValid //showError = !isValid
errorText = viewModel.checkbox.errorMessage errorText = viewModel.checkbox.errorMessage
isEnabled = viewModel.checkbox.enabled isEnabled = viewModel.checkbox.isEnabled
}) })
} }

View File

@ -36,5 +36,36 @@ import VDS
self.label = label self.label = label
self.subTitle = subTitle 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

@ -98,7 +98,7 @@ open class TooltipModel: MoleculeModelProtocol {
} }
extension TooltipModel { extension TooltipModel {
public func toVDSTooltipModel() -> Tooltip.TooltipModel { public func convertToVDSTooltipModel() -> Tooltip.TooltipModel {
var moleculeView: MoleculeViewProtocol? var moleculeView: MoleculeViewProtocol?
if let molecule, let view = ModelRegistry.createMolecule(molecule) { if let molecule, let view = ModelRegistry.createMolecule(molecule) {
moleculeView = view moleculeView = view

View File

@ -9,6 +9,7 @@
import Foundation import Foundation
import VDS import VDS
import VDSCoreTokens import VDSCoreTokens
import MVMCore
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Codable Extensions // MARK: - Codable Extensions
@ -57,6 +58,52 @@ extension VDS.Button.Size: RawRepresentableCodable {
public static var defaultValue: VDS.Button.Size? { nil } 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 // MARK: - Decodable Defaults
//-------------------------------------------------- //--------------------------------------------------

View File

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

View File

@ -106,7 +106,7 @@ import VDS
} }
//properties //properties
isEnabled = viewModel.radioButton.enabled && !viewModel.radioButton.readOnly isEnabled = viewModel.radioButton.isEnabled
isSelected = viewModel.radioButton.state isSelected = viewModel.radioButton.state
//forms //forms

View File

@ -40,5 +40,36 @@ import VDS
self.label = label self.label = label
self.subTitle = subTitle 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 == RadioButtonLabelModel {
internal func convertToVDSRadioButtonItemModel(surface: Surface,
delegateObject: MVMCoreUIDelegateObject?,
additionalData: [AnyHashable: Any]?) -> [RadioButtonGroup.RadioButtonItemModel] {
return compactMap({ model in
var item = RadioButtonGroup.RadioButtonItemModel()
item.inputId = model.radioButton.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.radioButton.state
item.enabled = model.radioButton.isEnabled
return item
})
}
} }

View File

@ -70,3 +70,43 @@ public extension Optional where Wrapped: Collection {
return self.isVisuallyEquivalent(to: models) return self.isVisuallyEquivalent(to: models)
} }
} }
func == (lhs: (any MoleculeModelComparisonProtocol)?, rhs: (any MoleculeModelComparisonProtocol)?) -> Bool {
switch (lhs, rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs.isEqual(to: rhs)
case (.none, .none):
return true
default:
return false
}
}
func != (lhs: (any MoleculeModelComparisonProtocol)?, rhs: (any MoleculeModelComparisonProtocol)?) -> Bool {
return !(lhs == rhs)
}
func == (lhs: [any MoleculeModelComparisonProtocol]?, rhs: [any MoleculeModelComparisonProtocol]?) -> Bool {
switch (lhs, rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs == rhs
case (.none, .none):
return true
default:
return false
}
}
func != (lhs: [any MoleculeModelComparisonProtocol]?, rhs: [any MoleculeModelComparisonProtocol]?) -> Bool {
return !(lhs == rhs)
}
func == (lhs: [any MoleculeModelComparisonProtocol], rhs: [any MoleculeModelComparisonProtocol]) -> Bool {
return lhs.elementsEqual(rhs, by: { (lhsElement, rhsElement) -> Bool in
return lhsElement == rhsElement
})
}
func != (lhs: [any MoleculeModelComparisonProtocol], rhs: [any MoleculeModelComparisonProtocol]) -> Bool {
return !(lhs == rhs)
}

View File

@ -14,10 +14,12 @@ import MVMCore
///----------------------------------------------------------------------------- ///-----------------------------------------------------------------------------
public protocol VDSMoleculeViewProtocol: MoleculeViewProtocol, MVMCoreViewProtocol { public protocol VDSMoleculeViewProtocol: MoleculeViewProtocol, MVMCoreViewProtocol {
associatedtype ViewModel: MoleculeModelProtocol associatedtype ViewModel: MoleculeModelProtocol
var shouldUpdateView: Bool { get set }
var viewModel: ViewModel! { get set } var viewModel: ViewModel! { get set }
var delegateObject: MVMCoreUIDelegateObject? { get set } var delegateObject: MVMCoreUIDelegateObject? { get set }
var additionalData: [AnyHashable: Any]? { get set } var additionalData: [AnyHashable: Any]? { get set }
func viewModelDidUpdate() func viewModelDidUpdate()
func setNeedsUpdate()
} }
extension VDSMoleculeViewProtocol { extension VDSMoleculeViewProtocol {
@ -32,7 +34,10 @@ extension VDSMoleculeViewProtocol {
self.delegateObject = delegateObject self.delegateObject = delegateObject
self.additionalData = additionalData self.additionalData = additionalData
viewModel = castedModel viewModel = castedModel
shouldUpdateView = false
viewModelDidUpdate() viewModelDidUpdate()
shouldUpdateView = true
setNeedsUpdate()
} }
public func update(viewModel: ViewModel){ public func update(viewModel: ViewModel){

View File

@ -41,6 +41,7 @@ open class CoreUIModelMapping: ModelMapping {
ModelRegistry.register(handler: ButtonGroup.self, for: ButtonGroupModel.self) ModelRegistry.register(handler: ButtonGroup.self, for: ButtonGroupModel.self)
// MARK:- Entry Field // MARK:- Entry Field
ModelRegistry.register(handler: DatePickerEntryField.self, for: DatePickerEntryFieldModel.self)
ModelRegistry.register(handler: InputEntryField.self, for: TextEntryFieldModel.self) ModelRegistry.register(handler: InputEntryField.self, for: TextEntryFieldModel.self)
ModelRegistry.register(handler: MdnEntryField.self, for: MdnEntryFieldModel.self) ModelRegistry.register(handler: MdnEntryField.self, for: MdnEntryFieldModel.self)
ModelRegistry.register(handler: DigitEntryField.self, for: DigitEntryFieldModel.self) ModelRegistry.register(handler: DigitEntryField.self, for: DigitEntryFieldModel.self)
@ -50,8 +51,10 @@ open class CoreUIModelMapping: ModelMapping {
// MARK:- Selectors // MARK:- Selectors
ModelRegistry.register(handler: RadioButton.self, for: RadioButtonModel.self) ModelRegistry.register(handler: RadioButton.self, for: RadioButtonModel.self)
ModelRegistry.register(handler: RadioButtons.self, for: RadioButtonsModel.self)
ModelRegistry.register(handler: RadioBoxes.self, for: RadioBoxesModel.self) ModelRegistry.register(handler: RadioBoxes.self, for: RadioBoxesModel.self)
ModelRegistry.register(handler: Checkbox.self, for: CheckboxModel.self) ModelRegistry.register(handler: Checkbox.self, for: CheckboxModel.self)
ModelRegistry.register(handler: Checkboxes.self, for: CheckboxesModel.self)
ModelRegistry.register(handler: RadioSwatches.self, for: RadioSwatchesModel.self) ModelRegistry.register(handler: RadioSwatches.self, for: RadioSwatchesModel.self)
ModelRegistry.register(handler: Tags.self, for: TagsModel.self) ModelRegistry.register(handler: Tags.self, for: TagsModel.self)
ModelRegistry.register(handler: Tag.self, for: TagModel.self) ModelRegistry.register(handler: Tag.self, for: TagModel.self)
@ -60,6 +63,7 @@ open class CoreUIModelMapping: ModelMapping {
ModelRegistry.register(handler: Star.self, for: StarModel.self) ModelRegistry.register(handler: Star.self, for: StarModel.self)
// MARK:- Other Atoms // MARK:- Other Atoms
ModelRegistry.register(handler: CalendarView.self, for: CalendarViewModel.self)
ModelRegistry.register(handler: ProgressBar.self, for: ProgressBarModel.self) ModelRegistry.register(handler: ProgressBar.self, for: ProgressBarModel.self)
ModelRegistry.register(handler: MultiProgress.self, for: MultiProgressBarModel.self) ModelRegistry.register(handler: MultiProgress.self, for: MultiProgressBarModel.self)
ModelRegistry.register(handler: CaretView.self, for: CaretViewModel.self) ModelRegistry.register(handler: CaretView.self, for: CaretViewModel.self)