Files
divkit/client/ios/DivKit/DivBlockModelingContext.swift
T
saabrosov 83401480c1 Added div-focus.next_focus_ids.forward support.
commit_hash:bb7c5bfa4c9750f354b2473f954797a83b31dca2
2024-10-21 16:18:02 +03:00

350 lines
12 KiB
Swift

import CoreGraphics
import LayoutKit
import VGSL
#if os(iOS)
import UIKit
#else
import AppKit
#endif
public struct DivBlockModelingContext {
let viewId: DivViewId
private(set) var cardLogId: String?
private(set) var parentDivStatePath: DivStatePath?
let stateManager: DivStateManager
public let actionHandler: DivActionHandler?
public let blockStateStorage: DivBlockStateStorage
let visibilityCounter: DivVisibilityCounting
let lastVisibleBoundsCache: DivLastVisibleBoundsCache
public let imageHolderFactory: DivImageHolderFactory
let highPriorityImageHolderFactory: DivImageHolderFactory?
let divCustomBlockFactory: DivCustomBlockFactory
public let fontProvider: DivFontProvider
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?
public private(set) var errorsStorage: DivErrorsStorage
private let persistentValuesStorage: DivPersistentValuesStorage
let tooltipViewFactory: DivTooltipViewFactory?
let functionsStorage: DivFunctionsStorage?
public let variablesStorage: DivVariablesStorage
let triggersStorage: DivTriggersStorage?
public private(set) var expressionResolver: ExpressionResolver
private let functionsProvider: FunctionsProvider
public let variableTracker: DivVariableTracker?
public private(set) var parentPath: UIElementPath
private(set) var elementId: 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()
public init(
cardId: DivCardID,
additionalId: String? = nil,
cardLogId: String? = nil,
parentPath: UIElementPath? = nil,
parentDivStatePath: DivStatePath? = nil,
stateManager: DivStateManager,
actionHandler: DivActionHandler? = nil,
blockStateStorage: DivBlockStateStorage = DivBlockStateStorage(),
visibilityCounter: DivVisibilityCounting? = nil,
lastVisibleBoundsCache: DivLastVisibleBoundsCache? = nil,
imageHolderFactory: DivImageHolderFactory,
highPriorityImageHolderFactory: DivImageHolderFactory? = nil,
divCustomBlockFactory: DivCustomBlockFactory? = nil,
fontProvider: DivFontProvider? = nil,
flagsInfo: DivFlagsInfo = .default,
extensionHandlers: [DivExtensionHandler] = [],
functionsStorage: DivFunctionsStorage? = nil,
variablesStorage: DivVariablesStorage = DivVariablesStorage(),
triggersStorage: DivTriggersStorage? = nil,
playerFactory: PlayerFactory? = nil,
debugParams: DebugParams = DebugParams(),
scheduler: Scheduling? = nil,
parentScrollView: ScrollView? = nil,
errorsStorage: DivErrorsStorage? = nil,
layoutDirection: UserInterfaceLayoutDirection = .leftToRight,
variableTracker: DivVariableTracker? = nil,
persistentValuesStorage: DivPersistentValuesStorage? = nil,
tooltipViewFactory: DivTooltipViewFactory? = nil,
layoutProviderHandler: DivLayoutProviderHandler? = nil
) {
self.init(
viewId: DivViewId(cardId: cardId, additionalId: additionalId),
cardLogId: cardLogId,
parentPath: parentPath,
parentDivStatePath: parentDivStatePath,
stateManager: stateManager,
actionHandler: actionHandler,
blockStateStorage: blockStateStorage,
visibilityCounter: visibilityCounter,
lastVisibleBoundsCache: lastVisibleBoundsCache,
imageHolderFactory: imageHolderFactory,
highPriorityImageHolderFactory: highPriorityImageHolderFactory,
divCustomBlockFactory: divCustomBlockFactory,
fontProvider: fontProvider,
flagsInfo: flagsInfo,
extensionHandlers: extensionHandlers.dictionary,
functionsStorage: functionsStorage,
variablesStorage: variablesStorage,
triggersStorage: triggersStorage,
playerFactory: playerFactory,
debugParams: debugParams,
scheduler: scheduler,
parentScrollView: parentScrollView,
errorsStorage: errorsStorage,
layoutDirection: layoutDirection,
variableTracker: variableTracker,
persistentValuesStorage: persistentValuesStorage,
tooltipViewFactory: tooltipViewFactory,
layoutProviderHandler: layoutProviderHandler,
idToPath: nil,
animatorController: nil
)
}
init(
viewId: DivViewId,
cardLogId: String?,
parentPath: UIElementPath?,
parentDivStatePath: DivStatePath?,
stateManager: DivStateManager,
actionHandler: DivActionHandler?,
blockStateStorage: DivBlockStateStorage,
visibilityCounter: DivVisibilityCounting?,
lastVisibleBoundsCache: DivLastVisibleBoundsCache?,
imageHolderFactory: DivImageHolderFactory,
highPriorityImageHolderFactory: 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?,
layoutDirection: UserInterfaceLayoutDirection,
variableTracker: DivVariableTracker?,
persistentValuesStorage: DivPersistentValuesStorage?,
tooltipViewFactory: DivTooltipViewFactory?,
layoutProviderHandler: DivLayoutProviderHandler?,
idToPath: IdToPath?,
animatorController: DivAnimatorController?
) {
self.viewId = viewId
self.cardLogId = cardLogId
let cardId = viewId.cardId
let parentPath = parentPath ?? UIElementPath(cardId.rawValue)
self.parentPath = parentPath
self.parentDivStatePath = parentDivStatePath
self.stateManager = stateManager
self.actionHandler = actionHandler
self.blockStateStorage = blockStateStorage
self.visibilityCounter = visibilityCounter ?? DivVisibilityCounter()
self.lastVisibleBoundsCache = lastVisibleBoundsCache ?? DivLastVisibleBoundsCache()
self.imageHolderFactory = imageHolderFactory
self.highPriorityImageHolderFactory = highPriorityImageHolderFactory
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.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
)
self.idToPath = idToPath ?? IdToPath()
self.animatorController = animatorController
expressionResolver = makeExpressionResolver(
functionsProvider: functionsProvider,
viewId: viewId,
path: parentPath,
variablesStorage: variablesStorage,
functionsStorage: functionsStorage,
localValues: nil,
variableTracker: variableTracker,
errorsStorage: errorsStorage
)
}
public var cardId: DivCardID {
viewId.cardId
}
public func getExtensionHandlers(for div: DivBase) -> [DivExtensionHandler] {
guard let extensions = div.extensions else {
return []
}
return extensions.compactMap {
let id = $0.id
if !extensionHandlers.keys.contains(id) {
addError(message: "No DivExtensionHandler for: \(id)")
}
return extensionHandlers[id]
}
}
public func addError(message: String) {
errorsStorage.add(DivBlockModelingError(message, path: parentPath))
}
public func addWarning(message: String) {
errorsStorage.add(DivBlockModelingWarning(message, path: parentPath))
}
func addError(error: Error) {
if let divError = error as? DivError {
errorsStorage.add(divError)
return
}
errorsStorage.add(DivUnknownError(error, path: parentPath))
}
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: parentPath, name: divVariableName) ?? defaultValue
let valueProp = Property<T>(
getter: { value },
setter: {
guard let newValue = DivVariableValue($0) else { return }
self.variablesStorage.update(
path: parentPath,
name: divVariableName,
value: newValue
)
}
)
return Binding(name: variableName, value: valueProp)
}
func modifying(
elementId: String? = nil,
cardLogId: String? = nil,
parentPath: UIElementPath? = nil,
parentDivStatePath: DivStatePath? = nil,
errorsStorage: DivErrorsStorage? = nil,
sizeModifier: DivSizeModifier? = nil,
prototypeParams: PrototypeParams? = nil
) -> Self {
var context = self
context.elementId = elementId
if let cardLogId {
context.cardLogId = cardLogId
}
if let parentDivStatePath {
context.parentDivStatePath = parentDivStatePath
}
if let sizeModifier {
context.sizeModifier = sizeModifier
}
if parentPath == 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
}
context.parentPath = parentPath ?? self.parentPath
context.errorsStorage = errorsStorage ?? self.errorsStorage
context.expressionResolver = makeExpressionResolver(
functionsProvider: functionsProvider,
viewId: viewId,
path: context.parentPath,
variablesStorage: variablesStorage,
functionsStorage: functionsStorage,
localValues: context.localValues,
variableTracker: variableTracker,
errorsStorage: context.errorsStorage
)
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))
}
)
}
extension [DivExtensionHandler] {
var dictionary: [String: DivExtensionHandler] {
var extensionsHandlersDictionary = [String: DivExtensionHandler]()
for extensionHandler in self {
let id = extensionHandler.id
if extensionsHandlersDictionary[id] != nil {
DivKitLogger.failure("Duplicate DivExtensionHandler for: \(id)")
continue
}
extensionsHandlersDictionary[id] = extensionHandler
}
return extensionsHandlersDictionary
}
}