mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
Fixed file path in precommit hook
commit_hash:f7df7bc7f68f49dd4b005691712d8784e092107e
This commit is contained in:
@@ -16,9 +16,15 @@ final class UpdateStructureActionHandler {
|
||||
}
|
||||
|
||||
let updatedValue: DivVariableValue?
|
||||
if let dict: DivDictionary = context.variablesStorage.getVariableValue(path: context.path, name: divVariableName) {
|
||||
if let dict: DivDictionary = context.variablesStorage.getVariableValue(
|
||||
path: context.path,
|
||||
name: divVariableName
|
||||
) {
|
||||
updatedValue = updateDictionary(dict, newValue: newValue, path: pathComponents)
|
||||
} else if let array: DivArray = context.variablesStorage.getVariableValue(path: context.path, name: divVariableName) {
|
||||
} else if let array: DivArray = context.variablesStorage.getVariableValue(
|
||||
path: context.path,
|
||||
name: divVariableName
|
||||
) {
|
||||
updatedValue = updateArray(array, newValue: newValue, path: pathComponents)
|
||||
} else {
|
||||
DivKitLogger.error("Action requires array or dictionary variable")
|
||||
@@ -106,9 +112,11 @@ private func updateElement(
|
||||
newValue: AnyHashable
|
||||
) -> AnyHashable? {
|
||||
if let nestedArray = currentElement as? DivArray {
|
||||
return updateArray(nestedArray, newValue: newValue, path: path, pathIndex: pathIndex)?.arrayValue
|
||||
return updateArray(nestedArray, newValue: newValue, path: path, pathIndex: pathIndex)?
|
||||
.arrayValue
|
||||
} else if let nestedDict = currentElement as? DivDictionary {
|
||||
return updateDictionary(nestedDict, newValue: newValue, path: path, pathIndex: pathIndex)?.dictValue
|
||||
return updateDictionary(nestedDict, newValue: newValue, path: path, pathIndex: pathIndex)?
|
||||
.dictValue
|
||||
} else {
|
||||
DivKitLogger.error(
|
||||
"Element with path '\(path[0..<pathIndex].joined(separator: "/"))' is not a structure"
|
||||
@@ -117,8 +125,8 @@ private func updateElement(
|
||||
}
|
||||
}
|
||||
|
||||
private extension Array {
|
||||
subscript(insert index: Int) -> Element? {
|
||||
extension Array {
|
||||
fileprivate subscript(insert index: Int) -> Element? {
|
||||
get {
|
||||
(0..<count).contains(index) ? self[index] : nil
|
||||
}
|
||||
@@ -138,16 +146,16 @@ private extension Array {
|
||||
}
|
||||
}
|
||||
|
||||
private extension DivVariableValue {
|
||||
var arrayValue: DivArray? {
|
||||
if case .array(let array) = self {
|
||||
extension DivVariableValue {
|
||||
fileprivate var arrayValue: DivArray? {
|
||||
if case let .array(array) = self {
|
||||
return array
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var dictValue: DivDictionary? {
|
||||
if case .dict(let dict) = self {
|
||||
fileprivate var dictValue: DivDictionary? {
|
||||
if case let .dict(dict) = self {
|
||||
return dict
|
||||
}
|
||||
return nil
|
||||
@@ -155,5 +163,6 @@ private extension DivVariableValue {
|
||||
}
|
||||
|
||||
private func logElementNotFound(path: [String], pathIndex: Int) {
|
||||
DivKitLogger.error("Element with path '\(path[0...pathIndex].joined(separator: "/"))' is not found")
|
||||
DivKitLogger
|
||||
.error("Element with path '\(path[0...pathIndex].joined(separator: "/"))' is not found")
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ public final class DivBlockStateStorage {
|
||||
!state.isDifferent(from: existingState) {
|
||||
shouldUpdatePipe = false
|
||||
}
|
||||
|
||||
|
||||
_states[key] = state
|
||||
}
|
||||
|
||||
@@ -245,7 +245,6 @@ extension DivBlockStateStorage: ElementStateObserver {
|
||||
}
|
||||
|
||||
private enum StateKey {
|
||||
|
||||
case path(UIElementPath)
|
||||
case id(IdAndCardId)
|
||||
|
||||
@@ -261,12 +260,12 @@ private enum StateKey {
|
||||
var cardID: DivCardID {
|
||||
id.cardId
|
||||
}
|
||||
|
||||
|
||||
private var id: IdAndCardId {
|
||||
switch self {
|
||||
case .path(let path):
|
||||
case let .path(path):
|
||||
IdAndCardId(path: path)
|
||||
case .id(let id):
|
||||
case let .id(id):
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ private struct AssetsImageProvider: DivImageHolderFactory {
|
||||
func make(_ url: URL?, _ placeholder: ImagePlaceholder?) -> ImageHolder {
|
||||
var localImage: ImageHolder?
|
||||
if url?.scheme == "divkit-asset", let name = url?.host {
|
||||
//To restrict access to resources, all divkit asset images must start with the 'divkit.' prefix.
|
||||
// To restrict access to resources, all divkit asset images must start with the 'divkit.'
|
||||
// prefix.
|
||||
localImage = Image(named: "divkit.\(name)")
|
||||
}
|
||||
return localImage ?? imageHolderFactory.make(url, placeholder)
|
||||
|
||||
@@ -50,7 +50,7 @@ struct CalcExpression {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func extractDynamicVariableNames(_ context: ExpressionContext) throws -> [String] {
|
||||
try root.extractDynamicVariableNames(context)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public struct ExpressionLink<T: Sendable>: Sendable {
|
||||
self.rawValue = rawValue
|
||||
self.validator = validator
|
||||
}
|
||||
|
||||
|
||||
func extractDynamicVariableNames(_ context: ExpressionContext) -> [String] {
|
||||
items.flatMap { item in
|
||||
switch item {
|
||||
|
||||
@@ -134,7 +134,7 @@ public final class ExpressionResolver {
|
||||
resolveNumeric(expression)
|
||||
}
|
||||
|
||||
func extractDynamicVariables<T>(_ link: ExpressionLink<T>) -> [String] {
|
||||
func extractDynamicVariables(_ link: ExpressionLink<some Any>) -> [String] {
|
||||
link.extractDynamicVariableNames(context)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ extension [String: Function] {
|
||||
|
||||
addFunctions("Url", _getUrl)
|
||||
addFunctions("OptUrl", _getOptUrl)
|
||||
|
||||
|
||||
addFunction("len", _len)
|
||||
|
||||
|
||||
addFunction("getDictKeys", _getDictKeys)
|
||||
addFunction("getDictValues", _getDictValues)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ extension [String: Function] {
|
||||
addFunction("isEmpty", _isEmpty)
|
||||
|
||||
addFunction("containsKey", _containsKey)
|
||||
|
||||
|
||||
addFunction("getKeys", _getDictKeys)
|
||||
addFunction("getValues", _getDictValues)
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ private struct FunctionEvaluator: Function {
|
||||
}
|
||||
|
||||
private struct DynamicVariablesEvaluator: Function {
|
||||
func invoke(_ args: [Any], context: ExpressionContext) throws -> Any {
|
||||
func invoke(_ args: [Any], context _: ExpressionContext) throws -> Any {
|
||||
guard let arg = args.first else {
|
||||
throw ExpressionError("There is no arguments in getValueFunction")
|
||||
}
|
||||
@@ -221,5 +221,5 @@ private let getValueFunctions = [
|
||||
"getIntegerValue",
|
||||
"getNumberValue",
|
||||
"getStringValue",
|
||||
"getUrlValue"
|
||||
"getUrlValue",
|
||||
]
|
||||
|
||||
@@ -130,8 +130,8 @@ extension DivBase {
|
||||
|
||||
return blockActions
|
||||
}
|
||||
|
||||
func setupContextWithVariablesAndFunctions(
|
||||
|
||||
func setupContextWithVariablesAndFunctions(
|
||||
context: DivBlockModelingContext
|
||||
) {
|
||||
context.functionsStorage?.setIfNeeded(
|
||||
@@ -245,9 +245,17 @@ extension Block {
|
||||
for extensionHandler in extensionHandlers {
|
||||
switch order {
|
||||
case .beforeBaseProperties:
|
||||
newBlock = extensionHandler.applyBeforeBaseProperties(to: newBlock, div: div, context: context)
|
||||
newBlock = extensionHandler.applyBeforeBaseProperties(
|
||||
to: newBlock,
|
||||
div: div,
|
||||
context: context
|
||||
)
|
||||
case .afterBaseProperties:
|
||||
newBlock = extensionHandler.applyAfterBaseProperties(to: newBlock, div: div, context: context)
|
||||
newBlock = extensionHandler.applyAfterBaseProperties(
|
||||
to: newBlock,
|
||||
div: div,
|
||||
context: context
|
||||
)
|
||||
}
|
||||
}
|
||||
return newBlock
|
||||
|
||||
@@ -6,7 +6,7 @@ extension DivCollectionItemBuilder {
|
||||
context: DivBlockModelingContext,
|
||||
mappedBy modificator: (Div, Block, DivBlockModelingContext) -> T
|
||||
) -> [T] {
|
||||
makeItemDivAndContexts(context: context).compactMap { (div, itemContext) in
|
||||
makeItemDivAndContexts(context: context).compactMap { div, itemContext in
|
||||
do {
|
||||
return try modifyError({
|
||||
DivBlockModelingError($0.message, path: itemContext.path)
|
||||
@@ -20,7 +20,7 @@ extension DivCollectionItemBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func makeItemDivAndContexts(
|
||||
context: DivBlockModelingContext
|
||||
) -> [(Div, DivBlockModelingContext)] {
|
||||
|
||||
@@ -230,7 +230,7 @@ extension DivContainer.Orientation {
|
||||
extension DivAlignmentHorizontal {
|
||||
func alignment(isRTLLayout: Bool) -> Alignment {
|
||||
switch self {
|
||||
case .left:
|
||||
case .left:
|
||||
isRTLLayout ? .trailing : .leading
|
||||
case .right:
|
||||
isRTLLayout ? .leading : .trailing
|
||||
|
||||
@@ -34,7 +34,8 @@ extension DivGalleryProtocol {
|
||||
crossAlignment: (
|
||||
direction.isHorizontal
|
||||
? div.value.resolveAlignmentVertical(expressionResolver)?.alignment
|
||||
: div.value.resolveAlignmentHorizontal(expressionResolver)?.alignment(isRTLLayout: context.layoutDirection == .rightToLeft)
|
||||
: div.value.resolveAlignmentHorizontal(expressionResolver)?
|
||||
.alignment(isRTLLayout: context.layoutDirection == .rightToLeft)
|
||||
) ?? defaultCrossAlignment,
|
||||
content: block
|
||||
)
|
||||
|
||||
@@ -66,7 +66,8 @@ extension DivGrid: DivBlockModeling {
|
||||
isRTLLayout: Bool
|
||||
) -> BlockAlignment2D {
|
||||
BlockAlignment2D(
|
||||
horizontal: resolveContentAlignmentHorizontal(expressionResolver).alignment(isRTLLayout: isRTLLayout),
|
||||
horizontal: resolveContentAlignmentHorizontal(expressionResolver)
|
||||
.alignment(isRTLLayout: isRTLLayout),
|
||||
vertical: resolveContentAlignmentVertical(expressionResolver).alignment
|
||||
)
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ extension DivTypedValue {
|
||||
}
|
||||
return nil
|
||||
case let .dictValue(value):
|
||||
if let dictValue = value.resolveValue(expressionResolver) {
|
||||
return DivDictionary.fromAny(dictValue)
|
||||
}
|
||||
if let dictValue = value.resolveValue(expressionResolver) {
|
||||
return DivDictionary.fromAny(dictValue)
|
||||
}
|
||||
return nil
|
||||
case let .integerValue(value):
|
||||
if let integerValue = value.resolveValue(expressionResolver) {
|
||||
|
||||
@@ -6,7 +6,6 @@ public enum ResourcePreloadFilter {
|
||||
}
|
||||
|
||||
public final class DivDataResourcesPreloader {
|
||||
|
||||
private let resourceRequester: URLResourceRequesting
|
||||
|
||||
public init(
|
||||
@@ -25,13 +24,13 @@ public final class DivDataResourcesPreloader {
|
||||
let validURLs = divData.flatMap(
|
||||
{
|
||||
let extensionURLs = $0.makeExtensionPreloadURLs(
|
||||
extensionHandlers: extensionHandlers,
|
||||
extensionHandlers: extensionHandlers,
|
||||
expressionResolver: $1.expressionResolver
|
||||
)
|
||||
let imageURLs = $0.makeImageURLs(with: $1.expressionResolver, filter: filter)
|
||||
let videoURLs = $0.makeVideoURLs(with: $1.expressionResolver, filter: filter)
|
||||
return extensionURLs + imageURLs + videoURLs
|
||||
},
|
||||
},
|
||||
context: context
|
||||
)
|
||||
.flatMap { $0 }
|
||||
@@ -62,11 +61,11 @@ public final class DivDataResourcesPreloader {
|
||||
|
||||
extension DivData {
|
||||
fileprivate func flatMap<T>(
|
||||
_ transform: (Div, DivBlockModelingContext) -> T,
|
||||
_ transform: (Div, DivBlockModelingContext) -> T,
|
||||
context: DivBlockModelingContext
|
||||
) -> [T] {
|
||||
var result: [T] = []
|
||||
|
||||
|
||||
context.functionsStorage?.setIfNeeded(
|
||||
path: context.path,
|
||||
functions: functions ?? []
|
||||
@@ -76,17 +75,17 @@ extension DivData {
|
||||
path: context.path,
|
||||
variables: variables?.extractDivVariableValues(context.expressionResolver) ?? [:]
|
||||
)
|
||||
|
||||
|
||||
func traverse(div: Div, divContext: DivBlockModelingContext) {
|
||||
div.value.setupContextWithVariablesAndFunctions(context: divContext)
|
||||
result.append(transform(div, divContext))
|
||||
|
||||
|
||||
if let container = div.value as? DivContainer, let itemBuilder = container.itemBuilder {
|
||||
itemBuilder.makeItemDivAndContexts(context: divContext).forEach { div, itemContext in
|
||||
traverse(div: div, divContext: itemContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
div.children.forEach {
|
||||
let childContext = $0.value.modifiedContextParentPath(divContext)
|
||||
traverse(div: $0, divContext: childContext)
|
||||
|
||||
@@ -68,14 +68,14 @@ extension Div {
|
||||
[]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func makeExtensionPreloadURLs(
|
||||
extensionHandlers: [String : DivExtensionHandler],
|
||||
extensionHandlers: [String: DivExtensionHandler],
|
||||
expressionResolver: ExpressionResolver
|
||||
) -> [URL] {
|
||||
guard !extensionHandlers.isEmpty else { return [] }
|
||||
return value.extensions?.compactMap {
|
||||
extensionHandlers[$0.id]?.getPreloadURLs(div: value, expressionResolver: expressionResolver)
|
||||
guard !extensionHandlers.isEmpty else { return [] }
|
||||
return value.extensions?.compactMap {
|
||||
extensionHandlers[$0.id]?.getPreloadURLs(div: value, expressionResolver: expressionResolver)
|
||||
}.flatMap { $0 } ?? []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ extension DivArray {
|
||||
extension DivArray {
|
||||
func isEqualUnordered(_ other: DivArray?) -> Bool {
|
||||
guard let other,
|
||||
self.count == other.count else {
|
||||
self.count == other.count else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return self.countElements() == other.countElements()
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,3 @@ extension DivDictionary {
|
||||
NSDictionary(dictionary: value) as? DivDictionary
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -260,7 +260,10 @@ extension Expression {
|
||||
fileprivate func getVariablesNames(_ resolver: ExpressionResolver) -> Set<DivVariableName> {
|
||||
switch self {
|
||||
case let .link(link):
|
||||
let dynamicNames = Set(resolver.extractDynamicVariables(link).map(DivVariableName.init(rawValue:)))
|
||||
let dynamicNames = Set(
|
||||
resolver.extractDynamicVariables(link)
|
||||
.map(DivVariableName.init(rawValue:))
|
||||
)
|
||||
let staticNames = Set(link.variablesNames.map(DivVariableName.init(rawValue:)))
|
||||
return staticNames.union(dynamicNames)
|
||||
case .value:
|
||||
|
||||
@@ -173,7 +173,7 @@ final class DivBlockProvider {
|
||||
self.divData = nil
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if let resourcesPreloader = divKitComponents.resourcesPreloader {
|
||||
resourcesPreloader.downloadResources(
|
||||
for: divData,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import DivKit
|
||||
import LayoutKitInterface
|
||||
import Foundation
|
||||
import LayoutKitInterface
|
||||
import VGSL
|
||||
|
||||
struct LottieExtensionParams {
|
||||
@@ -34,15 +34,24 @@ struct LottieExtensionParams {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.repeatCount = (try? paramsDictionary.getOptionalFloat("repeat_count", expressionResolver: expressionResolver)).map(Float.init)
|
||||
self
|
||||
.repeatCount = (
|
||||
try? paramsDictionary
|
||||
.getOptionalFloat("repeat_count", expressionResolver: expressionResolver)
|
||||
).map(Float.init)
|
||||
?? Defaults.defaultRepeatCount
|
||||
|
||||
self.repeatMode = (paramsDictionary["repeat_mode"] as? String)
|
||||
.flatMap(expressionResolver.resolveString)
|
||||
.flatMap(AnimationRepeatMode.init)
|
||||
?? Defaults.defaultRepeatMode
|
||||
.flatMap(expressionResolver.resolveString)
|
||||
.flatMap(AnimationRepeatMode.init)
|
||||
?? Defaults.defaultRepeatMode
|
||||
|
||||
self.isPlaying = (try? paramsDictionary.getOptionalBool("is_playing", expressionResolver: expressionResolver)) ?? Defaults.defaultIsPlaying
|
||||
self
|
||||
.isPlaying = (
|
||||
try? paramsDictionary
|
||||
.getOptionalBool("is_playing", expressionResolver: expressionResolver)
|
||||
) ?? Defaults
|
||||
.defaultIsPlaying
|
||||
}
|
||||
|
||||
init(source: Source, repeatCount: Float, repeatMode: AnimationRepeatMode, isPlaying: Bool) {
|
||||
@@ -53,8 +62,8 @@ struct LottieExtensionParams {
|
||||
}
|
||||
}
|
||||
|
||||
private extension AnimationRepeatMode {
|
||||
init?(repeatModeString: String) {
|
||||
extension AnimationRepeatMode {
|
||||
fileprivate init?(repeatModeString: String) {
|
||||
switch repeatModeString {
|
||||
case "reverse":
|
||||
self = .reverse
|
||||
|
||||
@@ -7,7 +7,10 @@ extension Div {
|
||||
var urls: [URL] = value.background?.compactMap {
|
||||
$0.resolveImageURL(expressionResolver)
|
||||
} ?? []
|
||||
if let url = LottieExtensionHandler.getPreloadURL(div: value, expressionResolver: expressionResolver) {
|
||||
if let url = LottieExtensionHandler.getPreloadURL(
|
||||
div: value,
|
||||
expressionResolver: expressionResolver
|
||||
) {
|
||||
urls.append(url)
|
||||
}
|
||||
switch self {
|
||||
|
||||
@@ -17,7 +17,7 @@ public final class InputPropertiesExtensionHandler: DivExtensionHandler {
|
||||
}
|
||||
|
||||
let params = getExtensionParams(div)
|
||||
|
||||
|
||||
var newBlock = textInputBlock
|
||||
|
||||
if let enablesReturnKeyAutomatically = try? params.getOptionalBool(
|
||||
|
||||
@@ -16,7 +16,7 @@ extension Dictionary where Key == String {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
func getOptionalFloat(
|
||||
_ key: Key,
|
||||
expressionResolver: ExpressionResolver
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
@testable import DivKitExtensions
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
final class InputExtensionHandlersTests: XCTestCase {
|
||||
private let variableStorage = DivVariableStorage()
|
||||
private var context: DivBlockModelingContext!
|
||||
|
||||
|
||||
override func setUp() {
|
||||
variableStorage.put(name: "input_variable", value: .string("Hello!"))
|
||||
context = DivBlockModelingContext(variableStorage: variableStorage)
|
||||
}
|
||||
|
||||
|
||||
func test_ApplyAllExtensionsToTextInput() throws {
|
||||
let contextWithExtensions = DivBlockModelingContext(
|
||||
extensionHandlers: [
|
||||
InputPropertiesExtensionHandler(),
|
||||
InputAutocorrectionExtensionHandler()
|
||||
InputAutocorrectionExtensionHandler(),
|
||||
],
|
||||
variableStorage: variableStorage
|
||||
)
|
||||
@@ -30,15 +30,15 @@ final class InputExtensionHandlersTests: XCTestCase {
|
||||
id: "input_properties",
|
||||
params: [
|
||||
"enables_return_key_automatically": true,
|
||||
"spell_checking": true
|
||||
"spell_checking": true,
|
||||
]
|
||||
),
|
||||
DivExtension(
|
||||
id: "input_autocorrection",
|
||||
params: [
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
]
|
||||
)
|
||||
),
|
||||
],
|
||||
textVariable: "input_variable"
|
||||
),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@testable import DivKit
|
||||
@testable import DivKitExtensions
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
@@ -13,7 +13,7 @@ final class LottieExtensionHandlerTests: XCTestCase {
|
||||
factory: factory,
|
||||
requester: requester
|
||||
)
|
||||
|
||||
|
||||
func test_getPreloadURLs() {
|
||||
let lottieURLString = "https://example.com/DivGif9.json"
|
||||
let divWithExtension = divContainer(
|
||||
@@ -24,9 +24,9 @@ final class LottieExtensionHandlerTests: XCTestCase {
|
||||
params: [
|
||||
"lottie_url": lottieURLString,
|
||||
"repeat_count": 2,
|
||||
"repeat_mode": "reverse"
|
||||
"repeat_mode": "reverse",
|
||||
]
|
||||
)
|
||||
),
|
||||
],
|
||||
items: []
|
||||
)
|
||||
@@ -61,9 +61,9 @@ final class LottieExtensionHandlerTests: XCTestCase {
|
||||
params: [
|
||||
"lottie_url": "@{lottie_url}",
|
||||
"repeat_count": "@{repeat_count}",
|
||||
"repeat_mode": "@{repeat_mode}"
|
||||
"repeat_mode": "@{repeat_mode}",
|
||||
]
|
||||
)
|
||||
),
|
||||
],
|
||||
items: []
|
||||
),
|
||||
@@ -85,8 +85,8 @@ final class LottieExtensionHandlerTests: XCTestCase {
|
||||
|
||||
private final class MockLottieAnimationFactory: AsyncSourceAnimatableViewFactory {
|
||||
var returnView = MockAnimatableView(frame: .zero)
|
||||
func createAsyncSourceAnimatableView(withMode mode: AnimationRepeatMode, repeatCount count: Float)
|
||||
-> AsyncSourceAnimatableView {
|
||||
func createAsyncSourceAnimatableView(withMode _: AnimationRepeatMode, repeatCount _: Float)
|
||||
-> AsyncSourceAnimatableView {
|
||||
returnView
|
||||
}
|
||||
}
|
||||
@@ -94,13 +94,13 @@ private final class MockLottieAnimationFactory: AsyncSourceAnimatableViewFactory
|
||||
private final class MockAnimatableView: UIView, AsyncSourceAnimatableView {
|
||||
var playCallsCount = 0
|
||||
var receivedSources: [any DivKitExtensions.AnimationSourceType] = []
|
||||
|
||||
|
||||
func play() {
|
||||
playCallsCount += 1
|
||||
}
|
||||
|
||||
|
||||
func pause() {}
|
||||
|
||||
|
||||
func setSourceAsync(_ source: any DivKitExtensions.AnimationSourceType) async {
|
||||
receivedSources.append(source)
|
||||
}
|
||||
|
||||
@@ -84,10 +84,10 @@ private func makeCachingPlayerFactory(requester: URLResourceRequesting) -> Playe
|
||||
private func makeResourcesPreloader(
|
||||
requestPerformer: URLRequestPerforming?
|
||||
) -> DivDataResourcesPreloader? {
|
||||
guard let requestPerformer = requestPerformer else {
|
||||
guard let requestPerformer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let cacheQueue = OperationQueue.serialQueue(
|
||||
name: "divkit.resources-preloader.cache-queue",
|
||||
qos: .utility
|
||||
@@ -100,7 +100,10 @@ private func makeResourcesPreloader(
|
||||
reportError: { _ in }
|
||||
)
|
||||
let networkRequester = NetworkURLResourceRequester(performer: requestPerformer)
|
||||
let cachedRequester = CachedURLResourceRequester(cache: diskCache, cachemissRequester: networkRequester)
|
||||
let cachedRequester = CachedURLResourceRequester(
|
||||
cache: diskCache,
|
||||
cachemissRequester: networkRequester
|
||||
)
|
||||
return DivDataResourcesPreloader(resourceRequester: cachedRequester)
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ extension RiveContainerView: AsyncSourceAnimatableView {
|
||||
func play() {
|
||||
riveViewModel?.play(loop: loop)
|
||||
}
|
||||
|
||||
|
||||
func pause() {
|
||||
riveViewModel?.pause()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ open class DivViewController: UIViewController {
|
||||
private let divKitComponents: DivKitComponents
|
||||
private let debugParams: DebugParams
|
||||
private var divView: DivView?
|
||||
|
||||
|
||||
private let scrollView = VisibilityTrackingScrollView()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@@ -65,7 +65,7 @@ open class DivViewController: UIViewController {
|
||||
|
||||
divView.setParentScrollView(scrollView)
|
||||
divView.accessibilityIdentifier = identifier
|
||||
|
||||
|
||||
return divView
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@testable @_spi(Internal) import DivKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import XCTest
|
||||
import DivKitTestsSupport
|
||||
|
||||
final class CustomFunctionTests: XCTestCase {
|
||||
private let incrementFunction = DivFunction(
|
||||
|
||||
@@ -5,7 +5,7 @@ import XCTest
|
||||
|
||||
final class ExpressionResolverAnyTests: XCTestCase {
|
||||
private var isErrorExpected = false
|
||||
private var error: String? = nil
|
||||
private var error: String?
|
||||
|
||||
private var variables: DivVariables = [:]
|
||||
|
||||
|
||||
@@ -535,7 +535,8 @@ final class ExpressionResolverTests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_extractDynamicVariables_WithNestedGetValueFunctionsAndUnknownVars() {
|
||||
let expression = "@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, ''), '')}"
|
||||
let expression =
|
||||
"@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, ''), '')}"
|
||||
|
||||
XCTAssertEqual(
|
||||
expressionResolver.extractDynamicVariables(
|
||||
@@ -546,7 +547,8 @@ final class ExpressionResolverTests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_extractDynamicVariables_WithGetValueFunctionsWithFallbackValue() {
|
||||
let expression = "@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, 'value'), '')}"
|
||||
let expression =
|
||||
"@{getStringValue('outer_var_' + getStringValue('inner_var_' + string_var, 'value'), '')}"
|
||||
|
||||
XCTAssertEqual(
|
||||
expressionResolver.extractDynamicVariables(
|
||||
|
||||
@@ -168,12 +168,12 @@ private struct ExpressionTestCase: Decodable {
|
||||
}
|
||||
|
||||
extension ExpressionTestCase: Hashable {
|
||||
static func == (lhs: ExpressionTestCase, rhs: ExpressionTestCase) -> Bool {
|
||||
return lhs.expression == rhs.expression &&
|
||||
lhs.variables == rhs.variables &&
|
||||
lhs.expected.description == rhs.expected.description
|
||||
static func ==(lhs: ExpressionTestCase, rhs: ExpressionTestCase) -> Bool {
|
||||
lhs.expression == rhs.expression &&
|
||||
lhs.variables == rhs.variables &&
|
||||
lhs.expected.description == rhs.expected.description
|
||||
}
|
||||
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(expression)
|
||||
hasher.combine(variables)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import Testing
|
||||
import UIKit
|
||||
import VGSL
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import XCTest
|
||||
|
||||
final class DivContainerExtensionsTests: XCTestCase {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import XCTest
|
||||
|
||||
final class DivDataExtensionsTests: XCTestCase {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import XCTest
|
||||
|
||||
final class DivSeparatorExtensionsTests: XCTestCase {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import XCTest
|
||||
|
||||
final class DivStateExtensionsTests: XCTestCase {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import DivKitTestsSupport
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
|
||||
@@ -75,8 +75,12 @@ private func runTest(_ testData: IntegrationTestData) async {
|
||||
cardId: cardId,
|
||||
name: DivVariableName(rawValue: name)
|
||||
)
|
||||
if case .unorderedArray(let expectedArray) = value {
|
||||
let variableArray: DivArray? = if case .array(let arr) = variableValue { arr } else { nil }
|
||||
if case let .unorderedArray(expectedArray) = value {
|
||||
let variableArray: DivArray? = if case let .array(arr) = variableValue {
|
||||
arr
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
XCTAssertTrue(
|
||||
expectedArray.isEqualUnordered(variableArray),
|
||||
"Variable name - '\(name)'"
|
||||
@@ -266,23 +270,23 @@ extension [Expected] {
|
||||
private func makeDefault(_ value: ExpectedValue) -> DivVariableValue? {
|
||||
switch value {
|
||||
case .string, .datetime:
|
||||
.string("")
|
||||
.string("")
|
||||
case .integer:
|
||||
.integer(0)
|
||||
.integer(0)
|
||||
case .double:
|
||||
.number(0.0)
|
||||
.number(0.0)
|
||||
case .bool:
|
||||
.bool(false)
|
||||
.bool(false)
|
||||
case .color:
|
||||
.color(.black)
|
||||
.color(.black)
|
||||
case .url:
|
||||
.url(URL(string: "empty://")!)
|
||||
.url(URL(string: "empty://")!)
|
||||
case .array:
|
||||
.array([])
|
||||
.array([])
|
||||
case .dict:
|
||||
.dict([:])
|
||||
.dict([:])
|
||||
case .unorderedArray:
|
||||
.array([])
|
||||
.array([])
|
||||
case .error:
|
||||
nil
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
let context = DivBlockModelingContext(
|
||||
extensionHandlers: [
|
||||
mockExtensionHandler,
|
||||
invalidExtensionHandler
|
||||
invalidExtensionHandler,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -127,90 +127,95 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
let divData = divData(divWithExtension)
|
||||
|
||||
downloadResourcesAndWait(
|
||||
for: divData,
|
||||
for: divData,
|
||||
using: preloader,
|
||||
context: context
|
||||
)
|
||||
|
||||
XCTAssertEqual(mockRequester.requestedURLs, [mockExtensionHandler.preloadURL])
|
||||
}
|
||||
|
||||
|
||||
func test_WhenContainerHasNestedItemBuilders_WithAllFilter_PreloadsAllResources() {
|
||||
let divData = createDivDataWithNestedItemBuilders()
|
||||
|
||||
|
||||
downloadResourcesAndWait(for: divData, using: preloader)
|
||||
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
|
||||
Set(mockRequester.requestedURLs.map(\.absoluteString)),
|
||||
Set([requiredImageURL, optionalImageURL, validImageURL])
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func test_WhenItemBuilderUsesVariables_WithRequiredFilter_PreloadsOnlyRequiredResources() {
|
||||
let mockRequester = MockURLResourceRequester()
|
||||
mockRequester.shouldSucceed = true
|
||||
let preloader = DivDataResourcesPreloader(resourceRequester: mockRequester)
|
||||
|
||||
|
||||
let variableStorage = DivVariableStorage()
|
||||
variableStorage.put(name: "image_url_var", value: .url(URL(string: requiredImageURL)!))
|
||||
variableStorage.put(name: "preload_required_var", value: .bool(true))
|
||||
|
||||
|
||||
let context = DivBlockModelingContext(variableStorage: variableStorage)
|
||||
let divData = createDivDataWithVariables()
|
||||
|
||||
downloadResourcesAndWait(for: divData, using: preloader, filter: .onlyRequired, context: context)
|
||||
|
||||
|
||||
downloadResourcesAndWait(
|
||||
for: divData,
|
||||
using: preloader,
|
||||
filter: .onlyRequired,
|
||||
context: context
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
|
||||
Set(mockRequester.requestedURLs.map(\.absoluteString)),
|
||||
Set([requiredImageURL])
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func test_WhenContainerHasItemBuilder_WithRequiredFilter_OnlyDownloadsRequiredResources() {
|
||||
let divData = createDivDataWithMixedItemBuilderResources()
|
||||
|
||||
|
||||
downloadResourcesAndWait(for: divData, using: preloader, filter: .onlyRequired)
|
||||
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
|
||||
Set(mockRequester.requestedURLs.map(\.absoluteString)),
|
||||
Set([requiredImageURL, requiredVideoURL])
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func test_WhenContainerHasItemBuilder_WithAllFilter_DownloadsAllResources() {
|
||||
let divData = createDivDataWithMixedItemBuilderResources()
|
||||
|
||||
|
||||
downloadResourcesAndWait(for: divData, using: preloader)
|
||||
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
|
||||
Set(mockRequester.requestedURLs.map(\.absoluteString)),
|
||||
Set([requiredImageURL, optionalImageURL, requiredVideoURL, optionalVideoURL])
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func test_WhenDivDataHasVariables_WithRequiredFilter_PreloadsCorrectly() {
|
||||
let divData = createDivDataWithDivVariables()
|
||||
|
||||
|
||||
downloadResourcesAndWait(for: divData, using: preloader, filter: .onlyRequired)
|
||||
|
||||
|
||||
XCTAssertEqual(
|
||||
Set(mockRequester.requestedURLs.map { $0.absoluteString }),
|
||||
Set(mockRequester.requestedURLs.map(\.absoluteString)),
|
||||
Set([requiredImageURL])
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private func createDivDataWithDivVariables() -> DivData {
|
||||
let imageWithVariable = divImage(
|
||||
id: "image_with_variable",
|
||||
imageUrlExpression: "@{image_var}",
|
||||
preloadRequired: true
|
||||
)
|
||||
|
||||
|
||||
let container = divContainer(
|
||||
id: "container_with_variables",
|
||||
items: [imageWithVariable]
|
||||
)
|
||||
|
||||
|
||||
return DivData(
|
||||
functions: nil,
|
||||
logId: DivBlockModelingContext.testCardId.rawValue,
|
||||
@@ -219,11 +224,11 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
transitionAnimationSelector: nil,
|
||||
variableTriggers: nil,
|
||||
variables: [
|
||||
.urlVariable(UrlVariable(name: "image_var", value: .value(URL(string: requiredImageURL)!)))
|
||||
.urlVariable(UrlVariable(name: "image_var", value: .value(URL(string: requiredImageURL)!))),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private func downloadResourcesAndWait(
|
||||
for divData: DivData,
|
||||
using preloader: DivDataResourcesPreloader,
|
||||
@@ -244,19 +249,19 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
|
||||
wait(for: [expectation], timeout: 1.0)
|
||||
}
|
||||
|
||||
|
||||
private func createDivDataWithNestedItemBuilders() -> DivData {
|
||||
let outerData: [Any] = [
|
||||
["type": "container", "items": [
|
||||
["url": requiredImageURL, "preload_required": true],
|
||||
["url": optionalImageURL, "preload_required": false]
|
||||
["url": optionalImageURL, "preload_required": false],
|
||||
]],
|
||||
["type": "image", "url": validImageURL, "preload_required": true]
|
||||
["type": "image", "url": validImageURL, "preload_required": true],
|
||||
]
|
||||
|
||||
|
||||
let innerImagePrototype = DivCollectionItemBuilder.Prototype(
|
||||
div: divImage(
|
||||
id: "inner_image",
|
||||
@@ -264,7 +269,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
preloadRequiredExpression: "@{getBooleanFromDict(item, 'preload_required')}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
let innerItemBuilder = DivCollectionItemBuilder(
|
||||
data: .value(outerData.flatMap { item in
|
||||
guard let dict = item as? [String: Any],
|
||||
@@ -274,7 +279,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
dataElementName: "item",
|
||||
prototypes: [innerImagePrototype]
|
||||
)
|
||||
|
||||
|
||||
let containerPrototype = DivCollectionItemBuilder.Prototype(
|
||||
div: divContainer(
|
||||
id: "container_from_item_builder",
|
||||
@@ -282,7 +287,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
),
|
||||
selector: expression("@{getStringFromDict(item, 'type') == 'container'}")
|
||||
)
|
||||
|
||||
|
||||
let imagePrototype = DivCollectionItemBuilder.Prototype(
|
||||
div: divImage(
|
||||
id: "image_from_item_builder",
|
||||
@@ -291,27 +296,27 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
),
|
||||
selector: expression("@{getStringFromDict(item, 'type') == 'image'}")
|
||||
)
|
||||
|
||||
|
||||
let outerItemBuilder = DivCollectionItemBuilder(
|
||||
data: .value(outerData),
|
||||
dataElementName: "item",
|
||||
prototypes: [containerPrototype, imagePrototype]
|
||||
)
|
||||
|
||||
|
||||
let container = divContainer(
|
||||
id: "container_with_nested_item_builders",
|
||||
itemBuilder: outerItemBuilder
|
||||
)
|
||||
|
||||
|
||||
return divData(container)
|
||||
}
|
||||
|
||||
|
||||
private func createDivDataWithVariables() -> DivData {
|
||||
let data: [Any] = [
|
||||
["type": "image", "use_variable": true],
|
||||
["type": "image", "url": optionalImageURL, "preload_required": false]
|
||||
["type": "image", "url": optionalImageURL, "preload_required": false],
|
||||
]
|
||||
|
||||
|
||||
let variableImagePrototype = DivCollectionItemBuilder.Prototype(
|
||||
div: divImage(
|
||||
id: "image_with_variable",
|
||||
@@ -320,27 +325,27 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
),
|
||||
selector: expression("@{getStringFromDict(item, 'type') == 'image'}")
|
||||
)
|
||||
|
||||
|
||||
let itemBuilder = DivCollectionItemBuilder(
|
||||
data: .value(data),
|
||||
dataElementName: "item",
|
||||
prototypes: [variableImagePrototype]
|
||||
)
|
||||
|
||||
|
||||
let container = divContainer(
|
||||
id: "container_with_variables",
|
||||
itemBuilder: itemBuilder
|
||||
)
|
||||
|
||||
|
||||
return divData(container)
|
||||
}
|
||||
|
||||
|
||||
private func createDivDataWithMixedItemBuilderResources() -> DivData {
|
||||
let data: [[String: Any]] = [
|
||||
["type": "image", "url": requiredImageURL, "preload_required": true],
|
||||
["type": "image", "url": optionalImageURL, "preload_required": false],
|
||||
["type": "video", "url": requiredVideoURL, "preload_required": true],
|
||||
["type": "video", "url": optionalVideoURL, "preload_required": false]
|
||||
["type": "video", "url": optionalVideoURL, "preload_required": false],
|
||||
]
|
||||
|
||||
let imagePrototype = DivCollectionItemBuilder.Prototype(
|
||||
@@ -351,7 +356,7 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
),
|
||||
selector: expression("@{getStringFromDict(it, 'type') == 'image'}")
|
||||
)
|
||||
|
||||
|
||||
let videoPrototype = DivCollectionItemBuilder.Prototype(
|
||||
div: divVideo(
|
||||
id: "video_from_item_builder",
|
||||
@@ -359,23 +364,23 @@ final class DivDataResourcesPreloaderTests: XCTestCase {
|
||||
divVideoSource(
|
||||
mimeType: "video/mp4",
|
||||
urlExpression: "@{getStringFromDict(it, 'url')}"
|
||||
)
|
||||
),
|
||||
],
|
||||
preloadRequiredExpression: "@{getBooleanFromDict(it, 'preload_required')}"
|
||||
),
|
||||
selector: expression("@{getStringFromDict(it, 'type') == 'video'}")
|
||||
)
|
||||
|
||||
|
||||
let itemBuilder = DivCollectionItemBuilder(
|
||||
data: .value(data),
|
||||
prototypes: [videoPrototype, imagePrototype]
|
||||
)
|
||||
|
||||
|
||||
let container = divContainer(
|
||||
id: "container_with_item_builder",
|
||||
itemBuilder: itemBuilder
|
||||
)
|
||||
|
||||
|
||||
return divData(container)
|
||||
}
|
||||
}
|
||||
@@ -566,7 +571,7 @@ private func createDivDataWithInvalidURLs() -> DivData {
|
||||
}
|
||||
|
||||
private func testURL(_ path: String, domain: String = "example.com") -> String {
|
||||
return "https://\(domain)/\(path)"
|
||||
"https://\(domain)/\(path)"
|
||||
}
|
||||
|
||||
private let requiredImageURL = testURL("required_image.jpg")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@testable import DivKit
|
||||
@testable import LayoutKit
|
||||
import Foundation
|
||||
@testable import LayoutKit
|
||||
import VGSL
|
||||
import XCTest
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ public func divImage(
|
||||
} else {
|
||||
.value(url(imageUrl))
|
||||
}
|
||||
|
||||
|
||||
let preloadRequiredValue: Expression<Bool>? = if let preloadRequiredExpression {
|
||||
expression(preloadRequiredExpression)
|
||||
} else {
|
||||
@@ -575,7 +575,6 @@ public func variable(_ name: String, _ value: String) -> DivVariable {
|
||||
.stringVariable(StringVariable(name: name, value: .value(value)))
|
||||
}
|
||||
|
||||
|
||||
public func solidBackground(_ color: RGBAColor) -> DivBackground {
|
||||
.divSolidBackground(DivSolidBackground(color: .value(color)))
|
||||
}
|
||||
@@ -591,7 +590,7 @@ public func divVideo(
|
||||
} else {
|
||||
preloadRequired.map { .value($0) }
|
||||
}
|
||||
|
||||
|
||||
return .divVideo(DivVideo(
|
||||
id: id,
|
||||
preloadRequired: preloadRequiredValue,
|
||||
|
||||
@@ -5,7 +5,7 @@ public final class MockURLResourceRequester: URLResourceRequesting {
|
||||
public var requestedURLs: [URL] = []
|
||||
public var shouldSucceed = true
|
||||
public var customBehavior: ((URL) -> Bool)?
|
||||
|
||||
|
||||
public init() {}
|
||||
|
||||
@MainActor
|
||||
|
||||
@@ -16,7 +16,7 @@ public final class PhoneMaskFormatter: MaskFormatter {
|
||||
var stringIndex = rawText.startIndex
|
||||
var newCursorPosition: CursorPosition?
|
||||
let mask = findMask(for: rawText)
|
||||
|
||||
|
||||
maskLoop: for element in mask {
|
||||
if element.isPlaceHolder {
|
||||
guard stringIndex < rawText.endIndex else {
|
||||
@@ -28,7 +28,7 @@ public final class PhoneMaskFormatter: MaskFormatter {
|
||||
break maskLoop
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
text.append(rawText[stringIndex])
|
||||
let textString = String(text)
|
||||
|
||||
@@ -42,7 +42,7 @@ public final class PhoneMaskFormatter: MaskFormatter {
|
||||
text.append(element)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let textString = String(text)
|
||||
if let rawCursorPosition {
|
||||
let cursorIndex = rawCursorPosition.cursorPosition.rawValue
|
||||
@@ -107,7 +107,7 @@ extension Character {
|
||||
fileprivate var isPlaceHolder: Bool {
|
||||
self == "0"
|
||||
}
|
||||
|
||||
|
||||
fileprivate var isHyphen: Bool {
|
||||
self == "-"
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ extension DetachableAnimationBlock: ElementStateUpdating {
|
||||
if updatedChild === child, animationIn == nil {
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
return Self(
|
||||
child: updatedChild,
|
||||
id: id,
|
||||
|
||||
@@ -27,7 +27,7 @@ public final class EmptyBlock: BlockWithTraits {
|
||||
public var isEmpty: Bool {
|
||||
self == EmptyBlock.zeroSized
|
||||
}
|
||||
|
||||
|
||||
public func equals(_ other: Block) -> Bool {
|
||||
guard let other = other as? EmptyBlock else {
|
||||
return false
|
||||
|
||||
@@ -7,7 +7,7 @@ public struct MarksConfigurationModel: Equatable {
|
||||
let activeMark: RoundedRectangle
|
||||
let inactiveMark: RoundedRectangle
|
||||
let layoutDirection: UserInterfaceLayoutDirection
|
||||
|
||||
|
||||
public init(
|
||||
minValue: CGFloat,
|
||||
maxValue: CGFloat,
|
||||
@@ -21,7 +21,7 @@ public struct MarksConfigurationModel: Equatable {
|
||||
self.inactiveMark = inactiveMark
|
||||
self.layoutDirection = layoutDirection
|
||||
}
|
||||
|
||||
|
||||
public static let empty = Self(
|
||||
minValue: 0,
|
||||
maxValue: 0,
|
||||
|
||||
@@ -4,19 +4,19 @@ final class MarksLayer: CALayer {
|
||||
var firstThumbProgress: CGFloat = 0
|
||||
var secondThumbProgress: CGFloat = 0
|
||||
var configuration = MarksConfiguration.empty
|
||||
|
||||
|
||||
private var maxValue: CGFloat {
|
||||
configuration.modelConfiguration.maxValue
|
||||
}
|
||||
|
||||
|
||||
private var minValue: CGFloat {
|
||||
configuration.modelConfiguration.minValue
|
||||
}
|
||||
|
||||
|
||||
private var activeMark: MarksConfigurationModel.RoundedRectangle {
|
||||
configuration.modelConfiguration.activeMark
|
||||
}
|
||||
|
||||
|
||||
private var inactiveMark: MarksConfigurationModel.RoundedRectangle {
|
||||
configuration.modelConfiguration.inactiveMark
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public struct SliderModel: Equatable {
|
||||
public let layoutDirection: UserInterfaceLayoutDirection
|
||||
public let path: UIElementPath?
|
||||
public let isEnabled: Bool
|
||||
|
||||
|
||||
public var marksConfiguration: MarksConfiguration {
|
||||
MarksConfiguration(
|
||||
modelConfiguration: marksModelConfiguration,
|
||||
@@ -110,7 +110,7 @@ public struct SliderModel: Equatable {
|
||||
public var valueRange: Int {
|
||||
maxValue - minValue
|
||||
}
|
||||
|
||||
|
||||
private let marksModelConfiguration: MarksConfigurationModel
|
||||
|
||||
public var sliderHeight: CGFloat {
|
||||
|
||||
@@ -196,8 +196,9 @@ public class DefaultTooltipManager: TooltipManager {
|
||||
guard tooltipWindowManager == nil else { return }
|
||||
|
||||
let scenes = UIApplication.shared.connectedScenes
|
||||
guard let windowScene = scenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
|
||||
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) else {
|
||||
guard let windowScene = scenes
|
||||
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
|
||||
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ private class GradientContainerView: UIView {
|
||||
private let model: TextBlock.GradientModel
|
||||
private let rangedTextWithColorTextBlockView: TextBlockView
|
||||
let gradientView: UIView
|
||||
|
||||
|
||||
init?(model: TextBlock.GradientModel?, mask: UIView) {
|
||||
guard let model else {
|
||||
return nil
|
||||
@@ -131,27 +131,29 @@ private class GradientContainerView: UIView {
|
||||
self.model = model
|
||||
gradientView = model.gradient.uiView
|
||||
gradientView.mask = mask
|
||||
|
||||
|
||||
rangedTextWithColorTextBlockView = TextBlockView()
|
||||
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
|
||||
gradientView.addSubview(rangedTextWithColorTextBlockView)
|
||||
addSubview(gradientView)
|
||||
}
|
||||
|
||||
|
||||
@available(*, unavailable)
|
||||
required init(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
gradientView.frame = bounds
|
||||
rangedTextWithColorTextBlockView.frame = bounds
|
||||
}
|
||||
|
||||
|
||||
func configureRangedTextColor(textBlockViewModel: TextBlockView.Model) {
|
||||
rangedTextWithColorTextBlockView.model = textBlockViewModel.updated(with: model.rangedTextWithColor)
|
||||
rangedTextWithColorTextBlockView.model = textBlockViewModel
|
||||
.updated(with: model.rangedTextWithColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-7
@@ -358,7 +358,7 @@ private final class TextInputBlockView: BlockView, VisibleBoundsTrackingLeaf {
|
||||
setSmartInsertDeleteType(.default)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func setSmartInsertDeleteType(_ type: UITextSmartInsertDeleteType) {
|
||||
singleLineInput.smartInsertDeleteType = type
|
||||
multiLineInput.smartInsertDeleteType = type
|
||||
@@ -909,7 +909,7 @@ extension UITextField {
|
||||
guard let selectedRange = self.selectedTextRange else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let beginning = self.beginningOfDocument
|
||||
let startPosition = self.offset(from: beginning, to: selectedRange.start)
|
||||
let endPosition = self.offset(from: beginning, to: selectedRange.end)
|
||||
@@ -939,12 +939,16 @@ private class PatchedUITextField: UITextField {
|
||||
guard let string = UIPasteboard.general.string else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let range = self.selectedNSRange else {
|
||||
return
|
||||
}
|
||||
|
||||
if (self.delegate as? TextInputBlockView)?.textField(self, shouldChangeCharactersIn: range, replacementString: string) == true {
|
||||
|
||||
if (self.delegate as? TextInputBlockView)?.textField(
|
||||
self,
|
||||
shouldChangeCharactersIn: range,
|
||||
replacementString: string
|
||||
) == true {
|
||||
super.paste(sender)
|
||||
}
|
||||
}
|
||||
@@ -962,8 +966,12 @@ private class PatchedUITextView: UITextView {
|
||||
guard let string = UIPasteboard.general.string else {
|
||||
return
|
||||
}
|
||||
|
||||
if (self.delegate as? TextInputBlockView)?.textView(self, shouldChangeTextIn: self.selectedRange, replacementText: string) == true {
|
||||
|
||||
if (self.delegate as? TextInputBlockView)?.textView(
|
||||
self,
|
||||
shouldChangeTextIn: self.selectedRange,
|
||||
replacementText: string
|
||||
) == true {
|
||||
isPasting = true
|
||||
super.paste(sender)
|
||||
}
|
||||
|
||||
@@ -178,16 +178,16 @@ final class TabContentsView: BlockView {
|
||||
relativeContentOffset = offset
|
||||
updateSelectedPageIndexIfNeeded(relativeContentOffset)
|
||||
}
|
||||
|
||||
|
||||
func updateSelectedPageIndexIfNeeded(_ idx: CGFloat) {
|
||||
guard selectedPageIndex.isApproximatelyNotEqualTo(idx) else {
|
||||
delegate?.tabContentsViewDidEndAnimation()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
selectedPageIndex = idx
|
||||
}
|
||||
|
||||
|
||||
private func updateContentOffset(animated: Bool = true) {
|
||||
collectionView.setContentOffset(selectedPageContentOffset, animated: animated)
|
||||
}
|
||||
@@ -203,7 +203,7 @@ final class TabContentsView: BlockView {
|
||||
collectionView.frame = bounds
|
||||
updateContentOffset(animated: false)
|
||||
}
|
||||
|
||||
|
||||
let newLayout = TabContentsViewLayout(
|
||||
pages: model.pages.map(\.block),
|
||||
footer: model.footer,
|
||||
@@ -232,7 +232,7 @@ final class TabContentsView: BlockView {
|
||||
return
|
||||
}
|
||||
let index = isIntermediate ? selectedPageIndex : roundedIndex
|
||||
|
||||
|
||||
updateSelectedPageIndexIfNeeded(index)
|
||||
updatesDelegate?.onSelectedPageIndexChanged(index, inModel: model)
|
||||
let state = TabViewState(selectedPageIndex: index, countOfPages: countOfPages)
|
||||
|
||||
@@ -46,7 +46,7 @@ final class TabListView: UIView {
|
||||
contentSize: CGSize,
|
||||
selection: CGFloat
|
||||
)?
|
||||
|
||||
|
||||
private var animationInfo: AnimationInfo? {
|
||||
didSet {
|
||||
setNeedsLayout()
|
||||
@@ -66,7 +66,7 @@ final class TabListView: UIView {
|
||||
init() {
|
||||
collectionView = makeCollectionView(layout: collectionViewLayout)
|
||||
delegate = TabListViewDelegate(collectionView: collectionView)
|
||||
|
||||
|
||||
super.init(frame: .zero)
|
||||
clipsToBounds = true
|
||||
|
||||
@@ -176,7 +176,7 @@ final class TabListView: UIView {
|
||||
func endScrollingAnimation() {
|
||||
animationInfo = nil
|
||||
}
|
||||
|
||||
|
||||
func setInitialModel(_ model: TabTitlesViewModel) {
|
||||
guard !setInitialModel else { return }
|
||||
self.model = model
|
||||
|
||||
@@ -26,7 +26,7 @@ public final class TabbedPagesView: BlockView, VisibleBoundsTrackingContainer {
|
||||
|
||||
public var effectiveBackgroundColor: UIColor? { tabContentsView.effectiveBackgroundColor }
|
||||
public var layoutReporter: LayoutReporter?
|
||||
|
||||
|
||||
public func configure(
|
||||
model: TabViewModel,
|
||||
state: TabViewState,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SwiftUI
|
||||
import DivKit
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var isPresented: Bool = false
|
||||
|
||||
Reference in New Issue
Block a user