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:
messageText = "📊 \(poll.text)"
case _ as TelegramMediaDice:
messageText = "🎲"
case let dice as TelegramMediaDice:
messageText = dice.emoji
default:
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)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil)))
} else if let _ = media as? TelegramMediaDice {
let input = Api.InputMedia.inputMediaDice
} else if let dice = media as? TelegramMediaDice {
let input = Api.InputMedia.inputMediaDice(emoticon: dice.emoji)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
} else {
return nil

View File

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

View File

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

View File

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

View File

@ -256,7 +256,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
var loadStickerSaveStatus: MediaId?
var loadCopyMediaResource: MediaResource?
var isAction = false
var isDice = false
var diceEmoji: String?
var canDiscuss = false
if messages.count == 1 {
for media in messages[0].media {
@ -272,8 +272,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
if !messages[0].containsSecretMedia {
loadCopyMediaResource = largestImageRepresentation(image.representations)?.resource
}
} else if let _ = media as? TelegramMediaDice {
isDice = true
} else if let dice = media as? TelegramMediaDice {
diceEmoji = dice.emoji
}
}
}
@ -425,7 +425,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
resourceAvailable = false
}
if !messages[0].text.isEmpty || resourceAvailable || isDice {
if !messages[0].text.isEmpty || resourceAvailable || diceEmoji != nil {
let message = messages[0]
var isExpired = false
for media in message.media {
@ -437,8 +437,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
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)
}, action: { _, f in
if isDice {
UIPasteboard.general.string = "🎲"
if let diceEmoji = diceEmoji {
UIPasteboard.general.string = diceEmoji
} else {
let copyTextWithEntities = {
var messageEntities: [MessageTextEntity]?

View File

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

View File

@ -1,6 +1,7 @@
import Foundation
import Display
import AsyncDisplayKit
import Postbox
import SyncCore
import TelegramCore
import SwiftSignalKit
@ -13,16 +14,43 @@ enum ManagedDiceAnimationState: Equatable {
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 {
private let context: AccountContext
private let emojis: [TelegramMediaFile]
private let dice: TelegramMediaDice
private var diceState: ManagedDiceAnimationState? = nil
private let disposable = MetaDisposable()
init(context: AccountContext, emojis: [TelegramMediaFile]) {
private let emojis = Promise<[TelegramMediaFile]>()
init(context: AccountContext, dice: TelegramMediaDice) {
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))
}
@ -35,98 +63,122 @@ final class ManagedDiceAnimationNode: ManagedAnimationNode, GenericAnimatedStick
let previousState = self.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 {
switch previousState {
case .rolling:
switch diceState {
case let .value(value, _):
guard self.emojis.count == 6 else {
return
}
let file = self.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))
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 animationItem: Signal<ManagedAnimationItem, NoError> = self.emojis.get()
|> mapToSignal { emojis -> Signal<ManagedAnimationItem, NoError> in
guard emojis.count > value else {
return .complete()
}
}
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 {
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:
break
}
case let .value(currentValue):
case .value:
switch diceState {
case .rolling:
self.trackTo(item: ManagedAnimationItem(source: .local("Dice_Rolling"), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 60), duration: 1.0, loop: true))
case let .value(value):
if let item = rollingAnimationItem(emoji: self.dice.emoji) {
self.trackTo(item: item)
}
case .value:
break
}
}
} else {
switch diceState {
case let .value(value, immediate):
guard self.emojis.count == 6 else {
return
}
let file = self.emojis[Int(value) - 1]
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))
let animationItem: Signal<ManagedAnimationItem, NoError> = self.emojis.get()
|> mapToSignal { emojis -> Signal<ManagedAnimationItem, NoError> in
guard emojis.count > value else {
return .complete()
}
} else {
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 file = emojis[Int(value) - 1]
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()
if let _ = context.account.postbox.mediaBox.completedResourcePath(file.resource) {
if immediate {
return .single(ManagedAnimationItem(source: .resource(context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: frameCount, endFrame: frameCount), duration: 0.0))
} else {
return .single(ManagedAnimationItem(source: .resource(context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: frameCount), duration: duration))
}
}
self.disposable.set((sticker |> deliverOnMainQueue).start(next: { [weak self] data in
if let strongSelf = self {
if immediate {
strongSelf.trackTo(item: ManagedAnimationItem(source: .resource(strongSelf.context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 180, endFrame: 180), duration: 0.0))
} else {
strongSelf.trackTo(item: ManagedAnimationItem(source: .resource(strongSelf.context.account.postbox.mediaBox, file.resource), frames: ManagedAnimationFrameRange(startFrame: 0, endFrame: 180), duration: 3.0))
} else {
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))
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:
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)
}
}
}
}