mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '9f94bc7f83f42523652ba414a1f8bb5811bcbdc7'
This commit is contained in:
commit
6ebfc0ee62
@ -6824,3 +6824,5 @@ Ads should no longer be synonymous with abuse of user privacy. Let us redefine h
|
||||
|
||||
"MESSAGE_NOTHEME" = "%1$@ changed theme to default one";
|
||||
"CHAT_MESSAGE_NOTHEME" = "%1$@ set theme to default one in the group %2$@";
|
||||
|
||||
"Activity.EnjoyingAnimations" = "enjoying %@ animations";
|
||||
|
@ -22,10 +22,11 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
public let contactsPeerIds: Set<EnginePeer.Id>
|
||||
public let channelDiscussionGroup: ChannelDiscussionGroupStatus
|
||||
public let animatedEmojiStickers: [String: [StickerPackItem]]
|
||||
public let additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]]
|
||||
public let forcedResourceStatus: FileMediaResourceStatus?
|
||||
public let currentlyPlayingMessageId: EngineMessage.Index?
|
||||
|
||||
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil) {
|
||||
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil) {
|
||||
self.automaticDownloadPeerType = automaticDownloadPeerType
|
||||
self.automaticDownloadNetworkType = automaticDownloadNetworkType
|
||||
self.isRecentActions = isRecentActions
|
||||
@ -33,6 +34,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
self.contactsPeerIds = contactsPeerIds
|
||||
self.channelDiscussionGroup = channelDiscussionGroup
|
||||
self.animatedEmojiStickers = animatedEmojiStickers
|
||||
self.additionalAnimatedEmojiStickers = additionalAnimatedEmojiStickers
|
||||
self.forcedResourceStatus = forcedResourceStatus
|
||||
self.currentlyPlayingMessageId = currentlyPlayingMessageId
|
||||
}
|
||||
@ -59,6 +61,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers {
|
||||
return false
|
||||
}
|
||||
if lhs.additionalAnimatedEmojiStickers != rhs.additionalAnimatedEmojiStickers {
|
||||
return false
|
||||
}
|
||||
if lhs.forcedResourceStatus != rhs.forcedResourceStatus {
|
||||
return false
|
||||
}
|
||||
|
@ -166,6 +166,12 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13
|
||||
}, initialValues: [:], queue: queue)
|
||||
}
|
||||
|
||||
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>, _ s13: Signal<T13, E>, _ s14: Signal<T14, E>) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), E> {
|
||||
return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14)], combine: { values in
|
||||
return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14)
|
||||
}, initialValues: [:], queue: queue)
|
||||
}
|
||||
|
||||
public func combineLatest<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
|
||||
if signals.count == 0 {
|
||||
return single([T](), E.self)
|
||||
|
@ -669,6 +669,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-2044933984] = { return Api.InputStickerSet.parse_inputStickerSetShortName($0) }
|
||||
dict[42402760] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmoji($0) }
|
||||
dict[-427863538] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) }
|
||||
dict[215889721] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmojiAnimations($0) }
|
||||
dict[-1231326505] = { return Api.messages.ChatAdminsWithInvites.parse_chatAdminsWithInvites($0) }
|
||||
dict[460632885] = { return Api.BotInfo.parse_botInfo($0) }
|
||||
dict[-2046910401] = { return Api.stickers.SuggestedShortName.parse_suggestedShortName($0) }
|
||||
|
@ -16939,6 +16939,7 @@ public extension Api {
|
||||
case inputStickerSetShortName(shortName: String)
|
||||
case inputStickerSetAnimatedEmoji
|
||||
case inputStickerSetDice(emoticon: String)
|
||||
case inputStickerSetAnimatedEmojiAnimations
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -16972,6 +16973,12 @@ public extension Api {
|
||||
buffer.appendInt32(-427863538)
|
||||
}
|
||||
serializeString(emoticon, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .inputStickerSetAnimatedEmojiAnimations:
|
||||
if boxed {
|
||||
buffer.appendInt32(215889721)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -16988,6 +16995,8 @@ public extension Api {
|
||||
return ("inputStickerSetAnimatedEmoji", [])
|
||||
case .inputStickerSetDice(let emoticon):
|
||||
return ("inputStickerSetDice", [("emoticon", emoticon)])
|
||||
case .inputStickerSetAnimatedEmojiAnimations:
|
||||
return ("inputStickerSetAnimatedEmojiAnimations", [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -17033,6 +17042,9 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputStickerSetAnimatedEmojiAnimations(_ reader: BufferReader) -> InputStickerSet? {
|
||||
return Api.InputStickerSet.inputStickerSetAnimatedEmojiAnimations
|
||||
}
|
||||
|
||||
}
|
||||
public enum BotInfo: TypeConstructorDescription {
|
||||
|
@ -1126,10 +1126,13 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedSynchronizeAppLogEventsOperations(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedNotificationSettingsBehaviors(postbox: self.postbox).start())
|
||||
self.managedOperationsDisposable.add(managedThemesUpdates(accountManager: accountManager, postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedChatThemesUpdates(accountManager: accountManager, network: self.network).start())
|
||||
if !self.testingEnvironment {
|
||||
self.managedOperationsDisposable.add(managedChatThemesUpdates(accountManager: accountManager, network: self.network).start())
|
||||
}
|
||||
|
||||
if !self.supplementary {
|
||||
self.managedOperationsDisposable.add(managedAnimatedEmojiUpdates(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedAnimatedEmojiAnimationsUpdates(postbox: self.postbox, network: self.network).start())
|
||||
}
|
||||
self.managedOperationsDisposable.add(managedGreetingStickers(postbox: self.postbox, network: self.network).start())
|
||||
|
||||
|
@ -60,6 +60,8 @@ extension StickerPackReference {
|
||||
self = .animatedEmoji
|
||||
case let .inputStickerSetDice(emoticon):
|
||||
self = .dice(emoticon)
|
||||
case .inputStickerSetAnimatedEmojiAnimations:
|
||||
self = .animatedEmojiAnimations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,3 +11,11 @@ func managedAnimatedEmojiUpdates(postbox: Postbox, network: Network) -> Signal<V
|
||||
}
|
||||
return (poll |> then(.complete() |> suspendAwareDelay(2.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||
}
|
||||
|
||||
func managedAnimatedEmojiAnimationsUpdates(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
||||
let poll = _internal_loadedStickerPack(postbox: postbox, network: network, reference: .animatedEmojiAnimations, forceActualized: true)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
return (poll |> then(.complete() |> suspendAwareDelay(2.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
|
||||
}
|
||||
|
@ -2,10 +2,20 @@ import Foundation
|
||||
import TelegramApi
|
||||
|
||||
public struct EmojiInteraction: Equatable {
|
||||
public let animation: Int
|
||||
public struct Animation: Equatable {
|
||||
public let index: Int
|
||||
public let timeOffset: Float
|
||||
|
||||
public init(index: Int, timeOffset: Float) {
|
||||
self.index = index
|
||||
self.timeOffset = timeOffset
|
||||
}
|
||||
}
|
||||
|
||||
public init(animation: Int) {
|
||||
self.animation = animation
|
||||
public let animations: [Animation]
|
||||
|
||||
public init(animations: [Animation]) {
|
||||
self.animations = animations
|
||||
}
|
||||
|
||||
public init?(apiDataJson: Api.DataJSON) {
|
||||
@ -15,10 +25,18 @@ public struct EmojiInteraction: Equatable {
|
||||
guard let item = decodedData as? [String: Any] else {
|
||||
return nil
|
||||
}
|
||||
guard let animation = item["animation"] as? Int else {
|
||||
guard let animationsArray = item["a"] as? [Any] else {
|
||||
return nil
|
||||
}
|
||||
self.animation = animation
|
||||
var animations: [EmojiInteraction.Animation] = []
|
||||
for animationDict in animationsArray {
|
||||
if let animationDict = animationDict as? [String: Any] {
|
||||
if let index = animationDict["i"] as? Int, let timeOffset = animationDict["t"] as? Float {
|
||||
animations.append(EmojiInteraction.Animation(index: index, timeOffset: timeOffset))
|
||||
}
|
||||
}
|
||||
}
|
||||
self.animations = animations
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
@ -28,7 +46,7 @@ public struct EmojiInteraction: Equatable {
|
||||
}
|
||||
|
||||
public var apiDataJson: Api.DataJSON {
|
||||
let dict = ["animation": animation]
|
||||
let dict = ["v": 1, "a": self.animations.map({ ["i": $0.index, "t": $0.timeOffset] })] as [String : Any]
|
||||
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []), let dataString = String(data: data, encoding: .utf8) {
|
||||
return .dataJSON(data: dataString)
|
||||
} else {
|
||||
|
@ -61,7 +61,7 @@ public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMe
|
||||
if !found {
|
||||
fetchReference = packReference
|
||||
}
|
||||
case .animatedEmoji, .dice:
|
||||
case .animatedEmoji, .animatedEmojiAnimations, .dice:
|
||||
break
|
||||
}
|
||||
if let fetchReference = fetchReference {
|
||||
|
@ -44,6 +44,7 @@ public struct Namespaces {
|
||||
public static let EmojiKeywords: Int32 = 2
|
||||
public static let CloudAnimatedEmoji: Int32 = 3
|
||||
public static let CloudDice: Int32 = 4
|
||||
public static let CloudAnimatedEmojiAnimations: Int32 = 5
|
||||
}
|
||||
|
||||
public struct OrderedItemList {
|
||||
|
@ -16,6 +16,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
|
||||
case name(String)
|
||||
case animatedEmoji
|
||||
case dice(String)
|
||||
case animatedEmojiAnimations
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("r", orElse: 0) {
|
||||
@ -27,6 +28,8 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
|
||||
self = .animatedEmoji
|
||||
case 3:
|
||||
self = .dice(decoder.decodeStringForKey("e", orElse: "🎲"))
|
||||
case 4:
|
||||
self = .animatedEmojiAnimations
|
||||
default:
|
||||
self = .name("")
|
||||
assertionFailure()
|
||||
@ -47,6 +50,8 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
|
||||
case let .dice(emoji):
|
||||
encoder.encodeInt32(3, forKey: "r")
|
||||
encoder.encodeString(emoji, forKey: "e")
|
||||
case .animatedEmojiAnimations:
|
||||
encoder.encodeInt32(4, forKey: "r")
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +81,12 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .animatedEmojiAnimations:
|
||||
if case .animatedEmojiAnimations = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,9 @@ func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo,
|
||||
case .animatedEmoji:
|
||||
namespace = Namespaces.ItemCollection.CloudAnimatedEmoji
|
||||
id = 0
|
||||
case .animatedEmojiAnimations:
|
||||
namespace = Namespaces.ItemCollection.CloudAnimatedEmojiAnimations
|
||||
id = 0
|
||||
case let .dice(emoji):
|
||||
namespace = Namespaces.ItemCollection.CloudDice
|
||||
id = Int64(murMurHashString32(emoji))
|
||||
@ -104,6 +107,20 @@ func _internal_cachedStickerPack(postbox: Postbox, network: Network, reference:
|
||||
} else {
|
||||
return (.fetching, true, nil)
|
||||
}
|
||||
case .animatedEmojiAnimations:
|
||||
let namespace = Namespaces.ItemCollection.CloudAnimatedEmojiAnimations
|
||||
let id: ItemCollectionId.Id = 0
|
||||
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
|
||||
previousHash = cached.hash
|
||||
let current: CachedStickerPackResult = .result(info, cached.items, false)
|
||||
if cached.hash != info.hash {
|
||||
return (current, true, previousHash)
|
||||
} else {
|
||||
return (current, false, previousHash)
|
||||
}
|
||||
} else {
|
||||
return (.fetching, true, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result, loadRemote, previousHash in
|
||||
@ -187,7 +204,19 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference
|
||||
}
|
||||
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
|
||||
return (info, cached.items, false)
|
||||
}
|
||||
}
|
||||
case .animatedEmojiAnimations:
|
||||
let namespace = Namespaces.ItemCollection.CloudAnimatedEmojiAnimations
|
||||
let id: ItemCollectionId.Id = 0
|
||||
if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo {
|
||||
let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id))
|
||||
if !items.isEmpty {
|
||||
return (currentInfo, items, true)
|
||||
}
|
||||
}
|
||||
if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id)))) as? CachedStickerPack, let info = cached.info {
|
||||
return (info, cached.items, false)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ extension StickerPackReference {
|
||||
return .inputStickerSetAnimatedEmoji
|
||||
case let .dice(emoji):
|
||||
return .inputStickerSetDice(emoticon: emoji)
|
||||
case .animatedEmojiAnimations:
|
||||
return .inputStickerSetAnimatedEmojiAnimations
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,9 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference:
|
||||
case let .dice(emoji):
|
||||
collectionId = nil
|
||||
input = .inputStickerSetDice(emoticon: emoji)
|
||||
case .animatedEmojiAnimations:
|
||||
collectionId = nil
|
||||
input = .inputStickerSetAnimatedEmojiAnimations
|
||||
}
|
||||
|
||||
let localSignal: (ItemCollectionId) -> Signal<(ItemCollectionInfo, [ItemCollectionItem])?, NoError> = { collectionId in
|
||||
|
@ -7435,7 +7435,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
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)
|
||||
for animation in interaction.animations {
|
||||
if animation.timeOffset > 0.0 {
|
||||
Queue.mainQueue().after(Double(animation.timeOffset)) {
|
||||
itemNode.playAdditionalAnimation(index: animation.index)
|
||||
}
|
||||
} else {
|
||||
itemNode.playAdditionalAnimation(index: animation.index)
|
||||
}
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
@ -311,7 +311,7 @@ private final class ChatHistoryTransactionOpaqueState {
|
||||
}
|
||||
}
|
||||
|
||||
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?) -> ChatMessageItemAssociatedData {
|
||||
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?) -> ChatMessageItemAssociatedData {
|
||||
var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
|
||||
var contactsPeerIds: Set<PeerId> = Set()
|
||||
var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown
|
||||
@ -360,7 +360,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist
|
||||
}
|
||||
}
|
||||
|
||||
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId)
|
||||
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId)
|
||||
}
|
||||
|
||||
private extension ChatHistoryLocationInput {
|
||||
@ -788,6 +788,32 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
return animatedEmojiStickers
|
||||
}
|
||||
|
||||
let additionalAnimatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|
||||
|> map { animatedEmoji -> [String: [Int: StickerPackItem]] in
|
||||
let sequence = "0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣"
|
||||
var animatedEmojiStickers: [String: [Int: StickerPackItem]] = [:]
|
||||
switch animatedEmoji {
|
||||
case let .result(_, items, _):
|
||||
for case let item as StickerPackItem in items {
|
||||
let indexKeys = item.getStringRepresentationsOfIndexKeys()
|
||||
if indexKeys.count > 1, let emoji = indexKeys.first, let indexEmoji = indexKeys.last?.first {
|
||||
if let strIndex = sequence.firstIndex(of: indexEmoji) {
|
||||
let emoji = emoji.strippedEmoji
|
||||
let index = sequence.distance(from: sequence.startIndex, to: strIndex)
|
||||
if animatedEmojiStickers[emoji] != nil {
|
||||
animatedEmojiStickers[emoji]![index] = item
|
||||
} else {
|
||||
animatedEmojiStickers[emoji] = [index: item]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return animatedEmojiStickers
|
||||
}
|
||||
|
||||
let previousHistoryAppearsCleared = Atomic<Bool?>(value: nil)
|
||||
|
||||
let updatingMedia = context.account.pendingUpdateMessageManager.updatingMessageMedia
|
||||
@ -871,11 +897,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self.pendingUnpinnedAllMessagesPromise.get(),
|
||||
self.pendingRemovedMessagesPromise.get(),
|
||||
animatedEmojiStickers,
|
||||
additionalAnimatedEmojiStickers,
|
||||
customChannelDiscussionReadState,
|
||||
customThreadOutgoingReadState,
|
||||
self.currentlyPlayingMessageIdPromise.get(),
|
||||
adMessages
|
||||
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId, adMessages in
|
||||
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId, adMessages in
|
||||
func applyHole() {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
@ -958,7 +985,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
reverse = reverseValue
|
||||
}
|
||||
|
||||
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId)
|
||||
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId)
|
||||
|
||||
let filteredEntries = chatHistoryEntriesForView(
|
||||
location: chatLocation,
|
||||
|
@ -168,9 +168,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
private(set) var placeholderNode: StickerShimmerEffectNode
|
||||
private(set) var animationNode: GenericAnimatedStickerNode?
|
||||
private var animationSize: CGSize?
|
||||
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
||||
private var didSetUpAnimationNode = false
|
||||
private var isPlaying = false
|
||||
|
||||
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
||||
private var enqueuedAdditionalAnimations: [(Int, Double)] = []
|
||||
private var additionalAnimationsCommitTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
||||
private var swipeToReplyFeedback: HapticFeedback?
|
||||
@ -183,6 +186,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
var emojiFile: TelegramMediaFile?
|
||||
var telegramDice: TelegramMediaDice?
|
||||
private let disposable = MetaDisposable()
|
||||
private let disposables = DisposableSet()
|
||||
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
private var forwardBackgroundNode: NavigationBackgroundNode?
|
||||
@ -305,7 +309,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.disposables.dispose()
|
||||
self.mediaStatusDisposable.set(nil)
|
||||
self.additionalAnimationsCommitTimer?.invalidate()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
@ -515,6 +521,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
|
||||
}
|
||||
self.updateVisibility()
|
||||
|
||||
if let animationItems = item.associatedData.additionalAnimatedEmojiStickers[item.message.text.strippedEmoji] {
|
||||
for (_, animationItem) in animationItems {
|
||||
self.disposables.add(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: animationItem.file)).start())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1306,32 +1318,51 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
func playAdditionalAnimation(index: Int, incoming: Bool) {
|
||||
private func startAdditionalAnimationsCommitTimer() {
|
||||
guard self.additionalAnimationsCommitTimer == nil else {
|
||||
return
|
||||
}
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in
|
||||
self?.commitEnqueuedAnimations()
|
||||
self?.additionalAnimationsCommitTimer?.invalidate()
|
||||
self?.additionalAnimationsCommitTimer = nil
|
||||
}, queue: Queue.mainQueue())
|
||||
self.additionalAnimationsCommitTimer = timer
|
||||
timer.start()
|
||||
}
|
||||
|
||||
private func commitEnqueuedAnimations() {
|
||||
guard let item = self.item, !self.enqueuedAdditionalAnimations.isEmpty else {
|
||||
return
|
||||
}
|
||||
let textEmoji = item.message.text.strippedEmoji
|
||||
|
||||
let enqueuedAnimations = self.enqueuedAdditionalAnimations
|
||||
self.enqueuedAdditionalAnimations.removeAll()
|
||||
|
||||
guard let startTimestamp = enqueuedAnimations.first?.1 else {
|
||||
return
|
||||
}
|
||||
|
||||
var animations: [EmojiInteraction.Animation] = []
|
||||
for (index, timestamp) in enqueuedAnimations {
|
||||
animations.append(EmojiInteraction.Animation(index: index, timeOffset: Float(max(0.0, timestamp - startTimestamp))))
|
||||
}
|
||||
|
||||
item.context.account.updateLocalInputActivity(peerId: PeerActivitySpace(peerId: item.message.id.peerId, category: .global), activity: .interactingWithEmoji(emoticon: textEmoji, interaction: EmojiInteraction(animations: animations)), isPresent: true)
|
||||
}
|
||||
|
||||
func playAdditionalAnimation(index: Int) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
guard let animationItems = item.associatedData.additionalAnimatedEmojiStickers[textEmoji], index < 10, let file = animationItems[index]?.file 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 {
|
||||
let source = AnimatedStickerResourceSource(account: item.context.account, resource: file.resource, fitzModifier: nil)
|
||||
guard let animationSize = self.animationSize, let animationNode = self.animationNode, self.additionalAnimationNodes.count < 4 else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -1340,12 +1371,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||
|
||||
self.supernode?.view.bringSubviewToFront(self.view)
|
||||
|
||||
let resource = BundleResource(name: animationName, path: path)
|
||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id)
|
||||
|
||||
|
||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||
let additionalAnimationNode = AnimatedStickerNode()
|
||||
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)
|
||||
@ -1380,10 +1407,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.additionalAnimationNodes.append(decorationNode)
|
||||
|
||||
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? {
|
||||
@ -1484,29 +1507,51 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let heart = 0x2764
|
||||
let peach = 0x1F351
|
||||
let coffin = 0x26B0
|
||||
let fireworks = 0x1F386
|
||||
|
||||
let appConfiguration = item.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> take(1)
|
||||
|> map { view in
|
||||
return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
|
||||
}
|
||||
|
||||
if let text = self.item?.message.text, var firstScalar = text.unicodeScalars.first {
|
||||
|
||||
let text = item.message.text
|
||||
if var firstScalar = text.unicodeScalars.first {
|
||||
var textEmoji = text.strippedEmoji
|
||||
let originalTextEmoji = textEmoji
|
||||
if beatingHearts.contains(firstScalar.value) {
|
||||
textEmoji = "❤️"
|
||||
firstScalar = UnicodeScalar(heart)!
|
||||
}
|
||||
return .optionalAction({
|
||||
if firstScalar.value == heart {
|
||||
if self.additionalAnimationNodes.count % 2 == 0 {
|
||||
self.playAdditionalAnimation(index: 1, incoming: false)
|
||||
} else {
|
||||
self.playAdditionalAnimation(index: 2, incoming: false)
|
||||
if let animationItems = item.associatedData.additionalAnimatedEmojiStickers[originalTextEmoji] {
|
||||
self.startAdditionalAnimationsCommitTimer()
|
||||
|
||||
let timestamp = CACurrentMediaTime()
|
||||
let previousAnimation = self.enqueuedAdditionalAnimations.last
|
||||
|
||||
var availableAnimations = animationItems
|
||||
var delay: Double = 0.0
|
||||
if availableAnimations.count > 1, let (previousIndex, _) = previousAnimation {
|
||||
availableAnimations.removeValue(forKey: previousIndex)
|
||||
}
|
||||
if let (_, previousTimestamp) = previousAnimation {
|
||||
delay = min(0.2, max(0.0, previousTimestamp + 0.2 - timestamp))
|
||||
}
|
||||
if let index = availableAnimations.randomElement()?.0 {
|
||||
if delay > 0.0 {
|
||||
Queue.mainQueue().after(delay) {
|
||||
self.enqueuedAdditionalAnimations.append((index, timestamp + delay))
|
||||
self.playAdditionalAnimation(index: index)
|
||||
|
||||
if self.additionalAnimationsCommitTimer == nil {
|
||||
self.startAdditionalAnimationsCommitTimer()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.enqueuedAdditionalAnimations.append((index, timestamp))
|
||||
self.playAdditionalAnimation(index: index)
|
||||
}
|
||||
}
|
||||
} else if firstScalar.value == fireworks {
|
||||
self.playAdditionalAnimation(index: 1, incoming: false)
|
||||
}
|
||||
|
||||
if shouldPlay {
|
||||
|
@ -339,17 +339,23 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
}
|
||||
|
||||
override func selected() {
|
||||
let wasSelected = self.item?.selected ?? false
|
||||
super.selected()
|
||||
|
||||
if let animatedStickerNode = self.animatedStickerNode {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
let started = animatedStickerNode.playIfNeeded()
|
||||
if started {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
if !wasSelected {
|
||||
animatedStickerNode.seekTo(.frameIndex(0))
|
||||
animatedStickerNode.play()
|
||||
|
||||
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.completed = { [weak animatedStickerNode, weak self] _ in
|
||||
guard let item = self?.item, item.selected else {
|
||||
return
|
||||
}
|
||||
animatedStickerNode?.transform = CATransform3DIdentity
|
||||
animatedStickerNode?.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
@ -392,12 +398,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var emoticon = item.emoticon
|
||||
if emoticon == "🦁" {
|
||||
emoticon = "🌳"
|
||||
} else if emoticon == "🔮" {
|
||||
emoticon = "🎆"
|
||||
}
|
||||
let emoticon = item.emoticon
|
||||
let title = NSAttributedString(string: emoticon != nil ? "" : "❌", font: Font.regular(22.0), textColor: .black)
|
||||
let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
@ -405,7 +406,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
return (itemLayout, { animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
|
||||
if updatedThemeReference || updatedWallpaper {
|
||||
if let themeReference = item.themeReference {
|
||||
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, emoticon: true))
|
||||
@ -420,6 +421,13 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
strongSelf.overlayNode.image = generateBorderImage(theme: item.theme, bordered: false, selected: item.selected)
|
||||
}
|
||||
|
||||
if !item.selected && currentItem?.selected == true, let animatedStickerNode = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode.transform = CATransform3DIdentity
|
||||
|
||||
let initialScale: CGFloat = CGFloat((animatedStickerNode.value(forKeyPath: "layer.presentationLayer.transform.scale.x") as? NSNumber)?.floatValue ?? 1.0)
|
||||
animatedStickerNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.45)
|
||||
}
|
||||
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((90.0 - textLayout.size.width) / 2.0), y: 24.0), size: textLayout.size)
|
||||
strongSelf.textNode.isHidden = item.emoticon != nil
|
||||
|
||||
@ -823,12 +831,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
var entries: [ThemeSettingsThemeEntry] = []
|
||||
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 == "🦁" {
|
||||
emoticon = "🌳"
|
||||
} else if emoticon == "🔮" {
|
||||
emoticon = "🎆"
|
||||
}
|
||||
let emoticon = theme.emoji
|
||||
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
|
||||
}
|
||||
|
||||
|
@ -344,7 +344,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
case .choosingSticker:
|
||||
stringValue = strings.Activity_ChoosingSticker
|
||||
case let .seeingEmojiInteraction(emoticon):
|
||||
stringValue = "enjoying \(emoticon) animations"
|
||||
stringValue = strings.Activity_EnjoyingAnimations(emoticon).string
|
||||
case .speakingInGroupCall, .interactingWithEmoji:
|
||||
stringValue = ""
|
||||
}
|
||||
|
@ -45,11 +45,18 @@ public func explicitUrl(_ url: String) -> String {
|
||||
return url
|
||||
}
|
||||
|
||||
private let validUrlSet: CharacterSet = {
|
||||
var set = CharacterSet(charactersIn: "a".unicodeScalars.first! ... "z".unicodeScalars.first!)
|
||||
set.insert(charactersIn: "A".unicodeScalars.first! ... "Z".unicodeScalars.first!)
|
||||
set.insert(charactersIn: "0".unicodeScalars.first! ... "9".unicodeScalars.first!)
|
||||
set.insert(charactersIn: ".?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ ")
|
||||
return set
|
||||
}()
|
||||
|
||||
public func urlEncodedStringFromString(_ string: String) -> String {
|
||||
var nsString: NSString = string as NSString
|
||||
if let value = nsString.removingPercentEncoding {
|
||||
nsString = value as NSString
|
||||
}
|
||||
|
||||
return nsString.addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: "?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ ")) ?? ""
|
||||
return nsString.addingPercentEncoding(withAllowedCharacters: validUrlSet) ?? ""
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user