From 379508e8fda2082e95f3ff28a7f9f47b9bf76159 Mon Sep 17 00:00:00 2001 From: robot-divkit Date: Mon, 30 Sep 2024 16:22:16 +0300 Subject: [PATCH] Release 30.20.0 commit_hash:ad75916b6896503f436dce62fde8a9c4f3e6f689 --- .mapping.json | 11 ++ DivKit/Actions/DivActionHandler.swift | 15 ++- DivKit/Actions/TimerActionHandler.swift | 44 +++++++ DivKit/Actions/VideoActionHandler.swift | 27 +++++ DivKit/DivBlockModelingContext.swift | 16 +++ DivKit/DivKitComponents.swift | 10 ++ DivKit/DivKitInfo.swift | 2 +- .../CustomFunctions/CustomFunction.swift | 68 +++++++++++ .../CustomFunctions/DivFunctionsStorage.swift | 112 ++++++++++++++++++ DivKit/Expressions/ExpressionContext.swift | 2 + DivKit/Expressions/ExpressionResolver.swift | 14 ++- DivKit/Expressions/FunctionsProvider.swift | 52 +++++++- .../DivBase/DivBaseExtensions.swift | 4 + .../DivEvaluableTypeExtensions.swift | 19 +++ DivKit/Extensions/DivTextExtensions.swift | 3 +- DivKit/Timers/DivTimerController.swift | 4 + DivKit/Timers/DivTimerStorage.swift | 4 + DivKit/Variables/DivTriggersStorage.swift | 4 + DivKit/generated_sources/DivText.swift | 25 ++-- .../generated_sources/DivTextTemplate.swift | 16 +++ .../InputAccessoryViewExtensionHandler.swift | 54 +++++++++ LayoutKit/LayoutKit/Blocks/TextBlock.swift | 14 ++- .../LayoutKit/Blocks/TextInputBlock.swift | 3 + .../ShadedBlock+UIViewRenderableBlock.swift | 3 +- ...TextInputBlock+UIViewRenderableBlock.swift | 6 + Specs/DivKit/30.20.0/DivKit.podspec | 24 ++++ .../30.20.0/DivKitExtensions.podspec | 22 ++++ .../30.20.0/DivKit_LayoutKit.podspec | 24 ++++ .../30.20.0/DivKit_LayoutKitInterface.podspec | 23 ++++ .../30.20.0/DivKit_Serialization.podspec | 23 ++++ 30 files changed, 630 insertions(+), 18 deletions(-) create mode 100644 DivKit/Actions/TimerActionHandler.swift create mode 100644 DivKit/Actions/VideoActionHandler.swift create mode 100644 DivKit/Expressions/CustomFunctions/CustomFunction.swift create mode 100644 DivKit/Expressions/CustomFunctions/DivFunctionsStorage.swift create mode 100644 DivKit/Extensions/DivEvaluableTypeExtensions.swift create mode 100644 DivKitExtensions/ExtensionHandlers/InputAccessoryViewExtensionHandler.swift create mode 100644 Specs/DivKit/30.20.0/DivKit.podspec create mode 100644 Specs/DivKitExtensions/30.20.0/DivKitExtensions.podspec create mode 100644 Specs/DivKit_LayoutKit/30.20.0/DivKit_LayoutKit.podspec create mode 100644 Specs/DivKit_LayoutKitInterface/30.20.0/DivKit_LayoutKitInterface.podspec create mode 100644 Specs/DivKit_Serialization/30.20.0/DivKit_Serialization.podspec diff --git a/.mapping.json b/.mapping.json index f12f4779..89431ef6 100644 --- a/.mapping.json +++ b/.mapping.json @@ -15,6 +15,8 @@ "DivKit/Actions/OverflowMode.swift":"divkit/public-ios/DivKit/Actions/OverflowMode.swift", "DivKit/Actions/ScrollMode.swift":"divkit/public-ios/DivKit/Actions/ScrollMode.swift", "DivKit/Actions/SetVariableActionHandler.swift":"divkit/public-ios/DivKit/Actions/SetVariableActionHandler.swift", + "DivKit/Actions/TimerActionHandler.swift":"divkit/public-ios/DivKit/Actions/TimerActionHandler.swift", + "DivKit/Actions/VideoActionHandler.swift":"divkit/public-ios/DivKit/Actions/VideoActionHandler.swift", "DivKit/Debug/Block+DebugInfo.swift":"divkit/public-ios/DivKit/Debug/Block+DebugInfo.swift", "DivKit/Debug/DebugParams.swift":"divkit/public-ios/DivKit/Debug/DebugParams.swift", "DivKit/Debug/ErrorListView.swift":"divkit/public-ios/DivKit/Debug/ErrorListView.swift", @@ -38,6 +40,8 @@ "DivKit/DivVisibilityCounter.swift":"divkit/public-ios/DivKit/DivVisibilityCounter.swift", "DivKit/Expressions/CalcExpression/CalcExpression.swift":"divkit/public-ios/DivKit/Expressions/CalcExpression/CalcExpression.swift", "DivKit/Expressions/CalcExpression/CalcExpressionSymbol.swift":"divkit/public-ios/DivKit/Expressions/CalcExpression/CalcExpressionSymbol.swift", + "DivKit/Expressions/CustomFunctions/CustomFunction.swift":"divkit/public-ios/DivKit/Expressions/CustomFunctions/CustomFunction.swift", + "DivKit/Expressions/CustomFunctions/DivFunctionsStorage.swift":"divkit/public-ios/DivKit/Expressions/CustomFunctions/DivFunctionsStorage.swift", "DivKit/Expressions/Expression.swift":"divkit/public-ios/DivKit/Expressions/Expression.swift", "DivKit/Expressions/ExpressionContext.swift":"divkit/public-ios/DivKit/Expressions/ExpressionContext.swift", "DivKit/Expressions/ExpressionError.swift":"divkit/public-ios/DivKit/Expressions/ExpressionError.swift", @@ -95,6 +99,7 @@ "DivKit/Extensions/DivDrawable/DivDrawableWidthTrait.swift":"divkit/public-ios/DivKit/Extensions/DivDrawable/DivDrawableWidthTrait.swift", "DivKit/Extensions/DivDrawable/DivShapeExtensions.swift":"divkit/public-ios/DivKit/Extensions/DivDrawable/DivShapeExtensions.swift", "DivKit/Extensions/DivEdgeInsetsExtensions.swift":"divkit/public-ios/DivKit/Extensions/DivEdgeInsetsExtensions.swift", + "DivKit/Extensions/DivEvaluableTypeExtensions.swift":"divkit/public-ios/DivKit/Extensions/DivEvaluableTypeExtensions.swift", "DivKit/Extensions/DivExtensions.swift":"divkit/public-ios/DivKit/Extensions/DivExtensions.swift", "DivKit/Extensions/DivFilterExtensions.swift":"divkit/public-ios/DivKit/Extensions/DivFilterExtensions.swift", "DivKit/Extensions/DivGallery/DivGalleryExtensions.swift":"divkit/public-ios/DivKit/Extensions/DivGallery/DivGalleryExtensions.swift", @@ -503,6 +508,7 @@ "DivKitExtensions/ExtensionHandlers/GestureExtensionHandler.swift":"divkit/public-ios/DivKitExtensions/ExtensionHandlers/GestureExtensionHandler.swift", "DivKitExtensions/ExtensionHandlers/ImageExtensionHandler.swift":"divkit/public-ios/DivKitExtensions/ExtensionHandlers/ImageExtensionHandler.swift", "DivKitExtensions/ExtensionHandlers/ImageThemeExtensionHandler.swift":"divkit/public-ios/DivKitExtensions/ExtensionHandlers/ImageThemeExtensionHandler.swift", + "DivKitExtensions/ExtensionHandlers/InputAccessoryViewExtensionHandler.swift":"divkit/public-ios/DivKitExtensions/ExtensionHandlers/InputAccessoryViewExtensionHandler.swift", "DivKitExtensions/ExtensionHandlers/PinchToZoomExtensionHandler.swift":"divkit/public-ios/DivKitExtensions/ExtensionHandlers/PinchToZoomExtensionHandler.swift", "DivKitExtensions/ExtensionHandlers/TextExtensionHandler.swift":"divkit/public-ios/DivKitExtensions/ExtensionHandlers/TextExtensionHandler.swift", "DivKitExtensions/ExtensionHandlers/VideoDurationExtensionHandler.swift":"divkit/public-ios/DivKitExtensions/ExtensionHandlers/VideoDurationExtensionHandler.swift", @@ -939,6 +945,7 @@ "Specs/DivKit/30.18.0/DivKit.podspec":"divkit/public-ios/Specs/DivKit/30.18.0/DivKit.podspec", "Specs/DivKit/30.19.0/DivKit.podspec":"divkit/public-ios/Specs/DivKit/30.19.0/DivKit.podspec", "Specs/DivKit/30.2.0/DivKit.podspec":"divkit/public-ios/Specs/DivKit/30.2.0/DivKit.podspec", + "Specs/DivKit/30.20.0/DivKit.podspec":"divkit/public-ios/Specs/DivKit/30.20.0/DivKit.podspec", "Specs/DivKit/30.3.0/DivKit.podspec":"divkit/public-ios/Specs/DivKit/30.3.0/DivKit.podspec", "Specs/DivKit/30.4.0/DivKit.podspec":"divkit/public-ios/Specs/DivKit/30.4.0/DivKit.podspec", "Specs/DivKit/30.5.0/DivKit.podspec":"divkit/public-ios/Specs/DivKit/30.5.0/DivKit.podspec", @@ -1010,6 +1017,7 @@ "Specs/DivKitExtensions/30.18.0/DivKitExtensions.podspec":"divkit/public-ios/Specs/DivKitExtensions/30.18.0/DivKitExtensions.podspec", "Specs/DivKitExtensions/30.19.0/DivKitExtensions.podspec":"divkit/public-ios/Specs/DivKitExtensions/30.19.0/DivKitExtensions.podspec", "Specs/DivKitExtensions/30.2.0/DivKitExtensions.podspec":"divkit/public-ios/Specs/DivKitExtensions/30.2.0/DivKitExtensions.podspec", + "Specs/DivKitExtensions/30.20.0/DivKitExtensions.podspec":"divkit/public-ios/Specs/DivKitExtensions/30.20.0/DivKitExtensions.podspec", "Specs/DivKitExtensions/30.3.0/DivKitExtensions.podspec":"divkit/public-ios/Specs/DivKitExtensions/30.3.0/DivKitExtensions.podspec", "Specs/DivKitExtensions/30.4.0/DivKitExtensions.podspec":"divkit/public-ios/Specs/DivKitExtensions/30.4.0/DivKitExtensions.podspec", "Specs/DivKitExtensions/30.5.0/DivKitExtensions.podspec":"divkit/public-ios/Specs/DivKitExtensions/30.5.0/DivKitExtensions.podspec", @@ -1063,6 +1071,7 @@ "Specs/DivKit_LayoutKit/30.18.0/DivKit_LayoutKit.podspec":"divkit/public-ios/Specs/DivKit_LayoutKit/30.18.0/DivKit_LayoutKit.podspec", "Specs/DivKit_LayoutKit/30.19.0/DivKit_LayoutKit.podspec":"divkit/public-ios/Specs/DivKit_LayoutKit/30.19.0/DivKit_LayoutKit.podspec", "Specs/DivKit_LayoutKit/30.2.0/DivKit_LayoutKit.podspec":"divkit/public-ios/Specs/DivKit_LayoutKit/30.2.0/DivKit_LayoutKit.podspec", + "Specs/DivKit_LayoutKit/30.20.0/DivKit_LayoutKit.podspec":"divkit/public-ios/Specs/DivKit_LayoutKit/30.20.0/DivKit_LayoutKit.podspec", "Specs/DivKit_LayoutKit/30.3.0/DivKit_LayoutKit.podspec":"divkit/public-ios/Specs/DivKit_LayoutKit/30.3.0/DivKit_LayoutKit.podspec", "Specs/DivKit_LayoutKit/30.4.0/DivKit_LayoutKit.podspec":"divkit/public-ios/Specs/DivKit_LayoutKit/30.4.0/DivKit_LayoutKit.podspec", "Specs/DivKit_LayoutKit/30.5.0/DivKit_LayoutKit.podspec":"divkit/public-ios/Specs/DivKit_LayoutKit/30.5.0/DivKit_LayoutKit.podspec", @@ -1116,6 +1125,7 @@ "Specs/DivKit_LayoutKitInterface/30.18.0/DivKit_LayoutKitInterface.podspec":"divkit/public-ios/Specs/DivKit_LayoutKitInterface/30.18.0/DivKit_LayoutKitInterface.podspec", "Specs/DivKit_LayoutKitInterface/30.19.0/DivKit_LayoutKitInterface.podspec":"divkit/public-ios/Specs/DivKit_LayoutKitInterface/30.19.0/DivKit_LayoutKitInterface.podspec", "Specs/DivKit_LayoutKitInterface/30.2.0/DivKit_LayoutKitInterface.podspec":"divkit/public-ios/Specs/DivKit_LayoutKitInterface/30.2.0/DivKit_LayoutKitInterface.podspec", + "Specs/DivKit_LayoutKitInterface/30.20.0/DivKit_LayoutKitInterface.podspec":"divkit/public-ios/Specs/DivKit_LayoutKitInterface/30.20.0/DivKit_LayoutKitInterface.podspec", "Specs/DivKit_LayoutKitInterface/30.3.0/DivKit_LayoutKitInterface.podspec":"divkit/public-ios/Specs/DivKit_LayoutKitInterface/30.3.0/DivKit_LayoutKitInterface.podspec", "Specs/DivKit_LayoutKitInterface/30.4.0/DivKit_LayoutKitInterface.podspec":"divkit/public-ios/Specs/DivKit_LayoutKitInterface/30.4.0/DivKit_LayoutKitInterface.podspec", "Specs/DivKit_LayoutKitInterface/30.5.0/DivKit_LayoutKitInterface.podspec":"divkit/public-ios/Specs/DivKit_LayoutKitInterface/30.5.0/DivKit_LayoutKitInterface.podspec", @@ -1169,6 +1179,7 @@ "Specs/DivKit_Serialization/30.18.0/DivKit_Serialization.podspec":"divkit/public-ios/Specs/DivKit_Serialization/30.18.0/DivKit_Serialization.podspec", "Specs/DivKit_Serialization/30.19.0/DivKit_Serialization.podspec":"divkit/public-ios/Specs/DivKit_Serialization/30.19.0/DivKit_Serialization.podspec", "Specs/DivKit_Serialization/30.2.0/DivKit_Serialization.podspec":"divkit/public-ios/Specs/DivKit_Serialization/30.2.0/DivKit_Serialization.podspec", + "Specs/DivKit_Serialization/30.20.0/DivKit_Serialization.podspec":"divkit/public-ios/Specs/DivKit_Serialization/30.20.0/DivKit_Serialization.podspec", "Specs/DivKit_Serialization/30.3.0/DivKit_Serialization.podspec":"divkit/public-ios/Specs/DivKit_Serialization/30.3.0/DivKit_Serialization.podspec", "Specs/DivKit_Serialization/30.4.0/DivKit_Serialization.podspec":"divkit/public-ios/Specs/DivKit_Serialization/30.4.0/DivKit_Serialization.podspec", "Specs/DivKit_Serialization/30.5.0/DivKit_Serialization.podspec":"divkit/public-ios/Specs/DivKit_Serialization/30.5.0/DivKit_Serialization.podspec", diff --git a/DivKit/Actions/DivActionHandler.swift b/DivKit/Actions/DivActionHandler.swift index 0f8c2207..6521bb81 100644 --- a/DivKit/Actions/DivActionHandler.swift +++ b/DivKit/Actions/DivActionHandler.swift @@ -13,6 +13,7 @@ public final class DivActionHandler { private let trackVisibility: TrackVisibility private let trackDisappear: TrackVisibility private let variablesStorage: DivVariablesStorage + private let functionsStorage: DivFunctionsStorage? private let persistentValuesStorage: DivPersistentValuesStorage private let blockStateStorage: DivBlockStateStorage private let updateCard: DivActionURLHandler.UpdateCardAction @@ -24,6 +25,8 @@ public final class DivActionHandler { private let copyToClipboardActionHandler = CopyToClipboardActionHandler() private let focusElementActionHandler = FocusElementActionHandler() private let setVariableActionHandler = SetVariableActionHandler() + private let timerActionHandler: TimerActionHandler + private let videoActionHandler = VideoActionHandler() /// Deprecated. Do not create `DivActionHandler`. Use the instance from `DivKitComponents`. public init( @@ -31,6 +34,7 @@ public final class DivActionHandler { blockStateStorage: DivBlockStateStorage = DivBlockStateStorage(), patchProvider: DivPatchProvider, variablesStorage: DivVariablesStorage = DivVariablesStorage(), + functionsStorage: DivFunctionsStorage? = nil, updateCard: @escaping DivActionURLHandler.UpdateCardAction, showTooltip: DivActionURLHandler.ShowTooltipAction? = nil, tooltipActionPerformer: TooltipActionPerformer? = nil, @@ -58,10 +62,12 @@ public final class DivActionHandler { self.trackVisibility = trackVisibility self.trackDisappear = trackDisappear self.variablesStorage = variablesStorage + self.functionsStorage = functionsStorage self.persistentValuesStorage = persistentValuesStorage self.blockStateStorage = blockStateStorage self.updateCard = updateCard self.reporter = reporter ?? DefaultDivReporter() + self.timerActionHandler = TimerActionHandler(performer: performTimerAction) } public func handle( @@ -106,6 +112,9 @@ public final class DivActionHandler { functionsProvider: FunctionsProvider( persistentValuesStorage: persistentValuesStorage ), + customFunctionsStorageProvider: { [weak functionsStorage] in + functionsStorage?.getStorage(path: path, contains: $0) + }, variableValueProvider: { [unowned variablesStorage] in if let value = localValues[$0] { return value @@ -141,7 +150,11 @@ public final class DivActionHandler { focusElementActionHandler.handle(action, context: context) case let .divActionSetVariable(action): setVariableActionHandler.handle(action, context: context) - case .divActionAnimatorStart, .divActionAnimatorStop, .divActionTimer, .divActionVideo, + case let .divActionTimer(action): + timerActionHandler.handle(action, context: context) + case let .divActionVideo(action): + videoActionHandler.handle(action, context: context) + case .divActionAnimatorStart, .divActionAnimatorStop, .divActionShowTooltip, .divActionSetState, .divActionHideTooltip: break case .none: diff --git a/DivKit/Actions/TimerActionHandler.swift b/DivKit/Actions/TimerActionHandler.swift new file mode 100644 index 00000000..e3eda676 --- /dev/null +++ b/DivKit/Actions/TimerActionHandler.swift @@ -0,0 +1,44 @@ +import Foundation +import LayoutKit +import VGSL + +final class TimerActionHandler { + private let performer: DivActionURLHandler.PerformTimerAction + + init (performer: @escaping DivActionURLHandler.PerformTimerAction) { + self.performer = performer + } + + func handle( + _ action: DivActionTimer, + context: DivActionHandlingContext + ) { + let expressionResolver = context.expressionResolver + + guard let id = action.resolveId(expressionResolver), + let command = action.resolveAction(expressionResolver) else { + return + } + + performer(context.path.cardId, id, command.toDivTimerAction()) + } +} + +extension DivActionTimer.Action { + func toDivTimerAction() -> DivTimerAction { + switch self { + case .start: + return .start + case .stop: + return .stop + case .pause: + return .pause + case .resume: + return .resume + case .cancel: + return .cancel + case .reset: + return .reset + } + } +} diff --git a/DivKit/Actions/VideoActionHandler.swift b/DivKit/Actions/VideoActionHandler.swift new file mode 100644 index 00000000..41f67764 --- /dev/null +++ b/DivKit/Actions/VideoActionHandler.swift @@ -0,0 +1,27 @@ +import Foundation +import LayoutKit +import VGSL + +final class VideoActionHandler { + func handle( + _ action: DivActionVideo, + context: DivActionHandlingContext + ) { + let expressionResolver = context.expressionResolver + + guard let id = action.resolveId(expressionResolver), + let command = action.resolveAction(expressionResolver) else { + return + } + + let cardId = context.path.cardId + + context.blockStateStorage.setState( + id: id, + cardId: cardId, + state: VideoBlockViewState(state: command == .start ? .playing : .paused) + ) + + context.updateCard(.state(cardId)) + } +} diff --git a/DivKit/DivBlockModelingContext.swift b/DivKit/DivBlockModelingContext.swift index a24fb2f0..46dc7d7c 100644 --- a/DivKit/DivBlockModelingContext.swift +++ b/DivKit/DivBlockModelingContext.swift @@ -14,6 +14,7 @@ public struct DivBlockModelingContext { 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 @@ -31,6 +32,7 @@ public struct DivBlockModelingContext { 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 @@ -49,6 +51,7 @@ public struct DivBlockModelingContext { parentPath: UIElementPath? = nil, parentDivStatePath: DivStatePath? = nil, stateManager: DivStateManager, + actionHandler: DivActionHandler? = nil, blockStateStorage: DivBlockStateStorage = DivBlockStateStorage(), visibilityCounter: DivVisibilityCounting? = nil, lastVisibleBoundsCache: DivLastVisibleBoundsCache? = nil, @@ -58,6 +61,7 @@ public struct DivBlockModelingContext { fontProvider: DivFontProvider? = nil, flagsInfo: DivFlagsInfo = .default, extensionHandlers: [DivExtensionHandler] = [], + functionsStorage: DivFunctionsStorage? = nil, variablesStorage: DivVariablesStorage = DivVariablesStorage(), triggersStorage: DivTriggersStorage? = nil, playerFactory: PlayerFactory? = nil, @@ -86,6 +90,7 @@ public struct DivBlockModelingContext { parentPath: parentPath, parentDivStatePath: parentDivStatePath, stateManager: stateManager, + actionHandler: actionHandler, blockStateStorage: blockStateStorage, visibilityCounter: visibilityCounter, lastVisibleBoundsCache: lastVisibleBoundsCache, @@ -95,6 +100,7 @@ public struct DivBlockModelingContext { fontProvider: fontProvider, flagsInfo: flagsInfo, extensionHandlers: extensionsHandlersDictionary, + functionsStorage: functionsStorage, variablesStorage: variablesStorage, triggersStorage: triggersStorage, playerFactory: playerFactory, @@ -116,6 +122,7 @@ public struct DivBlockModelingContext { parentPath: UIElementPath?, parentDivStatePath: DivStatePath?, stateManager: DivStateManager, + actionHandler: DivActionHandler?, blockStateStorage: DivBlockStateStorage, visibilityCounter: DivVisibilityCounting?, lastVisibleBoundsCache: DivLastVisibleBoundsCache?, @@ -125,6 +132,7 @@ public struct DivBlockModelingContext { fontProvider: DivFontProvider?, flagsInfo: DivFlagsInfo, extensionHandlers: [String: DivExtensionHandler], + functionsStorage: DivFunctionsStorage?, variablesStorage: DivVariablesStorage, triggersStorage: DivTriggersStorage?, playerFactory: PlayerFactory?, @@ -145,6 +153,7 @@ public struct DivBlockModelingContext { self.parentPath = parentPath self.parentDivStatePath = parentDivStatePath self.stateManager = stateManager + self.actionHandler = actionHandler self.blockStateStorage = blockStateStorage self.visibilityCounter = visibilityCounter ?? DivVisibilityCounter() self.lastVisibleBoundsCache = lastVisibleBoundsCache ?? DivLastVisibleBoundsCache() @@ -164,6 +173,7 @@ public struct DivBlockModelingContext { let persistentValuesStorage = persistentValuesStorage ?? DivPersistentValuesStorage() self.persistentValuesStorage = persistentValuesStorage self.tooltipViewFactory = tooltipViewFactory + self.functionsStorage = functionsStorage self.variablesStorage = variablesStorage self.triggersStorage = triggersStorage self.extensionHandlers = extensionHandlers @@ -176,6 +186,7 @@ public struct DivBlockModelingContext { viewId: viewId, path: parentPath, variablesStorage: variablesStorage, + functionsStorage: functionsStorage, localValues: nil, variableTracker: variableTracker, errorsStorage: errorsStorage @@ -277,6 +288,7 @@ public struct DivBlockModelingContext { viewId: viewId, path: context.parentPath, variablesStorage: variablesStorage, + functionsStorage: functionsStorage, localValues: context.localValues, variableTracker: variableTracker, errorsStorage: context.errorsStorage @@ -297,12 +309,16 @@ private func makeExpressionResolver( 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 diff --git a/DivKit/DivKitComponents.swift b/DivKit/DivKitComponents.swift index 49af544f..b4eb459d 100644 --- a/DivKit/DivKitComponents.swift +++ b/DivKit/DivKitComponents.swift @@ -42,6 +42,7 @@ public final class DivKitComponents { private let layoutProviderHandler: DivLayoutProviderHandler private let persistentValuesStorage = DivPersistentValuesStorage() private let timerStorage: DivTimerStorage + private let functionsStorage: DivFunctionsStorage private let updateAggregator: RunLoopCardUpdateAggregator private let updateCard: DivActionURLHandler.UpdateCardAction private let updateCardPipe: SignalPipe<[DivActionURLHandler.UpdateReason]> @@ -164,11 +165,14 @@ public final class DivKitComponents { self.tooltipManager = tooltipManager ?? DefaultTooltipManager() #endif + functionsStorage = DivFunctionsStorage(reporter: reporter) + actionHandler = DivActionHandler( stateUpdater: stateManagement, blockStateStorage: blockStateStorage, patchProvider: self.patchProvider, variablesStorage: variablesStorage, + functionsStorage: functionsStorage, updateCard: updateCard, showTooltip: showTooltip, tooltipActionPerformer: self.tooltipManager, @@ -182,6 +186,7 @@ public final class DivKitComponents { triggersStorage = DivTriggersStorage( variablesStorage: variablesStorage, + functionsStorage: functionsStorage, stateUpdates: blockStateStorage.stateUpdates, actionHandler: actionHandler, persistentValuesStorage: persistentValuesStorage, @@ -190,6 +195,7 @@ public final class DivKitComponents { timerStorage = DivTimerStorage( variablesStorage: variablesStorage, + functionsStorage: functionsStorage, actionHandler: actionHandler, updateCard: updateCard, persistentValuesStorage: persistentValuesStorage, @@ -211,6 +217,7 @@ public final class DivKitComponents { stateManagement.reset() variablesStorage.reset() triggersStorage.reset() + functionsStorage.reset() visibilityCounter.reset() timerStorage.reset() } @@ -221,6 +228,7 @@ public final class DivKitComponents { stateManagement.reset(cardId: cardId) variablesStorage.reset(cardId: cardId) triggersStorage.reset(cardId: cardId) + functionsStorage.reset(cardId: cardId) visibilityCounter.reset(cardId: cardId) timerStorage.reset(cardId: cardId) } @@ -289,6 +297,7 @@ public final class DivKitComponents { cardId: cardId, additionalId: additionalId, stateManager: stateManagement.getStateManagerForCard(cardId: cardId), + actionHandler: actionHandler, blockStateStorage: blockStateStorage, visibilityCounter: visibilityCounter, lastVisibleBoundsCache: lastVisibleBoundsCache, @@ -297,6 +306,7 @@ public final class DivKitComponents { fontProvider: fontProvider, flagsInfo: flagsInfo, extensionHandlers: extensionHandlers, + functionsStorage: functionsStorage, variablesStorage: variablesStorage, triggersStorage: triggersStorage, playerFactory: playerFactory, diff --git a/DivKit/DivKitInfo.swift b/DivKit/DivKitInfo.swift index 4827570b..cc7a7b00 100644 --- a/DivKit/DivKitInfo.swift +++ b/DivKit/DivKitInfo.swift @@ -1,3 +1,3 @@ public enum DivKitInfo { - public static let version = "30.19.0" + public static let version = "30.20.0" } diff --git a/DivKit/Expressions/CustomFunctions/CustomFunction.swift b/DivKit/Expressions/CustomFunctions/CustomFunction.swift new file mode 100644 index 00000000..114e04cf --- /dev/null +++ b/DivKit/Expressions/CustomFunctions/CustomFunction.swift @@ -0,0 +1,68 @@ +import Foundation + +final class CustomFunction: SimpleFunction { + var signature: FunctionSignature + + struct Signature: Hashable { + var name: String + var arguments: [DivEvaluableType] + } + + struct Argument { + let name: String + let type: DivEvaluableType + } + + let name: String + private let arguments: [Argument] + private let body: String + + init( + name: String, + arguments: [Argument], + body: String, + returnType: Any.Type + ) { + self.signature = FunctionSignature( + arguments: arguments.map { ArgumentSignature(type: $0.type.systemType) }, + resultType: returnType + ) + self.name = name + self.arguments = arguments + self.body = body + } + + func invoke(_ args: [Any], context: ExpressionContext) throws -> Any { + let matchedArguments: [String: Any] = Dictionary( + arguments.map { $0.name }, + args + ) + let resolver = ExpressionResolver( + variableValueProvider: { [weak self] name in + if let argument = matchedArguments[name] { + return argument + } + context.errorTracker( + ExpressionError( + "Argument with name: \(name) is not found", + expression: self?.body + ) + ) + return nil + }, + persistentValuesStorage: DivPersistentValuesStorage(), + errorTracker: context.errorTracker + ) + + guard let result = resolver.resolve(body) else { + throw ExpressionError( + makeErrorMessage(name: name, arguments: arguments.map { $0.type.rawValue }, body: body) + ) + } + return result + } +} + +private func makeErrorMessage(name: String, arguments: [String], body: String) -> String { + "Failed to evaluate custom function \(name)() with arguments: [\(arguments.joined(separator: ", "))] and body: \(body)." +} diff --git a/DivKit/Expressions/CustomFunctions/DivFunctionsStorage.swift b/DivKit/Expressions/CustomFunctions/DivFunctionsStorage.swift new file mode 100644 index 00000000..d395e5b4 --- /dev/null +++ b/DivKit/Expressions/CustomFunctions/DivFunctionsStorage.swift @@ -0,0 +1,112 @@ +import LayoutKit +import VGSL + +final public class DivFunctionsStorage { + private var functions: [CustomFunction.Signature: CustomFunction] = [:] + private var storages: [UIElementPath: DivFunctionsStorage] = [:] + + private let reporter: DivReporter + private let lock = AllocatedUnfairLock() + + let outerStorage: DivFunctionsStorage? + + init( + outerStorage: DivFunctionsStorage? = nil, + reporter: DivReporter = DefaultDivReporter() + ) { + self.outerStorage = outerStorage + self.reporter = reporter + } + + func setIfNeeded(path: UIElementPath, functions: [DivFunction]) { + lock.withLock { + if storages[path] != nil { + return + } + + let nearestStorage = getNearestStorage(path) + if functions.isEmpty { + storages[path] = nearestStorage + } else { + let storage = DivFunctionsStorage(outerStorage: nearestStorage) + functions.forEach { function in + let signature = CustomFunction.Signature( + name: function.name, + arguments: function.arguments.map { $0.type } + ) + if storage.functions[signature] != nil { + reporter.asExpressionErrorTracker(cardId: path.cardId)( + ExpressionError( + makeErrorMessage( + name: function.name, + arguments: function.arguments.map { $0.type.rawValue } + ) + ) + ) + } else { + storage.functions[signature] = CustomFunction( + name: function.name, + arguments: function.arguments.compactMap { arg in + CustomFunction.Argument(name: arg.name, type: arg.type) + }, + body: function.body, + returnType: function.returnType.systemType + ) + } + } + storages[path] = storage + } + } + } + + func getStorage( + path: UIElementPath, + contains name: String + ) -> DivFunctionsStorage? { + lock.withLock { + var storage: DivFunctionsStorage? = getNearestStorage(path) + while storage != nil { + if storage?.functions.contains(where: { $0.key.name == name }) == true { + return storage + } + storage = storage?.outerStorage + } + return nil + } + } + + func getFunctions(with name: String) -> [CustomFunction] { + functions.values.filter { $0.name == name } + } + + func reset() { + lock.withLock { + storages.removeAll() + } + } + + func reset(cardId: DivCardID) { + lock.withLock { + storages.keys.forEach { path in + if path.cardId == cardId { + storages.removeValue(forKey: path) + } + } + } + } + + func getNearestStorage(_ path: UIElementPath) -> DivFunctionsStorage { + var currentPath: UIElementPath? = path + while let path = currentPath { + if let storage = storages[path] { + return storage + } + currentPath = path.parent + } + return outerStorage ?? self + } +} + +private func makeErrorMessage(name: String, arguments: [String]) -> String { + "Function \(name)(\(arguments.joined(separator: ", "))) declared multiple times." +} diff --git a/DivKit/Expressions/ExpressionContext.swift b/DivKit/Expressions/ExpressionContext.swift index e7dd87ba..9fdbfac9 100644 --- a/DivKit/Expressions/ExpressionContext.swift +++ b/DivKit/Expressions/ExpressionContext.swift @@ -1,4 +1,6 @@ struct ExpressionContext { let evaluators: (CalcExpression.Symbol) -> Function? let variableValueProvider: (String) -> Any? + let customFunctionsStorageProvider: (String) -> DivFunctionsStorage? + let errorTracker: ExpressionErrorTracker } diff --git a/DivKit/Expressions/ExpressionResolver.swift b/DivKit/Expressions/ExpressionResolver.swift index b3b143a1..0857f2bf 100644 --- a/DivKit/Expressions/ExpressionResolver.swift +++ b/DivKit/Expressions/ExpressionResolver.swift @@ -9,33 +9,40 @@ public final class ExpressionResolver { /// Depreacated. public typealias VariableTracker = (Set) -> Void + private let customFunctionsStorageProvider: (String) -> DivFunctionsStorage? private let functionsProvider: FunctionsProvider private let variableValueProvider: (String) -> Any? private let errorTracker: ExpressionErrorTracker private lazy var context = ExpressionContext( evaluators: functionsProvider.evaluators, - variableValueProvider: variableValueProvider + variableValueProvider: variableValueProvider, + customFunctionsStorageProvider: customFunctionsStorageProvider, + errorTracker: errorTracker ) init( functionsProvider: FunctionsProvider, + customFunctionsStorageProvider: @escaping (String) -> DivFunctionsStorage? = { _ in nil }, variableValueProvider: @escaping (String) -> Any?, errorTracker: @escaping ExpressionErrorTracker ) { self.functionsProvider = functionsProvider + self.customFunctionsStorageProvider = customFunctionsStorageProvider self.variableValueProvider = variableValueProvider self.errorTracker = errorTracker } public init( variableValueProvider: @escaping (String) -> Any?, + customFunctionsStorageProvider: @escaping (String) -> DivFunctionsStorage? = { _ in nil }, persistentValuesStorage: DivPersistentValuesStorage, errorTracker: @escaping ExpressionErrorTracker ) { self.functionsProvider = FunctionsProvider( persistentValuesStorage: persistentValuesStorage ) + self.customFunctionsStorageProvider = customFunctionsStorageProvider self.variableValueProvider = variableValueProvider self.errorTracker = errorTracker } @@ -50,6 +57,7 @@ public final class ExpressionResolver { self.functionsProvider = FunctionsProvider( persistentValuesStorage: persistentValuesStorage ) + self.customFunctionsStorageProvider = { _ in nil } self.variableValueProvider = { let variableName = DivVariableName(rawValue: $0) variableTracker([variableName]) @@ -61,12 +69,16 @@ public final class ExpressionResolver { init( path: UIElementPath, variablesStorage: DivVariablesStorage, + functionsStorage: DivFunctionsStorage?, persistentValuesStorage: DivPersistentValuesStorage, reporter: DivReporter ) { self.functionsProvider = FunctionsProvider( persistentValuesStorage: persistentValuesStorage ) + self.customFunctionsStorageProvider = { + functionsStorage?.getStorage(path: path, contains: $0) + } self.variableValueProvider = { let variableName = DivVariableName(rawValue: $0) return variablesStorage.getVariableValue(path: path, name: variableName) diff --git a/DivKit/Expressions/FunctionsProvider.swift b/DivKit/Expressions/FunctionsProvider.swift index d59628e5..4524d7be 100644 --- a/DivKit/Expressions/FunctionsProvider.swift +++ b/DivKit/Expressions/FunctionsProvider.swift @@ -1,3 +1,5 @@ +import Foundation + import VGSL final class FunctionsProvider { @@ -40,7 +42,13 @@ final class FunctionsProvider { guard let self else { return nil } - return FunctionEvaluator(symbol, functions: self.functions) + return CustomFunctionEvaluator( + symbol, + fallbackEvaluator: FunctionEvaluator( + symbol, + functions: self.functions + ) + ) case let .method(name): return FunctionEvaluator(symbol, functions: methods) case .postfix: @@ -50,6 +58,48 @@ final class FunctionsProvider { } } +private struct CustomFunctionEvaluator: Function { + private let symbol: CalcExpression.Symbol + private let fallbackEvaluator: Function + + init( + _ symbol: CalcExpression.Symbol, + fallbackEvaluator: Function + ) { + self.symbol = symbol + self.fallbackEvaluator = fallbackEvaluator + } + + func invoke(_ args: [Any], context: ExpressionContext) throws -> Any { + let name = symbol.name + if let customFunctionsStorage = context.customFunctionsStorageProvider(name) { + var storage: DivFunctionsStorage? = customFunctionsStorage + + var result: Any? + while result == nil, storage != nil, let functions = storage?.getFunctions(with: name), + !functions.isEmpty { + do { + result = try OverloadedFunction( + functions: functions + ).invoke(args, context: context) + } catch { + if error is NoMatchingSignatureError { + storage = storage?.outerStorage + } else { + throw error + } + } + } + + if let result { + return result + } + } + + return try fallbackEvaluator.invoke(args, context: context) + } +} + private struct FunctionEvaluator: Function { private let symbol: CalcExpression.Symbol private let functions: [String: Function] diff --git a/DivKit/Extensions/DivBase/DivBaseExtensions.swift b/DivKit/Extensions/DivBase/DivBaseExtensions.swift index 4e883fc2..e8bd0de4 100644 --- a/DivKit/Extensions/DivBase/DivBaseExtensions.swift +++ b/DivKit/Extensions/DivBase/DivBaseExtensions.swift @@ -14,6 +14,10 @@ extension DivBase { ) throws -> Block { let path = context.parentPath + context.functionsStorage?.setIfNeeded( + path: path, + functions: functions ?? [] + ) context.variablesStorage.initializeIfNeeded( path: path, variables: variables?.extractDivVariableValues() ?? [:] diff --git a/DivKit/Extensions/DivEvaluableTypeExtensions.swift b/DivKit/Extensions/DivEvaluableTypeExtensions.swift new file mode 100644 index 00000000..0ad2a08d --- /dev/null +++ b/DivKit/Extensions/DivEvaluableTypeExtensions.swift @@ -0,0 +1,19 @@ +import Foundation + +import VGSL + +extension DivEvaluableType { + var systemType: Any.Type { + switch self { + case .integer: Int.self + case .number: Double.self + case .string: String.self + case .boolean: Bool.self + case .datetime: Date.self + case .color: Color.self + case .url: URL.self + case .dict: DivDictionary.self + case .array: [AnyHashable].self + } + } +} diff --git a/DivKit/Extensions/DivTextExtensions.swift b/DivKit/Extensions/DivTextExtensions.swift index 587881ba..133a4ec0 100644 --- a/DivKit/Extensions/DivTextExtensions.swift +++ b/DivKit/Extensions/DivTextExtensions.swift @@ -114,7 +114,8 @@ extension DivText: DivBlockModeling { accessibilityElement: nil, truncationToken: truncationToken, truncationImages: truncationImages, - canSelect: resolveSelectable(expressionResolver) + canSelect: resolveSelectable(expressionResolver), + tightenWidth: resolveTightenWidth(expressionResolver) ) } diff --git a/DivKit/Timers/DivTimerController.swift b/DivKit/Timers/DivTimerController.swift index b89c94bb..5f0f39c7 100644 --- a/DivKit/Timers/DivTimerController.swift +++ b/DivKit/Timers/DivTimerController.swift @@ -19,6 +19,7 @@ final class DivTimerController { private let runActions: RunActions private let updateCard: UpdateCard private let variablesStorage: DivVariablesStorage + private let functionsStorage: DivFunctionsStorage private let persistentValuesStorage: DivPersistentValuesStorage private let reporter: DivReporter @@ -40,6 +41,7 @@ final class DivTimerController { runActions: @escaping RunActions, updateCard: @escaping UpdateCard, variablesStorage: DivVariablesStorage, + functionsStorage: DivFunctionsStorage, persistentValuesStorage: DivPersistentValuesStorage, reporter: DivReporter ) { @@ -50,6 +52,7 @@ final class DivTimerController { self.runActions = runActions self.updateCard = updateCard self.variablesStorage = variablesStorage + self.functionsStorage = functionsStorage self.persistentValuesStorage = persistentValuesStorage self.reporter = reporter } @@ -62,6 +65,7 @@ final class DivTimerController { let expressionResolver = ExpressionResolver( path: cardId.path, variablesStorage: variablesStorage, + functionsStorage: functionsStorage, persistentValuesStorage: persistentValuesStorage, reporter: reporter ) diff --git a/DivKit/Timers/DivTimerStorage.swift b/DivKit/Timers/DivTimerStorage.swift index c90c8d2e..a2ec160e 100644 --- a/DivKit/Timers/DivTimerStorage.swift +++ b/DivKit/Timers/DivTimerStorage.swift @@ -5,6 +5,7 @@ import VGSL final class DivTimerStorage { private let variablesStorage: DivVariablesStorage + private let functionsStorage: DivFunctionsStorage private let actionHandler: DivActionHandler private let updateCard: DivActionURLHandler.UpdateCardAction private let timerScheduler = TimerScheduler() @@ -16,12 +17,14 @@ final class DivTimerStorage { init( variablesStorage: DivVariablesStorage, + functionsStorage: DivFunctionsStorage, actionHandler: DivActionHandler, updateCard: @escaping DivActionURLHandler.UpdateCardAction, persistentValuesStorage: DivPersistentValuesStorage, reporter: DivReporter ) { self.variablesStorage = variablesStorage + self.functionsStorage = functionsStorage self.actionHandler = actionHandler self.updateCard = updateCard self.persistentValuesStorage = persistentValuesStorage @@ -139,6 +142,7 @@ final class DivTimerStorage { self?.updateCard(.timer(cardId)) }, variablesStorage: variablesStorage, + functionsStorage: functionsStorage, persistentValuesStorage: persistentValuesStorage, reporter: reporter ) diff --git a/DivKit/Variables/DivTriggersStorage.swift b/DivKit/Variables/DivTriggersStorage.swift index 06e471c5..591b1ddb 100644 --- a/DivKit/Variables/DivTriggersStorage.swift +++ b/DivKit/Variables/DivTriggersStorage.swift @@ -27,6 +27,7 @@ public final class DivTriggersStorage { private let lock = AllocatedUnfairLock() private let variablesStorage: DivVariablesStorage + private let functionsStorage: DivFunctionsStorage? private let actionHandler: DivActionHandler? private let persistentValuesStorage: DivPersistentValuesStorage private let reporter: DivReporter @@ -34,12 +35,14 @@ public final class DivTriggersStorage { public init( variablesStorage: DivVariablesStorage, + functionsStorage: DivFunctionsStorage? = nil, stateUpdates: Signal = .empty, actionHandler: DivActionHandler, persistentValuesStorage: DivPersistentValuesStorage, reporter: DivReporter? = nil ) { self.variablesStorage = variablesStorage + self.functionsStorage = functionsStorage self.actionHandler = actionHandler self.persistentValuesStorage = persistentValuesStorage self.reporter = reporter ?? DefaultDivReporter() @@ -167,6 +170,7 @@ public final class DivTriggersStorage { let expressionResolver = ExpressionResolver( path: path, variablesStorage: variablesStorage, + functionsStorage: functionsStorage, persistentValuesStorage: persistentValuesStorage, reporter: reporter ) diff --git a/DivKit/generated_sources/DivText.swift b/DivKit/generated_sources/DivText.swift index b3c29713..447965ca 100644 --- a/DivKit/generated_sources/DivText.swift +++ b/DivKit/generated_sources/DivText.swift @@ -306,6 +306,7 @@ public final class DivText: DivBase { public let textColor: Expression // default value: #FF000000 public let textGradient: DivTextGradient? public let textShadow: DivShadow? + public let tightenWidth: Expression // default value: false public let tooltips: [DivTooltip]? public let transform: DivTransform? public let transitionChange: DivChangeTransition? @@ -416,6 +417,10 @@ public final class DivText: DivBase { resolver.resolveColor(textColor) ?? Color.colorWithARGBHexCode(0xFF000000) } + public func resolveTightenWidth(_ resolver: ExpressionResolver) -> Bool { + resolver.resolveNumeric(tightenWidth) ?? false + } + public func resolveUnderline(_ resolver: ExpressionResolver) -> DivLineStyle { resolver.resolveEnum(underline) ?? DivLineStyle.none } @@ -500,6 +505,7 @@ public final class DivText: DivBase { textColor: Expression? = nil, textGradient: DivTextGradient? = nil, textShadow: DivShadow? = nil, + tightenWidth: Expression? = nil, tooltips: [DivTooltip]? = nil, transform: DivTransform? = nil, transitionChange: DivChangeTransition? = nil, @@ -562,6 +568,7 @@ public final class DivText: DivBase { self.textColor = textColor ?? .value(Color.colorWithARGBHexCode(0xFF000000)) self.textGradient = textGradient self.textShadow = textShadow + self.tightenWidth = tightenWidth ?? .value(false) self.tooltips = tooltips self.transform = transform self.transitionChange = transitionChange @@ -694,34 +701,35 @@ extension DivText: Equatable { return false } guard + lhs.tightenWidth == rhs.tightenWidth, lhs.tooltips == rhs.tooltips, - lhs.transform == rhs.transform, - lhs.transitionChange == rhs.transitionChange + lhs.transform == rhs.transform else { return false } guard + lhs.transitionChange == rhs.transitionChange, lhs.transitionIn == rhs.transitionIn, - lhs.transitionOut == rhs.transitionOut, - lhs.transitionTriggers == rhs.transitionTriggers + lhs.transitionOut == rhs.transitionOut else { return false } guard + lhs.transitionTriggers == rhs.transitionTriggers, lhs.underline == rhs.underline, - lhs.variableTriggers == rhs.variableTriggers, - lhs.variables == rhs.variables + lhs.variableTriggers == rhs.variableTriggers else { return false } guard + lhs.variables == rhs.variables, lhs.visibility == rhs.visibility, - lhs.visibilityAction == rhs.visibilityAction, - lhs.visibilityActions == rhs.visibilityActions + lhs.visibilityAction == rhs.visibilityAction else { return false } guard + lhs.visibilityActions == rhs.visibilityActions, lhs.width == rhs.width else { return false @@ -783,6 +791,7 @@ extension DivText: Serializable { result["text_color"] = textColor.toValidSerializationValue() result["text_gradient"] = textGradient?.toDictionary() result["text_shadow"] = textShadow?.toDictionary() + result["tighten_width"] = tightenWidth.toValidSerializationValue() result["tooltips"] = tooltips?.map { $0.toDictionary() } result["transform"] = transform?.toDictionary() result["transition_change"] = transitionChange?.toDictionary() diff --git a/DivKit/generated_sources/DivTextTemplate.swift b/DivKit/generated_sources/DivTextTemplate.swift index 0015c767..f09ed0e3 100644 --- a/DivKit/generated_sources/DivTextTemplate.swift +++ b/DivKit/generated_sources/DivTextTemplate.swift @@ -830,6 +830,7 @@ public final class DivTextTemplate: TemplateValue { public let textColor: Field>? // default value: #FF000000 public let textGradient: Field? public let textShadow: Field? + public let tightenWidth: Field>? // default value: false public let tooltips: Field<[DivTooltipTemplate]>? public let transform: Field? public let transitionChange: Field? @@ -895,6 +896,7 @@ public final class DivTextTemplate: TemplateValue { textColor: dictionary.getOptionalExpressionField("text_color", transform: Color.color(withHexString:)), textGradient: dictionary.getOptionalField("text_gradient", templateToType: templateToType), textShadow: dictionary.getOptionalField("text_shadow", templateToType: templateToType), + tightenWidth: dictionary.getOptionalExpressionField("tighten_width"), tooltips: dictionary.getOptionalArray("tooltips", templateToType: templateToType), transform: dictionary.getOptionalField("transform", templateToType: templateToType), transitionChange: dictionary.getOptionalField("transition_change", templateToType: templateToType), @@ -961,6 +963,7 @@ public final class DivTextTemplate: TemplateValue { textColor: Field>? = nil, textGradient: Field? = nil, textShadow: Field? = nil, + tightenWidth: Field>? = nil, tooltips: Field<[DivTooltipTemplate]>? = nil, transform: Field? = nil, transitionChange: Field? = nil, @@ -1024,6 +1027,7 @@ public final class DivTextTemplate: TemplateValue { self.textColor = textColor self.textGradient = textGradient self.textShadow = textShadow + self.tightenWidth = tightenWidth self.tooltips = tooltips self.transform = transform self.transitionChange = transitionChange @@ -1088,6 +1092,7 @@ public final class DivTextTemplate: TemplateValue { let textColorValue = parent?.textColor?.resolveOptionalValue(context: context, transform: Color.color(withHexString:)) ?? .noValue let textGradientValue = parent?.textGradient?.resolveOptionalValue(context: context, useOnlyLinks: true) ?? .noValue let textShadowValue = parent?.textShadow?.resolveOptionalValue(context: context, useOnlyLinks: true) ?? .noValue + let tightenWidthValue = parent?.tightenWidth?.resolveOptionalValue(context: context) ?? .noValue let tooltipsValue = parent?.tooltips?.resolveOptionalValue(context: context, useOnlyLinks: true) ?? .noValue let transformValue = parent?.transform?.resolveOptionalValue(context: context, useOnlyLinks: true) ?? .noValue let transitionChangeValue = parent?.transitionChange?.resolveOptionalValue(context: context, useOnlyLinks: true) ?? .noValue @@ -1150,6 +1155,7 @@ public final class DivTextTemplate: TemplateValue { textColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "text_color", error: $0) }, textGradientValue.errorsOrWarnings?.map { .nestedObjectError(field: "text_gradient", error: $0) }, textShadowValue.errorsOrWarnings?.map { .nestedObjectError(field: "text_shadow", error: $0) }, + tightenWidthValue.errorsOrWarnings?.map { .nestedObjectError(field: "tighten_width", error: $0) }, tooltipsValue.errorsOrWarnings?.map { .nestedObjectError(field: "tooltips", error: $0) }, transformValue.errorsOrWarnings?.map { .nestedObjectError(field: "transform", error: $0) }, transitionChangeValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_change", error: $0) }, @@ -1221,6 +1227,7 @@ public final class DivTextTemplate: TemplateValue { textColor: textColorValue.value, textGradient: textGradientValue.value, textShadow: textShadowValue.value, + tightenWidth: tightenWidthValue.value, tooltips: tooltipsValue.value, transform: transformValue.value, transitionChange: transitionChangeValue.value, @@ -1290,6 +1297,7 @@ public final class DivTextTemplate: TemplateValue { var textColorValue: DeserializationResult> = parent?.textColor?.value() ?? .noValue var textGradientValue: DeserializationResult = .noValue var textShadowValue: DeserializationResult = .noValue + var tightenWidthValue: DeserializationResult> = parent?.tightenWidth?.value() ?? .noValue var tooltipsValue: DeserializationResult<[DivTooltip]> = .noValue var transformValue: DeserializationResult = .noValue var transitionChangeValue: DeserializationResult = .noValue @@ -1401,6 +1409,8 @@ public final class DivTextTemplate: TemplateValue { textGradientValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivTextGradientTemplate.self).merged(with: textGradientValue) case "text_shadow": textShadowValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivShadowTemplate.self).merged(with: textShadowValue) + case "tighten_width": + tightenWidthValue = deserialize(__dictValue).merged(with: tightenWidthValue) case "tooltips": tooltipsValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivTooltipTemplate.self).merged(with: tooltipsValue) case "transform": @@ -1523,6 +1533,8 @@ public final class DivTextTemplate: TemplateValue { textGradientValue = textGradientValue.merged(with: { deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivTextGradientTemplate.self) }) case parent?.textShadow?.link: textShadowValue = textShadowValue.merged(with: { deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivShadowTemplate.self) }) + case parent?.tightenWidth?.link: + tightenWidthValue = tightenWidthValue.merged(with: { deserialize(__dictValue) }) case parent?.tooltips?.link: tooltipsValue = tooltipsValue.merged(with: { deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: DivTooltipTemplate.self) }) case parent?.transform?.link: @@ -1636,6 +1648,7 @@ public final class DivTextTemplate: TemplateValue { textColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "text_color", error: $0) }, textGradientValue.errorsOrWarnings?.map { .nestedObjectError(field: "text_gradient", error: $0) }, textShadowValue.errorsOrWarnings?.map { .nestedObjectError(field: "text_shadow", error: $0) }, + tightenWidthValue.errorsOrWarnings?.map { .nestedObjectError(field: "tighten_width", error: $0) }, tooltipsValue.errorsOrWarnings?.map { .nestedObjectError(field: "tooltips", error: $0) }, transformValue.errorsOrWarnings?.map { .nestedObjectError(field: "transform", error: $0) }, transitionChangeValue.errorsOrWarnings?.map { .nestedObjectError(field: "transition_change", error: $0) }, @@ -1707,6 +1720,7 @@ public final class DivTextTemplate: TemplateValue { textColor: textColorValue.value, textGradient: textGradientValue.value, textShadow: textShadowValue.value, + tightenWidth: tightenWidthValue.value, tooltips: tooltipsValue.value, transform: transformValue.value, transitionChange: transitionChangeValue.value, @@ -1781,6 +1795,7 @@ public final class DivTextTemplate: TemplateValue { textColor: textColor ?? mergedParent.textColor, textGradient: textGradient ?? mergedParent.textGradient, textShadow: textShadow ?? mergedParent.textShadow, + tightenWidth: tightenWidth ?? mergedParent.tightenWidth, tooltips: tooltips ?? mergedParent.tooltips, transform: transform ?? mergedParent.transform, transitionChange: transitionChange ?? mergedParent.transitionChange, @@ -1850,6 +1865,7 @@ public final class DivTextTemplate: TemplateValue { textColor: merged.textColor, textGradient: merged.textGradient?.tryResolveParent(templates: templates), textShadow: merged.textShadow?.tryResolveParent(templates: templates), + tightenWidth: merged.tightenWidth, tooltips: merged.tooltips?.tryResolveParent(templates: templates), transform: merged.transform?.tryResolveParent(templates: templates), transitionChange: merged.transitionChange?.tryResolveParent(templates: templates), diff --git a/DivKitExtensions/ExtensionHandlers/InputAccessoryViewExtensionHandler.swift b/DivKitExtensions/ExtensionHandlers/InputAccessoryViewExtensionHandler.swift new file mode 100644 index 00000000..801d268e --- /dev/null +++ b/DivKitExtensions/ExtensionHandlers/InputAccessoryViewExtensionHandler.swift @@ -0,0 +1,54 @@ +import UIKit + +import DivKit +import LayoutKit +import VGSL + +public protocol InputAccessoryViewProvider { + func getView(actions: [String: Action]) -> UIView +} + +final public class InputAccessoryViewExtensionHandler: DivExtensionHandler { + public let id = "input_accessory_view" + private let viewProvider: InputAccessoryViewProvider + + public init(viewProvider: InputAccessoryViewProvider) { + self.viewProvider = viewProvider + } + + public func applyBeforeBaseProperties( + to block: Block, + div: DivBase, + context: DivBlockModelingContext + ) -> Block { + guard let textInputBlock = block as? TextInputBlock else { + DivKitLogger.error("Invalid block for InputAccessoryViewExtension") + return block + } + var uiActions = [String: Action]() + let params = getExtensionParams(div) + + if let actions = params["actions"] as? [String: [String: Any]] { + uiActions = actions.compactMapValues { + let action = DivTemplates + .empty + .parseValue( + type: DivActionTemplate.self, from: $0 + ).value?.uiAction(context: context) + if case let .divAction(params) = action?.payload { + return { [weak self] in + context.actionHandler?.handle( + params: params, + sender: self + ) + } + } + return nil + } + } + + let view = viewProvider.getView(actions: uiActions) + textInputBlock.accessoryView = view + return textInputBlock + } +} diff --git a/LayoutKit/LayoutKit/Blocks/TextBlock.swift b/LayoutKit/LayoutKit/Blocks/TextBlock.swift index 481a8d71..0881c1ee 100644 --- a/LayoutKit/LayoutKit/Blocks/TextBlock.swift +++ b/LayoutKit/LayoutKit/Blocks/TextBlock.swift @@ -30,6 +30,7 @@ public final class TextBlock: BlockWithTraits { public let images: [InlineImage] public let accessibilityElement: AccessibilityElement? public let canSelect: Bool + public let tightenWidth: Bool let attachments: [TextAttachment] let truncationToken: NSAttributedString? @@ -51,7 +52,8 @@ public final class TextBlock: BlockWithTraits { accessibilityElement: AccessibilityElement?, truncationToken: NSAttributedString? = nil, truncationImages: [TextBlock.InlineImage] = [], - canSelect: Bool = false + canSelect: Bool = false, + tightenWidth: Bool = false ) { self.widthTrait = widthTrait self.heightTrait = heightTrait @@ -63,6 +65,7 @@ public final class TextBlock: BlockWithTraits { self.images = images self.accessibilityElement = accessibilityElement self.canSelect = canSelect + self.tightenWidth = tightenWidth self.truncationImages = truncationImages if let truncationToken { (self.truncationToken, self.truncationAttachments) = setImagePlaceholders( @@ -85,7 +88,8 @@ public final class TextBlock: BlockWithTraits { images: [InlineImage] = [], truncationToken: NSAttributedString? = nil, truncationImages: [TextBlock.InlineImage] = [], - canSelect: Bool = false + canSelect: Bool = false, + tightenWidth: Bool = false ) { self.init( widthTrait: widthTrait, @@ -99,7 +103,8 @@ public final class TextBlock: BlockWithTraits { accessibilityElement: .staticText(label: text.string), truncationToken: truncationToken, truncationImages: truncationImages, - canSelect: canSelect + canSelect: canSelect, + tightenWidth: tightenWidth ) } @@ -110,7 +115,7 @@ public final class TextBlock: BlockWithTraits { return cached } - let width = ceil(text.sizeForWidth(.infinity).width) + let width = ceil(text.sizeForWidth(tightenWidth ? maxSize : .infinity).width) let result = clamp(width, min: minSize, max: maxSize) cachedIntrinsicWidth = result return result @@ -171,6 +176,7 @@ public final class TextBlock: BlockWithTraits { && lhs.minNumberOfHiddenLines == rhs.minNumberOfHiddenLines && lhs.images == rhs.images && lhs.accessibilityElement == rhs.accessibilityElement + && lhs.tightenWidth == rhs.tightenWidth } } diff --git a/LayoutKit/LayoutKit/Blocks/TextInputBlock.swift b/LayoutKit/LayoutKit/Blocks/TextInputBlock.swift index 40bcc43c..f10e98cf 100644 --- a/LayoutKit/LayoutKit/Blocks/TextInputBlock.swift +++ b/LayoutKit/LayoutKit/Blocks/TextInputBlock.swift @@ -67,6 +67,7 @@ public final class TextInputBlock: BlockWithTraits { public let textTypo: Typo public let multiLineMode: Bool public let inputType: InputType + public var accessoryView: ViewType? public let autocapitalizationType: AutocapitalizationType public let highlightColor: Color? public let maxVisibleLines: Int? @@ -95,6 +96,7 @@ public final class TextInputBlock: BlockWithTraits { textTypo: Typo, multiLineMode: Bool = true, inputType: InputType = .default, + accessoryView: ViewType? = nil, autocapitalizationType: AutocapitalizationType = .sentences, highlightColor: Color? = nil, maxVisibleLines: Int? = nil, @@ -122,6 +124,7 @@ public final class TextInputBlock: BlockWithTraits { self.textTypo = textTypo self.multiLineMode = multiLineMode self.inputType = inputType + self.accessoryView = accessoryView self.autocapitalizationType = autocapitalizationType self.highlightColor = highlightColor self.maxVisibleLines = maxVisibleLines diff --git a/LayoutKit/LayoutKit/UI/Blocks/ShadedBlock+UIViewRenderableBlock.swift b/LayoutKit/LayoutKit/UI/Blocks/ShadedBlock+UIViewRenderableBlock.swift index d7519bf7..efc37723 100644 --- a/LayoutKit/LayoutKit/UI/Blocks/ShadedBlock+UIViewRenderableBlock.swift +++ b/LayoutKit/LayoutKit/UI/Blocks/ShadedBlock+UIViewRenderableBlock.swift @@ -73,7 +73,8 @@ private final class ShadedBlockView: ViewWithShadow, BlockViewProtocol, } else { blockView = model.block.makeBlockView( observer: observer, - overscrollDelegate: overscrollDelegate + overscrollDelegate: overscrollDelegate, + renderingDelegate: renderingDelegate ) } shadow = model.shadow diff --git a/LayoutKit/LayoutKit/UI/Blocks/TextInputBlock+UIViewRenderableBlock.swift b/LayoutKit/LayoutKit/UI/Blocks/TextInputBlock+UIViewRenderableBlock.swift index 21725313..5ded93b9 100644 --- a/LayoutKit/LayoutKit/UI/Blocks/TextInputBlock+UIViewRenderableBlock.swift +++ b/LayoutKit/LayoutKit/UI/Blocks/TextInputBlock+UIViewRenderableBlock.swift @@ -14,6 +14,7 @@ extension TextInputBlock { let inputView = view as! TextInputBlockView inputView.setLayoutDirection(layoutDirection) inputView.setInputType(inputType) + inputView.setInputAccessoryView(accessoryView) inputView.setAutocapitalizationType(autocapitalizationType) inputView.setValidators(validators) inputView.setFilters(filters) @@ -189,6 +190,11 @@ private final class TextInputBlockView: BlockView, VisibleBoundsTrackingLeaf { } } + func setInputAccessoryView(_ accessoryView: ViewType?) { + multiLineInput.inputAccessoryView = accessoryView + singleLineInput.inputAccessoryView = accessoryView + } + func setAutocapitalizationType(_ type: TextInputBlock.AutocapitalizationType) { singleLineInput.autocapitalizationType = type.uiType multiLineInput.autocapitalizationType = type.uiType diff --git a/Specs/DivKit/30.20.0/DivKit.podspec b/Specs/DivKit/30.20.0/DivKit.podspec new file mode 100644 index 00000000..bce08ea7 --- /dev/null +++ b/Specs/DivKit/30.20.0/DivKit.podspec @@ -0,0 +1,24 @@ +Pod::Spec.new do |s| + s.name = 'DivKit' + s.version = '30.20.0' + s.summary = 'DivKit framework' + s.description = 'DivKit is a backend-driven UI framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5.9' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '13.0' } + + s.dependency 'DivKit_LayoutKit', s.version.to_s + s.dependency 'DivKit_Serialization', s.version.to_s + s.dependency 'VGSL', '~> 6.0' + + s.source_files = [ + 'DivKit/**/*' + ] +end diff --git a/Specs/DivKitExtensions/30.20.0/DivKitExtensions.podspec b/Specs/DivKitExtensions/30.20.0/DivKitExtensions.podspec new file mode 100644 index 00000000..fed990d8 --- /dev/null +++ b/Specs/DivKitExtensions/30.20.0/DivKitExtensions.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |s| + s.name = 'DivKitExtensions' + s.version = '30.20.0' + s.summary = 'DivKit framework extensions' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5.9' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '13.0' } + + s.dependency 'DivKit', s.version.to_s + + s.source_files = [ + 'DivKitExtensions/**/*' + ] +end diff --git a/Specs/DivKit_LayoutKit/30.20.0/DivKit_LayoutKit.podspec b/Specs/DivKit_LayoutKit/30.20.0/DivKit_LayoutKit.podspec new file mode 100644 index 00000000..50d647df --- /dev/null +++ b/Specs/DivKit_LayoutKit/30.20.0/DivKit_LayoutKit.podspec @@ -0,0 +1,24 @@ +Pod::Spec.new do |s| + s.name = 'DivKit_LayoutKit' + s.module_name = 'LayoutKit' + s.version = '30.20.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5.9' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '13.0' } + + s.dependency 'DivKit_LayoutKitInterface', s.version.to_s + s.dependency 'VGSL', '~> 6.0' + + s.source_files = [ + 'LayoutKit/LayoutKit/**/*' + ] +end diff --git a/Specs/DivKit_LayoutKitInterface/30.20.0/DivKit_LayoutKitInterface.podspec b/Specs/DivKit_LayoutKitInterface/30.20.0/DivKit_LayoutKitInterface.podspec new file mode 100644 index 00000000..90866a72 --- /dev/null +++ b/Specs/DivKit_LayoutKitInterface/30.20.0/DivKit_LayoutKitInterface.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'DivKit_LayoutKitInterface' + s.module_name = 'LayoutKitInterface' + s.version = '30.20.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5.9' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '13.0' } + + s.dependency 'VGSL', '~> 6.0' + + s.source_files = [ + 'LayoutKit/Interface/**/*' + ] +end diff --git a/Specs/DivKit_Serialization/30.20.0/DivKit_Serialization.podspec b/Specs/DivKit_Serialization/30.20.0/DivKit_Serialization.podspec new file mode 100644 index 00000000..fe954924 --- /dev/null +++ b/Specs/DivKit_Serialization/30.20.0/DivKit_Serialization.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'DivKit_Serialization' + s.module_name = 'Serialization' + s.version = '30.20.0' + s.summary = 'Part of DivKit framework' + s.description = 'Part of DivKit framework' + s.homepage = 'https://divkit.tech' + + s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } + s.author = { 'divkit' => 'divkit@yandex-team.ru' } + s.source = { :git => 'https://github.com/divkit/divkit-ios.git', :tag => s.version.to_s } + + s.swift_version = '5.9' + s.requires_arc = true + s.prefix_header_file = false + s.platforms = { :ios => '13.0' } + + s.dependency 'VGSL', '~> 6.0' + + s.source_files = [ + 'Serialization/**/*' + ] +end