mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-01 16:06:59 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
74805bb045
@ -2,22 +2,7 @@ import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
public struct ReactionGestureItemValue {
|
||||
public var value: String
|
||||
public var text: String
|
||||
public var file: TelegramMediaFile
|
||||
|
||||
public init(value: String, text: String, file: TelegramMediaFile) {
|
||||
self.value = value
|
||||
self.text = text
|
||||
self.file = file
|
||||
}
|
||||
}
|
||||
|
||||
public final class ReactionGestureItem {
|
||||
public let value: ReactionGestureItemValue
|
||||
|
||||
public init(value: ReactionGestureItemValue) {
|
||||
self.value = value
|
||||
}
|
||||
public enum ReactionGestureItem {
|
||||
case reaction(value: String, text: String, file: TelegramMediaFile)
|
||||
case reply
|
||||
}
|
||||
|
@ -5,11 +5,7 @@ import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private let shadowBlur: CGFloat = 8.0
|
||||
private let minimizedReactionSize: CGFloat = 30.0
|
||||
private let maximizedReactionSize: CGFloat = 60.0
|
||||
|
||||
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat) -> UIImage? {
|
||||
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
|
||||
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(foreground.cgColor)
|
||||
@ -17,7 +13,7 @@ private func generateBubbleImage(foreground: UIColor, diameter: CGFloat) -> UIIm
|
||||
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0))
|
||||
}
|
||||
|
||||
private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat) -> UIImage? {
|
||||
private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
|
||||
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
@ -34,11 +30,12 @@ private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat) -> UI
|
||||
private final class ReactionNode: ASDisplayNode {
|
||||
let reaction: ReactionGestureItem
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let imageNode: ASImageNode
|
||||
var isMaximized: Bool?
|
||||
private let intrinsicSize: CGSize
|
||||
private let intrinsicOffset: CGPoint
|
||||
|
||||
init(account: Account, reaction: ReactionGestureItem) {
|
||||
init(account: Account, reaction: ReactionGestureItem, maximizedReactionSize: CGFloat) {
|
||||
self.reaction = reaction
|
||||
|
||||
self.animationNode = AnimatedStickerNode()
|
||||
@ -47,18 +44,29 @@ private final class ReactionNode: ASDisplayNode {
|
||||
//self.animationNode.backgroundColor = .lightGray
|
||||
|
||||
var intrinsicSize = CGSize(width: maximizedReactionSize + 18.0, height: maximizedReactionSize + 18.0)
|
||||
switch reaction.value.value {
|
||||
case "😳":
|
||||
intrinsicSize.width += 8.0
|
||||
intrinsicSize.height += 8.0
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: -4.0)
|
||||
case "👍":
|
||||
intrinsicSize.width += 20.0
|
||||
intrinsicSize.height += 20.0
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 4.0)
|
||||
default:
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
switch reaction {
|
||||
case let .reaction(value, _, file):
|
||||
switch value {
|
||||
case "😳":
|
||||
intrinsicSize.width += 8.0
|
||||
intrinsicSize.height += 8.0
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: -4.0)
|
||||
case "👍":
|
||||
intrinsicSize.width += 20.0
|
||||
intrinsicSize.height += 20.0
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 4.0)
|
||||
default:
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
}
|
||||
self.animationNode.visibility = true
|
||||
self.animationNode.setup(account: account, resource: file.resource, width: Int(intrinsicSize.width) * 2, height: Int(intrinsicSize.height) * 2, mode: .direct)
|
||||
case .reply:
|
||||
self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0)
|
||||
self.imageNode.image = UIImage(named: "Chat/Context Menu/ReactionReply", in: Bundle(for: ReactionNode.self), compatibleWith: nil)
|
||||
}
|
||||
|
||||
self.intrinsicSize = intrinsicSize
|
||||
|
||||
super.init()
|
||||
@ -66,15 +74,17 @@ private final class ReactionNode: ASDisplayNode {
|
||||
//self.backgroundColor = .green
|
||||
|
||||
self.addSubnode(self.animationNode)
|
||||
self.animationNode.visibility = true
|
||||
self.animationNode.setup(account: account, resource: reaction.value.file.resource, width: Int(self.intrinsicSize.width) * 2, height: Int(self.intrinsicSize.height) * 2, mode: .direct)
|
||||
self.addSubnode(self.imageNode)
|
||||
self.animationNode.updateLayout(size: self.intrinsicSize)
|
||||
self.animationNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize)
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, scale: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
transition.updatePosition(node: self.animationNode, position: CGPoint(x: size.width / 2.0 + self.intrinsicOffset.x * scale, y: size.height / 2.0 + self.intrinsicOffset.y * scale), beginWithCurrentState: true)
|
||||
transition.updateTransformScale(node: self.animationNode, scale: scale, beginWithCurrentState: true)
|
||||
transition.updatePosition(node: self.imageNode, position: CGPoint(x: size.width / 2.0 + self.intrinsicOffset.x * scale, y: size.height / 2.0 + self.intrinsicOffset.y * scale), beginWithCurrentState: true)
|
||||
transition.updateTransformScale(node: self.imageNode, scale: scale, beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
func updateIsAnimating(_ isAnimating: Bool, animated: Bool) {
|
||||
@ -87,43 +97,46 @@ private final class ReactionNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
final class ReactionSelectionNode: ASDisplayNode {
|
||||
private let account: Account
|
||||
private let reactions: [ReactionGestureItem]
|
||||
|
||||
private let backgroundNode: ASImageNode
|
||||
private let backgroundShadowNode: ASImageNode
|
||||
private let bubbleNodes: [(ASImageNode, ASImageNode)]
|
||||
private let reactionNodes: [ReactionNode]
|
||||
private var reactionNodes: [ReactionNode] = []
|
||||
private var hasSelectedNode = false
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var shadowBlur: CGFloat = 8.0
|
||||
private var minimizedReactionSize: CGFloat = 30.0
|
||||
private var maximizedReactionSize: CGFloat = 60.0
|
||||
private var smallCircleSize: CGFloat = 8.0
|
||||
|
||||
public init(account: Account, reactions: [ReactionGestureItem]) {
|
||||
self.account = account
|
||||
self.reactions = reactions
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: 42.0)
|
||||
|
||||
self.backgroundShadowNode = ASImageNode()
|
||||
self.backgroundShadowNode.displaysAsynchronously = false
|
||||
self.backgroundShadowNode.displayWithoutProcessing = true
|
||||
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: 42.0)
|
||||
|
||||
self.bubbleNodes = (0 ..< 2).map { i -> (ASImageNode, ASImageNode) in
|
||||
let imageNode = ASImageNode()
|
||||
imageNode.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * 8.0)
|
||||
imageNode.displaysAsynchronously = false
|
||||
imageNode.displayWithoutProcessing = true
|
||||
|
||||
let shadowNode = ASImageNode()
|
||||
shadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * 8.0)
|
||||
shadowNode.displaysAsynchronously = false
|
||||
shadowNode.displayWithoutProcessing = true
|
||||
|
||||
return (imageNode, shadowNode)
|
||||
}
|
||||
|
||||
self.reactionNodes = reactions.map { reaction -> ReactionNode in
|
||||
return ReactionNode(account: account, reaction: reaction)
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.bubbleNodes.forEach { _, shadow in
|
||||
@ -134,18 +147,50 @@ final class ReactionSelectionNode: ASDisplayNode {
|
||||
self.addSubnode(foreground)
|
||||
}
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.reactionNodes.forEach(self.addSubnode(_:))
|
||||
}
|
||||
|
||||
func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool) {
|
||||
let backgroundHeight: CGFloat = 42.0
|
||||
let reactionSpacing: CGFloat = 6.0
|
||||
let initialAnchorX = startingPoint.x
|
||||
|
||||
if isInitial && self.reactionNodes.isEmpty {
|
||||
//let contentWidth: CGFloat = CGFloat(self.reactionNodes.count - 1) * (minimizedReactionSize) + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * reactionSpacing
|
||||
|
||||
//contentWidth = CGFloat(self.reactionNodes.count - 1) * X + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * 0.2 * X
|
||||
// contentWidth - maximizedReactionSize = CGFloat(self.reactionNodes.count - 1) * X + CGFloat(self.reactionNodes.count + 1) * 0.2 * X
|
||||
// (contentWidth - maximizedReactionSize) / (CGFloat(self.reactionNodes.count - 1) + CGFloat(self.reactionNodes.count + 1) * 0.2) = X
|
||||
let availableContentWidth = max(100.0, initialAnchorX)
|
||||
var minimizedReactionSize = (availableContentWidth - self.maximizedReactionSize) / (CGFloat(self.reactions.count - 1) + CGFloat(self.reactions.count + 1) * 0.2)
|
||||
minimizedReactionSize = max(16.0, floor(minimizedReactionSize))
|
||||
minimizedReactionSize = min(30.0, minimizedReactionSize)
|
||||
|
||||
self.minimizedReactionSize = minimizedReactionSize
|
||||
self.shadowBlur = floor(minimizedReactionSize * 0.26)
|
||||
self.smallCircleSize = 8.0
|
||||
|
||||
let backgroundHeight = floor(minimizedReactionSize * 1.4)
|
||||
|
||||
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
|
||||
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
|
||||
for i in 0 ..< self.bubbleNodes.count {
|
||||
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
|
||||
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
|
||||
}
|
||||
|
||||
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
|
||||
return ReactionNode(account: self.account, reaction: reaction, maximizedReactionSize: self.maximizedReactionSize)
|
||||
}
|
||||
self.reactionNodes.forEach(self.addSubnode(_:))
|
||||
}
|
||||
|
||||
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.4)
|
||||
|
||||
let reactionSpacing: CGFloat = floor(self.minimizedReactionSize * 0.2)
|
||||
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
|
||||
|
||||
let contentWidth: CGFloat = CGFloat(self.reactionNodes.count - 1) * (minimizedReactionSize) + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * reactionSpacing
|
||||
|
||||
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
|
||||
backgroundFrame = backgroundFrame.offsetBy(dx: startingPoint.x - contentWidth + backgroundHeight / 2.0 - 52.0, dy: startingPoint.y - backgroundHeight - 16.0)
|
||||
backgroundFrame = backgroundFrame.offsetBy(dx: initialAnchorX - contentWidth + backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0)
|
||||
|
||||
self.backgroundNode.frame = backgroundFrame
|
||||
self.backgroundShadowNode.frame = backgroundFrame
|
||||
@ -201,11 +246,11 @@ final class ReactionSelectionNode: ASDisplayNode {
|
||||
reactionX += reactionSize + reactionSpacing
|
||||
}
|
||||
|
||||
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - 8.0 - shadowBlur, y: backgroundFrame.maxY - shadowBlur - 8.0 - shadowBlur), size: CGSize(width: 16.0 + shadowBlur * 2.0, height: 16.0 + shadowBlur * 2.0))
|
||||
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0))
|
||||
self.bubbleNodes[1].0.frame = mainBubbleFrame
|
||||
self.bubbleNodes[1].1.frame = mainBubbleFrame
|
||||
|
||||
let secondaryBubbleFrame = CGRect(origin: CGPoint(x: mainBubbleFrame.midX - 9.0 - (8.0 + shadowBlur * 2.0) / 2.0, y: mainBubbleFrame.midY + 12.0 - (8.0 + shadowBlur * 2.0) / 2.0), size: CGSize(width: 8.0 + shadowBlur * 2.0, height: 8.0 + shadowBlur * 2.0))
|
||||
let secondaryBubbleFrame = CGRect(origin: CGPoint(x: mainBubbleFrame.midX - floor(self.smallCircleSize * 0.88) - (self.smallCircleSize + shadowBlur * 2.0) / 2.0, y: mainBubbleFrame.midY + floor(self.smallCircleSize * 4.0 / 3.0) - (self.smallCircleSize + shadowBlur * 2.0) / 2.0), size: CGSize(width: self.smallCircleSize + shadowBlur * 2.0, height: self.smallCircleSize + shadowBlur * 2.0))
|
||||
self.bubbleNodes[0].0.frame = secondaryBubbleFrame
|
||||
self.bubbleNodes[0].1.frame = secondaryBubbleFrame
|
||||
}
|
||||
|
@ -6,13 +6,16 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
||||
private var validatedGesture = false
|
||||
|
||||
private var firstLocation: CGPoint = CGPoint()
|
||||
private var currentLocation: CGPoint = CGPoint()
|
||||
private var currentReactions: [ReactionGestureItem] = []
|
||||
private var isActivated = false
|
||||
private var isAwaitingCompletion = false
|
||||
private weak var currentContainer: ReactionSelectionParentNode?
|
||||
private var activationTimer: Timer?
|
||||
|
||||
public var availableReactions: (() -> [ReactionGestureItem])?
|
||||
public var getReactionContainer: (() -> ReactionSelectionParentNode?)?
|
||||
public var began: (() -> Void)?
|
||||
public var updateOffset: ((CGFloat, Bool) -> Void)?
|
||||
public var completed: ((ReactionGestureItem?) -> Void)?
|
||||
public var displayReply: ((CGFloat) -> Void)?
|
||||
@ -31,6 +34,8 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
||||
self.currentReactions = []
|
||||
self.isActivated = false
|
||||
self.isAwaitingCompletion = false
|
||||
self.activationTimer?.invalidate()
|
||||
self.activationTimer = nil
|
||||
}
|
||||
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
@ -40,6 +45,7 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
||||
self.currentReactions = availableReactions
|
||||
let touch = touches.first!
|
||||
self.firstLocation = touch.location(in: nil)
|
||||
self.currentLocation = self.firstLocation
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
@ -55,6 +61,7 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
||||
guard let location = touches.first?.location(in: nil) else {
|
||||
return
|
||||
}
|
||||
self.currentLocation = location
|
||||
|
||||
var translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
|
||||
|
||||
@ -72,8 +79,38 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
||||
self.validatedGesture = true
|
||||
self.firstLocation = location
|
||||
translation = CGPoint()
|
||||
self.began?()
|
||||
self.updateOffset?(0.0, false)
|
||||
updatedOffset = true
|
||||
|
||||
self.activationTimer?.invalidate()
|
||||
final class TimerTarget: NSObject {
|
||||
let f: () -> Void
|
||||
|
||||
init(_ f: @escaping () -> Void) {
|
||||
self.f = f
|
||||
}
|
||||
|
||||
@objc func event() {
|
||||
self.f()
|
||||
}
|
||||
}
|
||||
let activationTimer = Timer(timeInterval: 0.3, target: TimerTarget { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.activationTimer = nil
|
||||
if strongSelf.validatedGesture {
|
||||
let location = strongSelf.currentLocation
|
||||
if !strongSelf.currentReactions.isEmpty, let reactionContainer = strongSelf.getReactionContainer?() {
|
||||
strongSelf.currentContainer = reactionContainer
|
||||
let reactionContainerLocation = reactionContainer.view.convert(location, from: nil)
|
||||
reactionContainer.displayReactions(strongSelf.currentReactions, at: reactionContainerLocation)
|
||||
}
|
||||
}
|
||||
}, selector: #selector(TimerTarget.event), userInfo: nil, repeats: false)
|
||||
self.activationTimer = activationTimer
|
||||
RunLoop.main.add(activationTimer, forMode: .common)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,11 +122,6 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
||||
if absTranslationX > 40.0 {
|
||||
self.isActivated = true
|
||||
self.displayReply?(-min(0.0, translation.x))
|
||||
if !self.currentReactions.isEmpty, let reactionContainer = self.getReactionContainer?() {
|
||||
self.currentContainer = reactionContainer
|
||||
let reactionContainerLocation = reactionContainer.view.convert(location, from: nil)
|
||||
reactionContainer.displayReactions(self.currentReactions, at: reactionContainerLocation)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let reactionContainer = self.currentContainer {
|
||||
@ -111,8 +143,8 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
||||
if self.validatedGesture {
|
||||
let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
|
||||
if let reaction = self.currentContainer?.selectedReaction() {
|
||||
self.completed?(reaction)
|
||||
self.isAwaitingCompletion = true
|
||||
self.completed?(reaction)
|
||||
} else {
|
||||
if translation.x < -40.0 {
|
||||
self.currentContainer?.dismissReactions(into: nil, hideTarget: false)
|
||||
|
22
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionReply.imageset/Contents.json
vendored
Normal file
22
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionReply.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ReplyReaction@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ReplyReaction@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionReply.imageset/ReplyReaction@2x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionReply.imageset/ReplyReaction@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionReply.imageset/ReplyReaction@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ReactionReply.imageset/ReplyReaction@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -418,9 +418,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.controllerInteraction.canSetupReply(item.message) {
|
||||
//return []
|
||||
}
|
||||
|
||||
let reactions: [(String, String)] = [
|
||||
("😒", "Sad"),
|
||||
@ -433,14 +430,23 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
||||
var result: [ReactionGestureItem] = []
|
||||
for (value, text) in reactions {
|
||||
if let file = item.associatedData.animatedEmojiStickers[value]?.file {
|
||||
result.append(ReactionGestureItem(value: ReactionGestureItemValue(value: value, text: text, file: file)))
|
||||
result.append(.reaction(value: value, text: text, file: file))
|
||||
}
|
||||
}
|
||||
if item.controllerInteraction.canSetupReply(item.message) {
|
||||
result.append(.reply)
|
||||
}
|
||||
return result
|
||||
}
|
||||
reactionRecognizer.getReactionContainer = { [weak self] in
|
||||
return self?.item?.controllerInteraction.reactionContainerNode()
|
||||
}
|
||||
reactionRecognizer.began = { [weak self] in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.cancelInteractiveKeyboardGestures()
|
||||
}
|
||||
reactionRecognizer.updateOffset = { [weak self] offset, animated in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -479,11 +485,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
if strongSelf.swipeToReplyNode == nil {
|
||||
if strongSelf.swipeToReplyFeedback == nil {
|
||||
strongSelf.swipeToReplyFeedback = HapticFeedback()
|
||||
}
|
||||
strongSelf.swipeToReplyFeedback?.tap()
|
||||
if strongSelf.swipeToReplyFeedback == nil {
|
||||
strongSelf.swipeToReplyFeedback = HapticFeedback()
|
||||
}
|
||||
strongSelf.swipeToReplyFeedback?.tap()
|
||||
if strongSelf.swipeToReplyNode == nil, false {
|
||||
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonFillColor, wallpaper: item.presentationData.theme.wallpaper), strokeColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonStrokeColor, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper))
|
||||
strongSelf.swipeToReplyNode = swipeToReplyNode
|
||||
strongSelf.insertSubnode(swipeToReplyNode, belowSubnode: strongSelf.messageAccessibilityArea)
|
||||
@ -497,8 +503,28 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
||||
return
|
||||
}
|
||||
if let item = strongSelf.item, let reaction = reaction {
|
||||
strongSelf.awaitingAppliedReaction = reaction.value.value
|
||||
item.controllerInteraction.updateMessageReaction(item.message.id, reaction.value.value)
|
||||
switch reaction {
|
||||
case let .reaction(value, _, _):
|
||||
strongSelf.awaitingAppliedReaction = value
|
||||
item.controllerInteraction.updateMessageReaction(item.message.id, value)
|
||||
case .reply:
|
||||
strongSelf.reactionRecognizer?.complete(into: nil, hideTarget: false)
|
||||
var bounds = strongSelf.bounds
|
||||
let offset = bounds.origin.x
|
||||
bounds.origin.x = 0.0
|
||||
strongSelf.bounds = bounds
|
||||
if !offset.isZero {
|
||||
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
|
||||
strongSelf.swipeToReplyNode = nil
|
||||
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
|
||||
swipeToReplyNode?.removeFromSupernode()
|
||||
})
|
||||
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
}
|
||||
item.controllerInteraction.setupReply(item.message.id)
|
||||
}
|
||||
} else {
|
||||
strongSelf.reactionRecognizer?.complete(into: nil, hideTarget: false)
|
||||
var bounds = strongSelf.bounds
|
||||
|
Loading…
x
Reference in New Issue
Block a user