diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 62792baf..36641951 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -172,6 +172,7 @@ 5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; }; 5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; }; 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; }; + 58E7561D2BE04C320088BB5D /* MoleculeComparisonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */; }; 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; }; @@ -780,6 +781,7 @@ 5878F0A42BD7E68800ADE23D /* mvmcoreui.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui.xcconfig; sourceTree = ""; }; 5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui_dev.xcconfig; sourceTree = ""; }; 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = ""; }; + 58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeComparisonProtocol.swift; sourceTree = ""; }; 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = ""; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = ""; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = ""; }; @@ -1254,6 +1256,7 @@ D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */, 27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */, 27577DCC286CA959001EC47E /* MoleculeMaskingProtocol.swift */, + 58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */, ); path = ModelProtocols; sourceTree = ""; @@ -3010,6 +3013,7 @@ EA1758482BC97ED800A5C0D9 /* BadgeIndicator.swift in Sources */, 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */, 0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */, + 58E7561D2BE04C320088BB5D /* MoleculeComparisonProtocol.swift in Sources */, D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */, AF1C336928859778006B1001 /* ActionAlertHandler.swift in Sources */, 9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index dec3480c..6812424b 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -186,4 +186,20 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) try container.encodeIfPresent(disabledAccessibilityTraits, forKey: .disabledAccessibilityTraits) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return title == model.title + && enabled == model.enabled + && inverted == model.inverted + && action.isEqual(to: model.action) + && accessibilityText == model.accessibilityText + && accessibilityIdentifier == model.accessibilityIdentifier + && style == model.style + && size == model.size + && groupName == model.groupName + && width == model.width + && accessibilityTraits == model.accessibilityTraits + && disabledAccessibilityTraits == model.disabledAccessibilityTraits + } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift index 6d293a34..134a42b9 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ImageButtonModel.swift @@ -33,7 +33,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro [image].compactMap({$0}) } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(at: &image, with: molecule) } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift index 14e041e5..38cbe673 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift @@ -99,6 +99,19 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode try container.encodeIfPresent(size, forKey: .size) try container.encode(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && title == model.title + && accessibilityText == model.accessibilityText + && accessibilityIdentifier == model.accessibilityIdentifier + && inverted == model.inverted + && enabled == model.enabled + && size == model.size + && shouldMaskRecordedView == model.shouldMaskRecordedView +// && action.isEqual(to: model.action) // TODO: Move to isVisiuallyEquivalent. + } } extension LinkModel { diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift index 3631bfc0..1bc399bb 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift @@ -130,4 +130,20 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro try container.encode(indicatorColor, forKey: .indicatorColor) try container.encodeIfPresent(position, forKey: .position) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && numberOfPages == model.numberOfPages + && currentIndex == model.currentIndex + && alwaysSendAction == model.alwaysSendAction + && animated == model.animated + && hidesForSinglePage == model.hidesForSinglePage + && accessibilityHasSlidesInsteadOfPage == model.accessibilityHasSlidesInsteadOfPage + && enabled == model.enabled + && inverted == model.inverted + && disabledIndicatorColor == model.disabledIndicatorColor + && indicatorColor == model.indicatorColor + && position == model.position + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift index 1ecbe874..e65af621 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift @@ -48,4 +48,9 @@ open class LabelAttributeActionModel: LabelAttributeModel { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModel(action, forKey: .action) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return action.isEqual(to: model.action) + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift index 8fa1aa58..68a59d4d 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift @@ -43,4 +43,9 @@ var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(textColor, forKey: .textColor) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return textColor == model.textColor + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift index 6a2a1af5..931c0d63 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift @@ -55,4 +55,11 @@ try container.encodeIfPresent(name, forKey: .name) try container.encodeIfPresent(size, forKey: .size) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return style == model.style + && name == model.name + && size == model.size + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift index 03d0ff08..0dceaf7d 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift @@ -69,4 +69,12 @@ class LabelAttributeImageModel: LabelAttributeModel { try container.encodeIfPresent(URL, forKey: .URL) try container.encodeIfPresent(tintColor, forKey: .tintColor) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return URL == model.URL + && name == model.name + && size == model.size + && tintColor == model.tintColor + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift index 88192720..35ad5700 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift @@ -75,4 +75,10 @@ try container.encode(location, forKey: .location) try container.encode(length, forKey: .length) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return location == model.location + && length == model.length + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeUnderlineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeUnderlineModel.swift index f3578418..b2a226e5 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeUnderlineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeUnderlineModel.swift @@ -66,6 +66,13 @@ import UIKit try container.encode(style, forKey: .style) try container.encodeIfPresent(pattern, forKey: .pattern) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return style == model.style + && color == model.color + && pattern == model.pattern + } } public enum UnderlineStyle: String, Codable { diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index fe37a2c0..d3611a2e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -132,6 +132,26 @@ import VDS try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && text == model.text + && accessibilityText == model.accessibilityText + && textColor == model.textColor + && fontStyle == model.fontStyle + && fontName == model.fontName + && fontSize == model.fontSize + && textAlignment == model.textAlignment + && attributes.areEqual(to: model.attributes) + && html == model.html + && hero == model.hero + && makeWholeViewClickable == model.makeWholeViewClickable + && numberOfLines == model.numberOfLines + && shouldMaskRecordedView == model.shouldMaskRecordedView + && accessibilityTraits == model.accessibilityTraits + && inverted == inverted + } } extension LabelModel { diff --git a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift index 2cfe9b99..7c3e66ec 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift @@ -129,4 +129,12 @@ public class LineModel: MoleculeModelProtocol, Invertable { try container.encodeIfPresent(frequency, forKey: .frequency) try container.encode(orientation == .vertical, forKey: .useVerticalLine) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return type == model.type + && inverted == model.inverted + && frequency == model.frequency + && orientation == model.orientation + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift index 895bb027..c60ac2ef 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/TileContainerModel.swift @@ -24,7 +24,7 @@ open class TileContainerModel: TileContainerBaseModel Bool { + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(at: &self.molecule, with: molecule) } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift index 0cf8410f..901cf057 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1ButtonModel.swift @@ -21,9 +21,13 @@ public class HeadersH1ButtonModel: HeaderModel, MoleculeModelProtocol, ParentMol [titleLockup, buttons] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &titleLockup, with: molecule) - || replaceChildMolecule(at: &buttons, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift index 651a7f89..995cb482 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1LandingPageHeaderModel.swift @@ -24,13 +24,17 @@ public class HeadersH1LandingPageHeaderModel: HeaderModel, MoleculeModelProtocol [headline, headline2, subHeadline, body, link, buttons] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &headline, with: molecule) - || replaceChildMolecule(at: &headline2, with: molecule) - || replaceChildMolecule(at: &subHeadline, with: molecule) - || replaceChildMolecule(at: &body, with: molecule) - || replaceChildMolecule(at: &link, with: molecule) - || replaceChildMolecule(at: &buttons, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &headline2, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &subHeadline, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift index 7450c9aa..9fec7e5b 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H1/HeadersH1NoButtonsBodyTextModel.swift @@ -19,7 +19,7 @@ public class HeadersH1NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol [titleLockup] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(at: &titleLockup, with: molecule) } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift index 9590fefc..6d9bef90 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2ButtonsModel.swift @@ -23,9 +23,13 @@ public class HeadersH2ButtonsModel: HeaderModel, MoleculeModelProtocol, ParentMo [titleLockup, buttons] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &titleLockup, with: molecule) - || replaceChildMolecule(at: &buttons, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift index 784966cf..1cb89f84 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2CaretLinkModel.swift @@ -20,9 +20,13 @@ public class HeadersH2CaretLinkModel: HeaderModel, MoleculeModelProtocol, Parent [titleLockup, caretLink] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &titleLockup, with: molecule) - || replaceChildMolecule(at: &caretLink, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &caretLink, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift index 0a104f9f..8ff7df5e 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2LinkModel.swift @@ -8,7 +8,7 @@ import Foundation -public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMoleculeModelProtocol { +public class HeadersH2LinkModel: HeaderModel, ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -22,9 +22,13 @@ public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMolec [titleLockup, link] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &titleLockup, with: molecule) - || replaceChildMolecule(at: &link, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift index 671e5c0b..e18aafe6 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2NoButtonsBodyTextModel.swift @@ -22,7 +22,7 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol [titleLockup] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(at: &titleLockup, with: molecule) } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift index 54a62b5b..b92e04d1 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2PricingTwoRowsModel.swift @@ -25,15 +25,19 @@ public class HeadersH2PricingTwoRowsModel: HeaderModel, MoleculeModelProtocol, P [headline, body, subBody, body2, subBody2, body3, subBody3].compactMap({$0}) } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &headline, with: molecule) - || replaceChildMolecule(at: &body, with: molecule) - || replaceChildMolecule(at: &subBody, with: molecule) - || replaceChildMolecule(at: &body2, with: molecule) - || replaceChildMolecule(at: &body2, with: molecule) - || replaceChildMolecule(at: &subBody2, with: molecule) - || replaceChildMolecule(at: &body3, with: molecule) - || replaceChildMolecule(at: &subBody3, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &subBody, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body2, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body2, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &subBody2, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body3, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &subBody3, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift index aba4e48b..a13c6b9e 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/H2/HeadersH2TinyButtonModel.swift @@ -23,9 +23,13 @@ public class HeadersH2TinyButtonModel: HeaderModel, MoleculeModelProtocol, Paren [titleLockup, button] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &titleLockup, with: molecule) - || replaceChildMolecule(at: &button, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &button, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift index ba7d4986..6e543477 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift @@ -20,9 +20,13 @@ open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelPr [checkbox, headlineBody] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &checkbox, with: molecule) - || replaceChildMolecule(at: &headlineBody, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &checkbox, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &headlineBody, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift index c7a32e78..1a18f676 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift @@ -20,9 +20,13 @@ public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModel return [image, eyebrowHeadlineBodyLink] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &image, with: molecule) - || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift index 9d5ce50b..af465e48 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretAllTextLinksModel.swift @@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel, return [image, eyebrowHeadlineBodyLink, rightLabel] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &image, with: molecule) - || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) - || replaceChildMolecule(at: &rightLabel, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift index 6b95c669..4bd6c0ed 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift @@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Par [image, headlineBody, rightLabel] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &image, with: molecule) - || replaceChildMolecule(at: &headlineBody, with: molecule) - || replaceChildMolecule(at: &rightLabel, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &headlineBody, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift index a804b8d4..08d70edb 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift @@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, ParentMolec return [image, leftLabel, rightLabel] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &image, with: molecule) - || replaceChildMolecule(at: &leftLabel, with: molecule) - || replaceChildMolecule(at: &rightLabel, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &leftLabel, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift index 808ee19e..167574cb 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonBodyTextModel.swift @@ -20,9 +20,13 @@ open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, ParentMolecu [radioButton, headlineBody] } - public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &radioButton, with: replacementMolecule) - || replaceChildMolecule(at: &headlineBody, with: replacementMolecule) + public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &radioButton, with: replacementMolecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &headlineBody, with: replacementMolecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift index aee85f74..3e4c66bf 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift @@ -39,7 +39,7 @@ public class ListOneColumnFullWidthTextBodyTextModel: ListItemModel, MoleculeMod return [headlineBody] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(at: &headlineBody, with: molecule) } @@ -68,4 +68,9 @@ public class ListOneColumnFullWidthTextBodyTextModel: ListItemModel, MoleculeMod try container.encode(moleculeName, forKey: .moleculeName) try container.encode(headlineBody, forKey: .headlineBody) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return headlineBody.isEqual(to: headlineBody) + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift index 3d68a8ff..bcace0b3 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableButtonAllTextAndLinksModel.swift @@ -40,9 +40,13 @@ public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, Molecul return [button, eyebrowHeadlineBodyLink] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &button, with: molecule) - || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &button, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift index 94d65c37..ca777e98 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariableRightCaretAlltextAndLinksModel.swift @@ -20,9 +20,13 @@ public class ListRightVariableRightCaretAllTextAndLinksModel: ListItemModel, Par [rightLabel, eyebrowHeadlineBodyLink] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &rightLabel, with: molecule) - || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //----------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift index a280e0b4..a9ae8602 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift @@ -9,7 +9,7 @@ import VDSTokens import VDS -public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { +public class TitleLockupModel: ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties @@ -34,10 +34,24 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco [eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &eyebrow, with: molecule) - || replaceChildMolecule(at: &title, with: molecule) - || replaceChildMolecule(at: &subTitle, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &eyebrow, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &title, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &subTitle, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil + } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return textAlignment == model.textAlignment + && subTitleColor == model.subTitleColor + && alignment == model.alignment + && inverted == model.inverted + && backgroundColor == model.backgroundColor + && children.areEqual(to: model.children) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift index af226405..6fea470f 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnFullWidthTextDividerSubsectionModel.swift @@ -22,9 +22,13 @@ public class ListOneColumnFullWidthTextDividerSubsectionModel: ListItemModel, Mo [headline, body].compactMap({$0}) } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &headline, with: molecule) - || replaceChildMolecule(at: &body, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift index 596b9ff0..141641e5 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerShortModel.swift @@ -22,9 +22,13 @@ public class ListOneColumnTextWithWhitespaceDividerShortModel: ListItemModel, Mo [headline, body].compactMap({$0}) } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &headline, with: molecule) - || replaceChildMolecule(at: &body, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift index ea4c3ce2..74fd2965 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/OneColumn/ListOneColumnTextWithWhitespaceDividerTallModel.swift @@ -22,9 +22,13 @@ public class ListOneColumnTextWithWhitespaceDividerTallModel: ListItemModel, Mol [headline, body].compactMap({$0}) } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &headline, with: molecule) - || replaceChildMolecule(at: &body, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift index 0ed1db11..dc471e0a 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -23,9 +23,13 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol { public var children: [MoleculeModelProtocol] { [primaryButton, secondaryButton].compactMap { $0 } } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &primaryButton, with: molecule) - || replaceChildMolecule(at: &secondaryButton, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &primaryButton, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &secondaryButton, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index 79fe22ab..c412b09c 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -85,8 +85,19 @@ try container.encodeIfPresent(peakingUI, forKey: .peakingUI) try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) try container.encodeIfPresent(analyticsData, forKey: .analyticsData) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encode(enabled, forKey: .enabled) try container.encode(readOnly, forKey: .readOnly) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return peakingUI == model.peakingUI + && peakingArrowColor == model.peakingArrowColor + && analyticsData == model.analyticsData + && fieldValue == model.fieldValue + && enabled == model.enabled + && readOnly == model.readOnly + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift index aea87731..b156743d 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift @@ -9,6 +9,7 @@ import MVMCore @objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index 2afda5a4..383c4692 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -73,4 +73,10 @@ try container.encodeIfPresent(border, forKey: .border) try super.encode(to: encoder) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return action.isEqual(to: model.action) + && border == border + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift index 9e230607..48b4fc2a 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift @@ -52,4 +52,18 @@ import MVMCore try container.encode(moleculeName, forKey: .moleculeName) try container.encodeModel(molecule, forKey: .molecule) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && action.isEqual(to: model.action) + && hideArrow == model.hideArrow + && line.isEqual(to: model.line) + && style == model.style + && gone == model.gone + && molecule.isEqual(to: model.molecule) + && accessibilityTraits == model.accessibilityTraits + && accessibilityValue == model.accessibilityValue + && accessibilityText == model.accessibilityText + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift index b5c42984..75dcfd4f 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift @@ -58,4 +58,12 @@ try container.encodeIfPresent(percent, forKey: .percent) try container.encode(gone, forKey: .gone) } + + public override func isEqual(to model: any ModelProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && spacing == model.spacing + && percent == model.percent + && gone == model.gone + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift index 6fa8b6fa..bd496f3a 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift @@ -36,4 +36,12 @@ required public init(from decoder: Decoder) throws { fatalError("init(from:) has not been implemented") } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && spacing == model.spacing + && percent == model.percent + && gone == model.gone + } } diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift index 4925d03b..ad45061e 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabelsModel.swift @@ -23,12 +23,16 @@ public class CornerLabelsModel: ParentMoleculeModelProtocol { [molecule, topLeftLabel, topRightLabel, bottomLeftLabel, bottomRightLabel].compactMap { $0 } } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &self.molecule, with: molecule) - || replaceChildMolecule(at: &topLeftLabel, with: molecule) - || replaceChildMolecule(at: &topRightLabel, with: molecule) - || replaceChildMolecule(at: &bottomLeftLabel, with: molecule) - || replaceChildMolecule(at: &bottomRightLabel, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &self.molecule, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &topLeftLabel, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &topRightLabel, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &bottomLeftLabel, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &bottomRightLabel, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } public init(with molecule: MoleculeModelProtocol?) { diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift index 6860b40a..ce271ba6 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift @@ -20,7 +20,7 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco return [molecule] } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(at: &self.molecule, with: molecule) } @@ -61,4 +61,10 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco try container.encodeModel(molecule, forKey: .molecule) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return molecule.isEqual(to: model.molecule) + && backgroundColor == model.backgroundColor + } } diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift index cbda8dde..5c6deee7 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerProtocol.swift @@ -19,7 +19,7 @@ public extension MoleculeContainerModelProtocol { } public extension MoleculeContainerModelProtocol where Self: AnyObject { - mutating func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + mutating func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(at: &molecule, with: replacementMolecule) } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift index 90f72be3..df7b897e 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift @@ -7,7 +7,7 @@ // -public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { +public class EyebrowHeadlineBodyLinkModel: ParentMoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -25,11 +25,15 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule [eyebrow, headline, body, link].compactMap { (molecule: MoleculeModelProtocol?) in molecule } } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &eyebrow, with: molecule) - || replaceChildMolecule(at: &headline, with: molecule) - || replaceChildMolecule(at: &body, with: molecule) - || replaceChildMolecule(at: &link, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &eyebrow, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- @@ -102,4 +106,10 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule try container.encodeIfPresent(body, forKey: .body) try container.encodeIfPresent(link, forKey: .link) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && children.areEqual(to: model.children) + } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index 093528d1..b8e62030 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -24,9 +24,13 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol { [headline, body].compactMap { $0 } } - public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at:&headline, with: replacementMolecule) - || replaceChildMolecule(at:&body, with: replacementMolecule) + public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at:&headline, with: replacementMolecule, replaced: &replacedMolecule) + || replaceChildMolecule(at:&body, with: replacementMolecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- @@ -89,6 +93,14 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol { try container.encodeIfPresent(style, forKey: .style) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return headline.isEqual(to: model.headline) + && body.isEqual(to: model.body) + && style == style + && backgroundColor == backgroundColor + } } public extension HeadlineBodyModel { diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index 976018dd..4e645a92 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -171,6 +171,29 @@ import UIKit try container.encode(enabled, forKey: .enabled) try container.encode(readOnly, forKey: .readOnly) } + + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == backgroundColor + && molecules.areEqual(to: model.molecules) + && spacing == model.spacing + && border == model.border + && loop == model.loop + && height == model.height + && itemWidthPercent == model.itemWidthPercent + && itemAlignment == model.itemAlignment + && pagingMolecule.isEqual(to: model.pagingMolecule) + && paging == model.paging + && useHorizontalMargins == model.useHorizontalMargins + && leftPadding == model.leftPadding + && rightPadding == model.rightPadding + && accessibilityText == model.accessibilityText + && baseValue == model.baseValue + && fieldKey == model.fieldKey + && groupName == model.groupName + && enabled == model.enabled + && readOnly == model.readOnly + } } extension CarouselModel { @@ -179,7 +202,7 @@ extension CarouselModel { return molecules } - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(in: &molecules, with: molecule) } diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index bc254727..7f03df14 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -79,4 +79,11 @@ try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } + public func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && molecules.areEqual(to: model.molecules) + && axis == model.axis + && spacing == model.spacing + } } diff --git a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift index 1456f6e5..3bff88fc 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift @@ -21,7 +21,7 @@ extension StackModelProtocol { extension StackModelProtocol where Self: AnyObject { - public mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return try replaceChildMolecule(in: &molecules, with: molecule) } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift index f2ac0715..907c2cfa 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift @@ -7,3 +7,19 @@ // import Foundation + +public protocol MoleculeModelComparisonProtocol: ModelProtocol { + + /** True if there are no visual differences between models. + + By default if the models are equal then they are visually equivalent. However, if there are parts of models that can be upddated without a UI update, this could be subset of properties. + **/ + func isVisuallyEquivalent(to model: MoleculeModelComparisonProtocol) -> Bool +} + +extension MoleculeModelComparisonProtocol { + + public func isVisuallyEquivalent(to model: MoleculeModelComparisonProtocol) -> Bool { + return isEqual(to: model) + } +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift index e56840e8..c355d3f6 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift @@ -5,7 +5,7 @@ public enum MolecularError: Swift.Error { case countImbalance(String) } -public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol, MoleculeTreeTraversalProtocol, MoleculeMaskingProtocol { +public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol, MoleculeTreeTraversalProtocol, MoleculeMaskingProtocol, MoleculeModelComparisonProtocol, CustomDebugStringConvertible { var moleculeName: String { get } var backgroundColor: Color? { get set } var id: String { get } @@ -18,6 +18,15 @@ public extension MoleculeModelProtocol { static var categoryName: String { "\(MoleculeModelProtocol.self)" } static var categoryCodingKey: String { "moleculeName" } + + func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return id == model.id + } + + var debugDescription: String { + return "\(moleculeName): \(id)" + } } // Helpers made due to swift not able to reconcile which category. diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift index ef53b177..88817e6a 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -8,52 +8,70 @@ import Foundation -public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol { +public protocol ParentModelProtocol: ModelProtocol, MoleculeTreeTraversalProtocol { /// Returns the direct children of this component. (Does not recurse.) var children: [MoleculeModelProtocol] { get } /// Method for replacing surface level children. (Does not recurse.) - mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool + mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? } public extension ParentModelProtocol where Self: AnyObject { - + /// Top level test to replace child molecules. Each parent molecule should attempt to replace. - func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { return false } + func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { return nil } + + func replaceChildMolecule(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + return try replaceChildMolecule(at: &childMolecule, with: replacementMolecule, replaced: &replacedMolecule) ? replacedMolecule : nil + } /// Helper function for replacing a single molecules with type and optionality checks. - func replaceChildMolecule(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + func replaceChildMolecule(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol, replaced: inout MoleculeModelProtocol?) throws -> Bool { guard let childIdMolecule = childMolecule as? MoleculeModelProtocol else { return false } if childIdMolecule.id == replacementMolecule.id { - guard let replacementMolecule = replacementMolecule as? T else { + guard let typedReplacementMolecule = replacementMolecule as? T else { throw MolecularError.error("Molecular replacement '\(replacementMolecule.id)' does not type match \(type(of: T.self)) of \(type(of: self))") } - childMolecule = replacementMolecule + replaced = childIdMolecule + childMolecule = typedReplacementMolecule return true } return false } + func replaceChildMolecule(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + return try replaceChildMolecule(at: &molecules, with: replacementMolecule, replaced: &replacedMolecule) ? replacedMolecule : nil + } + /// Helper for replacing a molecule in place within an array. Note the "in". - func replaceChildMolecule(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> Bool { - guard let moleculeIdModels = molecules as? [MoleculeModelProtocol], let matchingIndex = moleculeIdModels.firstIndex(where: { - $0.id == replacementMolecule.id - }) else { return false } - guard let replacementMolecule = replacementMolecule as? T else { + func replaceChildMolecule(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol, replaced: inout MoleculeModelProtocol?) throws -> Bool { + guard let moleculeIdModels = molecules as? [MoleculeModelProtocol], + let matchingIndex = moleculeIdModels.firstIndex(where: { + $0.id == replacementMolecule.id + }) + else { return false } + guard let typedReplacementMolecule = replacementMolecule as? T else { throw MolecularError.error("Molecular replacement '\(replacementMolecule.id)' does not type match \(type(of: T.self)) of \(type(of: self))") } - molecules[matchingIndex] = replacementMolecule + replaced = molecules[matchingIndex] as? MoleculeModelProtocol + molecules[matchingIndex] = typedReplacementMolecule return true } } public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelProtocol { - } public extension ParentMoleculeModelProtocol { + func isEqual(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return model.children.areEqual(to: model.children) + } + func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result { var result = initialResult if (options == .parentFirst) { @@ -64,7 +82,7 @@ public extension ParentMoleculeModelProtocol { // Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol. return additionalParent.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult) } - return molecule.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult) + return nextPartialResult(result, molecule, depth + 1) } if (options == .childFirst) { result = nextPartialResult(result, self, depth) @@ -88,7 +106,7 @@ public extension ParentMoleculeModelProtocol { // Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol. additionalParent.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept) } else { - child.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept) + onVisit(depth, child, &shouldStop) } guard !shouldStop else { return } } @@ -98,3 +116,29 @@ public extension ParentMoleculeModelProtocol { // if options == .leafOnly don't call on self. } } + +extension ParentModelProtocol { + + func deepCompare(_ anotherParent: ParentModelProtocol, with test: (ModelProtocol, ModelProtocol)->Bool) -> (Bool, myChild: ModelProtocol?, theirChild: ModelProtocol?) { + + guard test(self, anotherParent) else { return (false, myChild: self, theirChild: self)} + + let myChildren = children + let theirChildren = anotherParent.children + guard myChildren.count == theirChildren.count else { return (false, myChild: self, theirChild: self) } + for index in myChildren.indices { + if let myChild = myChildren[index] as? ParentModelProtocol { + if let theirChild = theirChildren[index] as? ParentModelProtocol { + let result = myChild.deepCompare(theirChild, with: test) + guard result.0 else { return result } + } else { + return (false, myChild: myChild, theirChild: theirChildren[index]) + } + } else if !test(myChildren[index], theirChildren[index]) { + return (false, myChild: myChildren[index], theirChild: theirChildren[index]) + } + } + + return (true, nil, nil) + } +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift index 296f73f5..5507eadb 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift @@ -42,27 +42,27 @@ public extension TemplateModelProtocol { extension TemplateModelProtocol { /// Recursively finds and replaces the first child matching the replacement molecule id property. - mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { + mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { // Attempt root level replacement on the template model first. - if try replaceChildMolecule(with: replacementMolecule) { - return true + if let replacedMolecule = try replaceChildMolecule(with: replacementMolecule) { + return replacedMolecule } - var didReplaceMolecule = false + var replacedMolecule: MoleculeModelProtocol? var possibleError: Error? // Dive into each root thereafter. depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } do { - didReplaceMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule) + replacedMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule) } catch { possibleError = error } - stop = didReplaceMolecule || possibleError != nil + stop = replacedMolecule != nil || possibleError != nil } if let error = possibleError { throw error } - return didReplaceMolecule + return replacedMolecule } } diff --git a/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift b/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift index c42797bc..8700fed1 100644 --- a/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/BaseTemplateModel.swift @@ -35,8 +35,12 @@ import Foundation public var hideLeftPanel: Bool? public var hideRightPanel: Bool? - public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try replaceChildMolecule(at: &navigationBar, with: molecule) + public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &navigationBar, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift index e94f8153..c0bb85b8 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift @@ -23,9 +23,14 @@ return super.rootMolecules } - public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try super.replaceChildMolecule(with: molecule) - || (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule)) + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + if let replacedMolecule = try super.replaceChildMolecule(with: molecule) { + return replacedMolecule + } + if molecules != nil, let replacedMolecule = try replaceChildMolecule(in: &(molecules!), with: molecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift index daf022a8..5671013b 100644 --- a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift @@ -28,10 +28,16 @@ return super.rootMolecules } - public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try super.replaceChildMolecule(with: molecule) - || (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule)) - || replaceChildMolecule(at: &line, with: molecule) + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + if let replacedMolecule = try super.replaceChildMolecule(with: molecule) { + return replacedMolecule + } + var replacedMolecule: MoleculeModelProtocol? + if try (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule, replaced: &replacedMolecule)) + || replaceChildMolecule(at: &line, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } /// This template requires content. diff --git a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift index d5d783d3..7035ab75 100644 --- a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift @@ -19,10 +19,14 @@ super.rootMolecules + [moleculeStack] } - public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + var replacedMolecule: MoleculeModelProtocol? return try super.replaceChildMolecule(with: molecule) - || replaceChildMolecule(at: &navigationBar, with: molecule) - || replaceChildMolecule(at: &moleculeStack, with: molecule) + if try replaceChildMolecule(at: &navigationBar, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &moleculeStack, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift index 6ca05550..921e74ab 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift @@ -21,10 +21,16 @@ [navigationBar, header, footer].compactMap { $0 } } - public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try super.replaceChildMolecule(with: molecule) - || replaceChildMolecule(at: &header, with: molecule) - || replaceChildMolecule(at: &footer, with: molecule) + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + if let replacedMolecule = try super.replaceChildMolecule(with: molecule) { + return replacedMolecule + } + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &header, with: molecule, replaced: &replacedMolecule) + || replaceChildMolecule(at: &footer, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift index d3acea86..068f2cab 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift @@ -22,9 +22,15 @@ return super.rootMolecules } - public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { - return try super.replaceChildMolecule(with: molecule) - || replaceChildMolecule(at: &middle, with: molecule) + public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + if let replacedMolecule = try super.replaceChildMolecule(with: molecule) { + return replacedMolecule + } + var replacedMolecule: MoleculeModelProtocol? + if try replaceChildMolecule(at: &middle, with: molecule, replaced: &replacedMolecule) { + return replacedMolecule + } + return nil } //-------------------------------------------------- diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 452d2962..ba38ad29 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -513,27 +513,34 @@ import MVMCore public func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol], completionHandler: (([MoleculeModelProtocol])->Void)? = nil) { pageUpdateQueue.addOperation { - let replacedModels:[MoleculeModelProtocol] = moleculeModels.compactMap { model in - guard self.attemptToReplace(with: model) else { + let replacedModels:[(MoleculeModelProtocol, MoleculeModelProtocol)] = moleculeModels.compactMap { model in + guard let replacedMolecule = self.attemptToReplace(with: model) else { return nil } - return model + return (model, replacedMolecule) } - if replacedModels.count > 0 { + let uiUpdatedModels: [MoleculeModelProtocol] = replacedModels.compactMap { new, existing in + guard !new.isVisuallyEquivalent(to: existing) else { + return nil + } + return new + } + if uiUpdatedModels.count > 0 { + MVMCoreLoggingHandler.shared()?.handleDebugMessage("Updating UI for molecules: \(uiUpdatedModels)") DispatchQueue.main.sync { - self.updateUI(for: replacedModels) + self.updateUI(for: uiUpdatedModels) } } - completionHandler?(replacedModels) + completionHandler?(replacedModels.map { $0.0 }) } } - open func attemptToReplace(with replacementModel: MoleculeModelProtocol) -> Bool { - guard var templateModel = getTemplateModel() else { return false } - var didReplace = false + open func attemptToReplace(with replacementModel: MoleculeModelProtocol) -> MoleculeModelProtocol? { + guard var templateModel = getTemplateModel() else { return nil } + var replacedMolecule: MoleculeModelProtocol? do { - didReplace = try templateModel.replaceMolecule(with: replacementModel) - if !didReplace { + replacedMolecule = try templateModel.replaceMolecule(with: replacementModel) + if replacedMolecule == nil { MVMCoreLoggingHandler.shared()?.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Failed to find '\(replacementModel.id)' in the current screen.", code: ErrorCode.viewControllerProcessingJSON.rawValue, domain: ErrorDomainSystem, location: String(describing: type(of: self)))!) } } catch { @@ -543,7 +550,7 @@ import MVMCore } MVMCoreLoggingHandler.shared()?.addError(toLog: coreError) } - return didReplace + return replacedMolecule } //-------------------------------------------------- diff --git a/MVMCoreUI/CustomPrimitives/Color.swift b/MVMCoreUI/CustomPrimitives/Color.swift index 9245153d..a67f8747 100644 --- a/MVMCoreUI/CustomPrimitives/Color.swift +++ b/MVMCoreUI/CustomPrimitives/Color.swift @@ -14,6 +14,7 @@ import UIKit Int and String and can be used the same. */ public final class Color: Codable { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -151,3 +152,9 @@ public final class Color: Codable { } } } + +extension Color: Equatable { + public static func == (lhs: Color, rhs: Color) -> Bool { + return lhs.hex == rhs.hex + } +}