Files
denlvovich 16e9eabab4 Preventing TransitioningView appearance during animation
commit_hash:e21f56a4180cf5a28a1b45218f9460c5dafdc111
2025-09-10 19:15:43 +03:00

170 lines
5.4 KiB
Swift

import CoreFoundation
import CoreGraphics
import Foundation
import LayoutKit
import VGSL
extension DivState: DivBlockModeling {
public func makeBlock(context parentContext: DivBlockModelingContext) throws -> Block {
let stateId = parentContext.overridenId ?? divId ?? id ?? ""
let context = modifiedContextParentPath(parentContext)
return try addSwipeHandling(
to: applyBaseProperties(
to: { try makeBaseBlock(context: context, id: stateId) },
context: context,
actionsHolder: self,
clipToBounds: resolveClipToBounds(context.expressionResolver)
),
context: context
)
}
private func makeBaseBlock(context: DivBlockModelingContext, id: String) throws -> Block {
if id == "" {
context.addWarning(message: "DivState has no id")
}
let divStatePath = context.parentDivStatePath + id
let defaultStateId = resolveDefaultStateId(context.expressionResolver)
let stateManager = context.stateManager
if let stateIdVariable {
let stateBinding = context.makeBinding(
variableName: stateIdVariable,
defaultValue: defaultStateId ?? states[0].stateId
)
stateManager.setState(stateBlockPath: divStatePath, stateBinding: stateBinding)
} else {
stateManager.resetBinding(for: divStatePath)
}
let stateManagerItem = stateManager.get(stateBlockPath: divStatePath)
let expressionResolver = context.expressionResolver
let activeState = states.first { $0.stateId == stateManagerItem?.currentStateID.rawValue }
?? states.first { $0.stateId == defaultStateId }
?? states[0]
let activeStateID = DivStateID(rawValue: activeState.stateId)
let activeStatePath = divStatePath + activeStateID
var previousBlock: Block?
var animationIn: [TransitioningAnimation]?
var animationOut: [TransitioningAnimation]?
let activeStateId = activeState.stateId
if let stateManagerItem,
let previousState = getPreviousState(stateManagerItem: stateManagerItem),
previousState.stateId != activeStateId,
let previousDiv = previousState.div {
context.triggersStorage?
.disableTriggers(path: context.path + previousState.stateId)
context.triggersStorage?.enableTriggers(path: context.path + activeStateId)
// state changed -> drop visibility cache for all children
if let parentPrefix = context.path.parent {
context.lastVisibleBoundsCache.dropVisibleBounds(prefix: parentPrefix)
}
previousBlock = try previousDiv.value.makeBlock(
context: context.makeContextForState(
id: id,
stateId: previousState.stateId
)
)
animationOut = previousState.animationOut?
.makeTransitioningAnimations(for: .disappearing, with: expressionResolver)
animationIn = activeState.animationIn?
.makeTransitioningAnimations(for: .appearing, with: expressionResolver)
}
if let div = activeState.div {
stateManager.updateBlockIdsWithStateChangeTransition(
statePath: activeStatePath,
div: div
)
}
let activeStateDiv = activeState.div?.value
let activeBlock = try activeStateDiv?.makeBlock(
context: context.makeContextForState(
id: id,
stateId: activeStateId
)
) ?? stub
// state is being applied, updating DivStateManager
stateManager.setState(stateBlockPath: divStatePath, stateID: activeStateID)
let defaultStateAlignment = BlockAlignment2D(
horizontal: .leading,
vertical: .leading
)
let stateAlignment = activeStateDiv?
.resolveAlignment(context, defaultAlignment: defaultStateAlignment)
?? defaultStateAlignment
let hasAnimation = animationOut != nil || animationIn != nil || activeState.animationOut != nil
let child = if hasAnimation {
LayeredBlock.Child(
content: TransitioningBlock(
from: previousBlock,
to: activeBlock,
animationOut: animationOut,
animationIn: animationIn
),
alignment: stateAlignment
)
} else {
LayeredBlock.Child(
content: activeBlock,
alignment: stateAlignment
)
}
return LayeredBlock(
widthTrait: resolveContentWidthTrait(context),
heightTrait: resolveContentHeightTrait(context),
children: [child]
).addingStateBlock(
ids: stateManager.getVisibleIds(statePath: activeStatePath)
)
}
private func addSwipeHandling(
to child: Block,
context: DivBlockModelingContext
) -> Block {
let swipeOutActions = states.compactMap(\.swipeOutActions).flatMap { $0 }
guard !swipeOutActions.isEmpty else { return child }
return SwipeContainerBlock(
child: child,
state: .default,
path: context.path,
swipeOutActions: swipeOutActions.uiActions(context: context)
)
}
private func getPreviousState(
stateManagerItem: DivStateManager.Item
) -> DivState.State? {
switch stateManagerItem.previousState {
case .empty:
nil
case .initial:
states[0]
case let .withID(stateId):
states.first(where: { $0.stateId == stateId.rawValue })
}
}
}
private let stub: Block = EmptyBlock.zeroSized
extension DivBlockModelingContext {
fileprivate func makeContextForState(
id: String,
stateId: String
) -> DivBlockModelingContext {
modifying(
pathSuffix: stateId,
parentDivStatePath: parentDivStatePath + id + stateId
)
}
}