Digital PCT265 story ONEAPP-7249 - Prevent UI updates when there are no model changes.

This commit is contained in:
Hedden, Kyle Matthew 2024-05-07 23:28:53 -04:00
parent fd296b9623
commit 31096a15a5
63 changed files with 589 additions and 155 deletions

View File

@ -172,6 +172,7 @@
5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; }; 5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; };
5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; }; 5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; };
58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.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 */; }; 608211282AC6B57E00C3FC39 /* MVMCoreUILoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */; };
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; }; 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.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 = "<group>"; }; 5878F0A42BD7E68800ADE23D /* mvmcoreui.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui.xcconfig; sourceTree = "<group>"; };
5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui_dev.xcconfig; sourceTree = "<group>"; }; 5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui_dev.xcconfig; sourceTree = "<group>"; };
58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = "<group>"; }; 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceableMoleculeBehaviorModel.swift; sourceTree = "<group>"; };
58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeComparisonProtocol.swift; sourceTree = "<group>"; };
608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = "<group>"; }; 608211262AC6AF8200C3FC39 /* MVMCoreUILoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUILoggingHandler.swift; sourceTree = "<group>"; };
8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; }; 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; };
8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = "<group>"; }; 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = "<group>"; };
@ -1254,6 +1256,7 @@
D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */, D28BA74C248589C800B75CB8 /* TabPageModelProtocol.swift */,
27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */, 27F6B08B26052AFF008529AA /* ParentMoleculeModelProtocol.swift */,
27577DCC286CA959001EC47E /* MoleculeMaskingProtocol.swift */, 27577DCC286CA959001EC47E /* MoleculeMaskingProtocol.swift */,
58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */,
); );
path = ModelProtocols; path = ModelProtocols;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3010,6 +3013,7 @@
EA1758482BC97ED800A5C0D9 /* BadgeIndicator.swift in Sources */, EA1758482BC97ED800A5C0D9 /* BadgeIndicator.swift in Sources */,
012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */, 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */,
0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */, 0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */,
58E7561D2BE04C320088BB5D /* MoleculeComparisonProtocol.swift in Sources */,
D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */, D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */,
AF1C336928859778006B1001 /* ActionAlertHandler.swift in Sources */, AF1C336928859778006B1001 /* ActionAlertHandler.swift in Sources */,
9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */, 9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */,

View File

@ -186,4 +186,20 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits)
try container.encodeIfPresent(disabledAccessibilityTraits, forKey: .disabledAccessibilityTraits) 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
}
} }

View File

@ -33,7 +33,7 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
[image].compactMap({$0}) [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) return try replaceChildMolecule(at: &image, with: molecule)
} }

View File

@ -99,6 +99,19 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode
try container.encodeIfPresent(size, forKey: .size) try container.encodeIfPresent(size, forKey: .size)
try container.encode(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) 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 { extension LinkModel {

View File

@ -130,4 +130,20 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
try container.encode(indicatorColor, forKey: .indicatorColor) try container.encode(indicatorColor, forKey: .indicatorColor)
try container.encodeIfPresent(position, forKey: .position) 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
}
} }

View File

@ -48,4 +48,9 @@ open class LabelAttributeActionModel: LabelAttributeModel {
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeModel(action, forKey: .action) 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)
}
} }

View File

@ -43,4 +43,9 @@
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(textColor, forKey: .textColor) 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
}
} }

View File

@ -55,4 +55,11 @@
try container.encodeIfPresent(name, forKey: .name) try container.encodeIfPresent(name, forKey: .name)
try container.encodeIfPresent(size, forKey: .size) 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
}
} }

View File

@ -69,4 +69,12 @@ class LabelAttributeImageModel: LabelAttributeModel {
try container.encodeIfPresent(URL, forKey: .URL) try container.encodeIfPresent(URL, forKey: .URL)
try container.encodeIfPresent(tintColor, forKey: .tintColor) 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
}
} }

View File

@ -75,4 +75,10 @@
try container.encode(location, forKey: .location) try container.encode(location, forKey: .location)
try container.encode(length, forKey: .length) 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
}
} }

View File

@ -66,6 +66,13 @@ import UIKit
try container.encode(style, forKey: .style) try container.encode(style, forKey: .style)
try container.encodeIfPresent(pattern, forKey: .pattern) 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 { public enum UnderlineStyle: String, Codable {

View File

@ -132,6 +132,26 @@ import VDS
try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) try container.encodeIfPresent(shouldMaskRecordedView, forKey: .shouldMaskRecordedView)
try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) 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 { extension LabelModel {

View File

@ -129,4 +129,12 @@ public class LineModel: MoleculeModelProtocol, Invertable {
try container.encodeIfPresent(frequency, forKey: .frequency) try container.encodeIfPresent(frequency, forKey: .frequency)
try container.encode(orientation == .vertical, forKey: .useVerticalLine) 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
}
} }

View File

@ -24,7 +24,7 @@ open class TileContainerModel: TileContainerBaseModel<TileContainer.Padding, Til
return [molecule] 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) return try replaceChildMolecule(at: &self.molecule, with: molecule)
} }

View File

@ -21,9 +21,13 @@ public class HeadersH1ButtonModel: HeaderModel, MoleculeModelProtocol, ParentMol
[titleLockup, buttons] [titleLockup, buttons]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &titleLockup, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &buttons, with: molecule) if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -24,13 +24,17 @@ public class HeadersH1LandingPageHeaderModel: HeaderModel, MoleculeModelProtocol
[headline, headline2, subHeadline, body, link, buttons] [headline, headline2, subHeadline, body, link, buttons]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &headline, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &headline2, with: molecule) if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &subHeadline, with: molecule) || replaceChildMolecule(at: &headline2, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body, with: molecule) || replaceChildMolecule(at: &subHeadline, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &link, with: molecule) || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &buttons, with: molecule) || replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -19,7 +19,7 @@ public class HeadersH1NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
[titleLockup] [titleLockup]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &titleLockup, with: molecule) return try replaceChildMolecule(at: &titleLockup, with: molecule)
} }

View File

@ -23,9 +23,13 @@ public class HeadersH2ButtonsModel: HeaderModel, MoleculeModelProtocol, ParentMo
[titleLockup, buttons] [titleLockup, buttons]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &titleLockup, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &buttons, with: molecule) if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &buttons, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,9 +20,13 @@ public class HeadersH2CaretLinkModel: HeaderModel, MoleculeModelProtocol, Parent
[titleLockup, caretLink] [titleLockup, caretLink]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &titleLockup, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &caretLink, with: molecule) if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &caretLink, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMoleculeModelProtocol { public class HeadersH2LinkModel: HeaderModel, ParentMoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
@ -22,9 +22,13 @@ public class HeadersH2LinkModel: HeaderModel, MoleculeModelProtocol, ParentMolec
[titleLockup, link] [titleLockup, link]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &titleLockup, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &link, with: molecule) if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &link, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,7 +22,7 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
[titleLockup] [titleLockup]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &titleLockup, with: molecule) return try replaceChildMolecule(at: &titleLockup, with: molecule)
} }

View File

@ -25,15 +25,19 @@ public class HeadersH2PricingTwoRowsModel: HeaderModel, MoleculeModelProtocol, P
[headline, body, subBody, body2, subBody2, body3, subBody3].compactMap({$0}) [headline, body, subBody, body2, subBody2, body3, subBody3].compactMap({$0})
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &headline, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &body, with: molecule) if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &subBody, with: molecule) || replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body2, with: molecule) || replaceChildMolecule(at: &subBody, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body2, with: molecule) || replaceChildMolecule(at: &body2, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &subBody2, with: molecule) || replaceChildMolecule(at: &body2, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body3, with: molecule) || replaceChildMolecule(at: &subBody2, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &subBody3, with: molecule) || replaceChildMolecule(at: &body3, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &subBody3, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -23,9 +23,13 @@ public class HeadersH2TinyButtonModel: HeaderModel, MoleculeModelProtocol, Paren
[titleLockup, button] [titleLockup, button]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &titleLockup, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &button, with: molecule) if try replaceChildMolecule(at: &titleLockup, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &button, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,9 +20,13 @@ open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelPr
[checkbox, headlineBody] [checkbox, headlineBody]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &checkbox, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &headlineBody, with: molecule) if try replaceChildMolecule(at: &checkbox, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &headlineBody, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,9 +20,13 @@ public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModel
return [image, eyebrowHeadlineBodyLink] return [image, eyebrowHeadlineBodyLink]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &image, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretAllTextLinksModel: ListItemModel,
return [image, eyebrowHeadlineBodyLink, rightLabel] return [image, eyebrowHeadlineBodyLink, rightLabel]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &image, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &rightLabel, with: molecule) || replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Par
[image, headlineBody, rightLabel] [image, headlineBody, rightLabel]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &image, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &headlineBody, with: molecule) if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &rightLabel, with: molecule) || replaceChildMolecule(at: &headlineBody, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -21,10 +21,14 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, ParentMolec
return [image, leftLabel, rightLabel] return [image, leftLabel, rightLabel]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &image, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &leftLabel, with: molecule) if try replaceChildMolecule(at: &image, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &rightLabel, with: molecule) || replaceChildMolecule(at: &leftLabel, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -20,9 +20,13 @@ open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, ParentMolecu
[radioButton, headlineBody] [radioButton, headlineBody]
} }
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &radioButton, with: replacementMolecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &headlineBody, with: replacementMolecule) if try replaceChildMolecule(at: &radioButton, with: replacementMolecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &headlineBody, with: replacementMolecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -39,7 +39,7 @@ public class ListOneColumnFullWidthTextBodyTextModel: ListItemModel, MoleculeMod
return [headlineBody] 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) 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(moleculeName, forKey: .moleculeName)
try container.encode(headlineBody, forKey: .headlineBody) 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)
}
} }

View File

@ -40,9 +40,13 @@ public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, Molecul
return [button, eyebrowHeadlineBodyLink] return [button, eyebrowHeadlineBodyLink]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &button, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) if try replaceChildMolecule(at: &button, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,9 +20,13 @@ public class ListRightVariableRightCaretAllTextAndLinksModel: ListItemModel, Par
[rightLabel, eyebrowHeadlineBodyLink] [rightLabel, eyebrowHeadlineBodyLink]
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &rightLabel, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule) if try replaceChildMolecule(at: &rightLabel, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &eyebrowHeadlineBodyLink, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//----------------------------------------------------- //-----------------------------------------------------

View File

@ -9,7 +9,7 @@
import VDSTokens import VDSTokens
import VDS import VDS
public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { public class TitleLockupModel: ParentMoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
@ -34,10 +34,24 @@ public class TitleLockupModel: MoleculeModelProtocol, ParentMoleculeModelProtoco
[eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule } [eyebrow, title, subTitle].compactMap { (molecule: MoleculeModelProtocol?) in molecule }
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &eyebrow, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &title, with: molecule) if try replaceChildMolecule(at: &eyebrow, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &subTitle, with: molecule) || 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)
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,9 +22,13 @@ public class ListOneColumnFullWidthTextDividerSubsectionModel: ListItemModel, Mo
[headline, body].compactMap({$0}) [headline, body].compactMap({$0})
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &headline, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &body, with: molecule) if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,9 +22,13 @@ public class ListOneColumnTextWithWhitespaceDividerShortModel: ListItemModel, Mo
[headline, body].compactMap({$0}) [headline, body].compactMap({$0})
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &headline, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &body, with: molecule) if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,9 +22,13 @@ public class ListOneColumnTextWithWhitespaceDividerTallModel: ListItemModel, Mol
[headline, body].compactMap({$0}) [headline, body].compactMap({$0})
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &headline, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &body, with: molecule) if try replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -23,9 +23,13 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
public var children: [MoleculeModelProtocol] { [primaryButton, secondaryButton].compactMap { $0 } } public var children: [MoleculeModelProtocol] { [primaryButton, secondaryButton].compactMap { $0 } }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &primaryButton, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &secondaryButton, with: molecule) if try replaceChildMolecule(at: &primaryButton, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &secondaryButton, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -85,8 +85,19 @@
try container.encodeIfPresent(peakingUI, forKey: .peakingUI) try container.encodeIfPresent(peakingUI, forKey: .peakingUI)
try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor)
try container.encodeIfPresent(analyticsData, forKey: .analyticsData) try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue) try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
try container.encode(enabled, forKey: .enabled) try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly) 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
}
} }

View File

@ -9,6 +9,7 @@
import MVMCore import MVMCore
@objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol { @objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -73,4 +73,10 @@
try container.encodeIfPresent(border, forKey: .border) try container.encodeIfPresent(border, forKey: .border)
try super.encode(to: encoder) 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
}
} }

View File

@ -52,4 +52,18 @@ import MVMCore
try container.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeModel(molecule, forKey: .molecule) 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
}
} }

View File

@ -58,4 +58,12 @@
try container.encodeIfPresent(percent, forKey: .percent) try container.encodeIfPresent(percent, forKey: .percent)
try container.encode(gone, forKey: .gone) 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
}
} }

View File

@ -36,4 +36,12 @@
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented") 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
}
} }

View File

@ -23,12 +23,16 @@ public class CornerLabelsModel: ParentMoleculeModelProtocol {
[molecule, topLeftLabel, topRightLabel, bottomLeftLabel, bottomRightLabel].compactMap { $0 } [molecule, topLeftLabel, topRightLabel, bottomLeftLabel, bottomRightLabel].compactMap { $0 }
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &self.molecule, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &topLeftLabel, with: molecule) if try replaceChildMolecule(at: &self.molecule, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &topRightLabel, with: molecule) || replaceChildMolecule(at: &topLeftLabel, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &bottomLeftLabel, with: molecule) || replaceChildMolecule(at: &topRightLabel, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &bottomRightLabel, with: molecule) || replaceChildMolecule(at: &bottomLeftLabel, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &bottomRightLabel, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
public init(with molecule: MoleculeModelProtocol?) { public init(with molecule: MoleculeModelProtocol?) {

View File

@ -20,7 +20,7 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco
return [molecule] 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) 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.encodeModel(molecule, forKey: .molecule)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) 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
}
} }

View File

@ -19,7 +19,7 @@ public extension MoleculeContainerModelProtocol {
} }
public extension MoleculeContainerModelProtocol where Self: AnyObject { 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) return try replaceChildMolecule(at: &molecule, with: replacementMolecule)
} }
} }

View File

@ -7,7 +7,7 @@
// //
public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { public class EyebrowHeadlineBodyLinkModel: ParentMoleculeModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
@ -25,11 +25,15 @@ public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol, ParentMolecule
[eyebrow, headline, body, link].compactMap { (molecule: MoleculeModelProtocol?) in molecule } [eyebrow, headline, body, link].compactMap { (molecule: MoleculeModelProtocol?) in molecule }
} }
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &eyebrow, with: molecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at: &headline, with: molecule) if try replaceChildMolecule(at: &eyebrow, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &body, with: molecule) || replaceChildMolecule(at: &headline, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &link, with: molecule) || 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(body, forKey: .body)
try container.encodeIfPresent(link, forKey: .link) 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)
}
} }

View File

@ -24,9 +24,13 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol {
[headline, body].compactMap { $0 } [headline, body].compactMap { $0 }
} }
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at:&headline, with: replacementMolecule) var replacedMolecule: MoleculeModelProtocol?
|| replaceChildMolecule(at:&body, with: replacementMolecule) 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(style, forKey: .style)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) 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 { public extension HeadlineBodyModel {

View File

@ -171,6 +171,29 @@ import UIKit
try container.encode(enabled, forKey: .enabled) try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly) 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 { extension CarouselModel {
@ -179,7 +202,7 @@ extension CarouselModel {
return molecules 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) return try replaceChildMolecule(in: &molecules, with: molecule)
} }

View File

@ -79,4 +79,11 @@
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) 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
}
} }

View File

@ -21,7 +21,7 @@ extension StackModelProtocol {
extension StackModelProtocol where Self: AnyObject { 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) return try replaceChildMolecule(in: &molecules, with: molecule)
} }
} }

View File

@ -7,3 +7,19 @@
// //
import Foundation 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)
}
}

View File

@ -5,7 +5,7 @@ public enum MolecularError: Swift.Error {
case countImbalance(String) case countImbalance(String)
} }
public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol, MoleculeTreeTraversalProtocol, MoleculeMaskingProtocol { public protocol MoleculeModelProtocol: ModelProtocol, AccessibilityModelProtocol, MoleculeTreeTraversalProtocol, MoleculeMaskingProtocol, MoleculeModelComparisonProtocol, CustomDebugStringConvertible {
var moleculeName: String { get } var moleculeName: String { get }
var backgroundColor: Color? { get set } var backgroundColor: Color? { get set }
var id: String { get } var id: String { get }
@ -18,6 +18,15 @@ public extension MoleculeModelProtocol {
static var categoryName: String { "\(MoleculeModelProtocol.self)" } static var categoryName: String { "\(MoleculeModelProtocol.self)" }
static var categoryCodingKey: String { "moleculeName" } 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. // Helpers made due to swift not able to reconcile which category.

View File

@ -8,52 +8,70 @@
import Foundation import Foundation
public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol { public protocol ParentModelProtocol: ModelProtocol, MoleculeTreeTraversalProtocol {
/// Returns the direct children of this component. (Does not recurse.) /// Returns the direct children of this component. (Does not recurse.)
var children: [MoleculeModelProtocol] { get } var children: [MoleculeModelProtocol] { get }
/// Method for replacing surface level children. (Does not recurse.) /// 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 { public extension ParentModelProtocol where Self: AnyObject {
/// Top level test to replace child molecules. Each parent molecule should attempt to replace. /// 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<T>(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. /// Helper function for replacing a single molecules with type and optionality checks.
func replaceChildMolecule<T>(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> Bool { func replaceChildMolecule<T>(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol, replaced: inout MoleculeModelProtocol?) throws -> Bool {
guard let childIdMolecule = childMolecule as? MoleculeModelProtocol else { return false } guard let childIdMolecule = childMolecule as? MoleculeModelProtocol else { return false }
if childIdMolecule.id == replacementMolecule.id { 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))") 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 true
} }
return false return false
} }
func replaceChildMolecule<T>(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". /// Helper for replacing a molecule in place within an array. Note the "in".
func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> Bool { func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol, replaced: inout MoleculeModelProtocol?) throws -> Bool {
guard let moleculeIdModels = molecules as? [MoleculeModelProtocol], let matchingIndex = moleculeIdModels.firstIndex(where: { guard let moleculeIdModels = molecules as? [MoleculeModelProtocol],
$0.id == replacementMolecule.id let matchingIndex = moleculeIdModels.firstIndex(where: {
}) else { return false } $0.id == replacementMolecule.id
guard let replacementMolecule = replacementMolecule as? T else { })
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))") 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 return true
} }
} }
public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelProtocol { public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelProtocol {
} }
public extension ParentMoleculeModelProtocol { 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<Result>(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result { func reduceDepthFirstTraverse<Result>(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result {
var result = initialResult var result = initialResult
if (options == .parentFirst) { 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. // 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 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) { if (options == .childFirst) {
result = nextPartialResult(result, self, depth) 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. // 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) additionalParent.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept)
} else { } else {
child.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept) onVisit(depth, child, &shouldStop)
} }
guard !shouldStop else { return } guard !shouldStop else { return }
} }
@ -98,3 +116,29 @@ public extension ParentMoleculeModelProtocol {
// if options == .leafOnly don't call on self. // 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)
}
}

View File

@ -42,27 +42,27 @@ public extension TemplateModelProtocol {
extension TemplateModelProtocol { extension TemplateModelProtocol {
/// Recursively finds and replaces the first child matching the replacement molecule id property. /// 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. // Attempt root level replacement on the template model first.
if try replaceChildMolecule(with: replacementMolecule) { if let replacedMolecule = try replaceChildMolecule(with: replacementMolecule) {
return true return replacedMolecule
} }
var didReplaceMolecule = false var replacedMolecule: MoleculeModelProtocol?
var possibleError: Error? var possibleError: Error?
// Dive into each root thereafter. // Dive into each root thereafter.
depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in
guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return } guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
do { do {
didReplaceMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule) replacedMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule)
} catch { } catch {
possibleError = error possibleError = error
} }
stop = didReplaceMolecule || possibleError != nil stop = replacedMolecule != nil || possibleError != nil
} }
if let error = possibleError { if let error = possibleError {
throw error throw error
} }
return didReplaceMolecule return replacedMolecule
} }
} }

View File

@ -35,8 +35,12 @@ import Foundation
public var hideLeftPanel: Bool? public var hideLeftPanel: Bool?
public var hideRightPanel: Bool? public var hideRightPanel: Bool?
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try replaceChildMolecule(at: &navigationBar, with: molecule) var replacedMolecule: MoleculeModelProtocol?
if try replaceChildMolecule(at: &navigationBar, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -23,9 +23,14 @@
return super.rootMolecules return super.rootMolecules
} }
public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try super.replaceChildMolecule(with: molecule) if let replacedMolecule = try super.replaceChildMolecule(with: molecule) {
|| (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule)) return replacedMolecule
}
if molecules != nil, let replacedMolecule = try replaceChildMolecule(in: &(molecules!), with: molecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -28,10 +28,16 @@
return super.rootMolecules return super.rootMolecules
} }
public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try super.replaceChildMolecule(with: molecule) if let replacedMolecule = try super.replaceChildMolecule(with: molecule) {
|| (molecules != nil && replaceChildMolecule(in: &(molecules!), with: molecule)) return replacedMolecule
|| replaceChildMolecule(at: &line, with: molecule) }
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. /// This template requires content.

View File

@ -19,10 +19,14 @@
super.rootMolecules + [moleculeStack] 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) return try super.replaceChildMolecule(with: molecule)
|| replaceChildMolecule(at: &navigationBar, with: molecule) if try replaceChildMolecule(at: &navigationBar, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &moleculeStack, with: molecule) || replaceChildMolecule(at: &moleculeStack, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -21,10 +21,16 @@
[navigationBar, header, footer].compactMap { $0 } [navigationBar, header, footer].compactMap { $0 }
} }
public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try super.replaceChildMolecule(with: molecule) if let replacedMolecule = try super.replaceChildMolecule(with: molecule) {
|| replaceChildMolecule(at: &header, with: molecule) return replacedMolecule
|| replaceChildMolecule(at: &footer, with: molecule) }
var replacedMolecule: MoleculeModelProtocol?
if try replaceChildMolecule(at: &header, with: molecule, replaced: &replacedMolecule)
|| replaceChildMolecule(at: &footer, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -22,9 +22,15 @@
return super.rootMolecules return super.rootMolecules
} }
public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { public override func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
return try super.replaceChildMolecule(with: molecule) if let replacedMolecule = try super.replaceChildMolecule(with: molecule) {
|| replaceChildMolecule(at: &middle, with: molecule) return replacedMolecule
}
var replacedMolecule: MoleculeModelProtocol?
if try replaceChildMolecule(at: &middle, with: molecule, replaced: &replacedMolecule) {
return replacedMolecule
}
return nil
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -513,27 +513,34 @@ import MVMCore
public func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol], completionHandler: (([MoleculeModelProtocol])->Void)? = nil) { public func replaceMoleculeData(_ moleculeModels: [MoleculeModelProtocol], completionHandler: (([MoleculeModelProtocol])->Void)? = nil) {
pageUpdateQueue.addOperation { pageUpdateQueue.addOperation {
let replacedModels:[MoleculeModelProtocol] = moleculeModels.compactMap { model in let replacedModels:[(MoleculeModelProtocol, MoleculeModelProtocol)] = moleculeModels.compactMap { model in
guard self.attemptToReplace(with: model) else { guard let replacedMolecule = self.attemptToReplace(with: model) else {
return nil 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 { 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 { open func attemptToReplace(with replacementModel: MoleculeModelProtocol) -> MoleculeModelProtocol? {
guard var templateModel = getTemplateModel() else { return false } guard var templateModel = getTemplateModel() else { return nil }
var didReplace = false var replacedMolecule: MoleculeModelProtocol?
do { do {
didReplace = try templateModel.replaceMolecule(with: replacementModel) replacedMolecule = try templateModel.replaceMolecule(with: replacementModel)
if !didReplace { 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)))!) 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 { } catch {
@ -543,7 +550,7 @@ import MVMCore
} }
MVMCoreLoggingHandler.shared()?.addError(toLog: coreError) MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
} }
return didReplace return replacedMolecule
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -14,6 +14,7 @@ import UIKit
Int and String and can be used the same. Int and String and can be used the same.
*/ */
public final class Color: Codable { public final class Color: Codable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // 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
}
}