Update API

This commit is contained in:
Ali 2022-08-19 01:05:37 +03:00
parent 29fe0f0879
commit b4f8d20d7b
13 changed files with 391 additions and 246 deletions

View File

@ -225,7 +225,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
var attributes: [MessageAttribute] = []
if let reaction = item.reaction {
attributes.append(ReactionsMessageAttribute(canViewList: false, reactions: [MessageReaction(value: reaction, count: 1, isSelected: true)], recentPeers: []))
attributes.append(ReactionsMessageAttribute(canViewList: false, reactions: [MessageReaction(value: reaction, count: 1, chosenOrder: 0)], recentPeers: []))
}
let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[userPeerId], text: messageText, attributes: attributes, media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: item.availableReactions, isCentered: true)

View File

@ -612,7 +612,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1992950669] = { return Api.Reaction.parse_reactionCustomEmoji($0) }
dict[455247544] = { return Api.Reaction.parse_reactionEmoji($0) }
dict[2046153753] = { return Api.Reaction.parse_reactionEmpty($0) }
dict[609529328] = { return Api.ReactionCount.parse_reactionCount($0) }
dict[-1546531968] = { return Api.ReactionCount.parse_reactionCount($0) }
dict[-1551583367] = { return Api.ReceivedNotifyMessage.parse_receivedNotifyMessage($0) }
dict[-1294306862] = { return Api.RecentMeUrl.parse_recentMeUrlChat($0) }
dict[-347535331] = { return Api.RecentMeUrl.parse_recentMeUrlChatInvite($0) }
@ -829,10 +829,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1821035490] = { return Api.Update.parse_updateSavedGifs($0) }
dict[1960361625] = { return Api.Update.parse_updateSavedRingtones($0) }
dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) }
dict[1135492588] = { return Api.Update.parse_updateStickerSets($0) }
dict[834816008] = { return Api.Update.parse_updateStickerSets($0) }
dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) }
dict[-2112423005] = { return Api.Update.parse_updateTheme($0) }
dict[8703322] = { return Api.Update.parse_updateTranscribedAudio($0) }
dict[674706841] = { return Api.Update.parse_updateUserEmojiStatus($0) }
dict[-1007549728] = { return Api.Update.parse_updateUserName($0) }
dict[88680979] = { return Api.Update.parse_updateUserPhone($0) }
dict[-232290676] = { return Api.Update.parse_updateUserPhoto($0) }

View File

@ -68,15 +68,16 @@ public extension Api {
}
public extension Api {
enum ReactionCount: TypeConstructorDescription {
case reactionCount(flags: Int32, reaction: Api.Reaction, count: Int32)
case reactionCount(flags: Int32, chosenOrder: Int32?, reaction: Api.Reaction, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .reactionCount(let flags, let reaction, let count):
case .reactionCount(let flags, let chosenOrder, let reaction, let count):
if boxed {
buffer.appendInt32(609529328)
buffer.appendInt32(-1546531968)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(chosenOrder!, buffer: buffer, boxed: false)}
reaction.serialize(buffer, true)
serializeInt32(count, buffer: buffer, boxed: false)
break
@ -85,25 +86,28 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .reactionCount(let flags, let reaction, let count):
return ("reactionCount", [("flags", String(describing: flags)), ("reaction", String(describing: reaction)), ("count", String(describing: count))])
case .reactionCount(let flags, let chosenOrder, let reaction, let count):
return ("reactionCount", [("flags", String(describing: flags)), ("chosenOrder", String(describing: chosenOrder)), ("reaction", String(describing: reaction)), ("count", String(describing: count))])
}
}
public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Reaction?
var _2: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() }
var _3: Api.Reaction?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Reaction
_3 = Api.parse(reader, signature: signature) as? Api.Reaction
}
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.ReactionCount.reactionCount(flags: _1!, reaction: _2!, count: _3!)
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.ReactionCount.reactionCount(flags: _1!, chosenOrder: _2, reaction: _3!, count: _4!)
}
else {
return nil

View File

@ -644,10 +644,11 @@ public extension Api {
case updateSavedGifs
case updateSavedRingtones
case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity])
case updateStickerSets
case updateStickerSets(flags: Int32)
case updateStickerSetsOrder(flags: Int32, order: [Int64])
case updateTheme(theme: Api.Theme)
case updateTranscribedAudio(flags: Int32, peer: Api.Peer, msgId: Int32, transcriptionId: Int64, text: String)
case updateUserEmojiStatus(userId: Int64, emojiStatus: Api.EmojiStatus)
case updateUserName(userId: Int64, firstName: String, lastName: String, username: String)
case updateUserPhone(userId: Int64, phone: String)
case updateUserPhoto(userId: Int64, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool)
@ -1461,11 +1462,11 @@ public extension Api {
item.serialize(buffer, true)
}
break
case .updateStickerSets:
case .updateStickerSets(let flags):
if boxed {
buffer.appendInt32(1135492588)
buffer.appendInt32(834816008)
}
serializeInt32(flags, buffer: buffer, boxed: false)
break
case .updateStickerSetsOrder(let flags, let order):
if boxed {
@ -1494,6 +1495,13 @@ public extension Api {
serializeInt64(transcriptionId, buffer: buffer, boxed: false)
serializeString(text, buffer: buffer, boxed: false)
break
case .updateUserEmojiStatus(let userId, let emojiStatus):
if boxed {
buffer.appendInt32(674706841)
}
serializeInt64(userId, buffer: buffer, boxed: false)
emojiStatus.serialize(buffer, true)
break
case .updateUserName(let userId, let firstName, let lastName, let username):
if boxed {
buffer.appendInt32(-1007549728)
@ -1736,14 +1744,16 @@ public extension Api {
return ("updateSavedRingtones", [])
case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities):
return ("updateServiceNotification", [("flags", String(describing: flags)), ("inboxDate", String(describing: inboxDate)), ("type", String(describing: type)), ("message", String(describing: message)), ("media", String(describing: media)), ("entities", String(describing: entities))])
case .updateStickerSets:
return ("updateStickerSets", [])
case .updateStickerSets(let flags):
return ("updateStickerSets", [("flags", String(describing: flags))])
case .updateStickerSetsOrder(let flags, let order):
return ("updateStickerSetsOrder", [("flags", String(describing: flags)), ("order", String(describing: order))])
case .updateTheme(let theme):
return ("updateTheme", [("theme", String(describing: theme))])
case .updateTranscribedAudio(let flags, let peer, let msgId, let transcriptionId, let text):
return ("updateTranscribedAudio", [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))])
case .updateUserEmojiStatus(let userId, let emojiStatus):
return ("updateUserEmojiStatus", [("userId", String(describing: userId)), ("emojiStatus", String(describing: emojiStatus))])
case .updateUserName(let userId, let firstName, let lastName, let username):
return ("updateUserName", [("userId", String(describing: userId)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("username", String(describing: username))])
case .updateUserPhone(let userId, let phone):
@ -3377,7 +3387,15 @@ public extension Api {
}
}
public static func parse_updateStickerSets(_ reader: BufferReader) -> Update? {
return Api.Update.updateStickerSets
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.Update.updateStickerSets(flags: _1!)
}
else {
return nil
}
}
public static func parse_updateStickerSetsOrder(_ reader: BufferReader) -> Update? {
var _1: Int32?
@ -3433,6 +3451,22 @@ public extension Api {
return nil
}
}
public static func parse_updateUserEmojiStatus(_ reader: BufferReader) -> Update? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Api.EmojiStatus?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.EmojiStatus
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updateUserEmojiStatus(userId: _1!, emojiStatus: _2!)
}
else {
return nil
}
}
public static func parse_updateUserName(_ reader: BufferReader) -> Update? {
var _1: Int64?
_1 = reader.readInt64()

View File

@ -5726,13 +5726,17 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.messages {
static func sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: Api.Reaction?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: [Api.Reaction]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1526634933)
buffer.appendInt32(-754091820)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {reaction!.serialize(buffer, true)}
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(reaction!.count))
for item in reaction! {
item.serialize(buffer, true)
}}
return (FunctionDescription(name: "messages.sendReaction", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("reaction", String(describing: reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
@ -6466,23 +6470,6 @@ public extension Api.functions.payments {
})
}
}
public extension Api.functions.payments {
static func requestRecurringPayment(userId: Api.InputUser, recurringInitCharge: String, invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(342791565)
userId.serialize(buffer, true)
serializeString(recurringInitCharge, buffer: buffer, boxed: false)
invoiceMedia.serialize(buffer, true)
return (FunctionDescription(name: "payments.requestRecurringPayment", parameters: [("userId", String(describing: userId)), ("recurringInitCharge", String(describing: recurringInitCharge)), ("invoiceMedia", String(describing: invoiceMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.payments {
static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentResult>) {
let buffer = Buffer()

View File

@ -10,9 +10,9 @@ extension ReactionsMessageAttribute {
let canViewList = (flags & (1 << 2)) != 0
var reactions = results.compactMap { result -> MessageReaction? in
switch result {
case let .reactionCount(flags, reaction, count):
case let .reactionCount(_, chosenOrder, reaction, count):
if let reaction = MessageReaction.Reaction(apiReaction: reaction) {
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
return MessageReaction(value: reaction, count: count, chosenOrder: chosenOrder.flatMap(Int.init))
} else {
return nil
}
@ -37,17 +37,17 @@ extension ReactionsMessageAttribute {
}
if min {
var currentSelectedReaction: MessageReaction.Reaction?
var currentSelectedReactions: [MessageReaction.Reaction: Int] = [:]
for reaction in self.reactions {
if reaction.isSelected {
currentSelectedReaction = reaction.value
if let chosenOrder = reaction.chosenOrder {
currentSelectedReactions[reaction.value] = chosenOrder
break
}
}
if let currentSelectedReaction = currentSelectedReaction {
if !currentSelectedReactions.isEmpty {
for i in 0 ..< reactions.count {
if reactions[i].value == currentSelectedReaction {
reactions[i].isSelected = true
if let chosenOrder = currentSelectedReactions[reactions[i].value] {
reactions[i].chosenOrder = chosenOrder
}
}
}
@ -76,6 +76,52 @@ public func mergedMessageReactionsAndPeers(message: Message) -> (reactions: [Mes
return (attribute.reactions, recentPeers)
}
private func mergeReactions(reactions: [MessageReaction], recentPeers: [ReactionsMessageAttribute.RecentPeer], pending: [PendingReactionsMessageAttribute.PendingReaction], accountPeerId: PeerId) -> ([MessageReaction], [ReactionsMessageAttribute.RecentPeer]) {
var result = reactions
var recentPeers = recentPeers
for pendingReaction in pending {
if let index = result.firstIndex(where: { $0.value == pendingReaction.value }) {
var merged = result[index]
if merged.chosenOrder == nil {
merged.chosenOrder = Int(Int32.max)
merged.count += 1
}
result[index] = merged
} else {
result.append(MessageReaction(value: pendingReaction.value, count: 1, chosenOrder: Int(Int32.max)))
}
if let index = recentPeers.firstIndex(where: { $0.value == pendingReaction.value && $0.peerId == accountPeerId }) {
recentPeers.remove(at: index)
}
recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: pendingReaction.value, isLarge: false, isUnseen: false, peerId: accountPeerId))
}
for i in (0 ..< result.count).reversed() {
if result[i].chosenOrder != nil {
if !pending.contains(where: { $0.value == result[i].value }) {
if let index = recentPeers.firstIndex(where: { $0.value == result[i].value && $0.peerId == accountPeerId }) {
recentPeers.remove(at: index)
}
if result[i].count <= 1 {
result.remove(at: i)
} else {
result[i].count -= 1
result[i].chosenOrder = nil
}
}
}
}
if recentPeers.count > 3 {
recentPeers.removeFirst(recentPeers.count - 3)
}
return (result, recentPeers)
}
public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsMessageAttribute? {
var current: ReactionsMessageAttribute?
var pending: PendingReactionsMessageAttribute?
@ -87,45 +133,14 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
}
}
if let pending = pending {
if let pending = pending, let accountPeerId = pending.accountPeerId {
var reactions = current?.reactions ?? []
var recentPeers = current?.recentPeers ?? []
if let value = pending.value {
var found = false
for i in 0 ..< reactions.count {
if reactions[i].value == value {
found = true
if !reactions[i].isSelected {
reactions[i].isSelected = true
reactions[i].count += 1
}
}
}
if !found {
reactions.append(MessageReaction(value: value, count: 1, isSelected: true))
}
}
if let accountPeerId = pending.accountPeerId {
for i in 0 ..< recentPeers.count {
if recentPeers[i].peerId == accountPeerId {
recentPeers.remove(at: i)
break
}
}
if let value = pending.value {
recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: value, isLarge: false, isUnseen: false, peerId: accountPeerId))
}
}
for i in (0 ..< reactions.count).reversed() {
if reactions[i].isSelected, pending.value != reactions[i].value {
if reactions[i].count == 1 {
reactions.remove(at: i)
} else {
reactions[i].isSelected = false
reactions[i].count -= 1
}
}
}
let (updatedReactions, updatedRecentPeers) = mergeReactions(reactions: reactions, recentPeers: recentPeers, pending: pending.reactions, accountPeerId: accountPeerId)
reactions = updatedReactions
recentPeers = updatedRecentPeers
if !reactions.isEmpty {
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, reactions: reactions, recentPeers: recentPeers)
} else {
@ -165,9 +180,9 @@ extension ReactionsMessageAttribute {
canViewList: canViewList,
reactions: results.compactMap { result -> MessageReaction? in
switch result {
case let .reactionCount(flags, reaction, count):
case let .reactionCount(_, chosenOrder, reaction, count):
if let reaction = MessageReaction.Reaction(apiReaction: reaction) {
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
return MessageReaction(value: reaction, count: count, chosenOrder: chosenOrder.flatMap(Int.init))
} else {
return nil
}

View File

@ -14,10 +14,10 @@ private func reactionGeneratedEvent(_ previousReactions: ReactionsMessageAttribu
})
}
let myUpdated = updatedReactions.reactions.filter { value in
return value.isSelected
return value.chosenOrder != nil
}.first
let myPrevious = prev.filter { value in
return value.isSelected
return value.chosenOrder != nil
}.first
let previousCount = prev.reduce(0, {
@ -28,7 +28,7 @@ private func reactionGeneratedEvent(_ previousReactions: ReactionsMessageAttribu
})
let newReaction = updated.filter {
!$0.isSelected
$0.chosenOrder == nil
}.first?.value
if !updated.isEmpty && myUpdated == myPrevious, updatedCount >= previousCount, let value = newReaction {

View File

@ -18,20 +18,29 @@ public enum UpdateMessageReaction {
}
}
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: UpdateMessageReaction?, isLarge: Bool) -> Signal<Never, NoError> {
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reactions: [UpdateMessageReaction], isLarge: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
let mappedReaction: MessageReaction.Reaction?
let isPremium = (transaction.getPeer(account.peerId) as? TelegramUser)?.isPremium ?? false
let appConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue
let maxCount: Int
if isPremium {
let limitsConfiguration = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: isPremium)
maxCount = Int(limitsConfiguration.maxReactionsPerMessage)
} else {
maxCount = 1
}
switch reaction {
case .none:
mappedReaction = nil
case let .custom(fileId, file):
mappedReaction = .custom(fileId)
if let file = file {
transaction.storeMediaIfNotPresent(media: file)
var mappedReactions: [PendingReactionsMessageAttribute.PendingReaction] = []
for reaction in reactions {
switch reaction {
case let .custom(fileId, file):
mappedReactions.append(PendingReactionsMessageAttribute.PendingReaction(value: .custom(fileId)))
if let file = file {
transaction.storeMediaIfNotPresent(media: file)
}
case let .builtin(value):
mappedReactions.append(PendingReactionsMessageAttribute.PendingReaction(value: .builtin(value)))
}
case let .builtin(value):
mappedReaction = .builtin(value)
}
transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction())
@ -47,7 +56,20 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
break loop
}
}
attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, value: mappedReaction, isLarge: isLarge))
var mappedReactions = mappedReactions
let updatedReactions = mergedMessageReactions(attributes: attributes + [PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge)])?.reactions ?? []
let updatedOutgoingReactions = updatedReactions.filter(\.isSelected)
if updatedOutgoingReactions.count > maxCount {
let sortedOutgoingReactions = updatedOutgoingReactions.sorted(by: { $0.chosenOrder! < $1.chosenOrder! })
mappedReactions = Array(sortedOutgoingReactions.suffix(maxCount).map { reaction -> PendingReactionsMessageAttribute.PendingReaction in
return PendingReactionsMessageAttribute.PendingReaction(value: reaction.value)
})
}
attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge))
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
@ -59,27 +81,29 @@ private enum RequestUpdateMessageReactionError {
}
private func requestUpdateMessageReaction(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, RequestUpdateMessageReactionError> {
return postbox.transaction { transaction -> (Peer, MessageReaction.Reaction?, Bool)? in
return postbox.transaction { transaction -> (Peer, [MessageReaction.Reaction]?, Bool)? in
guard let peer = transaction.getPeer(messageId.peerId) else {
return nil
}
guard let message = transaction.getMessage(messageId) else {
return nil
}
var value: MessageReaction.Reaction?
var reactions: [MessageReaction.Reaction]?
var isLarge: Bool = false
for attribute in message.attributes {
if let attribute = attribute as? PendingReactionsMessageAttribute {
value = attribute.value
if !attribute.reactions.isEmpty {
reactions = attribute.reactions.map(\.value)
}
isLarge = attribute.isLarge
break
}
}
return (peer, value, isLarge)
return (peer, reactions, isLarge)
}
|> castError(RequestUpdateMessageReactionError.self)
|> mapToSignal { peerAndValue in
guard let (peer, value, isLarge) = peerAndValue else {
guard let (peer, reactions, isLarge) = peerAndValue else {
return .fail(.generic)
}
guard let inputPeer = apiInputPeer(peer) else {
@ -90,14 +114,14 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st
}
var flags: Int32 = 0
if value != nil {
if reactions != nil {
flags |= 1 << 0
if isLarge {
flags |= 1 << 1
}
}
let signal: Signal<Never, RequestUpdateMessageReactionError> = network.request(Api.functions.messages.sendReaction(flags: flags, peer: inputPeer, msgId: messageId.id, reaction: value?.apiReaction))
let signal: Signal<Never, RequestUpdateMessageReactionError> = network.request(Api.functions.messages.sendReaction(flags: flags, peer: inputPeer, msgId: messageId.id, reaction: reactions?.map(\.apiReaction)))
|> mapError { _ -> RequestUpdateMessageReactionError in
return .generic
}
@ -276,7 +300,7 @@ public extension EngineMessageReactionListContext.State {
if let reactionsAttribute = message._asMessage().reactionsAttribute {
for messageReaction in reactionsAttribute.reactions {
if reaction == nil || messageReaction.value == reaction {
if messageReaction.isSelected {
if messageReaction.chosenOrder != nil {
hasOutgoingReaction = true
}
totalCount += Int(messageReaction.count)

View File

@ -13,6 +13,7 @@ public struct UserLimitsConfiguration: Equatable {
public let maxUploadFileParts: Int32
public let maxAboutLength: Int32
public let maxAnimatedEmojisInText: Int32
public let maxReactionsPerMessage: Int32
public static var defaultValue: UserLimitsConfiguration {
return UserLimitsConfiguration(
@ -26,7 +27,8 @@ public struct UserLimitsConfiguration: Equatable {
maxCaptionLength: 1024,
maxUploadFileParts: 4000,
maxAboutLength: 70,
maxAnimatedEmojisInText: 10
maxAnimatedEmojisInText: 10,
maxReactionsPerMessage: 1
)
}
@ -41,7 +43,8 @@ public struct UserLimitsConfiguration: Equatable {
maxCaptionLength: Int32,
maxUploadFileParts: Int32,
maxAboutLength: Int32,
maxAnimatedEmojisInText: Int32
maxAnimatedEmojisInText: Int32,
maxReactionsPerMessage: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxChannelsCount = maxChannelsCount
@ -54,6 +57,7 @@ public struct UserLimitsConfiguration: Equatable {
self.maxUploadFileParts = maxUploadFileParts
self.maxAboutLength = maxAboutLength
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
self.maxReactionsPerMessage = maxReactionsPerMessage
}
}
@ -89,5 +93,6 @@ extension UserLimitsConfiguration {
self.maxUploadFileParts = getValue("upload_max_fileparts", orElse: defaultValue.maxUploadFileParts)
self.maxAboutLength = getValue("about_length_limit", orElse: defaultValue.maxAboutLength)
self.maxAnimatedEmojisInText = getGeneralValue("message_animated_emoji_max", orElse: defaultValue.maxAnimatedEmojisInText)
self.maxReactionsPerMessage = getGeneralValue("reactions_user_max", orElse: 1)
}
}

View File

@ -2,7 +2,7 @@ import Postbox
import TelegramApi
public struct MessageReaction: Equatable, PostboxCoding {
public enum Reaction: Hashable, Codable {
public enum Reaction: Hashable, Codable, PostboxCoding {
case builtin(String)
case custom(Int64)
@ -16,6 +16,14 @@ public struct MessageReaction: Equatable, PostboxCoding {
}
}
public init(decoder: PostboxDecoder) {
if let value = decoder.decodeOptionalStringForKey("v") {
self = .builtin(value)
} else {
self = .custom(decoder.decodeInt64ForKey("cfid", orElse: 0))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
@ -26,16 +34,29 @@ public struct MessageReaction: Equatable, PostboxCoding {
try container.encode(fileId, forKey: "cfid")
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case let .builtin(value):
encoder.encodeString(value, forKey: "v")
case let .custom(fileId):
encoder.encodeInt64(fileId, forKey: "cfid")
}
}
}
public var value: Reaction
public var count: Int32
public var isSelected: Bool
public var chosenOrder: Int?
public init(value: Reaction, count: Int32, isSelected: Bool) {
public var isSelected: Bool {
return self.chosenOrder != nil
}
public init(value: Reaction, count: Int32, chosenOrder: Int?) {
self.value = value
self.count = count
self.isSelected = isSelected
self.chosenOrder = chosenOrder
}
public init(decoder: PostboxDecoder) {
@ -45,7 +66,13 @@ public struct MessageReaction: Equatable, PostboxCoding {
self.value = .custom(decoder.decodeInt64ForKey("cfid", orElse: 0))
}
self.count = decoder.decodeInt32ForKey("c", orElse: 0)
self.isSelected = decoder.decodeInt32ForKey("s", orElse: 0) != 0
if let chosenOrder = decoder.decodeOptionalInt32ForKey("cord") {
self.chosenOrder = Int(chosenOrder)
} else if let isSelected = decoder.decodeOptionalInt32ForKey("s"), isSelected != 0 {
self.chosenOrder = 0
} else {
self.chosenOrder = nil
}
}
public func encode(_ encoder: PostboxEncoder) {
@ -56,7 +83,11 @@ public struct MessageReaction: Equatable, PostboxCoding {
encoder.encodeInt64(fileId, forKey: "cfid")
}
encoder.encodeInt32(self.count, forKey: "c")
encoder.encodeInt32(self.isSelected ? 1 : 0, forKey: "s")
if let chosenOrder = self.chosenOrder {
encoder.encodeInt32(Int32(chosenOrder), forKey: "cord")
} else {
encoder.encodeNil(forKey: "cord")
}
}
}
@ -182,8 +213,24 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
}
public final class PendingReactionsMessageAttribute: MessageAttribute {
public struct PendingReaction: Equatable, PostboxCoding {
public var value: MessageReaction.Reaction
public init(value: MessageReaction.Reaction) {
self.value = value
}
public init(decoder: PostboxDecoder) {
self.value = decoder.decodeObjectForKey("val", decoder: { MessageReaction.Reaction(decoder: $0) }) as! MessageReaction.Reaction
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.value, forKey: "val")
}
}
public let accountPeerId: PeerId?
public let value: MessageReaction.Reaction?
public let reactions: [PendingReaction]
public let isLarge: Bool
public var associatedPeerIds: [PeerId] {
@ -194,21 +241,15 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
}
}
public init(accountPeerId: PeerId?, value: MessageReaction.Reaction?, isLarge: Bool) {
public init(accountPeerId: PeerId?, reactions: [PendingReaction], isLarge: Bool) {
self.accountPeerId = accountPeerId
self.value = value
self.reactions = reactions
self.isLarge = isLarge
}
required public init(decoder: PostboxDecoder) {
self.accountPeerId = decoder.decodeOptionalInt64ForKey("ap").flatMap(PeerId.init)
if let value = decoder.decodeOptionalStringForKey("v") {
self.value = .builtin(value)
} else if let fileId = decoder.decodeOptionalInt64ForKey("cfid") {
self.value = .custom(fileId)
} else {
self.value = nil
}
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("reac")
self.isLarge = decoder.decodeInt32ForKey("l", orElse: 0) != 0
}
@ -218,16 +259,9 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
} else {
encoder.encodeNil(forKey: "ap")
}
if let value = self.value {
switch value {
case let .builtin(value):
encoder.encodeString(value, forKey: "v")
case let .custom(fileId):
encoder.encodeInt64(fileId, forKey: "cfid")
}
} else {
encoder.encodeNil(forKey: "v")
}
encoder.encodeObjectArray(self.reactions, forKey: "reac")
encoder.encodeInt32(self.isLarge ? 1 : 0, forKey: "l")
}
}

View File

@ -62,6 +62,7 @@ public enum EngineConfiguration {
public let maxUploadFileParts: Int32
public let maxAboutLength: Int32
public let maxAnimatedEmojisInText: Int32
public let maxReactionsPerMessage: Int32
public static var defaultValue: UserLimits {
return UserLimits(UserLimitsConfiguration.defaultValue)
@ -78,7 +79,8 @@ public enum EngineConfiguration {
maxCaptionLength: Int32,
maxUploadFileParts: Int32,
maxAboutLength: Int32,
maxAnimatedEmojisInText: Int32
maxAnimatedEmojisInText: Int32,
maxReactionsPerMessage: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxChannelsCount = maxChannelsCount
@ -91,6 +93,7 @@ public enum EngineConfiguration {
self.maxUploadFileParts = maxUploadFileParts
self.maxAboutLength = maxAboutLength
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
self.maxReactionsPerMessage = maxReactionsPerMessage
}
}
}
@ -148,7 +151,8 @@ public extension EngineConfiguration.UserLimits {
maxCaptionLength: userLimitsConfiguration.maxCaptionLength,
maxUploadFileParts: userLimitsConfiguration.maxUploadFileParts,
maxAboutLength: userLimitsConfiguration.maxAboutLength,
maxAnimatedEmojisInText: userLimitsConfiguration.maxAnimatedEmojisInText
maxAnimatedEmojisInText: userLimitsConfiguration.maxAnimatedEmojisInText,
maxReactionsPerMessage: userLimitsConfiguration.maxReactionsPerMessage
)
}
}

View File

@ -330,7 +330,7 @@ public extension Message {
}
for attribute in self.attributes {
if let attribute = attribute as? PendingReactionsMessageAttribute {
return attribute.value != nil
return !attribute.reactions.isEmpty
}
}
return false

View File

@ -1308,7 +1308,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.push(controller)
}
controller.reactionSelected = { [weak controller] reaction, isLarge in
controller.reactionSelected = { [weak controller] chosenUpdatedReaction, isLarge in
guard let strongSelf = self else {
return
}
@ -1317,7 +1317,97 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
var updatedReaction: UpdateMessageReaction? = reaction
let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction
let currentReactions = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
var updatedReactions: [MessageReaction.Reaction] = currentReactions.filter(\.isSelected).map(\.value)
var removedReaction: MessageReaction.Reaction?
var isFirst = false
if let index = updatedReactions.firstIndex(where: { $0 == chosenReaction }) {
removedReaction = chosenReaction
updatedReactions.remove(at: index)
} else {
updatedReactions.append(chosenReaction)
isFirst = !currentReactions.contains(where: { $0.value == chosenReaction })
}
/*guard let allowedReactions = allowedReactions else {
itemNode.openMessageContextMenu()
return
}
switch allowedReactions {
case let .set(set):
if !messageAlreadyHasThisReaction && updatedReactions.contains(where: { !set.contains($0) }) {
itemNode.openMessageContextMenu()
return
}
case .all:
break
}*/
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
if item.message.id == message.id {
if removedReaction == nil && !updatedReactions.isEmpty {
itemNode.awaitingAppliedReaction = (chosenReaction, { [weak itemNode] in
guard let controller = controller else {
return
}
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) {
strongSelf.chatDisplayNode.messageTransitionNode.addMessageContextController(messageId: item.message.id, contextController: controller)
var hideTargetButton: UIView?
if isFirst {
hideTargetButton = targetView.superview
}
controller.dismissWithReaction(value: chosenReaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
}, completion: { [weak itemNode, weak targetView] in
guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else {
return
}
let _ = strongSelf
let _ = itemNode
let _ = targetView
})
}
})
} else {
itemNode.awaitingAppliedReaction = (nil, {
controller?.dismiss()
})
}
}
}
}
let mappedUpdatedReactions = updatedReactions.map { reaction -> UpdateMessageReaction in
switch reaction {
case let .builtin(value):
return .builtin(value)
case let .custom(fileId):
var customFile: TelegramMediaFile?
if case let .custom(customFileId, file) = chosenUpdatedReaction, fileId == customFileId {
customFile = file
}
return .custom(fileId: fileId, file: customFile)
}
}
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: isLarge).start()
/*let currentReactions = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
var updatedReactions: [MessageReaction.Reaction] = currentReactions.filter(\.isSelected).map(\.value)
var isFirst = true
for attribute in topMessage.attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
@ -1336,49 +1426,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
if item.message.id == message.id {
if let updatedReaction = updatedReaction {
itemNode.awaitingAppliedReaction = (updatedReaction.reaction, { [weak itemNode] in
guard let controller = controller else {
return
}
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: updatedReaction.reaction) {
strongSelf.chatDisplayNode.messageTransitionNode.addMessageContextController(messageId: item.message.id, contextController: controller)
var hideTargetButton: UIView?
if isFirst {
hideTargetButton = targetView.superview
}
controller.dismissWithReaction(value: updatedReaction.reaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
}, completion: { [weak itemNode, weak targetView] in
guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else {
return
}
let _ = strongSelf
let _ = itemNode
let _ = targetView
})
}
})
} else if updatedReaction == nil {
itemNode.awaitingAppliedReaction = (nil, {
controller?.dismiss()
})
}
}
}
}
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction, isLarge: isLarge).start()
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction, isLarge: isLarge).start()*/
}
strongSelf.forEachController({ controller in
@ -1461,92 +1510,71 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
var updatedReaction: UpdateMessageReaction?
let chosenReaction: MessageReaction.Reaction?
switch reaction {
case .default:
switch item.associatedData.defaultReaction {
case .none:
updatedReaction = nil
chosenReaction = nil
case let .builtin(value):
updatedReaction = .builtin(value)
chosenReaction = .builtin(value)
case let .custom(fileId):
updatedReaction = .custom(fileId: fileId, file: nil)
chosenReaction = .custom(fileId)
}
case let .reaction(value):
switch value {
case let .builtin(value):
updatedReaction = .builtin(value)
chosenReaction = .builtin(value)
case let .custom(fileId):
updatedReaction = .custom(fileId: fileId, file: nil)
chosenReaction = .custom(fileId)
}
}
guard let chosenReaction = chosenReaction else {
return
}
var removedReaction: MessageReaction.Reaction?
var messageAlreadyHasThisReaction = false
for attribute in message.attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
for listReaction in attribute.reactions {
switch reaction {
case .default:
if listReaction.isSelected {
updatedReaction = nil
removedReaction = listReaction.value
} else if listReaction.value == updatedReaction?.reaction {
messageAlreadyHasThisReaction = true
}
case let .reaction(value):
if listReaction.value == value {
messageAlreadyHasThisReaction = true
if listReaction.isSelected {
updatedReaction = nil
removedReaction = value
}
}
}
}
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
if attribute.value != nil {
switch reaction {
case .default:
updatedReaction = nil
removedReaction = attribute.value
case let .reaction(value):
if attribute.value == value {
updatedReaction = nil
removedReaction = value
}
}
}
}
let currentReactions = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
var updatedReactions: [MessageReaction.Reaction] = currentReactions.filter(\.isSelected).map(\.value)
if let index = updatedReactions.firstIndex(where: { $0 == chosenReaction }) {
removedReaction = chosenReaction
updatedReactions.remove(at: index)
} else {
updatedReactions.append(chosenReaction)
messageAlreadyHasThisReaction = currentReactions.contains(where: { $0.value == chosenReaction })
}
if let updatedReaction = updatedReaction {
guard let allowedReactions = allowedReactions else {
guard let allowedReactions = allowedReactions else {
itemNode.openMessageContextMenu()
return
}
switch allowedReactions {
case let .set(set):
if !messageAlreadyHasThisReaction && updatedReactions.contains(where: { !set.contains($0) }) {
itemNode.openMessageContextMenu()
return
}
switch allowedReactions {
case let .set(set):
if !messageAlreadyHasThisReaction && !set.contains(updatedReaction.reaction) {
itemNode.openMessageContextMenu()
return
}
case .all:
break
}
case .all:
break
}
if removedReaction == nil && !updatedReactions.isEmpty {
if strongSelf.selectPollOptionFeedback == nil {
strongSelf.selectPollOptionFeedback = HapticFeedback()
}
strongSelf.selectPollOptionFeedback?.tap()
itemNode.awaitingAppliedReaction = (updatedReaction.reaction, { [weak itemNode] in
itemNode.awaitingAppliedReaction = (chosenReaction, { [weak itemNode] in
guard let strongSelf = self else {
return
}
if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction.reaction) {
if let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: chosenReaction) {
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
@ -1555,7 +1583,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
continue
}
if reaction.value == updatedReaction.reaction {
if reaction.value == chosenReaction {
let standaloneReactionAnimation = StandaloneReactionAnimation()
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
@ -1620,7 +1648,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction, isLarge: false).start()
let mappedUpdatedReactions = updatedReactions.map { reaction -> UpdateMessageReaction in
switch reaction {
case let .builtin(value):
return .builtin(value)
case let .custom(fileId):
return .custom(fileId: fileId, file: nil)
}
}
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: false).start()
}
})
}, activateMessagePinch: { [weak self] sourceNode in