766 lines
37 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import AnimationCache
import MultiAnimationRenderer
import EntityKeyboard
import ComponentDisplayAdapters
import TelegramPresentationData
import AccountContext
import PagerComponent
import Postbox
import TelegramCore
import Lottie
import EmojiTextAttachmentView
import TextFormat
import AppBundle
public final class EmojiStatusSelectionComponent: Component {
public typealias EnvironmentType = Empty
public let theme: PresentationTheme
public let strings: PresentationStrings
public let deviceMetrics: DeviceMetrics
public let emojiContent: EmojiPagerContentComponent
public let backgroundColor: UIColor
public let separatorColor: UIColor
public init(
theme: PresentationTheme,
strings: PresentationStrings,
deviceMetrics: DeviceMetrics,
emojiContent: EmojiPagerContentComponent,
backgroundColor: UIColor,
separatorColor: UIColor
) {
self.theme = theme
self.strings = strings
self.deviceMetrics = deviceMetrics
self.emojiContent = emojiContent
self.backgroundColor = backgroundColor
self.separatorColor = separatorColor
}
public static func ==(lhs: EmojiStatusSelectionComponent, rhs: EmojiStatusSelectionComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings != rhs.strings {
return false
}
if lhs.deviceMetrics != rhs.deviceMetrics {
return false
}
if lhs.emojiContent != rhs.emojiContent {
return false
}
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.separatorColor != rhs.separatorColor {
return false
}
return true
}
public final class View: UIView {
private let keyboardView: ComponentView<Empty>
private let keyboardClippingView: UIView
private let panelHostView: PagerExternalTopPanelContainer
private let panelBackgroundView: BlurredBackgroundView
private let panelSeparatorView: UIView
private var component: EmojiStatusSelectionComponent?
override init(frame: CGRect) {
self.keyboardView = ComponentView<Empty>()
self.keyboardClippingView = UIView()
self.panelHostView = PagerExternalTopPanelContainer()
self.panelBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
self.panelSeparatorView = UIView()
super.init(frame: frame)
self.addSubview(self.keyboardClippingView)
self.addSubview(self.panelBackgroundView)
self.addSubview(self.panelSeparatorView)
self.addSubview(self.panelHostView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: EmojiStatusSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.backgroundColor = component.backgroundColor
let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85)
self.panelBackgroundView.updateColor(color: panelBackgroundColor, transition: .immediate)
self.panelSeparatorView.backgroundColor = component.separatorColor
self.component = component
let topPanelHeight: CGFloat = 42.0
let keyboardSize = self.keyboardView.update(
transition: transition,
component: AnyComponent(EntityKeyboardComponent(
theme: component.theme,
strings: component.strings,
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: 0.0, right: 0.0),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
emojiContent: component.emojiContent,
stickerContent: nil,
gifContent: nil,
hasRecentGifs: false,
availableGifSearchEmojies: [],
defaultToEmojiTab: true,
externalTopPanelContainer: self.panelHostView,
topPanelExtensionUpdated: { _, _ in },
hideInputUpdated: { _, _, _ in },
switchToTextInput: {},
switchToGifSubject: { _ in },
makeSearchContainerNode: { _ in return nil },
deviceMetrics: component.deviceMetrics,
hiddenInputHeight: 0.0,
displayBottomPanel: false,
isExpanded: false
)),
environment: {},
containerSize: availableSize
)
if let keyboardComponentView = self.keyboardView.view {
if keyboardComponentView.superview == nil {
self.keyboardClippingView.addSubview(keyboardComponentView)
}
if panelBackgroundColor.alpha < 0.01 {
self.keyboardClippingView.clipsToBounds = true
} else {
self.keyboardClippingView.clipsToBounds = false
}
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight)))
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight), size: keyboardSize))
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight)))
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
}
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class EmojiStatusSelectionController: ViewController {
private final class Node: ViewControllerTracingNode {
private weak var controller: EmojiStatusSelectionController?
private let context: AccountContext
private weak var sourceView: UIView?
private var globalSourceRect: CGRect?
private let componentHost: ComponentView<Empty>
private let componentShadowLayer: SimpleLayer
private let cloudLayer0: SimpleLayer
private let cloudShadowLayer0: SimpleLayer
private let cloudLayer1: SimpleLayer
private let cloudShadowLayer1: SimpleLayer
private var presentationData: PresentationData
private var validLayout: ContainerViewLayout?
private var emojiContentDisposable: Disposable?
private var emojiContent: EmojiPagerContentComponent?
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
private var availableReactions: AvailableReactions?
private var availableReactionsDisposable: Disposable?
private var hapticFeedback: HapticFeedback?
private var isDismissed: Bool = false
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>) {
self.controller = controller
self.context = context
if let sourceView = sourceView {
self.globalSourceRect = sourceView.convert(sourceView.bounds, to: nil)
}
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.componentHost = ComponentView<Empty>()
self.componentShadowLayer = SimpleLayer()
self.componentShadowLayer.shadowOpacity = 0.12
self.componentShadowLayer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.componentShadowLayer.shadowRadius = 16.0
self.cloudLayer0 = SimpleLayer()
self.cloudShadowLayer0 = SimpleLayer()
self.cloudShadowLayer0.shadowOpacity = 0.12
self.cloudShadowLayer0.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
self.cloudShadowLayer0.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.cloudShadowLayer0.shadowRadius = 16.0
self.cloudLayer1 = SimpleLayer()
self.cloudShadowLayer1 = SimpleLayer()
self.cloudShadowLayer1.shadowOpacity = 0.12
self.cloudShadowLayer1.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
self.cloudShadowLayer1.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.cloudShadowLayer1.shadowRadius = 16.0
super.init()
self.layer.addSublayer(self.componentShadowLayer)
self.layer.addSublayer(self.cloudShadowLayer0)
self.layer.addSublayer(self.cloudShadowLayer1)
self.layer.addSublayer(self.cloudLayer0)
self.layer.addSublayer(self.cloudLayer1)
self.emojiContentDisposable = (emojiContent
|> deliverOnMainQueue).start(next: { [weak self] emojiContent in
guard let strongSelf = self else {
return
}
strongSelf.controller?._ready.set(.single(true))
strongSelf.emojiContent = emojiContent
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { groupId, item, _, _, _, _ in
guard let strongSelf = self else {
return
}
strongSelf.applyItem(groupId: groupId, item: item)
},
deleteBackwards: {
},
openStickerSettings: {
},
openFeatured: {
},
addGroupAction: { groupId, isPremiumLocked in
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
return
}
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
let _ = (strongSelf.context.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { views in
guard let strongSelf = self, let view = views.views[viewKey] as? OrderedItemListView else {
return
}
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId {
if let strongSelf = self {
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId))
}
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
break
}
}
})
},
clearGroup: { groupId in
},
pushController: { c in
},
presentController: { c in
},
presentGlobalOverlayController: { c in
},
navigationController: {
return nil
},
sendSticker: nil,
chatPeerId: nil,
peekBehavior: nil,
customLayout: nil,
externalBackground: nil
)
strongSelf.refreshLayout(transition: .immediate)
})
self.availableReactionsDisposable = (context.engine.stickers.availableReactions()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] availableReactions in
guard let strongSelf = self else {
return
}
strongSelf.availableReactions = availableReactions
})
}
deinit {
self.emojiContentDisposable?.dispose()
self.availableReactionsDisposable?.dispose()
}
private func refreshLayout(transition: Transition) {
guard let layout = self.validLayout else {
return
}
self.containerLayoutUpdated(layout: layout, transition: transition)
}
func animateOut(completion: @escaping () -> Void) {
self.componentShadowLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
self.componentHost.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
completion()
})
self.cloudLayer0.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
self.cloudShadowLayer0.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
self.cloudLayer1.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
self.cloudShadowLayer1.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
}
func animateOutToStatus(groupId: AnyHashable, item: EmojiPagerContentComponent.Item, destinationView: UIView) {
guard let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem( groupId: groupId, item: item) else {
self.controller?.dismiss()
return
}
self.isUserInteractionEnabled = false
let hapticFeedback: HapticFeedback
if let current = self.hapticFeedback {
hapticFeedback = current
} else {
hapticFeedback = HapticFeedback()
self.hapticFeedback = hapticFeedback
}
hapticFeedback.prepareTap()
var itemCompleted = false
var contentCompleted = false
var effectCompleted = false
let completion: () -> Void = { [weak self] in
guard let strongSelf = self, itemCompleted, contentCompleted, effectCompleted else {
return
}
strongSelf.controller?.dismissNow()
}
var effectView: AnimationView?
if let itemFile = item.itemFile, let url = getAppBundle().url(forResource: "generic_reaction_small_effect", withExtension: "json"), let composition = Animation.filepath(url.path) {
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
view.animationSpeed = 1.0
view.backgroundColor = nil
view.isOpaque = false
let animationCache = self.context.animationCache
let animationRenderer = self.context.animationRenderer
for i in 1 ... 7 {
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
for animationLayer in allLayers {
let baseItemLayer = InlineStickerItemLayer(
context: self.context,
attemptSynchronousLoad: false,
emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: itemFile.fileId.id, file: itemFile),
file: item.itemFile,
cache: animationCache,
renderer: animationRenderer,
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
pointSize: CGSize(width: 32.0, height: 32.0)
)
if let sublayers = animationLayer.sublayers {
for sublayer in sublayers {
sublayer.isHidden = true
}
}
baseItemLayer.isVisibleForAnimations = true
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
animationLayer.addSublayer(baseItemLayer)
}
}
effectView = view
}
if let sourceCopyLayer = sourceLayer.snapshotContentTree() {
self.layer.addSublayer(sourceCopyLayer)
sourceCopyLayer.frame = sourceLayer.convert(sourceLayer.bounds, to: self.layer)
sourceLayer.isHidden = true
destinationView.isHidden = true
let previousSourceCopyFrame = sourceCopyLayer.frame
let localDestinationFrame = destinationView.convert(destinationView.bounds, to: self.view)
let destinationSize = max(localDestinationFrame.width, localDestinationFrame.height)
let effectFrame = localDestinationFrame.insetBy(dx: -destinationSize * 2.0, dy: -destinationSize * 2.0)
let destinationNormalScale = localDestinationFrame.width / previousSourceCopyFrame.width
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
sourceCopyLayer.position = localDestinationFrame.center
transition.animatePositionWithKeyframes(layer: sourceCopyLayer, keyframes: generateParabollicMotionKeyframes(from: previousSourceCopyFrame.center, to: localDestinationFrame.center, elevation: -(localDestinationFrame.center.y - previousSourceCopyFrame.center.y) + 30.0), completion: { [weak self, weak sourceCopyLayer, weak destinationView] _ in
guard let strongSelf = self else {
return
}
itemCompleted = true
sourceCopyLayer?.isHidden = true
if let destinationView = destinationView {
destinationView.isHidden = false
destinationView.layer.animateScale(from: 0.3, to: 1.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
hapticFeedback.tap()
if let effectView = effectView {
effectView.frame = effectFrame
strongSelf.view.addSubview(effectView)
effectView.play(completion: { _ in
effectCompleted = true
completion()
})
} else {
effectCompleted = true
}
completion()
})
let scaleKeyframes: [CGFloat] = [
1.0,
1.4,
1.0,
destinationNormalScale * 0.3
]
sourceCopyLayer.transform = CATransform3DMakeScale(scaleKeyframes[scaleKeyframes.count - 1], scaleKeyframes[scaleKeyframes.count - 1], 1.0)
sourceCopyLayer.animateKeyframes(values: scaleKeyframes.map({ $0 as NSNumber }), duration: 0.2, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.linear.rawValue)
} else {
itemCompleted = true
}
/*if let availableReactions = self.availableReactions, let availableReaction = availableReactions.reactions.first(where: { $0.value == }) {
} else {
effectCompleted = true
}*/
self.animateOut(completion: {
contentCompleted = true
completion()
})
}
func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) {
self.validLayout = layout
var transition = transition
guard let emojiContent = self.emojiContent else {
return
}
let listBackgroundColor: UIColor
let separatorColor: UIColor
if self.presentationData.theme.overallDarkAppearance {
listBackgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor
separatorColor = self.presentationData.theme.list.itemBlocksSeparatorColor
self.componentShadowLayer.shadowOpacity = 0.32
self.cloudShadowLayer0.shadowOpacity = 0.32
self.cloudShadowLayer1.shadowOpacity = 0.32
} else {
listBackgroundColor = self.presentationData.theme.list.plainBackgroundColor
separatorColor = self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
self.componentShadowLayer.shadowOpacity = 0.12
self.cloudShadowLayer0.shadowOpacity = 0.12
self.cloudShadowLayer1.shadowOpacity = 0.12
}
self.cloudLayer0.backgroundColor = listBackgroundColor.cgColor
self.cloudLayer1.backgroundColor = listBackgroundColor.cgColor
let sideInset: CGFloat = 16.0
if let scheduledEmojiContentAnimationHint = self.scheduledEmojiContentAnimationHint {
self.scheduledEmojiContentAnimationHint = nil
let contentAnimation = scheduledEmojiContentAnimationHint
transition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
}
let componentSize = self.componentHost.update(
transition: transition,
component: AnyComponent(EmojiStatusSelectionComponent(
theme: self.presentationData.theme,
strings: self.presentationData.strings,
deviceMetrics: layout.deviceMetrics,
emojiContent: emojiContent,
backgroundColor: listBackgroundColor,
separatorColor: separatorColor
)),
environment: {},
containerSize: CGSize(width: layout.size.width - sideInset * 2.0, height: min(308.0, layout.size.height))
)
if let componentView = self.componentHost.view {
var animateIn = false
if componentView.superview == nil {
self.view.addSubview(componentView)
animateIn = true
componentView.clipsToBounds = true
componentView.layer.cornerRadius = 24.0
}
let sourceOrigin: CGPoint
if let sourceView = self.sourceView {
let sourceRect = sourceView.convert(sourceView.bounds, to: self.view)
sourceOrigin = CGPoint(x: sourceRect.midX, y: sourceRect.maxY)
} else if let globalSourceRect = self.globalSourceRect {
let sourceRect = self.view.convert(globalSourceRect, from: nil)
sourceOrigin = CGPoint(x: sourceRect.midX, y: sourceRect.maxY)
} else {
sourceOrigin = CGPoint(x: layout.size.width / 2.0, y: floor(layout.size.height / 2.0 - componentSize.height))
}
let componentFrame = CGRect(origin: CGPoint(x: sideInset, y: sourceOrigin.y + 5.0), size: componentSize)
if self.componentShadowLayer.bounds.size != componentFrame.size {
let componentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: componentFrame.size), cornerRadius: 24.0).cgPath
self.componentShadowLayer.shadowPath = componentShadowPath
}
transition.setFrame(layer: self.componentShadowLayer, frame: componentFrame)
let cloudOffset0: CGFloat = 30.0
let cloudSize0: CGFloat = 16.0
var cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.minY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0))
var invertX = false
if cloudFrame0.maxX >= layout.size.width - layout.safeInsets.right - 32.0 {
cloudFrame0.origin.x = floor(sourceOrigin.x - cloudSize0 - cloudOffset0 + cloudSize0 / 2.0)
invertX = true
}
transition.setFrame(layer: self.cloudLayer0, frame: cloudFrame0)
if self.cloudShadowLayer0.bounds.size != cloudFrame0.size {
let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame0.size), cornerRadius: 24.0).cgPath
self.cloudShadowLayer0.shadowPath = cloudShadowPath
}
transition.setFrame(layer: self.cloudShadowLayer0, frame: cloudFrame0)
transition.setCornerRadius(layer: self.cloudLayer0, cornerRadius: cloudFrame0.width / 2.0)
let cloudOffset1 = CGPoint(x: -9.0, y: -14.0)
let cloudSize1: CGFloat = 8.0
var cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY + cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1))
if invertX {
cloudFrame1.origin.x = floor(cloudFrame0.midX - cloudSize1 - cloudOffset1.x + cloudSize1 / 2.0)
}
transition.setFrame(layer: self.cloudLayer1, frame: cloudFrame1)
if self.cloudShadowLayer1.bounds.size != cloudFrame1.size {
let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame1.size), cornerRadius: 24.0).cgPath
self.cloudShadowLayer1.shadowPath = cloudShadowPath
}
transition.setFrame(layer: self.cloudShadowLayer1, frame: cloudFrame1)
transition.setCornerRadius(layer: self.cloudLayer1, cornerRadius: cloudFrame1.width / 2.0)
transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
if animateIn {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in
self?.allowsGroupOpacity = false
})
let contentDuration: Double = 0.3
let contentDelay: Double = 0.14
let initialContentFrame = CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0))
if let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View {
emojiView.animateIn(fromLocation: self.view.convert(initialContentFrame.center, to: emojiView))
}
componentView.layer.animatePosition(from: initialContentFrame.center, to: componentFrame.center, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
componentView.layer.animateBounds(from: CGRect(origin: CGPoint(x: -(componentFrame.minX - initialContentFrame.minX), y: -(componentFrame.minY - initialContentFrame.minY)), size: initialContentFrame.size), to: CGRect(origin: CGPoint(), size: componentFrame.size), duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
self.componentShadowLayer.animateFrame(from: CGRect(origin: CGPoint(x: cloudFrame0.midX - 24.0, y: componentFrame.minY), size: CGSize(width: 24.0 * 2.0, height: 24.0 * 2.0)), to: componentView.frame, duration: contentDuration, delay: contentDelay, timingFunction: kCAMediaTimingFunctionSpring)
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
self.componentShadowLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: contentDelay)
let initialComponentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: initialContentFrame.size), cornerRadius: 24.0).cgPath
self.componentShadowLayer.animate(from: initialComponentShadowPath, to: self.componentShadowLayer.shadowPath!, keyPath: "shadowPath", timingFunction: kCAMediaTimingFunctionSpring, duration: contentDuration, delay: contentDelay)
self.cloudLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, delay: 0.05, timingFunction: kCAMediaTimingFunctionSpring)
self.cloudShadowLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, delay: 0.05, timingFunction: kCAMediaTimingFunctionSpring)
self.cloudLayer1.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
self.cloudShadowLayer1.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = super.hitTest(point, with: event) {
if self.isDismissed {
return self.view
}
if result === self.view {
self.isDismissed = true
self.controller?.dismiss()
}
return result
}
return nil
}
private func applyItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item?) {
guard let controller = self.controller else {
return
}
switch controller.mode {
case .statusSelection:
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|> deliverOnMainQueue).start()
case let .quickReactionSelection(completion):
if let item = item, let itemFile = item.itemFile {
var selectedReaction: MessageReaction.Reaction?
if let availableReactions = self.availableReactions {
for reaction in availableReactions.reactions {
if reaction.selectAnimation.fileId == itemFile.fileId {
selectedReaction = reaction.value
break
}
}
}
if selectedReaction == nil {
selectedReaction = .custom(itemFile.fileId.id)
}
if let selectedReaction = selectedReaction {
let _ = context.engine.stickers.updateQuickReaction(reaction: selectedReaction).start()
}
}
completion()
}
if let item = item, let destinationView = controller.destinationItemView() {
self.animateOutToStatus(groupId: groupId, item: item, destinationView: destinationView)
} else {
controller.dismiss()
}
}
}
public enum Mode {
case statusSelection
case quickReactionSelection(completion: () -> Void)
}
private let context: AccountContext
private weak var sourceView: UIView?
private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
private let mode: Mode
private let destinationItemView: () -> UIView?
fileprivate let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
public init(context: AccountContext, mode: Mode, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, destinationItemView: @escaping () -> UIView?) {
self.context = context
self.mode = mode
self.sourceView = sourceView
self.emojiContent = emojiContent
self.destinationItemView = destinationItemView
super.init(navigationBarPresentationData: nil)
self.lockOrientation = true
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder: NSCoder) {
preconditionFailure()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
private func dismissNow() {
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
override public func dismiss(completion: (() -> Void)? = nil) {
(self.displayNode as! Node).animateOut(completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})
}
override public func loadDisplayNode() {
self.displayNode = Node(controller: self, context: self.context, sourceView: self.sourceView, emojiContent: self.emojiContent)
super.displayNodeDidLoad()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
}
}
private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosition: CGPoint, elevation: CGFloat) -> [CGPoint] {
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - elevation)
let x1 = sourcePoint.x
let y1 = sourcePoint.y
let x2 = midPoint.x
let y2 = midPoint.y
let x3 = targetPosition.x
let y3 = targetPosition.y
var keyframes: [CGPoint] = []
if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 {
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = sourcePoint.y * (1.0 - k) + targetPosition.y * k
keyframes.append(CGPoint(x: x, y: y))
}
} else {
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = a * x * x + b * x + c
keyframes.append(CGPoint(x: x, y: y))
}
}
return keyframes
}