mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Emoji animations improvements
This commit is contained in:
parent
d592c8a0f3
commit
655600adb3
@ -6718,6 +6718,7 @@ Sorry for the inconvenience.";
|
||||
"Conversation.Theme.Apply" = "Apply Theme";
|
||||
"Conversation.Theme.NoTheme" = "No\nTheme";
|
||||
"Conversation.Theme.Reset" = "Reset Theme for This Chat";
|
||||
"Conversation.Theme.DontSetTheme" = "Do Not Set Theme";
|
||||
"Conversation.Theme.SwitchToDark" = "Switch to dark appearance";
|
||||
"Conversation.Theme.SwitchToLight" = "Switch to light appearance";
|
||||
|
||||
|
@ -1046,7 +1046,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
if frame.isLastFrame {
|
||||
var stopped = false
|
||||
var stopNow = false
|
||||
if case .once = strongSelf.playbackMode {
|
||||
if case .still = strongSelf.playbackMode {
|
||||
stopNow = true
|
||||
} else if case .once = strongSelf.playbackMode {
|
||||
stopNow = true
|
||||
} else if case let .count(count) = strongSelf.playbackMode {
|
||||
strongSelf.currentLoopCount += 1
|
||||
@ -1143,7 +1145,9 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
if frame.isLastFrame {
|
||||
var stopped = false
|
||||
var stopNow = false
|
||||
if case .once = strongSelf.playbackMode {
|
||||
if case .still = strongSelf.playbackMode {
|
||||
stopNow = true
|
||||
} else if case .once = strongSelf.playbackMode {
|
||||
stopNow = true
|
||||
} else if case let .count(count) = strongSelf.playbackMode {
|
||||
strongSelf.currentLoopCount += 1
|
||||
|
@ -1724,6 +1724,15 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.textNode.frame = textNodeFrame
|
||||
|
||||
var animateInputActivitiesFrame = false
|
||||
let inputActivities = inputActivities?.filter({
|
||||
switch $0.1 {
|
||||
case .speakingInGroupCall, .interactingWithEmoji, .seeingEmojiInteraction:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if let inputActivities = inputActivities, !inputActivities.isEmpty {
|
||||
if strongSelf.inputActivitiesNode.supernode == nil {
|
||||
strongSelf.contextContainer.addSubnode(strongSelf.inputActivitiesNode)
|
||||
|
@ -61,7 +61,7 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
||||
text = strings.DialogList_Typing
|
||||
case .choosingSticker:
|
||||
text = strings.Activity_ChoosingSticker
|
||||
case .speakingInGroupCall:
|
||||
case .speakingInGroupCall, .seeingEmojiInteraction, .interactingWithEmoji:
|
||||
text = ""
|
||||
}
|
||||
let string = NSAttributedString(string: text, font: textFont, textColor: color)
|
||||
@ -81,6 +81,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
||||
state = .typingText(string, lightColor)
|
||||
case .choosingSticker:
|
||||
state = .choosingSticker(string, lightColor)
|
||||
case .seeingEmojiInteraction, .interactingWithEmoji:
|
||||
state = .none
|
||||
}
|
||||
} else {
|
||||
let text: String
|
||||
@ -105,7 +107,7 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
||||
text = strings.DialogList_SingleTypingSuffix(peerTitle).string
|
||||
case .choosingSticker:
|
||||
text = strings.DialogList_SingleChoosingStickerSuffix(peerTitle).string
|
||||
case .speakingInGroupCall:
|
||||
case .speakingInGroupCall, .seeingEmojiInteraction, .interactingWithEmoji:
|
||||
text = ""
|
||||
}
|
||||
} else {
|
||||
@ -128,6 +130,8 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
|
||||
state = .typingText(string, lightColor)
|
||||
case .choosingSticker:
|
||||
state = .choosingSticker(string, lightColor)
|
||||
case .seeingEmojiInteraction, .interactingWithEmoji:
|
||||
state = .none
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -182,6 +182,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) }
|
||||
dict[-606432698] = { return Api.SendMessageAction.parse_sendMessageHistoryImportAction($0) }
|
||||
dict[-1336228175] = { return Api.SendMessageAction.parse_sendMessageChooseStickerAction($0) }
|
||||
dict[1781674934] = { return Api.SendMessageAction.parse_sendMessageEmojiInteraction($0) }
|
||||
dict[-1234857938] = { return Api.SendMessageAction.parse_sendMessageEmojiInteractionSeen($0) }
|
||||
dict[-1137792208] = { return Api.PrivacyKey.parse_privacyKeyStatusTimestamp($0) }
|
||||
dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) }
|
||||
dict[1030105979] = { return Api.PrivacyKey.parse_privacyKeyPhoneCall($0) }
|
||||
|
@ -4278,6 +4278,8 @@ public extension Api {
|
||||
case speakingInGroupCallAction
|
||||
case sendMessageHistoryImportAction(progress: Int32)
|
||||
case sendMessageChooseStickerAction
|
||||
case sendMessageEmojiInteraction(emoticon: String, interaction: Api.DataJSON)
|
||||
case sendMessageEmojiInteractionSeen(emoticon: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -4376,6 +4378,19 @@ public extension Api {
|
||||
buffer.appendInt32(-1336228175)
|
||||
}
|
||||
|
||||
break
|
||||
case .sendMessageEmojiInteraction(let emoticon, let interaction):
|
||||
if boxed {
|
||||
buffer.appendInt32(1781674934)
|
||||
}
|
||||
serializeString(emoticon, buffer: buffer, boxed: false)
|
||||
interaction.serialize(buffer, true)
|
||||
break
|
||||
case .sendMessageEmojiInteractionSeen(let emoticon):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1234857938)
|
||||
}
|
||||
serializeString(emoticon, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -4414,6 +4429,10 @@ public extension Api {
|
||||
return ("sendMessageHistoryImportAction", [("progress", progress)])
|
||||
case .sendMessageChooseStickerAction:
|
||||
return ("sendMessageChooseStickerAction", [])
|
||||
case .sendMessageEmojiInteraction(let emoticon, let interaction):
|
||||
return ("sendMessageEmojiInteraction", [("emoticon", emoticon), ("interaction", interaction)])
|
||||
case .sendMessageEmojiInteractionSeen(let emoticon):
|
||||
return ("sendMessageEmojiInteractionSeen", [("emoticon", emoticon)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -4513,6 +4532,33 @@ public extension Api {
|
||||
public static func parse_sendMessageChooseStickerAction(_ reader: BufferReader) -> SendMessageAction? {
|
||||
return Api.SendMessageAction.sendMessageChooseStickerAction
|
||||
}
|
||||
public static func parse_sendMessageEmojiInteraction(_ reader: BufferReader) -> SendMessageAction? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Api.DataJSON?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.DataJSON
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.SendMessageAction.sendMessageEmojiInteraction(emoticon: _1!, interaction: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_sendMessageEmojiInteractionSeen(_ reader: BufferReader) -> SendMessageAction? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.SendMessageAction.sendMessageEmojiInteractionSeen(emoticon: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PrivacyKey: TypeConstructorDescription {
|
||||
|
@ -131,6 +131,10 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa
|
||||
return .speakingInGroupCallAction
|
||||
case .choosingSticker:
|
||||
return .sendMessageChooseStickerAction
|
||||
case let .interactingWithEmoji(emoticon, interaction):
|
||||
return .sendMessageEmojiInteraction(emoticon: emoticon, interaction: interaction?.apiDataJson ?? .dataJSON(data: ""))
|
||||
case let .seeingEmojiInteraction(emoticon):
|
||||
return .sendMessageEmojiInteractionSeen(emoticon: emoticon)
|
||||
}
|
||||
} else {
|
||||
return .sendMessageCancelAction
|
||||
|
@ -1,6 +1,42 @@
|
||||
import Foundation
|
||||
import TelegramApi
|
||||
|
||||
public struct EmojiInteraction: Equatable {
|
||||
public let animation: Int
|
||||
|
||||
public init(animation: Int) {
|
||||
self.animation = animation
|
||||
}
|
||||
|
||||
public init?(apiDataJson: Api.DataJSON) {
|
||||
if case let .dataJSON(string) = apiDataJson, let data = string.data(using: .utf8) {
|
||||
do {
|
||||
let decodedData = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let item = decodedData as? [String: Any] else {
|
||||
return nil
|
||||
}
|
||||
guard let animation = item["animation"] as? Int else {
|
||||
return nil
|
||||
}
|
||||
self.animation = animation
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var apiDataJson: Api.DataJSON {
|
||||
let dict = ["animation": animation]
|
||||
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []), let dataString = String(data: data, encoding: .utf8) {
|
||||
return .dataJSON(data: dataString)
|
||||
} else {
|
||||
return .dataJSON(data: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PeerInputActivity: Comparable {
|
||||
case typingText
|
||||
case uploadingFile(progress: Int32)
|
||||
@ -12,6 +48,8 @@ public enum PeerInputActivity: Comparable {
|
||||
case uploadingInstantVideo(progress: Int32)
|
||||
case speakingInGroupCall(timestamp: Int32)
|
||||
case choosingSticker
|
||||
case interactingWithEmoji(emoticon: String, interaction: EmojiInteraction?)
|
||||
case seeingEmojiInteraction(emoticon: String)
|
||||
|
||||
public var key: Int32 {
|
||||
switch self {
|
||||
@ -35,6 +73,10 @@ public enum PeerInputActivity: Comparable {
|
||||
return 8
|
||||
case .choosingSticker:
|
||||
return 9
|
||||
case .interactingWithEmoji:
|
||||
return 10
|
||||
case .seeingEmojiInteraction:
|
||||
return 11
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +112,10 @@ extension PeerInputActivity {
|
||||
self = .choosingSticker
|
||||
case .sendMessageHistoryImportAction:
|
||||
return nil
|
||||
case let .sendMessageEmojiInteraction(emoticon, interaction):
|
||||
self = .interactingWithEmoji(emoticon: emoticon, interaction: EmojiInteraction(apiDataJson: interaction))
|
||||
case let .sendMessageEmojiInteractionSeen(emoticon):
|
||||
self = .seeingEmojiInteraction(emoticon: emoticon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +328,9 @@ final class PeerInputActivityManager {
|
||||
|
||||
let timeout: Double
|
||||
switch activity {
|
||||
case .speakingInGroupCall:
|
||||
case .interactingWithEmoji:
|
||||
timeout = 2.0
|
||||
case .speakingInGroupCall, .seeingEmojiInteraction:
|
||||
timeout = 3.0
|
||||
default:
|
||||
timeout = 8.0
|
||||
|
@ -2,7 +2,6 @@ import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
|
||||
extension JSON {
|
||||
private init?(_ object: Any) {
|
||||
if let object = object as? JSONValue {
|
||||
|
@ -2863,6 +2863,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return false
|
||||
}
|
||||
return strongSelf.chatDisplayNode.messageTransitionNode.isAnimatingMessage(stableId: stableId)
|
||||
}, getMessageTransitionNode: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return nil
|
||||
}
|
||||
return strongSelf.chatDisplayNode.messageTransitionNode
|
||||
}, requestMessageUpdate: { [weak self] id in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||
@ -7353,11 +7358,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let postbox = self.context.account.postbox
|
||||
let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:])
|
||||
var activityCategory: PeerActivitySpace.Category = .global
|
||||
if case let .replyThread(replyThreadMessage) = self.chatLocation {
|
||||
activityCategory = .thread(makeMessageThreadId(replyThreadMessage.messageId))
|
||||
|
||||
let activitySpace: PeerActivitySpace
|
||||
switch self.chatLocation {
|
||||
case let .peer(peerId):
|
||||
activitySpace = PeerActivitySpace(peerId: peerId, category: .global)
|
||||
case let .replyThread(replyThreadMessage):
|
||||
activitySpace = PeerActivitySpace(peerId: replyThreadMessage.messageId.peerId, category: .thread(makeMessageThreadId(replyThreadMessage.messageId)))
|
||||
}
|
||||
self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: PeerActivitySpace(peerId: peerId, category: activityCategory))
|
||||
|
||||
self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: activitySpace)
|
||||
|> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in
|
||||
var foundAllPeers = true
|
||||
var cachedResult: [(Peer, PeerInputActivity)] = []
|
||||
@ -7390,7 +7400,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] activities in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatTitleView?.inputActivities = (peerId, activities)
|
||||
let displayActivities = activities.filter({
|
||||
switch $0.1 {
|
||||
case .speakingInGroupCall, .interactingWithEmoji:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
strongSelf.chatTitleView?.inputActivities = (peerId, displayActivities)
|
||||
|
||||
for activity in activities {
|
||||
if case let .interactingWithEmoji(emoticon, maybeInteraction) = activity.1, let interaction = maybeInteraction {
|
||||
var found = false
|
||||
strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode({ itemNode in
|
||||
if !found, let itemNode = itemNode as? ChatMessageAnimatedStickerItemNode, let item = itemNode.item {
|
||||
if item.message.text.strippedEmoji == emoticon {
|
||||
itemNode.playAdditionalAnimation(index: interaction.animation, incoming: true)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if found {
|
||||
let _ = strongSelf.context.account.updateLocalInputActivity(peerId: activitySpace, activity: .seeingEmojiInteraction(emoticon: emoticon), isPresent: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ public final class ChatControllerInteraction {
|
||||
let copyText: (String) -> Void
|
||||
let displayUndo: (UndoOverlayContent) -> Void
|
||||
let isAnimatingMessage: (UInt32) -> Bool
|
||||
var getMessageTransitionNode: () -> ChatMessageTransitionNode?
|
||||
|
||||
let requestMessageUpdate: (MessageId) -> Void
|
||||
let cancelInteractiveKeyboardGestures: () -> Void
|
||||
@ -217,6 +218,7 @@ public final class ChatControllerInteraction {
|
||||
copyText: @escaping (String) -> Void,
|
||||
displayUndo: @escaping (UndoOverlayContent) -> Void,
|
||||
isAnimatingMessage: @escaping (UInt32) -> Bool,
|
||||
getMessageTransitionNode: @escaping () -> ChatMessageTransitionNode?,
|
||||
requestMessageUpdate: @escaping (MessageId) -> Void,
|
||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
|
||||
@ -297,6 +299,7 @@ public final class ChatControllerInteraction {
|
||||
self.copyText = copyText
|
||||
self.displayUndo = displayUndo
|
||||
self.isAnimatingMessage = isAnimatingMessage
|
||||
self.getMessageTransitionNode = getMessageTransitionNode
|
||||
self.requestMessageUpdate = requestMessageUpdate
|
||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||
|
||||
@ -353,6 +356,8 @@ public final class ChatControllerInteraction {
|
||||
}, displayUndo: { _ in
|
||||
}, isAnimatingMessage: { _ in
|
||||
return false
|
||||
}, getMessageTransitionNode: {
|
||||
return nil
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -1290,36 +1290,76 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
private func playAdditionalAnimation(_ name: String) {
|
||||
let source = AnimatedStickerNodeLocalFileSource(name: name)
|
||||
guard let item = self.item, let path = source.path, let animationSize = self.animationSize, let animationNode = self.animationNode, self.additionalAnimationNodes.count < 4 else {
|
||||
func playAdditionalAnimation(index: Int, incoming: Bool) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||
|
||||
let textEmoji = item.message.text.strippedEmoji
|
||||
let animationName: String?
|
||||
switch textEmoji {
|
||||
case "❤":
|
||||
if index == 2 {
|
||||
animationName = "TestHearts2"
|
||||
} else {
|
||||
animationName = "TestHearts"
|
||||
}
|
||||
case "🎆":
|
||||
animationName = "TestFireworks"
|
||||
default:
|
||||
animationName = nil
|
||||
}
|
||||
|
||||
guard let animationName = animationName else {
|
||||
return
|
||||
}
|
||||
|
||||
let source = AnimatedStickerNodeLocalFileSource(name: animationName)
|
||||
guard let path = source.path, let animationSize = self.animationSize, let animationNode = self.animationNode, self.additionalAnimationNodes.count < 4 else {
|
||||
return
|
||||
}
|
||||
|
||||
if let animationNode = animationNode as? AnimatedStickerNode {
|
||||
let _ = animationNode.playIfNeeded()
|
||||
}
|
||||
|
||||
let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||
|
||||
self.supernode?.view.bringSubviewToFront(self.view)
|
||||
|
||||
let resource = BundleResource(name: name, path: path)
|
||||
|
||||
let resource = BundleResource(name: animationName, path: path)
|
||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id)
|
||||
|
||||
let additionalAnimationNode = AnimatedStickerNode()
|
||||
additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 3.0), height: Int(animationSize.height * 3.0), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
|
||||
additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 2.0), height: Int(animationSize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
|
||||
var animationFrame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
|
||||
.offsetBy(dx: incomingMessage ? animationNode.frame.width - 10.0 : -animationNode.frame.width + 10.0, dy: 0.0)
|
||||
animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0))
|
||||
additionalAnimationNode.frame = animationFrame
|
||||
if incomingMessage {
|
||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
var decorationNode: ChatMessageTransitionNode.DecorationItemNode?
|
||||
if let transitionNode = item.controllerInteraction.getMessageTransitionNode() {
|
||||
decorationNode = transitionNode.add(decorationNode: additionalAnimationNode, itemNode: self)
|
||||
} else {
|
||||
self.addSubnode(additionalAnimationNode)
|
||||
}
|
||||
additionalAnimationNode.completed = { [weak self, weak additionalAnimationNode] _ in
|
||||
self?.additionalAnimationNodes.removeAll(where: { $0 === additionalAnimationNode })
|
||||
additionalAnimationNode?.removeFromSupernode()
|
||||
if let decorationNode = decorationNode, let transitionNode = item.controllerInteraction.getMessageTransitionNode() {
|
||||
transitionNode.remove(decorationNode: decorationNode)
|
||||
}
|
||||
}
|
||||
var animationFrame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
|
||||
.offsetBy(dx: incoming ? animationNode.frame.width - 10.0 : -animationNode.frame.width + 10.0, dy: 0.0)
|
||||
animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0))
|
||||
additionalAnimationNode.frame = animationFrame
|
||||
if incoming {
|
||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
self.addSubnode(additionalAnimationNode)
|
||||
|
||||
self.additionalAnimationNodes.append(additionalAnimationNode)
|
||||
|
||||
additionalAnimationNode.play()
|
||||
|
||||
if !incoming {
|
||||
item.context.account.updateLocalInputActivity(peerId: PeerActivitySpace(peerId: item.message.id.peerId, category: .global), activity: .interactingWithEmoji(emoticon: textEmoji, interaction: EmojiInteraction(animation: index)), isPresent: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
|
||||
@ -1437,12 +1477,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
return .optionalAction({
|
||||
if firstScalar.value == heart {
|
||||
if self.additionalAnimationNodes.count % 2 == 0 {
|
||||
self.playAdditionalAnimation("TestHearts")
|
||||
self.playAdditionalAnimation(index: 1, incoming: false)
|
||||
} else {
|
||||
self.playAdditionalAnimation("TestHearts2")
|
||||
self.playAdditionalAnimation(index: 2, incoming: false)
|
||||
}
|
||||
} else if firstScalar.value == fireworks {
|
||||
self.playAdditionalAnimation("TestFireworks")
|
||||
self.playAdditionalAnimation(index: 1, incoming: false)
|
||||
}
|
||||
|
||||
if shouldPlay {
|
||||
|
@ -172,6 +172,54 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
case videoMessage(VideoMessage)
|
||||
case mediaInput(MediaInput)
|
||||
}
|
||||
|
||||
final class DecorationItemNode: ASDisplayNode {
|
||||
let itemNode: ChatMessageItemView
|
||||
private let contentNode: ASDisplayNode
|
||||
private let getContentAreaInScreenSpace: () -> CGRect
|
||||
|
||||
private let scrollingContainer: ASDisplayNode
|
||||
private let containerNode: ASDisplayNode
|
||||
private let clippingNode: ASDisplayNode
|
||||
|
||||
init(itemNode: ChatMessageItemView, contentNode: ASDisplayNode, getContentAreaInScreenSpace: @escaping () -> CGRect) {
|
||||
self.itemNode = itemNode
|
||||
self.contentNode = contentNode
|
||||
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
|
||||
|
||||
self.clippingNode = ASDisplayNode()
|
||||
self.clippingNode.clipsToBounds = true
|
||||
|
||||
self.scrollingContainer = ASDisplayNode()
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.clippingNode)
|
||||
self.clippingNode.addSubnode(self.scrollingContainer)
|
||||
self.scrollingContainer.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.contentNode)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
self.clippingNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
let absoluteRect = self.itemNode.view.convert(self.itemNode.view.bounds, to: self.view)
|
||||
self.containerNode.frame = absoluteRect
|
||||
}
|
||||
|
||||
func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if transition.isAnimated {
|
||||
assert(true)
|
||||
}
|
||||
self.scrollingContainer.bounds = self.scrollingContainer.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||
transition.animateOffsetAdditive(node: self.scrollingContainer, offset: offset)
|
||||
}
|
||||
|
||||
func addContentOffset(offset: CGFloat) {
|
||||
self.scrollingContainer.bounds = self.scrollingContainer.bounds.offsetBy(dx: 0.0, dy: offset)
|
||||
}
|
||||
}
|
||||
|
||||
private final class AnimatingItemNode: ASDisplayNode {
|
||||
let itemNode: ChatMessageItemView
|
||||
@ -553,6 +601,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
private var currentPendingItem: (Int64, Source, () -> Void)?
|
||||
|
||||
private var animatingItemNodes: [AnimatingItemNode] = []
|
||||
private var decorationItemNodes: [DecorationItemNode] = []
|
||||
|
||||
var hasScheduledTransitions: Bool {
|
||||
return self.currentPendingItem != nil
|
||||
@ -585,6 +634,20 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
self.currentPendingItem = (correlationId, source, initiated)
|
||||
self.listNode.setCurrentSendAnimationCorrelationId(correlationId)
|
||||
}
|
||||
|
||||
func add(decorationNode: ASDisplayNode, itemNode: ChatMessageItemView) -> DecorationItemNode {
|
||||
let decorationItemNode = DecorationItemNode(itemNode: itemNode, contentNode: decorationNode, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
|
||||
decorationItemNode.updateLayout(size: self.bounds.size)
|
||||
self.decorationItemNodes.append(decorationItemNode)
|
||||
self.addSubnode(decorationItemNode)
|
||||
|
||||
return decorationItemNode
|
||||
}
|
||||
|
||||
func remove(decorationNode: DecorationItemNode) {
|
||||
self.decorationItemNodes.removeAll(where: { $0 === decorationNode })
|
||||
decorationNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
private func beginAnimation(itemNode: ChatMessageItemView, source: Source) {
|
||||
var contextSourceNode: ContextExtractedContentContainingNode?
|
||||
@ -646,12 +709,22 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
for animatingItemNode in self.animatingItemNodes {
|
||||
animatingItemNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode)
|
||||
}
|
||||
if itemNode == nil {
|
||||
for decorationItemNode in self.decorationItemNodes {
|
||||
decorationItemNode.addExternalOffset(offset: offset, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addContentOffset(offset: CGFloat, itemNode: ListViewItemNode?) {
|
||||
for animatingItemNode in self.animatingItemNodes {
|
||||
animatingItemNode.addContentOffset(offset: offset, itemNode: itemNode)
|
||||
}
|
||||
if itemNode == nil {
|
||||
for decorationItemNode in self.decorationItemNodes {
|
||||
decorationItemNode.addContentOffset(offset: offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isAnimatingMessage(stableId: UInt32) -> Bool {
|
||||
|
@ -527,6 +527,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, displayUndo: { _ in
|
||||
}, isAnimatingMessage: { _ in
|
||||
return false
|
||||
}, getMessageTransitionNode: {
|
||||
return nil
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
|
||||
|
@ -338,6 +338,27 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + emojiFrame.minX, y: rect.minY + emojiFrame.minY), size: emojiFrame.size), within: containerSize)
|
||||
}
|
||||
|
||||
override func selected() {
|
||||
super.selected()
|
||||
|
||||
if let animatedStickerNode = self.animatedStickerNode {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
let started = animatedStickerNode.playIfNeeded()
|
||||
if started {
|
||||
let scale: CGFloat = 2.6
|
||||
animatedStickerNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
animatedStickerNode.layer.animateSpring(from: 1.0 as NSNumber, to: scale as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
|
||||
animatedStickerNode.completed = { [weak animatedStickerNode] _ in
|
||||
animatedStickerNode?.transform = CATransform3DIdentity
|
||||
animatedStickerNode?.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func asyncLayout() -> (ThemeSettingsThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode)
|
||||
@ -437,8 +458,11 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
strongSelf.animatedStickerNode = animatedStickerNode
|
||||
strongSelf.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode)
|
||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 96, height: 96, mode: .direct(cachePathPrefix: pathPrefix))
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix))
|
||||
|
||||
animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0)
|
||||
}
|
||||
animatedStickerNode.autoplay = true
|
||||
animatedStickerNode.visibility = strongSelf.visibilityStatus
|
||||
|
||||
strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start())
|
||||
@ -734,7 +758,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
self.animationNode.isUserInteractionEnabled = false
|
||||
|
||||
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_Theme_Apply
|
||||
self.doneButton.title = initiallySelectedEmoticon == nil ? self.presentationData.strings.Conversation_Theme_DontSetTheme : self.presentationData.strings.Conversation_Theme_Apply
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
@ -783,9 +807,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
let presentationData = strongSelf.presentationData
|
||||
|
||||
var entries: [ThemeSettingsThemeEntry] = []
|
||||
if strongSelf.initiallySelectedEmoticon != nil {
|
||||
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||
}
|
||||
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||
for theme in themes {
|
||||
var emoticon = theme.emoji
|
||||
if emoticon == "🦁" {
|
||||
@ -804,7 +826,13 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
strongSelf.selectedEmoticon = emoticon
|
||||
let _ = ensureThemeVisible(listNode: strongSelf.listNode, emoticon: emoticon, animated: true)
|
||||
|
||||
strongSelf.doneButton.title = emoticon == nil ? strongSelf.presentationData.strings.Conversation_Theme_Reset : strongSelf.presentationData.strings.Conversation_Theme_Apply
|
||||
let doneButtonTitle: String
|
||||
if emoticon == nil {
|
||||
doneButtonTitle = strongSelf.initiallySelectedEmoticon == nil ? strongSelf.presentationData.strings.Conversation_Theme_DontSetTheme : strongSelf.presentationData.strings.Conversation_Theme_Reset
|
||||
} else {
|
||||
doneButtonTitle = strongSelf.presentationData.strings.Conversation_Theme_Apply
|
||||
}
|
||||
strongSelf.doneButton.title = doneButtonTitle
|
||||
}
|
||||
}
|
||||
let previousEntries = strongSelf.entries ?? []
|
||||
|
@ -343,7 +343,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
stringValue = strings.Activity_UploadingVideoMessage
|
||||
case .choosingSticker:
|
||||
stringValue = strings.Activity_ChoosingSticker
|
||||
case .speakingInGroupCall:
|
||||
case let .seeingEmojiInteraction(emoticon):
|
||||
stringValue = "enjoying \(emoticon) animations"
|
||||
case .speakingInGroupCall, .interactingWithEmoji:
|
||||
stringValue = ""
|
||||
}
|
||||
} else {
|
||||
@ -372,10 +374,12 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
state = .uploading(string, color)
|
||||
case .playingGame:
|
||||
state = .playingGame(string, color)
|
||||
case .speakingInGroupCall:
|
||||
case .speakingInGroupCall, .interactingWithEmoji:
|
||||
state = .typingText(string, color)
|
||||
case .choosingSticker:
|
||||
state = .choosingSticker(string, color)
|
||||
case .seeingEmojiInteraction:
|
||||
state = .choosingSticker(string, color)
|
||||
}
|
||||
} else {
|
||||
if let titleContent = self.titleContent {
|
||||
|
@ -153,6 +153,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
}, displayUndo: { _ in
|
||||
}, isAnimatingMessage: { _ in
|
||||
return false
|
||||
}, getMessageTransitionNode: {
|
||||
return nil
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -145,6 +145,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, displayUndo: { _ in
|
||||
}, isAnimatingMessage: { _ in
|
||||
return false
|
||||
}, getMessageTransitionNode: {
|
||||
return nil
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))
|
||||
|
@ -2180,6 +2180,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}, displayUndo: { _ in
|
||||
}, isAnimatingMessage: { _ in
|
||||
return false
|
||||
}, getMessageTransitionNode: {
|
||||
return nil
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
@ -1292,6 +1292,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, displayUndo: { _ in
|
||||
}, isAnimatingMessage: { _ in
|
||||
return false
|
||||
}, getMessageTransitionNode: {
|
||||
return nil
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
||||
|
Loading…
x
Reference in New Issue
Block a user