mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Reaction improvements
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user