mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 21:16:35 +00:00
Improve reaction selection UI
This commit is contained in:
parent
46d7c6ac38
commit
9d3c7d45dd
@ -920,7 +920,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOutToReaction(value: String, into targetNode: ASImageNode, hideNode: Bool, completion: @escaping () -> Void) {
|
func animateOutToReaction(value: String, into targetNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) {
|
||||||
guard let reactionContextNode = self.reactionContextNode else {
|
guard let reactionContextNode = self.reactionContextNode else {
|
||||||
self.animateOut(result: .default, completion: completion)
|
self.animateOut(result: .default, completion: completion)
|
||||||
return
|
return
|
||||||
@ -1481,7 +1481,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||||||
self.dismiss(result: .default, completion: completion)
|
self.dismiss(result: .default, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dismissWithReaction(value: String, into targetNode: ASImageNode, hideNode: Bool, completion: (() -> Void)?) {
|
public func dismissWithReaction(value: String, into targetNode: ASDisplayNode, hideNode: Bool, completion: (() -> Void)?) {
|
||||||
if !self.wasDismissed {
|
if !self.wasDismissed {
|
||||||
self.wasDismissed = true
|
self.wasDismissed = true
|
||||||
self.controllerNode.animateOutToReaction(value: value, into: targetNode, hideNode: hideNode, completion: { [weak self] in
|
self.controllerNode.animateOutToReaction(value: value, into: targetNode, hideNode: hideNode, completion: { [weak self] in
|
||||||
|
|||||||
@ -71,7 +71,7 @@ public extension CALayer {
|
|||||||
animation.isAdditive = additive
|
animation.isAdditive = additive
|
||||||
|
|
||||||
if !delay.isZero {
|
if !delay.isZero {
|
||||||
animation.beginTime = CACurrentMediaTime() + delay
|
animation.beginTime = CACurrentMediaTime() + delay * UIView.animationDurationFactor()
|
||||||
animation.fillMode = .both
|
animation.fillMode = .both
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ public extension CALayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !delay.isZero {
|
if !delay.isZero {
|
||||||
animation.beginTime = CACurrentMediaTime() + delay
|
animation.beginTime = CACurrentMediaTime() + delay * UIView.animationDurationFactor()
|
||||||
animation.fillMode = .both
|
animation.fillMode = .both
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ public extension CALayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !delay.isZero {
|
if !delay.isZero {
|
||||||
animation.beginTime = CACurrentMediaTime() + delay
|
animation.beginTime = CACurrentMediaTime() + delay * UIView.animationDurationFactor()
|
||||||
animation.fillMode = .both
|
animation.fillMode = .both
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -377,14 +377,12 @@ public final class ReactionContextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateOutToReaction(value: String, targetNode: ASImageNode, hideNode: Bool, completion: @escaping () -> Void) {
|
public func animateOutToReaction(value: String, targetNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) {
|
||||||
for itemNode in self.itemNodes {
|
for itemNode in self.itemNodes {
|
||||||
switch itemNode.reaction {
|
switch itemNode.reaction {
|
||||||
case let .reaction(itemValue, _, _):
|
case let .reaction(itemValue, _, _):
|
||||||
if itemValue == value {
|
if itemValue == value {
|
||||||
if let snapshotView = itemNode.view.snapshotContentTree(keepTransform: true) {
|
if let snapshotView = itemNode.view.snapshotContentTree(keepTransform: true), let targetSnapshotView = targetNode.view.snapshotContentTree() {
|
||||||
let targetSnapshotView = UIImageView()
|
|
||||||
targetSnapshotView.image = targetNode.image
|
|
||||||
targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view)
|
targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view)
|
||||||
itemNode.isHidden = true
|
itemNode.isHidden = true
|
||||||
self.view.addSubview(targetSnapshotView)
|
self.view.addSubview(targetSnapshotView)
|
||||||
|
|||||||
@ -255,7 +255,11 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.8)
|
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.8)
|
||||||
|
|
||||||
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
|
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: floor((constrainedSize.width - contentWidth) / 2.0), dy: startingPoint.y - backgroundHeight - 12.0)
|
if constrainedSize.width > 500.0 {
|
||||||
|
backgroundFrame = backgroundFrame.offsetBy(dx: constrainedSize.width - contentWidth - 44.0, dy: startingPoint.y - backgroundHeight - 12.0)
|
||||||
|
} else {
|
||||||
|
backgroundFrame = backgroundFrame.offsetBy(dx: floor((constrainedSize.width - contentWidth) / 2.0), dy: startingPoint.y - backgroundHeight - 12.0)
|
||||||
|
}
|
||||||
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
|
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
|
||||||
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
|
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
|
||||||
|
|
||||||
@ -383,9 +387,9 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
|
|
||||||
let backgroundOffset: CGPoint
|
let backgroundOffset: CGPoint
|
||||||
if self.isRightAligned {
|
if self.isRightAligned {
|
||||||
backgroundOffset = CGPoint(x: (self.backgroundNode.frame.width - shadowBlur) / 2.0 - 42.0, y: (self.backgroundNode.frame.height - shadowBlur) / 2.0)
|
backgroundOffset = CGPoint(x: (self.backgroundNode.frame.width - shadowBlur) / 2.0 - 42.0, y: 10.0)
|
||||||
} else {
|
} else {
|
||||||
backgroundOffset = CGPoint(x: -(self.backgroundNode.frame.width - shadowBlur) / 2.0 + 42.0, y: (self.backgroundNode.frame.height - shadowBlur) / 2.0)
|
backgroundOffset = CGPoint(x: -(self.backgroundNode.frame.width - shadowBlur) / 2.0 + 42.0, y: 10.0)
|
||||||
}
|
}
|
||||||
let damping: CGFloat = 100.0
|
let damping: CGFloat = 100.0
|
||||||
|
|
||||||
@ -393,14 +397,15 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
let animationOffset: Double = 1.0 - Double(i) / Double(self.reactionNodes.count - 1)
|
let animationOffset: Double = 1.0 - Double(i) / Double(self.reactionNodes.count - 1)
|
||||||
var nodeOffset: CGPoint
|
var nodeOffset: CGPoint
|
||||||
if self.isRightAligned {
|
if self.isRightAligned {
|
||||||
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.maxX - shadowBlur) / 2.0 - 42.0, y: self.reactionNodes[i].frame.minY - self.backgroundNode.frame.maxY - shadowBlur)
|
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.maxX - shadowBlur) / 2.0 - 42.0, y: 10.0)
|
||||||
} else {
|
} else {
|
||||||
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.minX + shadowBlur) / 2.0 - 42.0, y: self.reactionNodes[i].frame.minY - self.backgroundNode.frame.maxY - shadowBlur)
|
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.minX + shadowBlur) / 2.0 - 42.0, y: 10.0)
|
||||||
}
|
}
|
||||||
nodeOffset.x = -nodeOffset.x
|
nodeOffset.x = 0.0
|
||||||
nodeOffset.y = 30.0
|
nodeOffset.y = 30.0
|
||||||
self.reactionNodes[i].layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5 + animationOffset * 0.28, initialVelocity: 0.0, damping: damping)
|
self.reactionNodes[i].layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: animationOffset * 0.1)
|
||||||
self.reactionNodes[i].layer.animateSpring(from: NSValue(cgPoint: nodeOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
|
self.reactionNodes[i].layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, delay: animationOffset * 0.1, initialVelocity: 0.0, damping: damping)
|
||||||
|
//self.reactionNodes[i].layer.animateSpring(from: NSValue(cgPoint: nodeOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, delay: animationOffset * 0.1, initialVelocity: 0.0, damping: damping, additive: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
|
self.backgroundNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
|
||||||
@ -409,7 +414,7 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
self.backgroundShadowNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
|
self.backgroundShadowNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(into targetNode: ASImageNode?, hideTarget: Bool, completion: @escaping () -> Void) {
|
func animateOut(into targetNode: ASDisplayNode?, hideTarget: Bool, completion: @escaping () -> Void) {
|
||||||
self.hapticFeedback.prepareTap()
|
self.hapticFeedback.prepareTap()
|
||||||
|
|
||||||
var completedContainer = false
|
var completedContainer = false
|
||||||
@ -424,9 +429,8 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
if let targetNode = targetNode {
|
if let targetNode = targetNode {
|
||||||
for i in 0 ..< self.reactionNodes.count {
|
for i in 0 ..< self.reactionNodes.count {
|
||||||
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
|
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
|
||||||
if let snapshotView = self.reactionNodes[i].view.snapshotContentTree() {
|
targetNode.recursivelyEnsureDisplaySynchronously(true)
|
||||||
let targetSnapshotView = UIImageView()
|
if let snapshotView = self.reactionNodes[i].view.snapshotContentTree(), let targetSnapshotView = targetNode.view.snapshotContentTree() {
|
||||||
targetSnapshotView.image = targetNode.image
|
|
||||||
targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view)
|
targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view)
|
||||||
self.reactionNodes[i].isHidden = true
|
self.reactionNodes[i].isHidden = true
|
||||||
self.view.addSubview(targetSnapshotView)
|
self.view.addSubview(targetSnapshotView)
|
||||||
@ -485,7 +489,7 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
//self.backgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
self.backgroundShadowNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
self.backgroundShadowNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
self.backgroundShadowNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
self.backgroundShadowNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
|||||||
@ -47,7 +47,7 @@ public final class ReactionSelectionParentNode: ASDisplayNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dismissReactions(into targetNode: ASImageNode?, hideTarget: Bool) {
|
func dismissReactions(into targetNode: ASDisplayNode?, hideTarget: Bool) {
|
||||||
if let currentNode = self.currentNode {
|
if let currentNode = self.currentNode {
|
||||||
currentNode.animateOut(into: targetNode, hideTarget: hideTarget, completion: { [weak currentNode] in
|
currentNode.animateOut(into: targetNode, hideTarget: hideTarget, completion: { [weak currentNode] in
|
||||||
currentNode?.removeFromSupernode()
|
currentNode?.removeFromSupernode()
|
||||||
|
|||||||
@ -102,7 +102,7 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
|||||||
}
|
}
|
||||||
let elevate = self.shouldElevateAnchorPoint?() ?? false
|
let elevate = self.shouldElevateAnchorPoint?() ?? false
|
||||||
|
|
||||||
let activationTimer = Timer(timeInterval: elevate ? 0.1 : 0.01, target: TimerTarget { [weak self] in
|
let activationTimer = Timer(timeInterval: elevate ? 0.15 : 0.01, target: TimerTarget { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func complete(into targetNode: ASImageNode?, hideTarget: Bool) {
|
public func complete(into targetNode: ASDisplayNode?, hideTarget: Bool) {
|
||||||
if self.isAwaitingCompletion {
|
if self.isAwaitingCompletion {
|
||||||
self.currentContainer?.dismissReactions(into: targetNode, hideTarget: hideTarget)
|
self.currentContainer?.dismissReactions(into: targetNode, hideTarget: hideTarget)
|
||||||
self.state = .ended
|
self.state = .ended
|
||||||
|
|||||||
@ -567,7 +567,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
var reactionItems: [ReactionContextItem] = []
|
var reactionItems: [ReactionContextItem] = []
|
||||||
|
|
||||||
let reactions: [(String, String, String)] = [
|
/*let reactions: [(String, String, String)] = [
|
||||||
("😔", "Sad", "sad"),
|
("😔", "Sad", "sad"),
|
||||||
("😳", "Surprised", "surprised"),
|
("😳", "Surprised", "surprised"),
|
||||||
("😂", "Fun", "lol"),
|
("😂", "Fun", "lol"),
|
||||||
@ -586,7 +586,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let path = getAppBundle().path(forResource: name, ofType: "tgs") {
|
if let path = getAppBundle().path(forResource: name, ofType: "tgs") {
|
||||||
reactionItems.append(ReactionContextItem(value: value, text: text, path: path))
|
reactionItems.append(ReactionContextItem(value: value, text: text, path: path))
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
|
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
|
||||||
reactionItems = []
|
reactionItems = []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,7 +98,7 @@ public final class ChatControllerInteraction {
|
|||||||
let sendScheduledMessagesNow: ([MessageId]) -> Void
|
let sendScheduledMessagesNow: ([MessageId]) -> Void
|
||||||
let editScheduledMessagesTime: ([MessageId]) -> Void
|
let editScheduledMessagesTime: ([MessageId]) -> Void
|
||||||
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
|
let performTextSelectionAction: (UInt32, String, TextSelectionAction) -> Void
|
||||||
let updateMessageReaction: (MessageId, String) -> Void
|
let updateMessageReaction: (MessageId, String?) -> Void
|
||||||
let openMessageReactions: (MessageId) -> Void
|
let openMessageReactions: (MessageId) -> Void
|
||||||
let displaySwipeToReplyHint: () -> Void
|
let displaySwipeToReplyHint: () -> Void
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ public final class ChatControllerInteraction {
|
|||||||
var searchTextHighightState: (String, [MessageIndex])?
|
var searchTextHighightState: (String, [MessageIndex])?
|
||||||
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
||||||
|
|
||||||
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
|
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
|
||||||
self.openMessage = openMessage
|
self.openMessage = openMessage
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.openPeerMention = openPeerMention
|
self.openPeerMention = openPeerMention
|
||||||
|
|||||||
@ -1051,7 +1051,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
return self.contentImageNode?.playMediaWithSound()
|
return self.contentImageNode?.playMediaWithSound()
|
||||||
}
|
}
|
||||||
|
|
||||||
func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.statusNode.isHidden {
|
if !self.statusNode.isHidden {
|
||||||
return self.statusNode.reactionNode(value: value)
|
return self.statusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,7 +177,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
|
|||||||
func updateIsExtractedToContextPreview(_ value: Bool) {
|
func updateIsExtractedToContextPreview(_ value: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -522,8 +522,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
if let item = strongSelf.item, let reaction = reaction {
|
if let item = strongSelf.item, let reaction = reaction {
|
||||||
switch reaction {
|
switch reaction {
|
||||||
case let .reaction(value, _, _):
|
case let .reaction(value, _, _):
|
||||||
strongSelf.awaitingAppliedReaction = (value, {})
|
var resolvedValue: String?
|
||||||
item.controllerInteraction.updateMessageReaction(item.message.id, value)
|
if let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes), reactionsAttribute.reactions.contains(where: { $0.value == value }) {
|
||||||
|
resolvedValue = nil
|
||||||
|
} else {
|
||||||
|
resolvedValue = value
|
||||||
|
}
|
||||||
|
strongSelf.awaitingAppliedReaction = (resolvedValue, {})
|
||||||
|
item.controllerInteraction.updateMessageReaction(item.message.id, resolvedValue)
|
||||||
case .reply:
|
case .reply:
|
||||||
strongSelf.reactionRecognizer?.complete(into: nil, hideTarget: false)
|
strongSelf.reactionRecognizer?.complete(into: nil, hideTarget: false)
|
||||||
var bounds = strongSelf.bounds
|
var bounds = strongSelf.bounds
|
||||||
@ -1994,13 +2000,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.awaitingAppliedReaction = nil
|
strongSelf.awaitingAppliedReaction = nil
|
||||||
var targetNode: ASImageNode?
|
var targetNode: ASDisplayNode?
|
||||||
var hideTarget = false
|
var hideTarget = false
|
||||||
for contentNode in strongSelf.contentNodes {
|
if let awaitingAppliedReaction = awaitingAppliedReaction {
|
||||||
if let (reactionNode, count) = contentNode.reactionTargetNode(value: awaitingAppliedReaction) {
|
for contentNode in strongSelf.contentNodes {
|
||||||
targetNode = reactionNode
|
if let (reactionNode, count) = contentNode.reactionTargetNode(value: awaitingAppliedReaction) {
|
||||||
hideTarget = count == 1
|
targetNode = reactionNode
|
||||||
break
|
hideTarget = count == 1
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)
|
strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)
|
||||||
@ -2853,7 +2861,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
self.contextSourceNode.contentNode.addSubnode(accessoryItemNode)
|
self.contextSourceNode.contentNode.addSubnode(accessoryItemNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func targetReactionNode(value: String) -> (ASImageNode, Int)? {
|
override func targetReactionNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
for contentNode in self.contentNodes {
|
for contentNode in self.contentNodes {
|
||||||
if let (reactionNode, count) = contentNode.reactionTargetNode(value: value) {
|
if let (reactionNode, count) = contentNode.reactionTargetNode(value: value) {
|
||||||
return (reactionNode, count)
|
return (reactionNode, count)
|
||||||
|
|||||||
@ -346,7 +346,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.dateAndStatusNode.isHidden {
|
if !self.dateAndStatusNode.isHidden {
|
||||||
return self.dateAndStatusNode.reactionNode(value: value)
|
return self.dateAndStatusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Display
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import AppBundle
|
||||||
|
|
||||||
private let reactionCountFont = Font.semibold(11.0)
|
private let reactionCountFont = Font.semibold(11.0)
|
||||||
|
|
||||||
@ -42,26 +43,90 @@ enum ChatMessageDateAndStatusType: Equatable {
|
|||||||
case FreeOutgoing(ChatMessageDateAndStatusOutgoingType)
|
case FreeOutgoing(ChatMessageDateAndStatusOutgoingType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let reactionSize: CGFloat = 19.0
|
private let reactionSize: CGFloat = 20.0
|
||||||
private let reactionFont = Font.regular(12.0)
|
private let reactionFont = Font.regular(12.0)
|
||||||
|
|
||||||
private final class StatusReactionNode: ASImageNode {
|
private final class StatusReactionNodeParameters: NSObject {
|
||||||
|
let value: String
|
||||||
|
let previousValue: String?
|
||||||
|
|
||||||
|
init(value: String, previousValue: String?) {
|
||||||
|
self.value = value
|
||||||
|
self.previousValue = previousValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func drawReaction(context: CGContext, value: String, in rect: CGRect) {
|
||||||
|
var fileId: Int?
|
||||||
|
switch value {
|
||||||
|
case "😔":
|
||||||
|
fileId = 8
|
||||||
|
case "😳":
|
||||||
|
fileId = 19
|
||||||
|
case "😂":
|
||||||
|
fileId = 17
|
||||||
|
case "👍":
|
||||||
|
fileId = 6
|
||||||
|
case "❤":
|
||||||
|
fileId = 13
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let fileId = fileId, let path = getAppBundle().path(forResource: "simplereaction_\(fileId)@2x", ofType: "png"), let image = UIImage(contentsOfFile: path) {
|
||||||
|
context.saveGState()
|
||||||
|
context.translateBy(x: rect.midX, y: rect.midY)
|
||||||
|
context.scaleBy(x: 1.0, y: -1.0)
|
||||||
|
context.translateBy(x: -rect.midX, y: -rect.midY)
|
||||||
|
context.draw(image.cgImage!, in: rect)
|
||||||
|
context.restoreGState()
|
||||||
|
} else {
|
||||||
|
let string = NSAttributedString(string: value, font: reactionFont, textColor: .black)
|
||||||
|
string.draw(at: CGPoint(x: rect.minX + 1.0, y: rect.minY + 3.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StatusReactionNode: ASDisplayNode {
|
||||||
let value: String
|
let value: String
|
||||||
var count: Int
|
var count: Int
|
||||||
|
var previousValue: String? {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(value: String, count: Int) {
|
init(value: String, count: Int, previousValue: String?) {
|
||||||
self.value = value
|
self.value = value
|
||||||
self.count = count
|
self.count = count
|
||||||
|
self.previousValue = previousValue
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.image = generateImage(CGSize(width: reactionSize, height: reactionSize), rotatedContext: { size, context in
|
self.isOpaque = false
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
self.backgroundColor = nil
|
||||||
UIGraphicsPushContext(context)
|
}
|
||||||
let string = NSAttributedString(string: value, font: reactionFont, textColor: .black)
|
|
||||||
string.draw(at: CGPoint(x: 1.0, y: 3.0))
|
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||||
UIGraphicsPopContext()
|
return StatusReactionNodeParameters(value: self.value, previousValue: self.previousValue)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||||
|
let context = UIGraphicsGetCurrentContext()!
|
||||||
|
|
||||||
|
if !isRasterizing {
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
context.fill(bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let parameters = parameters as? StatusReactionNodeParameters else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
drawReaction(context: context, value: parameters.value, in: bounds)
|
||||||
|
if let previousValue = parameters.previousValue {
|
||||||
|
let previousRect = bounds.offsetBy(dx: -14.0, dy: 0)
|
||||||
|
context.setBlendMode(.destinationOut)
|
||||||
|
drawReaction(context: context, value: previousValue, in: previousRect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,9 +411,11 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||||
|
|
||||||
|
let reactionSpacing: CGFloat = -4.0
|
||||||
|
let reactionTrailingSpacing: CGFloat = 4.0
|
||||||
var reactionInset: CGFloat = 0.0
|
var reactionInset: CGFloat = 0.0
|
||||||
if !reactions.isEmpty {
|
if !reactions.isEmpty {
|
||||||
reactionInset = 5.0 + CGFloat(reactions.count) * reactionSize
|
reactionInset = 5.0 + CGFloat(reactions.count) * reactionSize + CGFloat(reactions.count - 1) * reactionSpacing + reactionTrailingSpacing
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
for reaction in reactions {
|
for reaction in reactions {
|
||||||
@ -499,8 +566,9 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
if strongSelf.reactionNodes.count > i, strongSelf.reactionNodes[i].value == reactions[i].value {
|
if strongSelf.reactionNodes.count > i, strongSelf.reactionNodes[i].value == reactions[i].value {
|
||||||
node = strongSelf.reactionNodes[i]
|
node = strongSelf.reactionNodes[i]
|
||||||
node.count = Int(reactions[i].count)
|
node.count = Int(reactions[i].count)
|
||||||
|
node.previousValue = i == 0 ? nil : reactions[i - 1].value
|
||||||
} else {
|
} else {
|
||||||
node = StatusReactionNode(value: reactions[i].value, count: Int(reactions[i].count))
|
node = StatusReactionNode(value: reactions[i].value, count: Int(reactions[i].count), previousValue: i == 0 ? nil : reactions[i - 1].value)
|
||||||
if strongSelf.reactionNodes.count > i {
|
if strongSelf.reactionNodes.count > i {
|
||||||
let previousNode = strongSelf.reactionNodes[i]
|
let previousNode = strongSelf.reactionNodes[i]
|
||||||
if animated {
|
if animated {
|
||||||
@ -522,11 +590,15 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize))
|
node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize))
|
||||||
reactionOffset += reactionSize
|
reactionOffset += reactionSize + reactionSpacing
|
||||||
|
}
|
||||||
|
if !reactions.isEmpty {
|
||||||
|
reactionOffset += reactionTrailingSpacing
|
||||||
}
|
}
|
||||||
for _ in reactions.count ..< strongSelf.reactionNodes.count {
|
for _ in reactions.count ..< strongSelf.reactionNodes.count {
|
||||||
let node = strongSelf.reactionNodes.removeLast()
|
let node = strongSelf.reactionNodes.removeLast()
|
||||||
if animated {
|
if animated {
|
||||||
|
node.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false)
|
||||||
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
||||||
node?.removeFromSupernode()
|
node?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
@ -558,7 +630,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strongSelf.reactionNodes.isEmpty {
|
if false, !strongSelf.reactionNodes.isEmpty {
|
||||||
if strongSelf.reactionButtonNode == nil {
|
if strongSelf.reactionButtonNode == nil {
|
||||||
let reactionButtonNode = HighlightTrackingButtonNode()
|
let reactionButtonNode = HighlightTrackingButtonNode()
|
||||||
strongSelf.reactionButtonNode = reactionButtonNode
|
strongSelf.reactionButtonNode = reactionButtonNode
|
||||||
@ -610,7 +682,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reactionNode(value: String) -> (ASImageNode, Int)? {
|
func reactionNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
for node in self.reactionNodes {
|
for node in self.reactionNodes {
|
||||||
if node.value == value {
|
if node.value == value {
|
||||||
return (node, node.count)
|
return (node, node.count)
|
||||||
|
|||||||
@ -119,7 +119,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
return self.interactiveFileNode.reactionTargetNode(value: value)
|
return self.interactiveFileNode.reactionTargetNode(value: value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -134,7 +134,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return self.contentNode.transitionNode(media: media)
|
return self.contentNode.transitionNode(media: media)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
return self.contentNode.reactionTargetNode(value: value)
|
return self.contentNode.reactionTargetNode(value: value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -947,7 +947,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
self.playerUpdateTimer = nil
|
self.playerUpdateTimer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.dateAndStatusNode.isHidden {
|
if !self.dateAndStatusNode.isHidden {
|
||||||
return self.dateAndStatusNode.reactionNode(value: value)
|
return self.dateAndStatusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,7 +137,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return self.contentNode.transitionNode(media: media)
|
return self.contentNode.transitionNode(media: media)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
return self.contentNode.reactionTargetNode(value: value)
|
return self.contentNode.reactionTargetNode(value: value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -634,7 +634,7 @@ public class ChatMessageItemView: ListViewItemNode {
|
|||||||
var item: ChatMessageItem?
|
var item: ChatMessageItem?
|
||||||
var accessibilityData: ChatMessageAccessibilityData?
|
var accessibilityData: ChatMessageAccessibilityData?
|
||||||
|
|
||||||
var awaitingAppliedReaction: (String, () -> Void)?
|
var awaitingAppliedReaction: (String?, () -> Void)?
|
||||||
|
|
||||||
public required convenience init() {
|
public required convenience init() {
|
||||||
self.init(layerBacked: false)
|
self.init(layerBacked: false)
|
||||||
@ -814,7 +814,7 @@ public class ChatMessageItemView: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func targetReactionNode(value: String) -> (ASImageNode, Int)? {
|
func targetReactionNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -469,7 +469,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.dateAndStatusNode.isHidden {
|
if !self.dateAndStatusNode.isHidden {
|
||||||
return self.dateAndStatusNode.reactionNode(value: value)
|
return self.dateAndStatusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -386,7 +386,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.dateAndStatusNode.isHidden {
|
if !self.dateAndStatusNode.isHidden {
|
||||||
return self.dateAndStatusNode.reactionNode(value: value)
|
return self.dateAndStatusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -955,7 +955,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.statusNode.isHidden {
|
if !self.statusNode.isHidden {
|
||||||
return self.statusNode.reactionNode(value: value)
|
return self.statusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -245,7 +245,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.statusNode.isHidden {
|
if !self.statusNode.isHidden {
|
||||||
return self.statusNode.reactionNode(value: value)
|
return self.statusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -579,7 +579,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
if !self.statusNode.isHidden {
|
if !self.statusNode.isHidden {
|
||||||
return self.statusNode.reactionNode(value: value)
|
return self.statusNode.reactionNode(value: value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -511,7 +511,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) })
|
self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reactionTargetNode(value: String) -> (ASImageNode, Int)? {
|
override func reactionTargetNode(value: String) -> (ASDisplayNode, Int)? {
|
||||||
return self.contentNode.reactionTargetNode(value: value)
|
return self.contentNode.reactionTargetNode(value: value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user