Add support for generic dice-like messages

This commit is contained in:
Ilya Laktyushin 2020-04-17 01:34:31 +04:00
parent 36f89567ad
commit a63457d7e7
9 changed files with 156 additions and 104 deletions

Binary file not shown.

View File

@ -160,8 +160,8 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
} }
case let poll as TelegramMediaPoll: case let poll as TelegramMediaPoll:
messageText = "📊 \(poll.text)" messageText = "📊 \(poll.text)"
case _ as TelegramMediaDice: case let dice as TelegramMediaDice:
messageText = "🎲" messageText = dice.emoji
default: default:
break break
} }

View File

@ -187,8 +187,8 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
} }
let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: poll.text, answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities) let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: poll.text, answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil))) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil)))
} else if let _ = media as? TelegramMediaDice { } else if let dice = media as? TelegramMediaDice {
let input = Api.InputMedia.inputMediaDice let input = Api.InputMedia.inputMediaDice(emoticon: dice.emoji)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil))) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
} else { } else {
return nil return nil

View File

@ -43,7 +43,7 @@ public enum MessageContentKind: Equatable {
case expiredVideo case expiredVideo
case poll(String) case poll(String)
case restricted(String) case restricted(String)
case dice case dice(String)
public var key: MessageContentKindKey { public var key: MessageContentKindKey {
switch self { switch self {
@ -169,8 +169,8 @@ public func mediaContentKind(_ media: Media, message: Message? = nil, strings: P
} }
case let poll as TelegramMediaPoll: case let poll as TelegramMediaPoll:
return .poll(poll.text) return .poll(poll.text)
case _ as TelegramMediaDice: case let dice as TelegramMediaDice:
return .dice return .dice(dice.emoji)
default: default:
return nil return nil
} }
@ -218,8 +218,8 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
return ("📊 \(text)", false) return ("📊 \(text)", false)
case let .restricted(text): case let .restricted(text):
return (text, false) return (text, false)
case .dice: case let .dice(emoji):
return ("🎲", true) return (emoji, true)
} }
} }

View File

@ -2260,8 +2260,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var messages: [EnqueueMessage] = [] var messages: [EnqueueMessage] = []
let effectiveInputText = effectivePresentationInterfaceState.interfaceState.composeInputState.inputText let effectiveInputText = effectivePresentationInterfaceState.interfaceState.composeInputState.inputText
if case let .peer(peerId) = effectivePresentationInterfaceState.chatLocation, peerId.namespace != Namespaces.Peer.SecretChat, effectiveInputText.string.trimmingCharacters(in: .whitespacesAndNewlines) == "🎲" { let trimmedInputText = effectiveInputText.string.trimmingCharacters(in: .whitespacesAndNewlines)
messages.append(.message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice()), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)) if case let .peer(peerId) = effectivePresentationInterfaceState.chatLocation, peerId.namespace != Namespaces.Peer.SecretChat, ["🎲", "🎯"].contains(trimmedInputText) {
messages.append(.message(text: "", attributes: [], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: trimmedInputText)), replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil))
} else { } else {
let inputText = convertMarkdownToAttributes(effectiveInputText) let inputText = convertMarkdownToAttributes(effectiveInputText)

View File

@ -614,8 +614,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
|> distinctUntilChanged |> distinctUntilChanged
let animatedEmojiStickers = combineLatest(loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .animatedEmoji, forceActualized: false), loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .dice, forceActualized: false))
|> map { animatedEmoji, dice -> [String: [StickerPackItem]] in // loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .dice, forceActualized: false)
let animatedEmojiStickers = loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: [StickerPackItem]] in
var animatedEmojiStickers: [String: [StickerPackItem]] = [:] var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
switch animatedEmoji { switch animatedEmoji {
case let .result(_, items, _): case let .result(_, items, _):
@ -631,23 +634,21 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
default: default:
break break
} }
switch dice { // switch dice {
case let .result(_, items, _): // case let .result(_, items, _):
var diceStickers: [StickerPackItem] = [] // var diceStickers: [StickerPackItem] = []
for case let item as StickerPackItem in items { // for case let item as StickerPackItem in items {
diceStickers.append(item) // diceStickers.append(item)
} // }
animatedEmojiStickers["dice"] = diceStickers // animatedEmojiStickers["dice"] = diceStickers
default: // default:
break // break
} // }
return animatedEmojiStickers return animatedEmojiStickers
} }
let previousHistoryAppearsCleared = Atomic<Bool?>(value: nil) let previousHistoryAppearsCleared = Atomic<Bool?>(value: nil)
let nextTransitionVersion = Atomic<Int>(value: 0)
let updatingMedia = context.account.pendingUpdateMessageManager.updatingMessageMedia let updatingMedia = context.account.pendingUpdateMessageManager.updatingMessageMedia
|> map { value -> [MessageId: ChatUpdatingMessageMedia] in |> map { value -> [MessageId: ChatUpdatingMessageMedia] in
var result = value var result = value

View File

@ -256,7 +256,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
var loadStickerSaveStatus: MediaId? var loadStickerSaveStatus: MediaId?
var loadCopyMediaResource: MediaResource? var loadCopyMediaResource: MediaResource?
var isAction = false var isAction = false
var isDice = false var diceEmoji: String?
var canDiscuss = false var canDiscuss = false
if messages.count == 1 { if messages.count == 1 {
for media in messages[0].media { for media in messages[0].media {
@ -272,8 +272,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
if !messages[0].containsSecretMedia { if !messages[0].containsSecretMedia {
loadCopyMediaResource = largestImageRepresentation(image.representations)?.resource loadCopyMediaResource = largestImageRepresentation(image.representations)?.resource
} }
} else if let _ = media as? TelegramMediaDice { } else if let dice = media as? TelegramMediaDice {
isDice = true diceEmoji = dice.emoji
} }
} }
} }
@ -425,7 +425,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
resourceAvailable = false resourceAvailable = false
} }
if !messages[0].text.isEmpty || resourceAvailable || isDice { if !messages[0].text.isEmpty || resourceAvailable || diceEmoji != nil {
let message = messages[0] let message = messages[0]
var isExpired = false var isExpired = false
for media in message.media { for media in message.media {
@ -437,8 +437,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
if isDice { if let diceEmoji = diceEmoji {
UIPasteboard.general.string = "🎲" UIPasteboard.general.string = diceEmoji
} else { } else {
let copyTextWithEntities = { let copyTextWithEntities = {
var messageEntities: [MessageTextEntity]? var messageEntities: [MessageTextEntity]?

View File

@ -208,11 +208,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
return return
} }
if let _ = self.telegramDice { if let telegramDice = self.telegramDice {
if let diceEmojis = item.associatedData.animatedEmojiStickers["dice"] { let animationNode = ManagedDiceAnimationNode(context: item.context, dice: telegramDice)
let animationNode = ManagedDiceAnimationNode(context: item.context, emojis: diceEmojis.map { $0.file }) self.animationNode = animationNode
self.animationNode = animationNode
}
} else { } else {
let animationNode = AnimatedStickerNode() let animationNode = AnimatedStickerNode()
animationNode.started = { [weak self] in animationNode.started = { [weak self] in

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import Postbox
import SyncCore import SyncCore
import TelegramCore import TelegramCore
import SwiftSignalKit import SwiftSignalKit
@ -13,16 +14,43 @@ enum ManagedDiceAnimationState: Equatable {
case value(Int32, Bool) case value(Int32, Bool)
} }
private func rollingAnimationItem(emoji: String) -> ManagedAnimationItem? {
switch emoji {
case "🎲":
return ManagedAnimationItem(source: .local("Dice_Rolling"), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 60), duration: 1.0, loop: true)
case "🎯":
return ManagedAnimationItem(source: .local("Darts_Aiming"), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 90), duration: 1.5, loop: true)
default:
return nil
}
}
final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStickerNode { final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStickerNode {
private let context: AccountContext private let context: AccountContext
private let emojis: [TelegramMediaFile] private let dice: TelegramMediaDice
private var diceState: ManagedDiceAnimationState? = nil private var diceState: ManagedDiceAnimationState? = nil
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
init(context: AccountContext, emojis: [TelegramMediaFile]) { private let emojis = Promise<[TelegramMediaFile]>()
init(context: AccountContext, dice: TelegramMediaDice) {
self.context = context self.context = context
self.emojis = emojis self.dice = dice
self.emojis.set(loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: .dice(dice.emoji), forceActualized: false)
|> mapToSignal { stickerPack -> Signal<[TelegramMediaFile], NoError> in
switch stickerPack {
case let .result(_, items, _):
var emojiStickers: [TelegramMediaFile] = []
for case let item as StickerPackItem in items {
emojiStickers.append(item.file)
}
return .single(emojiStickers)
default:
return .complete()
}
})
super.init(size: CGSize(width: 184.0, height: 184.0)) super.init(size: CGSize(width: 184.0, height: 184.0))
} }
@ -35,98 +63,122 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick
let previousState = self.diceState let previousState = self.diceState
self.diceState = diceState self.diceState = diceState
let frameCount: Int
let duration: Double
switch dice.emoji {
case "🎲":
frameCount = 180
duration = 3.0
case "🎯":
frameCount = 100
duration = 1.6
default:
frameCount = 180
duration = 1.6
}
let context = self.context
if let previousState = previousState { if let previousState = previousState {
switch previousState { switch previousState {
case .rolling: case .rolling:
switch diceState { switch diceState {
case let .value(value, _): case let .value(value, _):
guard self.emojis.count == 6 else { let animationItem: Signal<ManagedAnimationItem, NoError> = self.emojis.get()
return |> mapToSignal { emojis -> Signal<ManagedAnimationItem, NoError> in
} guard emojis.count > value else {
let file = self.emojis[Int(value) - 1] return .complete()
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedSize = dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0))
let fetched = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: file))
let sticker = Signal<Void, NoError> { subscriber in
let fetchedDisposable = fetched.start()
let resourceDisposable = (chatMessageAnimationData(postbox: self.context.account.postbox, resource: file.resource, fitzModifier: nil, width: Int(fittedSize.width), height: Int(fittedSize.height), synchronousLoad: false)
|> filter { data in
return data.complete
}).start(next: { next in
subscriber.putNext(Void())
})
return ActionDisposable {
fetchedDisposable.dispose()
resourceDisposable.dispose()
} }
} let file = emojis[Int(value) - 1]
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedSize = dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0))
self.disposable.set((sticker |> deliverOnMainQueue).start(next: { [weak self] data in let fetched = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file))
let animationItem = Signal<ManagedAnimationItem, NoError> { subscriber in
let fetchedDisposable = fetched.start()
let resourceDisposable = (chatMessageAnimationData(postbox: context.account.postbox, resource: file.resource, fitzModifier: nil, width: Int(fittedSize.width), height: Int(fittedSize.height), synchronousLoad: false)
|> filter { data in
return data.complete
}).start(next: { next in
subscriber.putNext(ManagedAnimationItem(source: .resource(context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: frameCount), duration: duration))
})
return ActionDisposable {
fetchedDisposable.dispose()
resourceDisposable.dispose()
}
}
return animationItem
}
self.disposable.set((animationItem |> deliverOnMainQueue).start(next: { [weak self] item in
if let strongSelf = self { if let strongSelf = self {
strongSelf.trackTo(item: ManagedAnimationItem(source: .resource(strongSelf.context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 180), duration: 3.0)) strongSelf.trackTo(item: item)
} }
})) }))
case .rolling: case .rolling:
break break
} }
case let .value(currentValue): case .value:
switch diceState { switch diceState {
case .rolling: case .rolling:
self.trackTo(item: ManagedAnimationItem(source: .local("Dice_Rolling"), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 60), duration: 1.0, loop: true)) if let item = rollingAnimationItem(emoji: self.dice.emoji) {
case let .value(value): self.trackTo(item: item)
}
case .value:
break break
} }
} }
} else { } else {
switch diceState { switch diceState {
case let .value(value, immediate): case let .value(value, immediate):
guard self.emojis.count == 6 else { let animationItem: Signal<ManagedAnimationItem, NoError> = self.emojis.get()
return |> mapToSignal { emojis -> Signal<ManagedAnimationItem, NoError> in
} guard emojis.count > value else {
let file = self.emojis[Int(value) - 1] return .complete()
if let _ = self.context.account.postbox.mediaBox.completedResourcePath(file.resource) {
if immediate {
self.trackTo(item: ManagedAnimationItem(source: .resource(self.context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 180, endFrame: 180), duration: 0.0))
} else {
self.trackTo(item: ManagedAnimationItem(source: .resource(self.context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 180), duration: 3.0))
} }
} else { let file = emojis[Int(value) - 1]
self.setState(.rolling)
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedSize = dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0))
let fetched = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: file)) if let _ = context.account.postbox.mediaBox.completedResourcePath(file.resource) {
let sticker = Signal<Void, NoError> { subscriber in if immediate {
let fetchedDisposable = fetched.start() return .single(ManagedAnimationItem(source: .resource(context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: frameCount, endFrame: frameCount), duration: 0.0))
let resourceDisposable = (chatMessageAnimationData(postbox: self.context.account.postbox, resource: file.resource, fitzModifier: nil, width: Int(fittedSize.width), height: Int(fittedSize.height), synchronousLoad: false) } else {
|> filter { data in return .single(ManagedAnimationItem(source: .resource(context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: frameCount), duration: duration))
return data.complete
}).start(next: { next in
subscriber.putNext(Void())
})
return ActionDisposable {
fetchedDisposable.dispose()
resourceDisposable.dispose()
} }
} } else {
self.setState(.rolling)
self.disposable.set((sticker |> deliverOnMainQueue).start(next: { [weak self] data in
if let strongSelf = self { let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
if immediate { let fittedSize = dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0))
strongSelf.trackTo(item: ManagedAnimationItem(source: .resource(strongSelf.context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 180, endFrame: 180), duration: 0.0))
} else { let fetched = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: file))
strongSelf.trackTo(item: ManagedAnimationItem(source: .resource(strongSelf.context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 180), duration: 3.0)) let animationItem = Signal<ManagedAnimationItem, NoError> { subscriber in
let fetchedDisposable = fetched.start()
let resourceDisposable = (chatMessageAnimationData(postbox: self.context.account.postbox, resource: file.resource, fitzModifier: nil, width: Int(fittedSize.width), height: Int(fittedSize.height), synchronousLoad: false)
|> filter { data in
return data.complete
}).start(next: { next in
subscriber.putNext(ManagedAnimationItem(source: .resource(context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: frameCount), duration: duration))
})
return ActionDisposable {
fetchedDisposable.dispose()
resourceDisposable.dispose()
} }
} }
})) return animationItem
}
} }
self.disposable.set((animationItem |> deliverOnMainQueue).start(next: { [weak self] item in
if let strongSelf = self {
strongSelf.trackTo(item: item)
}
}))
case .rolling: case .rolling:
self.trackTo(item: ManagedAnimationItem(source: .local("Dice_Rolling"), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 60), duration: 1.0, loop: true)) if let item = rollingAnimationItem(emoji: self.dice.emoji) {
self.trackTo(item: item)
}
} }
} }
} }