From 04581558e355bc1be0056ed37d7dd02e6dcfb0bd Mon Sep 17 00:00:00 2001 From: "Hedden, Kyle Matthew" Date: Wed, 8 May 2024 20:34:08 -0400 Subject: [PATCH] Digital PCT265 story ONEAPP-7249 - isVisuallyEquivalent build out to work with stabilizing carousel refreshes. --- .../Atomic/Atoms/Buttons/ButtonModel.swift | 16 ++++++- .../Atomic/Atoms/Buttons/Link/LinkModel.swift | 15 ++++++- .../CarouselIndicatorModel.swift | 7 ++- .../Label/LabelAttributeActionModel.swift | 6 ++- .../Label/LabelAttributeColorModel.swift | 2 +- .../Views/Label/LabelAttributeFontModel.swift | 2 +- .../Label/LabelAttributeImageModel.swift | 2 +- .../Views/Label/LabelAttributeModel.swift | 4 +- .../Label/LabelAttributeUnderlineModel.swift | 2 +- .../Atomic/Atoms/Views/Label/LabelModel.swift | 23 +++++++++- MVMCoreUI/Atomic/Atoms/Views/LineModel.swift | 2 +- ...tOneColumnFullWidthTextBodyTextModel.swift | 5 --- .../LockUps/TitleLockupModel.swift | 14 +++++- .../Molecules/Items/CarouselItemModel.swift | 10 ++++- .../Molecules/Items/ListItemModel.swift | 28 +++++++++++- .../Items/MoleculeCollectionItemModel.swift | 11 +++-- .../Items/MoleculeListItemModel.swift | 20 ++++----- .../Items/MoleculeStackItemModel.swift | 12 ++++-- .../Items/MoleculeTableViewCell.swift | 5 ++- .../Molecules/Items/StackItemModel.swift | 2 +- .../MoleculeContainerModel.swift | 7 ++- .../EyebrowHeadlineBodyLinkModel.swift | 4 +- .../HeadlineBodyModel.swift | 2 +- .../Atomic/Organisms/Carousel/Carousel.swift | 10 +++++ .../Organisms/Carousel/CarouselModel.swift | 25 ++++++++++- MVMCoreUI/Atomic/Organisms/StackModel.swift | 4 +- .../MoleculeComparisonProtocol.swift | 43 +++++++++++++++++-- .../MoleculeModelProtocol.swift | 2 +- .../ParentMoleculeModelProtocol.swift | 16 ++++++- .../BaseControllers/ViewController.swift | 3 +- .../Containers/Views/ContainerModel.swift | 6 ++- 31 files changed, 248 insertions(+), 62 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 6812424b..8cc0bdda 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -187,7 +187,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat try container.encodeIfPresent(disabledAccessibilityTraits, forKey: .disabledAccessibilityTraits) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return title == model.title && enabled == model.enabled @@ -195,11 +195,25 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat && action.isEqual(to: model.action) && accessibilityText == model.accessibilityText && accessibilityIdentifier == model.accessibilityIdentifier + && accessibilityTraits == model.accessibilityTraits + && disabledAccessibilityTraits == model.disabledAccessibilityTraits && style == model.style && size == model.size && groupName == model.groupName && width == model.width + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return title == model.title + && enabled == model.enabled + && inverted == model.inverted + && accessibilityText == model.accessibilityText + && accessibilityIdentifier == model.accessibilityIdentifier && accessibilityTraits == model.accessibilityTraits && disabledAccessibilityTraits == model.disabledAccessibilityTraits + && style == model.style + && size == model.size + && width == model.width } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift index 38cbe673..a8a80629 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift @@ -100,7 +100,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode try container.encode(shouldMaskRecordedView, forKey: .shouldMaskRecordedView) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return backgroundColor == model.backgroundColor && title == model.title @@ -110,7 +110,18 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol, EnableableMode && enabled == model.enabled && size == model.size && shouldMaskRecordedView == model.shouldMaskRecordedView -// && action.isEqual(to: model.action) // TODO: Move to isVisiuallyEquivalent. + && action.isEqual(to: model.action) + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> 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 } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift index 7a836146..8aa1ca48 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CarouselIndicator/CarouselIndicatorModel.swift @@ -21,6 +21,8 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro public var id: String = UUID().uuidString public var backgroundColor: Color? public var moleculeName: String? + + // Assigned and computed by parent. public var numberOfPages: Int = 0 /// Sets the current Index to focus on. @@ -49,7 +51,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro case moleculeName case backgroundColor case currentIndex - case numberOfPages case alwaysSendAction case animated case hidesForSinglePage @@ -118,7 +119,6 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro try container.encode(id, forKey: .id) try container.encodeIfPresent(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encode(numberOfPages, forKey: .numberOfPages) try container.encode(currentIndex, forKey: .currentIndex) try container.encode(alwaysSendAction, forKey: .alwaysSendAction) try container.encode(animated, forKey: .animated) @@ -131,10 +131,9 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro try container.encodeIfPresent(position, forKey: .position) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> 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 diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift index e65af621..1dbac837 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeActionModel.swift @@ -49,8 +49,12 @@ open class LabelAttributeActionModel: LabelAttributeModel { try container.encodeModel(action, forKey: .action) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard super.isEqual(to: model), let model = model as? Self else { return false } return action.isEqual(to: model.action) } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + return super.isEqual(to: model) + } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift index 68a59d4d..e1c4ccb1 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeColorModel.swift @@ -44,7 +44,7 @@ try container.encodeIfPresent(textColor, forKey: .textColor) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> 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 931c0d63..26cde0fc 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift @@ -56,7 +56,7 @@ try container.encodeIfPresent(size, forKey: .size) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard super.isEqual(to: model), let model = model as? Self else { return false } return style == model.style && name == model.name diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift index 0dceaf7d..2c9fdcd7 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeImageModel.swift @@ -70,7 +70,7 @@ class LabelAttributeImageModel: LabelAttributeModel { try container.encodeIfPresent(tintColor, forKey: .tintColor) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard super.isEqual(to: model), let model = model as? Self else { return false } return URL == model.URL && name == model.name diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift index 35ad5700..6da10fcc 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeModel.swift @@ -7,7 +7,7 @@ // -@objcMembers open class LabelAttributeModel: ModelProtocol { +@objcMembers open class LabelAttributeModel: ModelProtocol, ModelComparisonProtocol, MoleculeModelComparisonProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -76,7 +76,7 @@ try container.encode(length, forKey: .length) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> 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 b2a226e5..cf616ef0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeUnderlineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeUnderlineModel.swift @@ -67,7 +67,7 @@ import UIKit try container.encodeIfPresent(pattern, forKey: .pattern) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard super.isEqual(to: model), let model = model as? Self else { return false } return style == model.style && color == model.color diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index d3611a2e..dbddacef 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -133,7 +133,7 @@ import VDS try container.encodeIfPresent(accessibilityTraits, forKey: .accessibilityTraits) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return backgroundColor == model.backgroundColor && text == model.text @@ -143,12 +143,31 @@ import VDS && fontName == model.fontName && fontSize == model.fontSize && textAlignment == model.textAlignment - && attributes.areEqual(to: model.attributes) + && attributes.isEqual(to: model.attributes) && html == model.html && hero == model.hero && makeWholeViewClickable == model.makeWholeViewClickable && numberOfLines == model.numberOfLines + && accessibilityTraits == model.accessibilityTraits + && inverted == inverted && shouldMaskRecordedView == model.shouldMaskRecordedView + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && text == model.text + && textColor == model.textColor + && fontStyle == model.fontStyle + && fontName == model.fontName + && fontSize == model.fontSize + && textAlignment == model.textAlignment + && attributes.isEqual(to: model.attributes) + && html == model.html + && hero == model.hero + && makeWholeViewClickable == model.makeWholeViewClickable + && numberOfLines == model.numberOfLines + && accessibilityText == model.accessibilityText && accessibilityTraits == model.accessibilityTraits && inverted == inverted } diff --git a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift index 7c3e66ec..8ac93a4f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LineModel.swift @@ -130,7 +130,7 @@ public class LineModel: MoleculeModelProtocol, Invertable { try container.encode(orientation == .vertical, forKey: .useVerticalLine) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return type == model.type && inverted == model.inverted diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift index 3e4c66bf..c40331c7 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextBodyTextModel.swift @@ -68,9 +68,4 @@ 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/LockUps/TitleLockupModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift index 582b3095..14b0969d 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/LockUps/TitleLockupModel.swift @@ -46,14 +46,24 @@ public class TitleLockupModel: ParentMoleculeModelProtocol { return nil } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> 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) + && children.isEqual(to: model.children) + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> 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.isVisuallyEquivalent(to: model.children) } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index c412b09c..f7b875c1 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -91,7 +91,7 @@ try container.encode(readOnly, forKey: .readOnly) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard super.isEqual(to: model), let model = model as? Self else { return false } return peakingUI == model.peakingUI && peakingArrowColor == model.peakingArrowColor @@ -100,4 +100,12 @@ && enabled == model.enabled && readOnly == model.readOnly } + + public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false } + return peakingUI == model.peakingUI + && peakingArrowColor == model.peakingArrowColor + && enabled == model.enabled + && readOnly == model.readOnly + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift index b156743d..bf629f7a 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift @@ -8,7 +8,7 @@ // A base class that has common list item boilerplate model stuffs. import MVMCore -@objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol { +@objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol, ModelComparisonProtocol, MoleculeModelComparisonProtocol { //-------------------------------------------------- // MARK: - Properties @@ -23,6 +23,7 @@ import MVMCore public var accessibilityTraits: UIAccessibilityTraits? public var accessibilityValue: String? public var accessibilityText: String? + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -129,4 +130,29 @@ import MVMCore try container.encodeIfPresent(accessibilityValue, forKey: .accessibilityValue) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) } + + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && hideArrow == model.hideArrow + && style == model.style + && gone == model.gone + && accessibilityText == model.accessibilityText + && accessibilityValue == model.accessibilityValue + && accessibilityTraits == model.accessibilityTraits + && line.isEqual(to: model.line) + && action.isEqual(to: model.action) + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && hideArrow == model.hideArrow + && style == model.style + && gone == model.gone + && accessibilityText == model.accessibilityText + && accessibilityValue == model.accessibilityValue + && accessibilityTraits == model.accessibilityTraits + && line.isVisuallyEquivalent(to: model.line) + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index 383c4692..9bfa69c0 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -74,9 +74,14 @@ try super.encode(to: encoder) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard super.isEqual(to: model), let model = model as? Self else { return false } - return action.isEqual(to: model.action) - && border == border + return border == border + && action.isEqual(to: model.action) + } + + public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false } + return border == border } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift index 48b4fc2a..82958a12 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeListItemModel.swift @@ -53,17 +53,13 @@ import MVMCore 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 + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return molecule.isEqual(to: model) + } + + public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false } + return molecule.isVisuallyEquivalent(to: model.molecule) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift index 75dcfd4f..72b9ee24 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift @@ -59,10 +59,16 @@ try container.encode(gone, forKey: .gone) } - public override func isEqual(to model: any ModelProtocol) -> Bool { + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard super.isEqual(to: model), let model = model as? Self else { return false } - return backgroundColor == model.backgroundColor - && spacing == model.spacing + return spacing == model.spacing + && percent == model.percent + && gone == model.gone + } + + public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false } + return spacing == model.spacing && percent == model.percent && gone == model.gone } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeTableViewCell.swift index 19e70358..731cfccd 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeTableViewCell.swift @@ -30,7 +30,10 @@ import UIKit } public override class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { - MoleculeContainer.nameForReuse(with: model, delegateObject) + if let listModel = model as? ListItemModel, listModel.hasStableId { + return "\(MoleculeContainer.nameForReuse(with: model, delegateObject) ?? "")<\(listModel.id)>" + } + return MoleculeContainer.nameForReuse(with: model, delegateObject) } public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { diff --git a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift index bd496f3a..85c4cf86 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift @@ -37,7 +37,7 @@ fatalError("init(from:) has not been implemented") } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return backgroundColor == model.backgroundColor && spacing == model.spacing diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift index ce271ba6..a31f801b 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainerModel.swift @@ -62,9 +62,14 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return molecule.isEqual(to: model.molecule) && backgroundColor == model.backgroundColor } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift index df7b897e..bc9dad7f 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift @@ -107,9 +107,9 @@ public class EyebrowHeadlineBodyLinkModel: ParentMoleculeModelProtocol { try container.encodeIfPresent(link, forKey: .link) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return backgroundColor == model.backgroundColor - && children.areEqual(to: model.children) + && children.isEqual(to: model.children) } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index a062677b..f556d675 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -94,7 +94,7 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol { try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return headline.isEqual(to: model.headline) && body.isEqual(to: model.body) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 914e6427..fdd7f5b6 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -166,9 +166,19 @@ open class Carousel: View { public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { self.delegateObject = delegateObject + let originalModel = self.model as? CarouselModel super.set(with: model, delegateObject, additionalData) guard let carouselModel = model as? CarouselModel else { return } + + if #available(iOS 15.0, *) { + if let originalModel, carouselModel.isVisuallyEquivalent(to: originalModel) { + collectionView.reconfigureItems(at: collectionView.indexPathsForVisibleItems) + FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate) + return + } + } + accessibilityLabel = carouselModel.accessibilityText collectionView.layer.borderColor = UIColor.mvmCoolGray3.cgColor collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0 diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift index 4e645a92..449d2abf 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift @@ -172,10 +172,10 @@ import UIKit try container.encode(readOnly, forKey: .readOnly) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return backgroundColor == backgroundColor - && molecules.areEqual(to: model.molecules) + && molecules.isEqual(to: model.molecules) && spacing == model.spacing && border == model.border && loop == model.loop @@ -194,6 +194,27 @@ import UIKit && enabled == model.enabled && readOnly == model.readOnly } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == backgroundColor + && spacing == model.spacing + && border == model.border + && loop == model.loop + && height == model.height + && itemWidthPercent == model.itemWidthPercent + && itemAlignment == model.itemAlignment + && paging == model.paging + && useHorizontalMargins == model.useHorizontalMargins + && leftPadding == model.leftPadding + && rightPadding == model.rightPadding + && accessibilityText == model.accessibilityText + && baseValue == model.baseValue + && enabled == model.enabled + && readOnly == model.readOnly + && pagingMolecule.isVisuallyEquivalent(to: model.pagingMolecule) + && molecules.isVisuallyEquivalent(to: model.molecules) + } } extension CarouselModel { diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index 7f03df14..087717c1 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -79,10 +79,10 @@ try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } - public func isEqual(to model: any ModelProtocol) -> Bool { + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { guard let model = model as? Self else { return false } return backgroundColor == model.backgroundColor - && molecules.areEqual(to: model.molecules) + && molecules.isEqual(to: model.molecules) && axis == model.axis && spacing == model.spacing } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift index 907c2cfa..5465c8e4 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeComparisonProtocol.swift @@ -8,11 +8,12 @@ import Foundation -public protocol MoleculeModelComparisonProtocol: ModelProtocol { +public protocol MoleculeModelComparisonProtocol: ModelComparisonProtocol { - /** True if there are no visual differences between models. + /** + 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. + 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 } @@ -23,3 +24,39 @@ extension MoleculeModelComparisonProtocol { return isEqual(to: model) } } + +public extension Optional { + + /// Checks if the curent model is equal to another model. + func isVisuallyEquivalent(to model: MoleculeModelComparisonProtocol?) -> Bool { + guard let self = self as? MoleculeModelComparisonProtocol else { + return model == nil + } + guard let model = model else { + return false + } + return self.isVisuallyEquivalent(to: model) + } +} + +public extension Collection { + /// Checks if all the models in the given collection match another given collection. + func isVisuallyEquivalent(to models: [MoleculeModelComparisonProtocol]) -> Bool { + guard count == models.count, let self = self as? [MoleculeModelComparisonProtocol] else { return false } + return models.indices.allSatisfy { index in + self[index].isVisuallyEquivalent(to: models[index]) + } + } +} + +public extension Optional where Wrapped: Collection { + func isVisuallyEquivalent(to models: [MoleculeModelComparisonProtocol]?) -> Bool { + guard let self = self as? [MoleculeModelComparisonProtocol] else { + return models == nil + } + guard let models = models else { + return false + } + return self.isVisuallyEquivalent(to: models) + } +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift index c355d3f6..bf55de18 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/MoleculeModelProtocol.swift @@ -19,7 +19,7 @@ public extension MoleculeModelProtocol { static var categoryCodingKey: String { "moleculeName" } - func isEqual(to model: any ModelProtocol) -> Bool { + func isEqual(to model: ModelProtocol) -> Bool { guard let model = model as? Self else { return false } return id == model.id } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift index 88817e6a..c2f1e886 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ParentMoleculeModelProtocol.swift @@ -69,7 +69,12 @@ 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) + return model.children.isEqual(to: model.children) + } + + func areVisuallyEquivalent(to model: any ModelProtocol) -> Bool { + guard let model = model as? Self else { return false } + return model.children.isVisuallyEquivalent(to: model.children) } func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result { @@ -119,7 +124,14 @@ public extension ParentMoleculeModelProtocol { extension ParentModelProtocol { - func deepCompare(_ anotherParent: ParentModelProtocol, with test: (ModelProtocol, ModelProtocol)->Bool) -> (Bool, myChild: ModelProtocol?, theirChild: ModelProtocol?) { + public typealias DeepCompareResult = (matched: Bool, myChild: ModelProtocol?, theirChild: ModelProtocol?) + + public func deepEquals(to model: any ModelProtocol) -> DeepCompareResult { + guard let model = model as? ParentModelProtocol else { return (false, self, model) } + return deepCompare(model) { $0.isEqual(to: $1) } + } + + func deepCompare(_ anotherParent: ParentModelProtocol, with test: (ModelProtocol, ModelProtocol)->Bool) -> DeepCompareResult { guard test(self, anotherParent) else { return (false, myChild: self, theirChild: self)} diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index c146afa0..fe13c3eb 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -522,7 +522,8 @@ import MVMCore return (model, replacedMolecule) } let uiUpdatedModels: [MoleculeModelProtocol] = replacedModels.compactMap { new, existing in - guard !new.isVisuallyEquivalent(to: existing) else { + guard !new.isEqual(to: existing) else { + MVMCoreLoggingHandler.shared()?.handleDebugMessage("UI for molecules: \(new) is the same. Skip UI update.") return nil } return new diff --git a/MVMCoreUI/Containers/Views/ContainerModel.swift b/MVMCoreUI/Containers/Views/ContainerModel.swift index b54590dd..81b7c769 100644 --- a/MVMCoreUI/Containers/Views/ContainerModel.swift +++ b/MVMCoreUI/Containers/Views/ContainerModel.swift @@ -14,6 +14,7 @@ open class ContainerModel: ContainerModelProtocol, Codable { //-------------------------------------------------- public var id: String = UUID().uuidString + public var hasStableId = false public var horizontalAlignment: UIStackView.Alignment? public var useHorizontalMargins: Bool? @@ -78,7 +79,10 @@ open class ContainerModel: ContainerModelProtocol, Codable { required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString + if let id = try typeContainer.decodeIfPresent(String.self, forKey: .id) { + self.id = id + hasStableId = true + } if let verticalAlignmentString = try typeContainer.decodeIfPresent(String.self, forKey: .verticalAlignment) { verticalAlignment = ContainerHelper.getAlignment(for: verticalAlignmentString)