Files
booster 7d5991e2f8 gallery scroll actions collision fix
commit_hash:e27066de5f833e93cf589451554c6f93d252f6be
2026-05-15 17:16:31 +03:00

235 lines
8.4 KiB
Swift

import CoreGraphics
import Foundation
import LayoutKit
import VGSL
extension DivTabs: DivBlockModeling {
public func makeBlock(context: DivBlockModelingContext) throws -> Block {
let context = modifiedContextParentPath(context)
return try modifyError({ DivBlockModelingError($0.message, path: context.path) }) {
try applyBaseProperties(
to: { try makeBaseBlock(context: context) },
context: context,
actionsHolder: nil
)
}
}
private func makeBaseBlock(context: DivBlockModelingContext) throws -> Block {
let itemContext = context.modifying(errorsStorage: DivErrorsStorage(errors: []))
let tabs = items.iterativeFlatMap { item, index in
do {
return try item.makeTab(context: itemContext, index: index)
} catch {
itemContext.addError(error: error)
return nil
}
}
if tabs.isEmpty {
throw DivBlockModelingError(
"Tabs error: missing children",
path: context.path,
causes: itemContext.errorsStorage.errors
)
} else {
context.errorsStorage.add(contentsOf: itemContext.errorsStorage)
}
let titleStyle = tabTitleStyle ?? DivTabs.TabTitleStyle()
let listModel = TabListViewModel(
tabTitles: tabs.map(\.title),
titleStyle: titleStyle.resolve(context),
layoutDirection: context.layoutDirection,
listPaddings: titlePaddings.resolve(context),
delimiterStyle: resolveTabTitleDelimiter(context)
)
let expressionResolver = context.expressionResolver
let contentsModel = try TabContentsViewModel(
pages: tabs.map(\.page),
pagesHeight: resolveDynamicHeight(expressionResolver)
? .bySelectedPage
: .byHighestPage,
path: context.path,
scrollingEnabled: resolveSwitchTabsByContentSwipeEnabled(expressionResolver),
layoutDirection: context.layoutDirection
)
return try TabsBlock(
model: TabViewModel(
listModel: listModel,
contentsModel: contentsModel,
separatorStyle: resolveSeparatorStyle(context),
tabTitleDelimiter: resolveTabTitleDelimiter(context)
),
state: makeState(context: context, tabs: tabs),
widthTrait: resolveContentWidthTrait(context),
heightTrait: resolveContentHeightTrait(context)
)
}
private func resolveSeparatorStyle(
_ context: DivBlockModelingContext
) -> TabSeparatorStyle? {
let expressionResolver = context.expressionResolver
return resolveHasSeparator(expressionResolver)
? TabSeparatorStyle(
color: resolveSeparatorColor(expressionResolver),
insets: separatorPaddings.resolve(context)
) : nil
}
private func resolveTabTitleDelimiter(
_ context: DivBlockModelingContext
) -> TabTitleDelimiterStyle? {
guard let delimiter = tabTitleDelimiter else { return nil }
let expressionResolver = context.expressionResolver
guard let imageURL = delimiter.resolveImageUrl(expressionResolver) else { return nil }
let imageHolder = context.imageHolderFactory.make(imageURL)
return TabTitleDelimiterStyle(
imageHolder: imageHolder,
width: delimiter.width.resolveScaledValue(expressionResolver, defaultValue: 12),
height: delimiter.height.resolveScaledValue(expressionResolver, defaultValue: 12)
)
}
private func makeState(
context: DivBlockModelingContext,
tabs: [Tab]
) -> TabViewState {
let stateStorage = context.blockStateStorage
let path = context.path
let index: CGFloat = if let pending = stateStorage
.takePendingState(path) as? TabViewState {
pending.selectedPageIndex
} else if let state: TabViewState = stateStorage.getState(path) {
state.selectedPageIndex
} else {
CGFloat(resolveSelectedTab(context.expressionResolver))
}
let newState = TabViewState(
selectedPageIndex: min(index, CGFloat(tabs.count) - 1),
countOfPages: tabs.count
)
stateStorage.setState(path: path, state: newState)
return newState
}
}
// IMPORTANT: Here we are changing `private` to `internal` due to bug in swift
// compiler. If this typealias is private when resuliting swiftmodule file is
// unstable.
// TODO(ilyakuteev): Fix this in swift toolchain.
typealias Tab = (title: UILink, page: TabPageViewModel)
extension DivTabs.TabTitleStyle {
private func makeTypo(
fontProvider: DivFontProvider,
fontWeight: DivFontWeight,
fontWeightValue: Int?,
fontVariationSettings: [String: NSNumber]?,
expressionResolver: ExpressionResolver
) -> Typo {
let fontFamily = resolveFontFamily(expressionResolver) ?? ""
let size = resolveFontSizeUnit(expressionResolver)
.makeScaledValue(resolveFontSize(expressionResolver))
let font = if let fontWeightValue {
fontProvider.font(family: fontFamily, weight: fontWeightValue, size: size)
} else {
fontProvider.font(family: fontFamily, weight: fontWeight, size: size)
}
return Typo(font: font.withVariationSettings(axisTagToValue: fontVariationSettings))
.with(height: resolveLineHeight(expressionResolver))
.kerned(CGFloat(resolveLetterSpacing(expressionResolver)))
.allowHeightOverrun
}
fileprivate func resolve(
_ context: DivBlockModelingContext
) -> TabTitleStyle {
let expressionResolver = context.expressionResolver
let fontProvider = context.fontProvider
let defaultFontWeight = resolveFontWeight(expressionResolver)
return TabTitleStyle(
typo: makeTypo(
fontProvider: fontProvider,
fontWeight: resolveActiveFontWeight(expressionResolver) ?? defaultFontWeight,
fontWeightValue: resolveActiveFontWeightValue(expressionResolver),
fontVariationSettings: resolveActiveFontVariationSettings(expressionResolver)?
.mapValues { $0 as? NSNumber }.filteringNilValues(),
expressionResolver: expressionResolver
),
inactiveTypo: makeTypo(
fontProvider: fontProvider,
fontWeight: resolveInactiveFontWeight(expressionResolver) ?? defaultFontWeight,
fontWeightValue: resolveInactiveFontWeightValue(expressionResolver),
fontVariationSettings: resolveInactiveFontVariationSettings(expressionResolver)?
.mapValues { $0 as? NSNumber }.filteringNilValues(),
expressionResolver: expressionResolver
),
paddings: paddings.resolve(context),
cornerRadius: resolveCornerRadii(expressionResolver),
baseTextColor: resolveInactiveTextColor(expressionResolver),
activeTextColor: resolveActiveTextColor(expressionResolver),
activeBackgroundColor: resolveActiveBackgroundColor(expressionResolver),
inactiveBackgroundColor: resolveInactiveBackgroundColor(expressionResolver) ?? .clear,
itemSpacing: CGFloat(resolveItemSpacing(expressionResolver))
)
}
private func resolveCornerRadii(_ expressionResolver: ExpressionResolver) -> CornerRadii? {
let cornerRadius = resolveCornerRadius(expressionResolver)
let topLeft = cornersRadius?.resolveTopLeft(expressionResolver)
?? cornerRadius
let topRight = cornersRadius?.resolveTopRight(expressionResolver)
?? cornerRadius
let bottomLeft = cornersRadius?.resolveBottomLeft(expressionResolver)
?? cornerRadius
let bottomRight = cornersRadius?.resolveBottomRight(expressionResolver)
?? cornerRadius
if topLeft == nil, topRight == nil, bottomLeft == nil, bottomRight == nil {
return nil
}
return CornerRadii(
topLeft: CGFloat(topLeft ?? 0),
topRight: CGFloat(topRight ?? 0),
bottomLeft: CGFloat(bottomLeft ?? 0),
bottomRight: CGFloat(bottomRight ?? 0)
)
}
}
extension Typo {
fileprivate func with(height: Int?) -> Typo {
if let height {
with(height: CGFloat(height))
} else {
self
}
}
}
extension DivTabs.Item {
fileprivate func makeTab(context: DivBlockModelingContext, index: Int) throws -> Tab {
let titleContext = context.modifying(pathSuffix: "title\(index)")
let title = makeTitle(context: titleContext)
let pageContext = context.modifying(pathSuffix: "\(index)")
let page = try div.value
.makeBlock(context: pageContext)
.makeTabPage(with: pageContext.path)
return (title: title, page: page)
}
private func makeTitle(context: DivBlockModelingContext) -> UILink {
let action = titleClickAction?.uiAction(context: context)
return UILink(
text: resolveTitle(context.expressionResolver) ?? "",
url: action?.url,
path: context.path
)
}
}