diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index dbddacef..e3d37cf4 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -162,7 +162,7 @@ import VDS && fontName == model.fontName && fontSize == model.fontSize && textAlignment == model.textAlignment - && attributes.isEqual(to: model.attributes) + && attributes.isVisuallyEquivalent(to: model.attributes) && html == model.html && hero == model.hero && makeWholeViewClickable == model.makeWholeViewClickable diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift index 9a700903..03bd6dc8 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabBarModel.swift @@ -106,9 +106,28 @@ open class TabBarModel: MoleculeModelProtocol { try container.encode(selectedTab, forKey: .selectedTab) try container.encodeIfPresent(style, forKey: .style) } + + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && selectedColor == model.selectedColor + && selectedTab == model.selectedTab + && style == model.style + && tabs == model.tabs + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && selectedColor == model.selectedColor + && selectedTab == model.selectedTab + && style == model.style + && tabs.isVisuallyEquivalent(to: model.tabs) + } } -open class TabBarItemModel: Codable { +open class TabBarItemModel: Codable, Equatable, MoleculeModelComparisonProtocol { + open var title: String? open var image: String open var action: ActionModelProtocol @@ -142,4 +161,18 @@ open class TabBarItemModel: Codable { try container.encodeModel(action, forKey: .action) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) } + + public static func == (lhs: TabBarItemModel, rhs: TabBarItemModel) -> Bool { + return lhs.title == rhs.title + && lhs.image == rhs.image + && lhs.accessibilityText == rhs.accessibilityText + && lhs.action.isEqual(to: rhs.action) + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return image == model.image + && accessibilityText == model.accessibilityText + && title == model.title + } } diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift index a7acadf6..9f9ef766 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift @@ -105,11 +105,39 @@ open class TabsModel: MoleculeModelProtocol { try container.encode(borderLine, forKey: .borderLine) try container.encodeIfPresent(minWidth, forKey: .minWidth) } + + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && style == model.style + && orientation == model.orientation + && indicatorPosition == model.indicatorPosition + && overflow == model.overflow + && fillContainer == model.fillContainer + && size == model.size + && borderLine == model.borderLine + && minWidth == model.minWidth + && selectedIndex == model.selectedIndex + && tabs.isEqual(to: model.tabs) + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && style == model.style + && orientation == model.orientation + && indicatorPosition == model.indicatorPosition + && overflow == model.overflow + && fillContainer == model.fillContainer + && size == model.size + && borderLine == model.borderLine + && minWidth == model.minWidth + //&& selectedIndex == model.selectedIndex // Selected index could have been either reset locally or by server. For now ignore.c + && tabs.isVisuallyEquivalent(to: model.tabs) + } } - - -open class TabItemModel: Codable { +open class TabItemModel: Codable, Equatable, MoleculeModelComparisonProtocol { open var label: LabelModel open var action: ActionModelProtocol? @@ -146,4 +174,14 @@ open class TabItemModel: Codable { try container.encodeModel(label, forKey: .label) try container.encodeModelIfPresent(action, forKey: .action) } + + public static func == (lhs: TabItemModel, rhs: TabItemModel) -> Bool { + return lhs.label.isEqual(to: rhs.label) + && lhs.action.isEqual(to: rhs.action) + } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return label.isVisuallyEquivalent(to: model.label) + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift index 1924820b..a769eb89 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift @@ -23,17 +23,18 @@ public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol { return molecules.flatMap { $0 } } - public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool { - guard let replacementMolecule = replacementMolecule as? ListItemModelProtocol & MoleculeModelProtocol else { return false } + public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { + guard let replacementMolecule = replacementMolecule as? ListItemModelProtocol & MoleculeModelProtocol else { return nil } for (tabIndex, _) in molecules.enumerated() { for (elementIndex, _) in molecules[tabIndex].enumerated() { if molecules[tabIndex][elementIndex].id == replacementMolecule.id { + let replacedMolecule = molecules[tabIndex][elementIndex] molecules[tabIndex][elementIndex] = replacementMolecule - return true + return replacedMolecule } } } - return false + return nil } //-------------------------------------------------- @@ -90,6 +91,20 @@ public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol { try container.encode(tabs, forKey: .tabs) try container.encodeModels2D(molecules, forKey: .molecules) } + + public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { + guard super.isEqual(to: model), let model = model as? Self else { return false } + return tabs.isEqual(to: model.tabs) + && molecules.count == model.molecules.count + && zip(molecules, model.molecules).allSatisfy({ $0.0.isEqual(to: $0.1) }) + } + + public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false } + return tabs.isVisuallyEquivalent(to: model.tabs) + && molecules.count == model.molecules.count + && zip(molecules, model.molecules).allSatisfy({ $0.0.isVisuallyEquivalent(to: $0.1) }) + } } extension TabsListItemModel: PageBehaviorProtocolRequirer { diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift index 67fc779f..f4839612 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift @@ -141,6 +141,17 @@ open class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtoc try container.encodeIfPresent(style, forKey: .style) try container.encodeIfPresent(titleOffset, forKey: .titleOffset) } + + public func isEqual(to model: any ModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && title == model.title + && hidden == model.hidden + && tintColor == model.tintColor + && line.isEqual(to: model.line) + && hidesSystemBackButton == model.hidesSystemBackButton + && style == model.style + } } extension NavigationItemModel: ParentMoleculeModelProtocol { diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index f556d675..1c2e03a7 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -101,6 +101,14 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol { && style == style && backgroundColor == backgroundColor } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return headline.isVisuallyEquivalent(to: model.headline) + && body.isVisuallyEquivalent(to: model.body) + && style == style + && backgroundColor == backgroundColor + } } public extension HeadlineBodyModel { diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index 087717c1..65baa962 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -86,4 +86,12 @@ && axis == model.axis && spacing == model.spacing } + + public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { + guard let model = model as? Self else { return false } + return backgroundColor == model.backgroundColor + && molecules.isVisuallyEquivalent(to: model.molecules) + && axis == model.axis + && spacing == model.spacing + } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift index b030d92a..f8e95f7b 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift @@ -38,7 +38,7 @@ public extension MoleculeTreeTraversalProtocol { func printMolecules(options: TreeTraversalOptions = .parentFirst) { depthFirstTraverse(options: options, depth: 1) { depth, molecule, stop in - print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule): \(molecule.id)]") + print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule)]") } } diff --git a/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift index 66712434..698162f6 100644 --- a/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift +++ b/MVMCoreUI/Behaviors/ReplaceableMoleculeBehaviorModel.swift @@ -65,17 +65,17 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co } fileprivate func findAndReplace(_ moleculeModels: [any MoleculeModelProtocol], in rootMolecules: [any MoleculeModelProtocol]) -> [any MoleculeModelProtocol]? { - debugLog("onPageNew replacing \(moleculeModels.map { $0.id })") + debugLog("attempting to replace \(moleculeModels.map { $0.id }) in \(rootMolecules)") var hasReplacement = false let updatedRootMolecules = rootMolecules.map { rootMolecule in // Top level check to return a new root molecule. if let updatedMolecule = moleculeModels.first(where: { rootMolecule.id == $0.id }) { guard !updatedMolecule.isEqual(to: rootMolecule) else { - debugLog("onPageNew molecule \(updatedMolecule) is the same as \(rootMolecule). skipping...") + debugLog("molecule \(updatedMolecule) is the same as \(rootMolecule). skipping...") return rootMolecule } - debugLog("onPageNew replacing \(rootMolecule) with \(updatedMolecule)") + debugLog("replacing \(rootMolecule) with \(updatedMolecule)") logUpdated(molecule: updatedMolecule) hasReplacement = true return updatedMolecule @@ -89,10 +89,10 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co if let replacedMolecule = try parentMolecule.replaceChildMolecule(with: newMolecule) { guard !replacedMolecule.isEqual(to: newMolecule) else { // Note: Slight risk here of replacing the something in the original tree and misreporting that is it not replaced based on equality. - debugLog("onPageNew molecule \(newMolecule) is the same as \(replacedMolecule). skipping...") + debugLog("molecule \(newMolecule) is the same as \(replacedMolecule). skipping...") return } - debugLog("onPageNew replacing \(replacedMolecule) with \(newMolecule)") + debugLog("replacing \(replacedMolecule) with \(newMolecule)") logUpdated(molecule: newMolecule) hasReplacement = true } @@ -106,6 +106,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co } return parentMolecule } + debugLog("replacing \(hasReplacement ? updatedRootMolecules.count : 0) molecules") return hasReplacement ? updatedRootMolecules : nil }