Merge remote-tracking branch 'origin/develop' into feature/monarch

This commit is contained in:
Hedden, Kyle Matthew 2024-06-13 12:24:41 -04:00
commit 2e36423fae
9 changed files with 52 additions and 39 deletions

View File

@ -24,6 +24,7 @@
public var fieldKey: String? public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable? public var baseValue: AnyHashable?
public var gone: Bool = false
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Validation // MARK: - Validation
@ -53,6 +54,7 @@
case groupName case groupName
case enabled case enabled
case readOnly case readOnly
case gone
} }
//-------------------------------------------------- //--------------------------------------------------
@ -76,6 +78,7 @@
if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) { if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) {
self.readOnly = readOnly self.readOnly = readOnly
} }
gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) ?? false
try super.init(from: decoder) try super.init(from: decoder)
} }
@ -99,6 +102,7 @@
&& fieldValue == model.fieldValue && fieldValue == model.fieldValue
&& enabled == model.enabled && enabled == model.enabled
&& readOnly == model.readOnly && readOnly == model.readOnly
&& gone == model.gone
} }
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
@ -107,5 +111,6 @@
&& peakingArrowColor == model.peakingArrowColor && peakingArrowColor == model.peakingArrowColor
&& enabled == model.enabled && enabled == model.enabled
&& readOnly == model.readOnly && readOnly == model.readOnly
&& gone == model.gone
} }
} }

View File

@ -90,7 +90,7 @@ open class Carousel: View {
showPeaking(false) showPeaking(false)
// Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled.
guard let model = model as? CarouselModel, !model.molecules.isEmpty else { return } guard let model = model as? CarouselModel, !model.visibleMolecules.isEmpty else { return }
guard (model.paging == true || loop == true) else { guard (model.paging == true || loop == true) else {
DispatchQueue.main.async { [self] in DispatchQueue.main.async { [self] in
updatePagerVisibility() updatePagerVisibility()
@ -174,7 +174,9 @@ open class Carousel: View {
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)") MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)")
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
if let originalModel, carouselModel.isDeeplyVisuallyEquivalent(to: originalModel) { if let originalModel, carouselModel.isDeeplyVisuallyEquivalent(to: originalModel),
originalModel.visibleMolecules.isVisuallyEquivalent(to: molecules ?? []) // Since the carousel model's children are in place replaced and we do not have a deep copy of this model tree, add in this hack to check if the prior captured carousel items match the newly visible ones.
{
// Prevents a carousel reset while still updating the cell backing data through reconfigureItems. // Prevents a carousel reset while still updating the cell backing data through reconfigureItems.
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...") MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...")
prepareMolecules(with: carouselModel) prepareMolecules(with: carouselModel)
@ -229,7 +231,7 @@ open class Carousel: View {
//-------------------------------------------------- //--------------------------------------------------
func prepareMolecules(with carouselModel: CarouselModel?) { func prepareMolecules(with carouselModel: CarouselModel?) {
guard let newMolecules = carouselModel?.molecules else { guard let newMolecules = carouselModel?.visibleMolecules else {
numberOfPages = 0 numberOfPages = 0
molecules = nil molecules = nil
return return

View File

@ -44,6 +44,10 @@ import UIKit
public var selectable = false public var selectable = false
public var selectedIndex: Int? public var selectedIndex: Int?
public var visibleMolecules: [MoleculeModelProtocol & CarouselItemModelProtocol] {
molecules.filter { !$0.gone }
}
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) { public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
self.molecules = molecules self.molecules = molecules
} }

View File

@ -9,6 +9,7 @@
public protocol CarouselItemModelProtocol: FormFieldProtocol, ContainerModelProtocol { public protocol CarouselItemModelProtocol: FormFieldProtocol, ContainerModelProtocol {
var analyticsData: JSONValueDictionary? { get set } var analyticsData: JSONValueDictionary? { get set }
var gone: Bool { get set }
} }
public extension CarouselItemModelProtocol { public extension CarouselItemModelProtocol {
@ -16,4 +17,9 @@ public extension CarouselItemModelProtocol {
get { nil } get { nil }
set { analyticsData = newValue } set { analyticsData = newValue }
} }
var gone: Bool{
get { false }
set { }
}
} }

View File

@ -43,7 +43,7 @@ public extension ParentModelProtocol where Self: AnyObject {
func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
var replacedMolecule: MoleculeModelProtocol? var replacedMolecule: MoleculeModelProtocol?
return try replaceChildMolecule(at: &molecules, with: replacementMolecule, replaced: &replacedMolecule) ? replacedMolecule : nil return try replaceChildMolecule(in: &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".
@ -67,6 +67,26 @@ public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelP
public extension ParentMoleculeModelProtocol { public extension ParentMoleculeModelProtocol {
/// Recursively finds and replaces the first child matching the replacement molecule id property.
mutating func deepReplaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
var replacedMolecule: MoleculeModelProtocol?
var possibleError: Error?
// Dive into each root.
depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in
guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
do {
replacedMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule)
} catch {
possibleError = error
}
stop = replacedMolecule != nil || possibleError != nil
}
if let error = possibleError {
throw error
}
return replacedMolecule
}
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) {

View File

@ -40,31 +40,3 @@ public extension TemplateModelProtocol {
return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit) return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit)
} }
} }
extension TemplateModelProtocol {
/// Recursively finds and replaces the first child matching the replacement molecule id property.
mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
// Attempt root level replacement on the template model first.
if let replacedMolecule = try replaceChildMolecule(with: replacementMolecule) {
return replacedMolecule
}
var replacedMolecule: MoleculeModelProtocol?
var possibleError: Error?
// Dive into each root thereafter.
depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in
guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
do {
replacedMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule)
} catch {
possibleError = error
}
stop = replacedMolecule != nil || possibleError != nil
}
if let error = possibleError {
throw error
}
return replacedMolecule
}
}

View File

@ -146,6 +146,11 @@ import MVMCore
do { do {
let template = try parsePageJSON(loadObject: loadObject) let template = try parsePageJSON(loadObject: loadObject)
pageModel = template // TODO: Eventually this page parsing should be done outside of this class and then set by the caller. For now, double duty. pageModel = template // TODO: Eventually this page parsing should be done outside of this class and then set by the caller. For now, double duty.
// Needed for PageMoleculeTransformationBehavior + PageLocalDataShareBehavior behaviors.
if let behaviorContainer = template as? (PageBehaviorContainerModelProtocol & TemplateModelProtocol) {
var behaviorHandler = self
behaviorHandler.applyBehaviors(pageBehaviorModel: behaviorContainer)
}
isFirstRender = true // Assuming this is only on the first page load from the handler. Might need to revist later. isFirstRender = true // Assuming this is only on the first page load from the handler. Might need to revist later.
if let backgroundRequest = loadObject.requestParameters?.backgroundRequest, !backgroundRequest, let pageType, let identifier = loadObject.identifier { if let backgroundRequest = loadObject.requestParameters?.backgroundRequest, !backgroundRequest, let pageType, let identifier = loadObject.identifier {
MVMCoreLoggingHandler.shared()?.logCoreEvent(.pageProcessingComplete(pageType: pageType, requestUUID: identifier, webUrl: nil)) MVMCoreLoggingHandler.shared()?.logCoreEvent(.pageProcessingComplete(pageType: pageType, requestUUID: identifier, webUrl: nil))
@ -230,11 +235,11 @@ import MVMCore
open func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) { open func handleNewData(_ pageModel: PageModelProtocol? = nil, shouldTriggerRender: Bool = true) {
guard var newPageModel = pageModel ?? self.pageModel else { return } guard var newPageModel = pageModel ?? self.pageModel else { return }
let originalModel = isFirstRender ? nil : self.pageModel as? MVMControllerModelProtocol let originalModel = self.pageModel as? MVMControllerModelProtocol
// Refresh our behaviors if there is a page change. // Refresh our behaviors if there is a page change. Originally set up in shouldFinishProcessingLoad.
if let behaviorContainer = newPageModel as? (PageBehaviorContainerModelProtocol & TemplateModelProtocol), if let behaviorContainer = newPageModel as? (PageBehaviorContainerModelProtocol & TemplateModelProtocol),
(originalModel == nil || originalModel!.id != behaviorContainer.id) { (originalModel == nil || originalModel!.id != behaviorContainer.id) {
var behaviorHandler = self var behaviorHandler = self
behaviorHandler.applyBehaviors(pageBehaviorModel: behaviorContainer) behaviorHandler.applyBehaviors(pageBehaviorModel: behaviorContainer)
} }
@ -277,7 +282,6 @@ import MVMCore
let allUpdatedMolecules = behaviorUpdatedModels //+ pageUpdatedModels let allUpdatedMolecules = behaviorUpdatedModels //+ pageUpdatedModels
// Notify the manager of new data. // Notify the manager of new data.
// Warning: Some flows cause table reloads. Until the UI update is decoupled, should be after the updateUI.
manager?.newDataReceived?(in: self) manager?.newDataReceived?(in: self)
guard shouldTriggerRender else { return } guard shouldTriggerRender else { return }

View File

@ -41,7 +41,7 @@ public extension PageBehaviorHandlerProtocol {
// Apply them to the page. // Apply them to the page.
self.behaviors = behaviors.count > 0 ? behaviors : nil self.behaviors = behaviors.count > 0 ? behaviors : nil
// Ask the session to apply any more. (Curently inverted contol due to Swift <--> Obj-C conflict. // Ask the session to apply any more. (Currently inverted contol due to Swift <--> Obj-C conflict.)
if let viewController = self as? UIViewController { if let viewController = self as? UIViewController {
MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController) MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController)
} }

View File

@ -41,7 +41,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
self.delegateObject = delegateObject self.delegateObject = delegateObject
guard let pageType = delegateObject?.moleculeDelegate?.getTemplateModel()?.pageType else { return } guard let pageType = delegateObject?.moleculeDelegate?.getTemplateModel()?.pageType else { return }
MVMCoreViewControllerMappingObject.shared()?.addOptionalModules(toMapping: moleculeIds, forPageType: pageType) MVMCoreViewControllerMappingObject.shared()?.addOptionalModules(toMapping: moleculeIds, forPageType: pageType)
Self.debugLog("Initializing for \((model as! ReplaceableMoleculeBehaviorModel).moleculeIds)") Self.debugLog("Initializing for \(moleculeIds)")
} }
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?, changes: inout [MoleculeModelProtocol]) -> [MoleculeModelProtocol]? { public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?, changes: inout [MoleculeModelProtocol]) -> [MoleculeModelProtocol]? {
@ -85,7 +85,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
moleculeModels.forEach { newMolecule in moleculeModels.forEach { newMolecule in
do { do {
if let replacedMolecule = try parentMolecule.replaceChildMolecule(with: newMolecule) { if let replacedMolecule = try parentMolecule.deepReplaceMolecule(with: newMolecule) {
guard !replacedMolecule.deepEquals(to: newMolecule) else { guard !replacedMolecule.deepEquals(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. // Note: Slight risk here of replacing the something in the original tree and misreporting that is it not replaced based on equality.
debugLog("deep molecule \(newMolecule) is the same as \(replacedMolecule). skipping...") debugLog("deep molecule \(newMolecule) is the same as \(replacedMolecule). skipping...")