Emoji animations improvements

This commit is contained in:
Ilya Laktyushin 2021-09-10 23:26:54 +03:00
parent d592c8a0f3
commit 655600adb3
21 changed files with 350 additions and 37 deletions

View File

@ -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";

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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) }

View File

@ -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 {

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -2,7 +2,6 @@ import Foundation
import Postbox
import TelegramApi
extension JSON {
private init?(_ object: Any) {
if let object = object as? JSONValue {

View File

@ -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)
}
}
}
}
})
}

View File

@ -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,

View File

@ -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 {

View File

@ -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 {

View File

@ -527,6 +527,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, displayUndo: { _ in
}, isAnimatingMessage: { _ in
return false
}, getMessageTransitionNode: {
return nil
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,

View File

@ -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 ?? []

View File

@ -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 {

View File

@ -153,6 +153,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, displayUndo: { _ in
}, isAnimatingMessage: { _ in
return false
}, getMessageTransitionNode: {
return nil
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -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))

View File

@ -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,

View File

@ -1292,6 +1292,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, displayUndo: { _ in
}, isAnimatingMessage: { _ in
return false
}, getMessageTransitionNode: {
return nil
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,