Skip to content

Commit

Permalink
Release 25.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
robot-divkit committed Jun 5, 2023
1 parent 23a53db commit 9f8d428
Show file tree
Hide file tree
Showing 24 changed files with 707 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Core/BaseTinyPublic/iOS.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2021 Yandex LLC. All rights reserved.

import CoreGraphics
import UIKit
@_exported import UIKit

public typealias Color = RGBAColor
public typealias SystemColor = UIColor
Expand Down
2 changes: 1 addition & 1 deletion DivKit/DivKitInfo.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
public enum DivKitInfo {
public static let version = "25.2.0"
public static let version = "25.3.0"
}
2 changes: 1 addition & 1 deletion DivKit/DivStatePath.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CommonCorePublic
@_exported import CommonCorePublic
import LayoutKit

public enum CardIDTag {}
Expand Down
12 changes: 10 additions & 2 deletions DivKit/Extensions/DivContainerExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,15 @@ extension DivContainer: DivBlockModeling {
guard let separator = separator else {
return nil
}
let separatorBlock = try separator.style.makeBlock(context: context, corners: .all)
let separatorBlock = try separator.style.makeBlock(
context: context, corners: .all
).addingEdgeInsets(separator.margins.makeEdgeInsets(with: context.expressionResolver))

let style = ContainerBlock.Child(
content: separatorBlock,
crossAlignment: .center
)

return ContainerBlock.Separator(
style: style,
showAtEnd: separator.resolveShowAtEnd(context.expressionResolver),
Expand All @@ -291,11 +295,15 @@ extension DivContainer: DivBlockModeling {
guard let lineSeparator = lineSeparator else {
return nil
}
let lineSeparatorBlock = try lineSeparator.style.makeBlock(context: context, corners: .all)
let lineSeparatorBlock = try lineSeparator.style.makeBlock(
context: context, corners: .all
).addingEdgeInsets(lineSeparator.margins.makeEdgeInsets(with: context.expressionResolver))

let style = ContainerBlock.Child(
content: lineSeparatorBlock,
crossAlignment: .center
)

return ContainerBlock.Separator(
style: style,
showAtEnd: lineSeparator.resolveShowAtEnd(context.expressionResolver),
Expand Down
40 changes: 40 additions & 0 deletions DivKit/Extensions/DivInputExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,22 @@ extension DivInput: DivBlockModeling {
$0.uiAction(context: context.actionContext)
}

let maskValidator = mask?.makeMaskValidator(context.expressionResolver)
let rawTextValue: Binding<String>? = mask?.makeRawVariable(context)

return TextInputBlock(
widthTrait: makeContentWidthTrait(with: context),
heightTrait: makeContentHeightTrait(with: context),
hint: hintValue.with(typo: hintTypo),
textValue: textValue,
rawTextValue: rawTextValue,
textTypo: textTypo,
multiLineMode: keyboardType == .multiLineText,
inputType: keyboardType.system,
highlightColor: highlightColor,
maxVisibleLines: maxVisibleLines,
selectAllOnFocus: selectAllOnFocus,
maskValidator: maskValidator,
path: context.parentPath,
onFocusActions: onFocusActions,
onBlurActions: onBlurActions,
Expand Down Expand Up @@ -108,3 +113,38 @@ extension DivInput.KeyboardType {
}
}
}

extension DivInputMask {
fileprivate func makeMaskValidator(_ resolver: ExpressionResolver) -> MaskValidator? {
switch self {
case let .divFixedLengthInputMask(divFixedLengthInputMask):
return MaskValidator(
pattern: divFixedLengthInputMask.resolvePattern(resolver) ?? "",
alwaysVisible: divFixedLengthInputMask.resolveAlwaysVisible(resolver),
patternElements: divFixedLengthInputMask.patternElements
.map { $0.makePatternElement(resolver) }
)
case .divCurrencyInputMask:
return nil
}
}

fileprivate func makeRawVariable(_ context: DivBlockModelingContext) -> Binding<String>? {
switch self {
case let .divFixedLengthInputMask(divFixedLengthInputMask):
return .init(context: context, name: divFixedLengthInputMask.rawTextVariable)
case .divCurrencyInputMask:
return nil
}
}
}

extension DivFixedLengthInputMask.PatternElement {
fileprivate func makePatternElement(_ resolver: ExpressionResolver) -> PatternElement {
PatternElement(
key: (resolveKey(resolver) ?? "").first!,
regex: try! NSRegularExpression(pattern: resolveRegex(resolver) ?? ""),
placeholder: resolvePlaceholder(resolver).first!
)
}
}
1 change: 1 addition & 0 deletions DivKit/Extensions/DivSelectExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extension DivSelect: DivBlockModeling {
heightTrait: makeContentHeightTrait(with: context),
hint: hintValue.with(typo: hintTypo),
textValue: textValue,
rawTextValue: nil,
textTypo: textTypo,
inputType: makeInputType(context.expressionResolver),
path: context.parentPath,
Expand Down
12 changes: 7 additions & 5 deletions DivKit/Extensions/DivVideoExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,19 @@ extension DivVideo: DivBlockModeling {
}

extension DivVideoSource {
func makeVideo(resolver: ExpressionResolver) -> Video {
public func makeVideo(resolver: ExpressionResolver) -> Video {
let resolution: CGSize? = resolution.flatMap { resolution in
CGSize(
width: resolution.resolveWidth(resolver) ?? 0,
height: resolution.resolveHeight(resolver) ?? 0
)
}
return Video(url: resolveUrl(resolver)!,
resolution: resolution,
bitrate: resolveBitrate(resolver).flatMap { Double($0) },
mimeType: resolveMimeType(resolver))
return Video(
url: resolveUrl(resolver)!,
resolution: resolution,
bitrate: resolveBitrate(resolver).flatMap { Double($0) },
mimeType: resolveMimeType(resolver)
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion DivKit/Variables/Binding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension Binding where T == String {
name: name,
getValue: { context.expressionResolver.getVariableValue($0) ?? "" },
userInterfaceActionFactory: { name, value in
URL(string: "div-action://set_variable?name=\(name)&value=\(value.percentEncodedURLString)")
URL(string: "div-action://set_variable?name=\(name)&value=\(value.percentEncoded())")
.flatMap {
DivAction(logId: "binding", url: .value($0))
}?.uiAction(context: context.actionContext)
Expand Down
203 changes: 203 additions & 0 deletions LayoutKit/LayoutKit/Base/MaskValidator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import BaseTinyPublic
import Foundation

public final class MaskValidator: Equatable {
private let pattern: String
private let alwaysVisible: Bool
private let decoding: [Character: Pattern]
private let inputLength: Int

public init(pattern: String, alwaysVisible: Bool, patternElements: [PatternElement]) {
self.pattern = pattern
self.alwaysVisible = alwaysVisible
let decoding = [Character: Pattern].init(
patternElements.map(\.key),
patternElements
.map { Pattern(regexp: $0.regex, placeHolder: $0.placeholder) }
)
self.decoding = decoding
self.inputLength = pattern.reduce(0) { $0 + (decoding.keys.contains($1) ? 1 : 0) }
}

public func formatted(rawText: String, rawCursorPosition: CursorData? = nil) -> InputData {
var text = [Character]()
var rawData = [InputData.RawCharacter]()
var stringIndex = rawText.startIndex
var newCursorPosition: CursorPosition?
for (index, element) in pattern.enumerated() {
guard stringIndex != rawText.endIndex else {
for ch in pattern.suffix(pattern.count - index) {
if let element = decoding[ch]?.placeHolder {
guard alwaysVisible else { break }
text.append(element)
} else {
text.append(ch)
if let rawCursorPosition, rawData.count == rawCursorPosition.cursorPosition.rawValue,
rawCursorPosition.afterNonDecodingSymbols {
newCursorPosition = .init(rawValue: text.count)
}
}
}
break
}
if let decodingElement = decoding[element] {
let regexp = decodingElement.regexp
let placeholder = decodingElement.placeHolder
while stringIndex != rawText.endIndex,
regexp.numberOfMatches(in: String(rawText[stringIndex]), range: NSRange(0..<1)) == 0 {
stringIndex = rawText.index(after: stringIndex)
}
guard stringIndex != rawText.endIndex else { break }
if regexp.numberOfMatches(in: String(rawText[stringIndex]), range: NSRange(0..<1)) != 0 {
text.append(rawText[stringIndex])
rawData.append(.init(char: rawText[stringIndex], index: String(text).endIndex))
stringIndex = rawText.index(after: stringIndex)
} else {
text.append(placeholder)
}

if let rawCursorPosition, rawData.count <= rawCursorPosition.cursorPosition.rawValue {
newCursorPosition = .init(rawValue: text.count)
}
} else {
text.append(element)
if let rawCursorPosition, rawData.count < rawCursorPosition.cursorPosition.rawValue {
newCursorPosition = .init(rawValue: text.count)
} else if let rawCursorPosition, rawData.count == rawCursorPosition.cursorPosition.rawValue,
rawCursorPosition.afterNonDecodingSymbols {
newCursorPosition = .init(rawValue: text.count)
}
}
}
return InputData(text: String(text), cursorPosition: newCursorPosition, rawData: rawData)
}

public func removeSymbols(at pos: Int, data: InputData) -> (String, CursorData?) {
var data = data
let removeIndex = data.rawData.lastIndex { data.text.distance(
from: data.text.index(data.text.startIndex, offsetBy: pos),
to: $0.index
) <= 0 }
if let removeIndex = removeIndex {
data.rawData.remove(at: removeIndex)
}
return (
data.rawText,
removeIndex
.flatMap { CursorData(cursorPosition: .init(rawValue: $0), afterNonDecodingSymbols: false) }
)
}

public func removeSymbols(at range: Range<Int>, data: InputData) -> (String, CursorData?) {
let index = data.rawData.firstIndex { data.text.distance(
from: data.text.index(data.text.startIndex, offsetBy: range.lowerBound),
to: $0.index
) > 0 }
return (
String(data.rawData.filter {
data.text.distance(
from: data.text.index(data.text.startIndex, offsetBy: range.lowerBound),
to: $0.index
) <= 0 ||
data.text.distance(
from: data.text.index(data.text.startIndex, offsetBy: range.upperBound),
to: $0.index
) > 0
}.map(\.char)),
index
.flatMap { CursorData(cursorPosition: .init(rawValue: $0), afterNonDecodingSymbols: false) }
)
}

public func addSymbols(
at pos: Int,
data: InputData,
string: String
) -> (String, CursorData?) {
let addIndex = data.rawData.firstIndex { data.text.distance(
from: data.text.index(data.text.startIndex, offsetBy: pos),
to: $0.index
) > 0 } ?? data.rawData.count

let prefix = String(data.rawData[0..<addIndex].map(\.char))
let suffix = String(data.rawData[addIndex..<data.rawData.count].map(\.char))
return (
String((prefix + string + suffix).prefix(inputLength)),
CursorData(
cursorPosition: .init(rawValue: prefix.count + string.count),
afterNonDecodingSymbols: true
)
)
}

public func addSymbols(
at range: Range<Int>,
data: InputData,
string: String
) -> (String, CursorData?) {
let leftIndex = data.rawData.firstIndex { data.text.distance(
from: data.text.index(data.text.startIndex, offsetBy: range.lowerBound),
to: $0.index
) > 0 } ?? data.rawData.count

let rightIndex = data.rawData.firstIndex { data.text.distance(
from: data.text.index(data.text.startIndex, offsetBy: range.upperBound),
to: $0.index
) > 0 } ?? data.rawData.count

let prefix = String(data.rawData[0..<leftIndex].map(\.char))
let suffix = String(data.rawData[rightIndex..<data.rawData.count].map(\.char))
return (
String((prefix + string + suffix).prefix(inputLength)),
CursorData(
cursorPosition: .init(rawValue: prefix.count + string.count),
afterNonDecodingSymbols: true
)
)
}

public static func ==(lhs: MaskValidator, rhs: MaskValidator) -> Bool {
lhs.decoding == rhs.decoding &&
lhs.pattern == rhs.pattern &&
lhs.alwaysVisible == rhs.alwaysVisible
}
}

public struct PatternElement {
let key: Character
let regex: NSRegularExpression
let placeholder: Character

public init(key: Character, regex: NSRegularExpression, placeholder: Character) {
self.key = key
self.regex = regex
self.placeholder = placeholder
}
}

public enum CursorPositionTag {}
public typealias CursorPosition = Tagged<CursorPositionTag, Int>

public struct CursorData {
let cursorPosition: CursorPosition
let afterNonDecodingSymbols: Bool
}

public struct InputData {
public struct RawCharacter {
let char: Character
let index: String.Index
}

public let text: String
public let cursorPosition: CursorPosition?
public var rawData: [RawCharacter]
public var rawText: String {
String(rawData.map(\.char))
}
}

public struct Pattern: Equatable {
public let regexp: NSRegularExpression
public var placeHolder: Character
}
Loading

0 comments on commit 9f8d428

Please sign in to comment.