Reaction improvements

This commit is contained in:
Ali
2021-12-18 00:47:09 +04:00
parent b0d3aa2578
commit 95a492a25f
45 changed files with 1802 additions and 479 deletions

View File

@@ -503,6 +503,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
transition: ContainedViewLayoutTransition
) -> (size: CGSize, apparentHeight: CGFloat) {
let contentLayout = self.contentNode.update(
presentationData: presentationData,
constrainedWidth: constrainedSize.width,
maxHeight: constrainedSize.height,
bottomInset: 0.0,
@@ -555,18 +556,65 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> Co
final class ContextControllerActionsStackNode: ASDisplayNode {
final class NavigationContainer: ASDisplayNode {
var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
var requestPop: (() -> Void)?
var transitionFraction: CGFloat = 0.0
private var panRecognizer: UIPanGestureRecognizer?
var isNavigationEnabled: Bool = false {
didSet {
self.panRecognizer?.isEnabled = self.isNavigationEnabled
}
}
override init() {
super.init()
self.clipsToBounds = true
self.cornerRadius = 14.0
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
self.panRecognizer = panRecognizer
self.view.addGestureRecognizer(panRecognizer)
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
self.transitionFraction = 0.0
case .changed:
let distanceFactor: CGFloat = recognizer.translation(in: self.view).x / self.bounds.width
let transitionFraction = max(0.0, min(1.0, distanceFactor))
if self.transitionFraction != transitionFraction {
self.transitionFraction = transitionFraction
self.requestUpdate?(.immediate)
}
case .ended, .cancelled:
let distanceFactor: CGFloat = recognizer.translation(in: self.view).x / self.bounds.width
let transitionFraction = max(0.0, min(1.0, distanceFactor))
if transitionFraction > 0.2 {
self.transitionFraction = 0.0
self.requestPop?()
} else {
self.transitionFraction = 0.0
self.requestUpdate?(.animated(duration: 0.45, curve: .spring))
}
default:
break
}
}
func update(presentationData: PresentationData, size: CGSize, transition: ContainedViewLayoutTransition) {
}
}
final class ItemContainer: ASDisplayNode {
let requestUpdate: (ContainedViewLayoutTransition) -> Void
let node: ContextControllerActionsStackItemNode
let dimNode: ASDisplayNode
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem])?
var storedScrollingState: CGFloat?
let positionLock: CGFloat?
init(
@@ -586,18 +634,24 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
requestUpdateApparentHeight: requestUpdateApparentHeight
)
self.dimNode = ASDisplayNode()
self.dimNode.isUserInteractionEnabled = false
self.dimNode.alpha = 0.0
self.reactionItems = reactionItems
self.positionLock = positionLock
super.init()
self.addSubnode(self.node)
self.addSubnode(self.dimNode)
}
func update(
presentationData: PresentationData,
constrainedSize: CGSize,
standardWidth: CGFloat,
transitionFraction: CGFloat,
transition: ContainedViewLayoutTransition
) -> (size: CGSize, apparentHeight: CGFloat) {
let (size, apparentHeight) = self.node.update(
@@ -606,10 +660,24 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
standardWidth: standardWidth,
transition: transition
)
transition.updateFrame(node: self.node, frame: CGRect(origin: CGPoint(), size: size))
let maxScaleOffset: CGFloat = 10.0
let scaleOffset: CGFloat = 0.0 * transitionFraction + maxScaleOffset * (1.0 - transitionFraction)
let scale: CGFloat = (size.width - scaleOffset) / size.width
let yOffset: CGFloat = size.height * (1.0 - scale)
transition.updatePosition(node: self.node, position: CGPoint(x: size.width / 2.0 + scaleOffset / 2.0, y: size.height / 2.0 - yOffset / 2.0))
transition.updateBounds(node: self.node, bounds: CGRect(origin: CGPoint(), size: size))
transition.updateTransformScale(node: self.node, scale: scale)
return (size, apparentHeight)
}
func updateDimNode(presentationData: PresentationData, size: CGSize, transitionFraction: CGFloat, transition: ContainedViewLayoutTransition) {
self.dimNode.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateAlpha(node: self.dimNode, alpha: 1.0 - transitionFraction)
}
}
private let getController: () -> ContextControllerProtocol?
@@ -628,6 +696,10 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
return self.itemContainers.last?.positionLock
}
var storedScrollingState: CGFloat? {
return self.itemContainers.last?.storedScrollingState
}
init(
getController: @escaping () -> ContextControllerProtocol?,
requestDismiss: @escaping (ContextMenuActionResult) -> Void,
@@ -642,6 +714,20 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
super.init()
self.addSubnode(self.navigationContainer)
self.navigationContainer.requestUpdate = { [weak self] transition in
guard let strongSelf = self else {
return
}
strongSelf.requestUpdate(transition)
}
self.navigationContainer.requestPop = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.pop()
}
}
func replace(item: ContextControllerActionsStackItem, animated: Bool) {
@@ -653,11 +739,15 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
}
}
self.itemContainers.removeAll()
self.navigationContainer.isNavigationEnabled = self.itemContainers.count > 1
self.push(item: item, positionLock: nil, animated: animated)
self.push(item: item, currentScrollingState: nil, positionLock: nil, animated: animated)
}
func push(item: ContextControllerActionsStackItem, positionLock: CGFloat?, animated: Bool) {
func push(item: ContextControllerActionsStackItem, currentScrollingState: CGFloat?, positionLock: CGFloat?, animated: Bool) {
if let itemContainer = self.itemContainers.last {
itemContainer.storedScrollingState = currentScrollingState
}
let itemContainer = ItemContainer(
getController: self.getController,
requestDismiss: self.requestDismiss,
@@ -674,6 +764,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
)
self.itemContainers.append(itemContainer)
self.navigationContainer.addSubnode(itemContainer)
self.navigationContainer.isNavigationEnabled = self.itemContainers.count > 1
let transition: ContainedViewLayoutTransition
if animated {
@@ -684,6 +775,10 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
self.requestUpdate(transition)
}
func clearStoredScrollingState() {
self.itemContainers.last?.storedScrollingState = nil
}
func pop() {
if self.itemContainers.count == 1 {
//dismiss
@@ -693,6 +788,8 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
self.dismissingItemContainers.append((itemContainer, true))
}
self.navigationContainer.isNavigationEnabled = self.itemContainers.count > 1
let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring)
self.requestUpdate(transition)
}
@@ -706,8 +803,17 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
let animateAppearingContainers = transition.isAnimated && !self.dismissingItemContainers.isEmpty
struct ItemLayout {
var size: CGSize
var apparentHeight: CGFloat
var transitionFraction: CGFloat
var alphaTransitionFraction: CGFloat
var itemTransition: ContainedViewLayoutTransition
var animateAppearingContainer: Bool
}
var topItemSize = CGSize()
var topItemApparentHeight: CGFloat = 0.0
var itemLayouts: [ItemLayout] = []
for i in 0 ..< self.itemContainers.count {
let itemContainer = self.itemContainers[i]
@@ -720,31 +826,77 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
let itemConstrainedHeight: CGFloat = constrainedSize.height
let transitionFraction: CGFloat
let alphaTransitionFraction: CGFloat
if i == self.itemContainers.count - 1 {
transitionFraction = self.navigationContainer.transitionFraction
alphaTransitionFraction = 1.0
} else if i == self.itemContainers.count - 2 {
transitionFraction = self.navigationContainer.transitionFraction - 1.0
alphaTransitionFraction = self.navigationContainer.transitionFraction
} else {
transitionFraction = 0.0
alphaTransitionFraction = 0.0
}
let itemSize = itemContainer.update(
presentationData: presentationData,
constrainedSize: CGSize(width: constrainedSize.width, height: itemConstrainedHeight),
standardWidth: 260.0,
standardWidth: 250.0,
transitionFraction: alphaTransitionFraction,
transition: itemContainerTransition
)
if i == self.itemContainers.count - 1 {
topItemSize = itemSize.size
topItemApparentHeight = itemSize.apparentHeight
}
let itemFrame: CGRect
if i == self.itemContainers.count - 1 {
itemFrame = CGRect(origin: CGPoint(), size: itemSize.size)
} else {
itemFrame = CGRect(origin: CGPoint(x: -itemSize.size.width, y: 0.0), size: itemSize.size)
}
itemContainerTransition.updateFrame(node: itemContainer, frame: itemFrame)
if animateAppearingContainer {
transition.animatePositionAdditive(node: itemContainer, offset: CGPoint(x: itemContainer.bounds.width, y: 0.0))
}
itemLayouts.append(ItemLayout(
size: itemSize.size,
apparentHeight: itemSize.apparentHeight,
transitionFraction: transitionFraction,
alphaTransitionFraction: alphaTransitionFraction,
itemTransition: itemContainerTransition,
animateAppearingContainer: animateAppearingContainer
))
}
transition.updateFrame(node: self.navigationContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: topItemSize.width, height: max(44.0, topItemApparentHeight))))
let topItemApparentHeight: CGFloat
let topItemWidth: CGFloat
if itemLayouts.isEmpty {
topItemApparentHeight = 0.0
topItemWidth = 0.0
} else if itemLayouts.count == 1 {
topItemApparentHeight = itemLayouts[0].apparentHeight
topItemWidth = itemLayouts[0].size.width
} else {
let lastItemLayout = itemLayouts[itemLayouts.count - 1]
let previousItemLayout = itemLayouts[itemLayouts.count - 2]
let transitionFraction = self.navigationContainer.transitionFraction
topItemApparentHeight = lastItemLayout.apparentHeight * (1.0 - transitionFraction) + previousItemLayout.apparentHeight * transitionFraction
topItemWidth = lastItemLayout.size.width * (1.0 - transitionFraction) + previousItemLayout.size.width * transitionFraction
}
let navigationContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: topItemWidth, height: max(14 * 2.0, topItemApparentHeight)))
transition.updateFrame(node: self.navigationContainer, frame: navigationContainerFrame)
self.navigationContainer.update(presentationData: presentationData, size: navigationContainerFrame.size, transition: transition)
for i in 0 ..< self.itemContainers.count {
let xOffset: CGFloat
if itemLayouts[i].transitionFraction < 0.0 {
xOffset = itemLayouts[i].transitionFraction * itemLayouts[i].size.width
} else {
xOffset = itemLayouts[i].transitionFraction * topItemWidth
}
let itemFrame = CGRect(origin: CGPoint(x: xOffset, y: 0.0), size: itemLayouts[i].size)
itemLayouts[i].itemTransition.updateFrame(node: self.itemContainers[i], frame: itemFrame)
if itemLayouts[i].animateAppearingContainer {
transition.animatePositionAdditive(node: self.itemContainers[i], offset: CGPoint(x: itemFrame.width, y: 0.0))
}
self.itemContainers[i].updateDimNode(presentationData: presentationData, size: CGSize(width: itemLayouts[i].size.width, height: navigationContainerFrame.size.height), transitionFraction: itemLayouts[i].alphaTransitionFraction, transition: transition)
}
for (itemContainer, isPopped) in self.dismissingItemContainers {
transition.updatePosition(node: itemContainer, position: CGPoint(x: isPopped ? itemContainer.bounds.width * 3.0 / 2.0 : -itemContainer.bounds.width / 2.0, y: itemContainer.position.y), completion: { [weak itemContainer] _ in
@@ -753,6 +905,6 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
}
self.dismissingItemContainers.removeAll()
return CGSize(width: topItemSize.width, height: topItemSize.height)
return CGSize(width: topItemWidth, height: topItemSize.height)
}
}