Files
divkit/client/ios/DivKit/DivBlockModelingContext.swift
tabaina 8e868feec9 supported get/set stored values by card_id
commit_hash:d7fcc6410470b5d9b6830747c6e106da972bf713
2026-04-30 18:20:45 +03:00

364 lines
12 KiB
Swift

import CoreGraphics
import LayoutKit
import VGSL
#if os(iOS)
import UIKit
#else
import AppKit
#endif
public struct DivBlockModelingContext {
public let actionHandler: DivActionHandler?
public let blockStateStorage: DivBlockStateStorage
public let imageHolderFactory: DivImageHolderFactory
public let fontProvider: DivFontProvider
public private(set) var errorsStorage: DivErrorsStorage
public let variablesStorage: DivVariablesStorage
public private(set) var expressionResolver: ExpressionResolver
public let variableTracker: DivVariableTracker?
public private(set) var path: UIElementPath
public private(set) var currentDivId: String?
private(set) var viewId: DivViewId
private(set) var cardLogId: String?
private(set) var parentDivStatePath: DivStatePath?
private(set) var parentVisibility: DivVisibility = .visible
let stateManager: DivStateManager
let visibilityCounter: DivVisibilityCounter
let lastVisibleBoundsCache: DivLastVisibleBoundsCache
let divCustomBlockFactory: DivCustomBlockFactory
let flagsInfo: DivFlagsInfo
let extensionHandlers: [String: DivExtensionHandler]
let layoutDirection: UserInterfaceLayoutDirection
let debugParams: DebugParams
let scheduler: Scheduling
let playerFactory: PlayerFactory?
private(set) weak var parentScrollView: ScrollView?
let debugErrorCollector: DebugErrorCollector?
let tooltipViewFactory: DivTooltipViewFactory?
let functionsStorage: DivFunctionsStorage?
let triggersStorage: DivTriggersStorage?
// Overriden id for modified contexts of child divs, used in prototypes
private(set) var overridenId: String?
private(set) var sizeModifier: DivSizeModifier?
private(set) var localValues = [String: AnyHashable]()
let layoutProviderHandler: DivLayoutProviderHandler?
let idToPath: IdToPath
let animatorController: DivAnimatorController?
private(set) var accessibilityElementsStorage = DivAccessibilityElementsStorage()
private let persistentValuesStorage: DivPersistentValuesStorage
private let functionsProvider: FunctionsProvider
// Deprecated, `parentPath` was changed to `path`
public var parentPath: UIElementPath {
path
}
public var cardId: DivCardID {
viewId.cardId
}
@_spi(Internal)
public init(
cardId: DivCardID,
additionalId: String? = nil,
stateManager: DivStateManager = DivStateManager(),
actionHandler: DivActionHandler? = nil,
blockStateStorage: DivBlockStateStorage = DivBlockStateStorage(),
imageHolderFactory: DivImageHolderFactory,
extensionHandlers: [DivExtensionHandler] = [],
variablesStorage: DivVariablesStorage = DivVariablesStorage(),
scheduler: Scheduling? = nil
) {
self.init(
viewId: DivViewId(cardId: cardId, additionalId: additionalId),
stateManager: stateManager,
actionHandler: actionHandler,
blockStateStorage: blockStateStorage,
visibilityCounter: nil,
lastVisibleBoundsCache: nil,
imageHolderFactory: imageHolderFactory,
divCustomBlockFactory: nil,
fontProvider: nil,
flagsInfo: .default,
extensionHandlers: extensionHandlers.dictionary,
functionsStorage: nil,
variablesStorage: variablesStorage,
triggersStorage: nil,
playerFactory: nil,
debugParams: DebugParams(),
scheduler: scheduler,
parentScrollView: nil,
errorsStorage: nil,
debugErrorCollector: nil,
layoutDirection: .leftToRight,
variableTracker: nil,
persistentValuesStorage: nil,
tooltipViewFactory: nil,
layoutProviderHandler: nil,
idToPath: nil,
animatorController: nil
)
}
init(
viewId: DivViewId,
stateManager: DivStateManager,
actionHandler: DivActionHandler?,
blockStateStorage: DivBlockStateStorage,
visibilityCounter: DivVisibilityCounter?,
lastVisibleBoundsCache: DivLastVisibleBoundsCache?,
imageHolderFactory: DivImageHolderFactory,
divCustomBlockFactory: DivCustomBlockFactory?,
fontProvider: DivFontProvider?,
flagsInfo: DivFlagsInfo,
extensionHandlers: [String: DivExtensionHandler],
functionsStorage: DivFunctionsStorage?,
variablesStorage: DivVariablesStorage,
triggersStorage: DivTriggersStorage?,
playerFactory: PlayerFactory?,
debugParams: DebugParams,
scheduler: Scheduling?,
parentScrollView: ScrollView?,
errorsStorage: DivErrorsStorage?,
debugErrorCollector: DebugErrorCollector?,
layoutDirection: UserInterfaceLayoutDirection,
variableTracker: DivVariableTracker?,
persistentValuesStorage: DivPersistentValuesStorage?,
tooltipViewFactory: DivTooltipViewFactory?,
layoutProviderHandler: DivLayoutProviderHandler?,
idToPath: IdToPath?,
animatorController: DivAnimatorController?
) {
self.viewId = viewId
let path = makePath(viewId: viewId)
self.path = path
self.stateManager = stateManager
self.actionHandler = actionHandler
self.blockStateStorage = blockStateStorage
self.visibilityCounter = visibilityCounter ?? DivVisibilityCounter()
self.lastVisibleBoundsCache = lastVisibleBoundsCache ?? DivLastVisibleBoundsCache()
self.imageHolderFactory = imageHolderFactory
self.divCustomBlockFactory = divCustomBlockFactory ?? EmptyDivCustomBlockFactory()
self.flagsInfo = flagsInfo
self.fontProvider = fontProvider ?? DefaultFontProvider()
self.playerFactory = playerFactory
self.debugParams = debugParams
self.scheduler = scheduler ?? TimerScheduler()
self.parentScrollView = parentScrollView
let errorsStorage = errorsStorage ?? DivErrorsStorage(errors: [])
self.debugErrorCollector = debugErrorCollector
self.errorsStorage = errorsStorage
self.layoutDirection = layoutDirection
self.variableTracker = variableTracker
let persistentValuesStorage = persistentValuesStorage ?? DivPersistentValuesStorage()
self.persistentValuesStorage = persistentValuesStorage
self.tooltipViewFactory = tooltipViewFactory
self.functionsStorage = functionsStorage
self.variablesStorage = variablesStorage
self.triggersStorage = triggersStorage
self.extensionHandlers = extensionHandlers
self.layoutProviderHandler = layoutProviderHandler
self.functionsProvider = FunctionsProvider(
persistentValuesStorage: persistentValuesStorage,
cardId: path.cardId
)
self.idToPath = idToPath ?? IdToPath()
self.animatorController = animatorController
expressionResolver = makeExpressionResolver(
functionsProvider: functionsProvider,
viewId: viewId,
path: path,
variablesStorage: variablesStorage,
functionsStorage: functionsStorage,
localValues: nil,
variableTracker: variableTracker,
errorsStorage: errorsStorage
)
}
public func getExtensionHandlers(for div: DivBase) -> [DivExtensionHandler] {
(div.extensions ?? []).compactMap {
let id = $0.id
if !extensionHandlers.keys.contains(id) {
addError(message: "No DivExtensionHandler for: \(id)")
}
return extensionHandlers[id]
} + debugParams.widcardExtensionHandlers
}
public func addError(message: String) {
errorsStorage.add(DivBlockModelingError(message, path: path))
}
public func addWarning(message: String) {
errorsStorage.add(DivBlockModelingWarning(message, path: path))
}
func addError(error: Error) {
if let divError = error as? DivError {
errorsStorage.add(divError)
return
}
errorsStorage.add(DivUnknownError(error, path: path))
}
func makeBinding<T>(variableName: String?, defaultValue: T) -> Binding<T> {
guard let variableName else {
return Binding(name: "", value: Property(initialValue: defaultValue))
}
let divVariableName = DivVariableName(rawValue: variableName)
variableTracker?.onVariableUsed(id: viewId, variable: divVariableName)
let value: T = variablesStorage
.getVariableValue(path: path, name: divVariableName) ?? defaultValue
let valueProp = Property<T>(
getter: { value },
setter: { [weak variablesStorage, path] val in
guard let variablesStorage, let newValue = DivVariableValue(val) else { return }
variablesStorage.update(
path: path,
name: divVariableName,
value: newValue
)
}
)
return Binding(name: variableName, value: valueProp)
}
func modifying(
overridenId: String? = nil,
cardLogId: String? = nil,
currentDivId: String? = nil,
pathSuffix: String? = nil,
parentDivStatePath: DivStatePath? = nil,
errorsStorage: DivErrorsStorage? = nil,
sizeModifier: DivSizeModifier? = nil,
prototypeParams: PrototypeParams? = nil,
parentVisibility: DivVisibility? = nil
) -> Self {
var context = self
// It is important to set nil value for elementId if nothing passed.
// This fiels is used for overriding ids in prototype items
context.overridenId = overridenId
context.currentDivId = currentDivId
if let cardLogId {
context.cardLogId = cardLogId
}
if let parentDivStatePath {
context.parentDivStatePath = parentDivStatePath
}
if let sizeModifier {
context.sizeModifier = sizeModifier
}
if let parentVisibility {
context.parentVisibility = DivVisibility.min(
self.parentVisibility,
parentVisibility
)
}
if pathSuffix == nil,
errorsStorage == nil,
prototypeParams == nil {
return context
}
if let prototypeParams {
var localValues = self.localValues
localValues[prototypeParams.variableName] = prototypeParams.value
localValues["index"] = prototypeParams.index
context.localValues = localValues
}
if let pathSuffix {
context.path = context.path + pathSuffix
}
context.errorsStorage = errorsStorage ?? self.errorsStorage
context.expressionResolver = makeExpressionResolver(
functionsProvider: functionsProvider,
viewId: viewId,
path: context.path,
variablesStorage: variablesStorage,
functionsStorage: functionsStorage,
localValues: context.localValues,
variableTracker: variableTracker,
errorsStorage: context.errorsStorage
)
return context
}
func cloneForTooltip(tooltipId: String) -> Self {
var context = self
let viewId = DivViewId(cardId: cardId, additionalId: tooltipId)
context.viewId = DivViewId(cardId: cardId, additionalId: tooltipId)
context.path = makePath(viewId: viewId)
return context
}
}
struct PrototypeParams {
let index: Int
let variableName: String
let value: DivDictionary
}
private func makeExpressionResolver(
functionsProvider: FunctionsProvider,
viewId: DivViewId,
path: UIElementPath,
variablesStorage: DivVariablesStorage,
functionsStorage: DivFunctionsStorage?,
localValues: [String: AnyHashable]?,
variableTracker: DivVariableTracker?,
errorsStorage: DivErrorsStorage
) -> ExpressionResolver {
ExpressionResolver(
functionsProvider: functionsProvider,
customFunctionsStorageProvider: {
functionsStorage?.getStorage(path: path, contains: $0)
},
variableValueProvider: {
if let value = localValues?[$0] {
return value
}
let variableName = DivVariableName(rawValue: $0)
variableTracker?.onVariableUsed(id: viewId, variable: variableName)
return variablesStorage.getVariableValue(path: path, name: variableName)
},
errorTracker: { error in
errorsStorage.add(DivExpressionError(error, path: path))
}
)
}
private func makePath(viewId: DivViewId) -> UIElementPath {
let cardIdPath = UIElementPath(viewId.cardId.rawValue)
return if let additionalId = viewId.additionalId {
cardIdPath + additionalId
} else {
cardIdPath
}
}
extension [DivExtensionHandler] {
var dictionary: [String: DivExtensionHandler] {
var extensionsHandlersDictionary = [String: DivExtensionHandler]()
for extensionHandler in self {
let id = extensionHandler.id
if extensionsHandlersDictionary[id] != nil {
DivKitLogger.error("Duplicate DivExtensionHandler for: \(id)")
continue
}
extensionsHandlersDictionary[id] = extensionHandler
}
return extensionsHandlersDictionary
}
}