#if os(iOS) import Foundation import VGSL /// Utility for preloading the contents of `DivView'. /// /// ``DivViewPreloader`` can be useful when it is important to calculate layout before building /// ``DivView``, for example in `UICollectionView`. @MainActor public final class DivViewPreloader { /// Represents a change in estimated size for a specific ``DivView``. public struct DivViewSizeChange { public let cardId: DivCardID public let estimatedSize: DivViewSize } private(set) var setSourceTask: Task? private var blockProviders = [DivCardID: DivBlockProvider]() private let divKitComponents: DivKitComponents private let changeEventsPipe = SignalPipe() var changeEvents: Signal { changeEventsPipe.signal } /// Initializes a new ``DivViewPreloader`` instance. /// /// - Parameters: /// - divKitComponents: The ``DivKitComponents`` instance used for creating `DivBlockProvider`. public init(divKitComponents: DivKitComponents) { self.divKitComponents = divKitComponents } /// Sets the source for ``DivViewPreloader`` and updates the layout. /// - Parameters: /// - source: The source of the ``DivView``. /// - debugParams: Optional debug configurations for the ``DivView``. public func setSource( _ source: DivViewSource, debugParams: DebugParams = DebugParams() ) async { setSourceTask = Task { [oldTask = setSourceTask] in try? await oldTask?.value try Task.checkCancellation() let blockProvider: DivBlockProvider = blockProvider(for: source.id.cardId) await blockProvider.setSource( source, debugParams: debugParams ) blockProviders[source.id.cardId] = blockProvider } try? await setSourceTask?.value } /// Sets the source for ``DivViewPreloader`` and updates the layout. /// - Parameters: /// - source: The source of the ``DivView``. /// - debugParams: Optional debug configurations for the ``DivView``. @_spi(Legacy) public func setSource( _ source: DivViewSource, debugParams: DebugParams = DebugParams() ) { let blockProvider: DivBlockProvider = blockProvider(for: source.id.cardId) blockProvider.setSource( source, debugParams: debugParams ) blockProviders[source.id.cardId] = blockProvider } /// Sets the sources for ``DivViewPreloader`` and updates the layout. /// - Parameters: /// - sources: The sources of the ``DivView``. /// - debugParams: Optional debug configurations for the ``DivView``. public func setSources( _ sources: [DivViewSource], debugParams: DebugParams = DebugParams() ) async { setSourceTask = Task { [oldTask = setSourceTask] in try? await oldTask?.value let blockProviders = sources.map { blockProvider(for: $0.id.cardId) } await withTaskGroup(of: Void.self) { group in for (blockProvider, source) in zip(blockProviders, sources) { group.addTask { await blockProvider.setSource(source, debugParams: debugParams) } } } } try? await setSourceTask?.value } /// Fetches the expected size for a ``DivView`` with a specific identifier. /// /// - Parameters: /// - cardId: The unique identifier of the desired ``DivView``. /// /// - Returns: An optional `DivViewSize` representing the expected size if available, otherwise /// nil. @MainActor public func expectedSize(for cardId: DivCardID) -> DivViewSize? { blockProvider(for: cardId).cardSize } /// Adds an observer to listen for any ``DivView`` estimated size changes. /// /// - Parameters: /// - onCardSizeChanged: A closure that gets invoked whenever a ``DivView`` estimated size /// changes. /// /// - Returns: A `Disposable` which can be used to unregister the observer when it's no longer /// needed. public func addObserver(_ onCardSizeChanged: @escaping (DivViewSizeChange) -> Void) -> Disposable { changeEvents.addObserver(onCardSizeChanged) } func blockProvider(for cardId: DivCardID) -> DivBlockProvider { if let blockProvider = blockProviders[cardId] { return blockProvider } else { let blockProvider = DivBlockProvider(divKitComponents: divKitComponents) { [weak self] in self?.changeEventsPipe.send(DivViewSizeChange(cardId: $0, estimatedSize: $1)) } blockProviders[cardId] = blockProvider return blockProvider } } public func reset(cardId: DivCardID) { blockProviders.removeValue(forKey: cardId) } public func resetAll() { blockProviders.removeAll() } } #endif