Various improvements

This commit is contained in:
Ilya Laktyushin
2022-05-27 01:24:45 +04:00
parent 01d4131ce7
commit bdd7d0d9a2
5 changed files with 222 additions and 85 deletions
@@ -878,6 +878,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
public init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, action: @escaping () -> Void) {
super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, action: action), navigationBarAppearance: .none)
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationPresentation = .flatModal
}
@@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import AsyncDisplayKit
import ComponentFlow
import TelegramCore
@@ -66,7 +67,9 @@ final class ReactionsCarouselComponent: Component {
}
if isDisplaying && !self.isVisible {
self.node?.animateIn()
self.node?.setVisible(true)
} else if !isDisplaying && self.isVisible {
self.node?.setVisible(false)
}
self.isVisible = isDisplaying
@@ -85,6 +88,9 @@ final class ReactionsCarouselComponent: Component {
private let itemSize = CGSize(width: 110.0, height: 110.0)
//private let order = ["👌","😍","🤡","🕊","🥱","🥴"]
private let order = ["😍","👌","🥴","🥱","🕊","🤡"]
private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
private let context: AccountContext
private let theme: PresentationTheme
@@ -105,10 +111,27 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
private let positionDelta: Double
private var previousInteractionTimestamp: Double = 0.0
private var timer: SwiftSignalKit.Timer?
private var hasIdleAnimations = false
init(context: AccountContext, theme: PresentationTheme, reactions: [AvailableReactions.Reaction]) {
self.context = context
self.theme = theme
self.reactions = Array(reactions.shuffled().prefix(6))
var reactionMap: [String: AvailableReactions.Reaction] = [:]
for reaction in reactions {
reactionMap[reaction.value] = reaction
}
var sortedReactions: [AvailableReactions.Reaction] = []
for emoji in order {
if let reaction = reactionMap[emoji] {
sortedReactions.append(reaction)
}
}
self.reactions = sortedReactions
self.scrollNode = ASScrollNode()
self.tapNode = ASDisplayNode()
@@ -123,6 +146,10 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.setup()
}
deinit {
self.timer?.invalidate()
}
override func didLoad() {
super.didLoad()
@@ -134,6 +161,8 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
}
@objc private func reactionTapped(_ gestureRecognizer: UITapGestureRecognizer) {
self.previousInteractionTimestamp = CACurrentMediaTime()
guard self.animator == nil, self.scrollStartPosition == nil else {
return
}
@@ -143,11 +172,42 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
return
}
self.scrollTo(index, playReaction: true, duration: 0.4)
self.scrollTo(index, playReaction: true, immediately: true, duration: 0.4)
}
func setVisible(_ visible: Bool) {
if visible {
self.animateIn()
} else {
self.scrollTo(0, playReaction: false, immediately: false, duration: 0.0, clockwise: false)
self.timer?.invalidate()
self.timer = nil
self.playingIndices.removeAll()
self.standaloneReactionAnimation?.removeFromSupernode()
}
}
func animateIn() {
self.scrollTo(1, playReaction: true, duration: 0.5, clockwise: true)
self.scrollTo(1, playReaction: true, immediately: false, duration: 0.5, clockwise: true)
if self.timer == nil {
self.previousInteractionTimestamp = CACurrentMediaTime()
self.timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
if let strongSelf = self {
let currentTimestamp = CACurrentMediaTime()
if currentTimestamp > strongSelf.previousInteractionTimestamp + 5.0 {
var nextIndex = strongSelf.currentIndex - 1
if nextIndex < 0 {
nextIndex = strongSelf.reactions.count + nextIndex
}
strongSelf.scrollTo(nextIndex, playReaction: true, immediately: true, duration: 0.3, clockwise: true)
strongSelf.previousInteractionTimestamp = currentTimestamp
}
}
}, queue: Queue.mainQueue())
self.timer?.start()
}
}
func animateOut() {
@@ -156,7 +216,7 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
}
}
func scrollTo(_ index: Int, playReaction: Bool, duration: Double, clockwise: Bool? = nil) {
func scrollTo(_ index: Int, playReaction: Bool, immediately: Bool, duration: Double, clockwise: Bool? = nil) {
guard index >= 0 && index < self.itemNodes.count else {
return
}
@@ -184,25 +244,36 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
}
}
self.animator = DisplayLinkAnimator(duration: duration * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] t in
let t = listViewAnimationCurveSystem(t)
var updatedPosition = startPosition + change * t
while updatedPosition >= 1.0 {
updatedPosition -= 1.0
if immediately {
self.playReaction(index: index)
}
if duration.isZero {
self.currentPosition = newPosition
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
while updatedPosition < 0.0 {
updatedPosition += 1.0
}
self?.currentPosition = updatedPosition
if let size = self?.validLayout {
self?.updateLayout(size: size, transition: .immediate)
}
}, completion: { [weak self] in
self?.animator = nil
if playReaction {
self?.playReaction()
}
})
} else {
self.animator = DisplayLinkAnimator(duration: duration * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] t in
let t = listViewAnimationCurveSystem(t)
var updatedPosition = startPosition + change * t
while updatedPosition >= 1.0 {
updatedPosition -= 1.0
}
while updatedPosition < 0.0 {
updatedPosition += 1.0
}
self?.currentPosition = updatedPosition
if let size = self?.validLayout {
self?.updateLayout(size: size, transition: .immediate)
}
}, completion: { [weak self] in
self?.animator = nil
if playReaction && !immediately {
self?.playReaction(index: nil)
}
})
}
}
func setup() {
@@ -240,14 +311,19 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.ignoreContentOffsetChange = false
}
func playReaction() {
let delta = self.positionDelta
let index = max(0, Int(round(self.currentPosition / delta)) % self.itemNodes.count)
func playReaction(index: Int?) {
let index = index ?? max(0, Int(round(self.currentPosition / self.positionDelta)) % self.itemNodes.count)
guard !self.playingIndices.contains(index) else {
return
}
if let current = self.standaloneReactionAnimation, let dismiss = current.currentDismissAnimation {
dismiss()
current.currentDismissAnimation = nil
self.playingIndices.removeAll()
}
let reaction = self.reactions[index]
let targetContainerNode = self.itemContainerNodes[index]
let targetView = self.itemNodes[index].view
@@ -284,10 +360,13 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
forceSmallEffectAnimation: true,
targetView: targetView,
addStandaloneReactionAnimation: nil,
currentItemNode: self.itemNodes[index],
completion: { [weak standaloneReactionAnimation, weak self] in
standaloneReactionAnimation?.removeFromSupernode()
self?.standaloneReactionAnimation = nil
self?.playingIndices.remove(index)
if self?.standaloneReactionAnimation === standaloneReactionAnimation {
self?.standaloneReactionAnimation = nil
self?.playingIndices.remove(index)
}
}
)
}
@@ -301,6 +380,10 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
private let hapticFeedback = HapticFeedback()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.isTracking {
self.previousInteractionTimestamp = CACurrentMediaTime()
}
guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition) = self.scrollStartPosition else {
return
}
@@ -347,17 +430,21 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.previousInteractionTimestamp = CACurrentMediaTime()
self.resetScrollPosition()
let delta = self.positionDelta
let index = max(0, Int(round(self.currentPosition / delta)) % self.itemNodes.count)
self.scrollTo(index, playReaction: true, duration: 0.2)
self.scrollTo(index, playReaction: true, immediately: true, duration: 0.2)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.previousInteractionTimestamp = CACurrentMediaTime()
self.resetScrollPosition()
self.playReaction()
self.playReaction(index: nil)
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
@@ -936,7 +936,9 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
self.animateReactionSelection(context: context, theme: theme, reaction: reaction, avatarPeers: avatarPeers, playHaptic: playHaptic, isLarge: isLarge, forceSmallEffectAnimation: forceSmallEffectAnimation, targetView: targetView, addStandaloneReactionAnimation: addStandaloneReactionAnimation, currentItemNode: nil, completion: completion)
}
func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) {
public var currentDismissAnimation: (() -> Void)?
public func animateReactionSelection(context: AccountContext, theme: PresentationTheme, reaction: ReactionItem, avatarPeers: [EnginePeer], playHaptic: Bool, isLarge: Bool, forceSmallEffectAnimation: Bool = false, targetView: UIView, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, currentItemNode: ReactionNode?, completion: @escaping () -> Void) {
guard let sourceSnapshotView = targetView.snapshotContentTree() else {
completion()
return
@@ -955,12 +957,14 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
itemNode = ReactionNode(context: context, theme: theme, item: reaction)
}
self.itemNode = itemNode
if let targetView = targetView as? ReactionIconView, !isLarge {
self.itemNodeIsEmbedded = true
targetView.addSubnode(itemNode)
} else {
self.addSubnode(itemNode)
if !forceSmallEffectAnimation {
if let targetView = targetView as? ReactionIconView, !isLarge {
self.itemNodeIsEmbedded = true
targetView.addSubnode(itemNode)
} else {
self.addSubnode(itemNode)
}
}
itemNode.expandedAnimationDidBegin = { [weak self, weak targetView] in
@@ -975,7 +979,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
targetView.isHidden = true
}
}
itemNode.isExtracted = true
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
@@ -1077,7 +1081,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
completion()
}
}
var didBeginDismissAnimation = false
let beginDismissAnimation: () -> Void = { [weak self] in
if !didBeginDismissAnimation {
@@ -1089,62 +1093,91 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
return
}
if isLarge {
strongSelf.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: true, completion: {
if let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
let standaloneReactionAnimation = StandaloneReactionAnimation()
if forceSmallEffectAnimation {
additionalAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak additionalAnimationNode] _ in
additionalAnimationNode?.removeFromSupernode()
})
mainAnimationCompleted = true
intermediateCompletion()
} else {
if isLarge {
strongSelf.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: true, completion: {
if let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
let standaloneReactionAnimation = StandaloneReactionAnimation()
addStandaloneReactionAnimation(standaloneReactionAnimation)
standaloneReactionAnimation.animateReactionSelection(
context: itemNode.context,
theme: itemNode.context.sharedContext.currentPresentationData.with({ $0 }).theme,
reaction: itemNode.item,
avatarPeers: avatarPeers,
playHaptic: false,
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
addStandaloneReactionAnimation(standaloneReactionAnimation)
standaloneReactionAnimation.animateReactionSelection(
context: itemNode.context,
theme: itemNode.context.sharedContext.currentPresentationData.with({ $0 }).theme,
reaction: itemNode.item,
avatarPeers: avatarPeers,
playHaptic: false,
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
mainAnimationCompleted = true
intermediateCompletion()
})
} else {
if let targetView = strongSelf.targetView {
if let targetView = targetView as? ReactionIconView, !isLarge {
targetView.imageView.isHidden = false
} else {
targetView.alpha = 1.0
targetView.isHidden = false
}
}
if strongSelf.itemNodeIsEmbedded {
strongSelf.itemNode?.removeFromSupernode()
}
mainAnimationCompleted = true
intermediateCompletion()
})
} else {
if let targetView = strongSelf.targetView {
if let targetView = targetView as? ReactionIconView, !isLarge {
targetView.imageView.isHidden = false
} else {
targetView.alpha = 1.0
targetView.isHidden = false
}
}
if strongSelf.itemNodeIsEmbedded {
strongSelf.itemNode?.removeFromSupernode()
}
mainAnimationCompleted = true
intermediateCompletion()
}
}
}
self.currentDismissAnimation = beginDismissAnimation
let maybeBeginDismissAnimation: () -> Void = {
if mainAnimationCompleted && additionalAnimationCompleted {
beginDismissAnimation()
}
}
if forceSmallEffectAnimation {
itemNode.mainAnimationCompletion = {
mainAnimationCompleted = true
maybeBeginDismissAnimation()
}
}
additionalAnimationNode.completed = { _ in
additionalAnimationCompleted = true
intermediateCompletion()
beginDismissAnimation()
if forceSmallEffectAnimation {
maybeBeginDismissAnimation()
} else {
beginDismissAnimation()
}
}
additionalAnimationNode.visibility = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: {
beginDismissAnimation()
})
if !forceSmallEffectAnimation {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: {
beginDismissAnimation()
})
}
}
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
@@ -45,6 +45,7 @@ protocol ReactionItemNode: ASDisplayNode {
public final class ReactionNode: ASDisplayNode, ReactionItemNode {
let context: AccountContext
let item: ReactionItem
private let hasAppearAnimation: Bool
private var animateInAnimationNode: AnimatedStickerNode?
private let staticAnimationNode: AnimatedStickerNode
@@ -67,6 +68,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, hasAppearAnimation: Bool = true) {
self.context = context
self.item = item
self.hasAppearAnimation = hasAppearAnimation
self.staticAnimationNode = AnimatedStickerNode()
@@ -113,6 +115,8 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
}
}
public var mainAnimationCompletion: (() -> Void)?
public func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
let intrinsicSize = size
@@ -130,7 +134,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
let expandedAnimationFrame = animationFrame
if isExpanded, self.animationNode == nil {
if isExpanded && !self.hasAppearAnimation {
self.staticAnimationNode.play(fromIndex: 0)
} else if isExpanded, self.animationNode == nil {
let animationNode = AnimatedStickerNode()
animationNode.automaticallyLoadFirstFrame = true
self.animationNode = animationNode
@@ -143,6 +149,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
self?.expandedAnimationDidBegin?()
}
}
animationNode.completed = { [weak self] _ in
self?.mainAnimationCompletion?()
}
if largeExpanded {
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
@@ -274,7 +283,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
if self.animationNode == nil {
self.didSetupStillAnimation = true
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
if !self.hasAppearAnimation {
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
} else {
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
}
self.staticAnimationNode.position = animationFrame.center
self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
self.staticAnimationNode.updateLayout(size: animationFrame.size)
@@ -1044,11 +1044,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if !reaction.isEnabled {
continue
}
if reaction.isPremium && !hasPremium {
hasPremiumPlaceholder = true
continue
}
switch allowedReactions {
case let .set(set):
if !set.contains(reaction.value) {
@@ -1057,6 +1053,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .all:
break
}
if reaction.isPremium && !hasPremium {
hasPremiumPlaceholder = true
continue
}
actions.reactionItems.append(.reaction(ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,