Reaction and status improvements

This commit is contained in:
Ali 2022-08-23 18:26:41 +03:00
parent 5d969ed2af
commit 53690ab199
22 changed files with 866 additions and 263 deletions

View File

@ -7977,3 +7977,5 @@ Sorry for the inconvenience.";
"Premium.EmojiStatusText" = "Emoji status is a premium feature.\n Other features included in **Telegram Premium**:";
"Login.SelectCountry" = "Country";
"ReportPeer.ReportReaction" = "Report Reaction";

View File

@ -853,7 +853,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
isStandalone: false,
isStatusSelection: true,
isReactionSelection: false,
reactionItems: [],
topReactionItems: [],
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,
chatPeerId: self.context.account.peerId

View File

@ -1121,7 +1121,7 @@ public final class ReactionButtonsAsyncLayoutContainer {
}
return false
}).prefix(10) {
}) {
validIds.insert(reaction.reaction.value)
var avatarPeers = reaction.peers

View File

@ -184,6 +184,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private var emojiContentLayout: EmojiPagerContentComponent.CustomLayout?
private var emojiContent: EmojiPagerContentComponent?
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
private var emojiContentDisposable: Disposable?
private var horizontalExpandRecognizer: UIPanGestureRecognizer?
@ -746,8 +747,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.updateEmojiContent(emojiContent)
if let reactionSelectionComponentHost = strongSelf.reactionSelectionComponentHost, let componentView = reactionSelectionComponentHost.view {
var emojiTransition: Transition = .immediate
if let scheduledEmojiContentAnimationHint = strongSelf.scheduledEmojiContentAnimationHint {
strongSelf.scheduledEmojiContentAnimationHint = nil
let contentAnimation = scheduledEmojiContentAnimationHint
emojiTransition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
}
let _ = reactionSelectionComponentHost.update(
transition: .immediate,
transition: emojiTransition,
component: AnyComponent(EmojiStatusSelectionComponent(
theme: strongSelf.presentationData.theme,
strings: strongSelf.presentationData.strings,
@ -766,6 +774,12 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if let emojiContent = emojiContent {
self.updateEmojiContent(emojiContent)
if let scheduledEmojiContentAnimationHint = self.scheduledEmojiContentAnimationHint {
self.scheduledEmojiContentAnimationHint = nil
let contentAnimation = scheduledEmojiContentAnimationHint
componentTransition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
}
let _ = reactionSelectionComponentHost.update(
transition: componentTransition,
component: AnyComponent(EmojiStatusSelectionComponent(
@ -892,6 +906,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
guard let strongSelf = self, let itemFile = item.itemFile else {
return
}
var found = false
if let groupId = groupId.base as? String, groupId == "recent" {
for reactionItem in strongSelf.items {
@ -928,7 +943,34 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
},
openFeatured: {
},
addGroupAction: { _, _ in
addGroupAction: { [weak self] groupId, isPremiumLocked in
guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else {
return
}
if isPremiumLocked {
strongSelf.premiumReactionsSelected?()
return
}
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
let _ = (strongSelf.context.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { views in
guard let strongSelf = self, let view = views.views[viewKey] as? OrderedItemListView else {
return
}
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredEmojiPack.info.id == collectionId {
if let strongSelf = self {
strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId))
}
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info, items: featuredEmojiPack.topItems).start()
break
}
}
})
},
clearGroup: { _ in
},
@ -1457,6 +1499,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
public func expand() {
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.tap()
self.animateFromExtensionDistance = self.extensionDistance
self.extensionDistance = 0.0
self.visibleExtensionDistance = 0.0

View File

@ -269,7 +269,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
self.stillAnimationNode = stillAnimationNode
self.addSubnode(stillAnimationNode)
stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
stillAnimationNode.position = animationFrame.center
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
stillAnimationNode.updateLayout(size: animationFrame.size)
@ -325,9 +325,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
self.staticAnimationNode.automaticallyLoadFirstFrame = true
if !self.hasAppearAnimation {
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isVideoSticker), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
} else {
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
}
self.staticAnimationNode.position = animationFrame.center
self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
@ -335,7 +335,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
self.staticAnimationNode.visibility = true
if let animateInAnimationNode = self.animateInAnimationNode {
animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id)))
animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource, isVideo: self.item.appearAnimation.isVideoEmoji || self.item.appearAnimation.isVideoSticker), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id)))
animateInAnimationNode.position = animationFrame.center
animateInAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
animateInAnimationNode.updateLayout(size: animationFrame.size)

View File

@ -828,6 +828,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) }
dict[1757493555] = { return Api.Update.parse_updateReadMessagesContents($0) }
dict[821314523] = { return Api.Update.parse_updateRecentEmojiStatuses($0) }
dict[1870160884] = { return Api.Update.parse_updateRecentReactions($0) }
dict[-1706939360] = { return Api.Update.parse_updateRecentStickers($0) }
dict[-1821035490] = { return Api.Update.parse_updateSavedGifs($0) }
dict[1960361625] = { return Api.Update.parse_updateSavedRingtones($0) }
@ -1007,6 +1008,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[978610270] = { return Api.messages.Messages.parse_messagesSlice($0) }
dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) }
dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) }
dict[-352454890] = { return Api.messages.Reactions.parse_reactions($0) }
dict[-1334846497] = { return Api.messages.Reactions.parse_reactionsNotModified($0) }
dict[-1999405994] = { return Api.messages.RecentStickers.parse_recentStickers($0) }
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
dict[-2069878259] = { return Api.messages.SavedGifs.parse_savedGifs($0) }
@ -1780,6 +1783,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.PeerSettings:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.Reactions:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.RecentStickers:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.SavedGifs:

View File

@ -640,6 +640,7 @@ public extension Api {
case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32)
case updateReadMessagesContents(messages: [Int32], pts: Int32, ptsCount: Int32)
case updateRecentEmojiStatuses
case updateRecentReactions
case updateRecentStickers
case updateSavedGifs
case updateSavedRingtones
@ -1428,6 +1429,12 @@ public extension Api {
buffer.appendInt32(821314523)
}
break
case .updateRecentReactions:
if boxed {
buffer.appendInt32(1870160884)
}
break
case .updateRecentStickers:
if boxed {
@ -1736,6 +1743,8 @@ public extension Api {
return ("updateReadMessagesContents", [("messages", String(describing: messages)), ("pts", String(describing: pts)), ("ptsCount", String(describing: ptsCount))])
case .updateRecentEmojiStatuses:
return ("updateRecentEmojiStatuses", [])
case .updateRecentReactions:
return ("updateRecentReactions", [])
case .updateRecentStickers:
return ("updateRecentStickers", [])
case .updateSavedGifs:
@ -3347,6 +3356,9 @@ public extension Api {
public static func parse_updateRecentEmojiStatuses(_ reader: BufferReader) -> Update? {
return Api.Update.updateRecentEmojiStatuses
}
public static func parse_updateRecentReactions(_ reader: BufferReader) -> Update? {
return Api.Update.updateRecentReactions
}
public static func parse_updateRecentStickers(_ reader: BufferReader) -> Update? {
return Api.Update.updateRecentStickers
}

View File

@ -134,6 +134,64 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum Reactions: TypeConstructorDescription {
case reactions(hash: Int64, reactions: [Api.Reaction])
case reactionsNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .reactions(let hash, let reactions):
if boxed {
buffer.appendInt32(-352454890)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(reactions.count))
for item in reactions {
item.serialize(buffer, true)
}
break
case .reactionsNotModified:
if boxed {
buffer.appendInt32(-1334846497)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .reactions(let hash, let reactions):
return ("reactions", [("hash", String(describing: hash)), ("reactions", String(describing: reactions))])
case .reactionsNotModified:
return ("reactionsNotModified", [])
}
}
public static func parse_reactions(_ reader: BufferReader) -> Reactions? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.Reaction]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.Reactions.reactions(hash: _1!, reactions: _2!)
}
else {
return nil
}
}
public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? {
return Api.messages.Reactions.reactionsNotModified
}
}
}
public extension Api.messages {
enum RecentStickers: TypeConstructorDescription {
case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32])
@ -1372,111 +1430,3 @@ public extension Api.payments {
}
}
public extension Api.phone {
enum ExportedGroupCallInvite: TypeConstructorDescription {
case exportedGroupCallInvite(link: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedGroupCallInvite(let link):
if boxed {
buffer.appendInt32(541839704)
}
serializeString(link, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedGroupCallInvite(let link):
return ("exportedGroupCallInvite", [("link", String(describing: link))])
}
}
public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!)
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum GroupCall: TypeConstructorDescription {
case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users):
if boxed {
buffer.appendInt32(-1636664659)
}
call.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(participants.count))
for item in participants {
item.serialize(buffer, true)
}
serializeString(participantsNextOffset, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users):
return ("groupCall", [("call", String(describing: call)), ("participants", String(describing: participants)), ("participantsNextOffset", String(describing: participantsNextOffset)), ("chats", String(describing: chats)), ("users", String(describing: users))])
}
}
public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? {
var _1: Api.GroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.GroupCall
}
var _2: [Api.GroupCallParticipant]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
}
var _3: String?
_3 = parseString(reader)
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,111 @@
public extension Api.phone {
enum ExportedGroupCallInvite: TypeConstructorDescription {
case exportedGroupCallInvite(link: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedGroupCallInvite(let link):
if boxed {
buffer.appendInt32(541839704)
}
serializeString(link, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedGroupCallInvite(let link):
return ("exportedGroupCallInvite", [("link", String(describing: link))])
}
}
public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!)
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum GroupCall: TypeConstructorDescription {
case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users):
if boxed {
buffer.appendInt32(-1636664659)
}
call.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(participants.count))
for item in participants {
item.serialize(buffer, true)
}
serializeString(participantsNextOffset, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users):
return ("groupCall", [("call", String(describing: call)), ("participants", String(describing: participants)), ("participantsNextOffset", String(describing: participantsNextOffset)), ("chats", String(describing: chats)), ("users", String(describing: users))])
}
}
public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? {
var _1: Api.GroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.GroupCall
}
var _2: [Api.GroupCallParticipant]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
}
var _3: String?
_3 = parseString(reader)
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!)
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum GroupCallStreamChannels: TypeConstructorDescription {
case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel])
@ -1318,59 +1426,3 @@ public extension Api.updates {
}
}
public extension Api.upload {
enum CdnFile: TypeConstructorDescription {
case cdnFile(bytes: Buffer)
case cdnFileReuploadNeeded(requestToken: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .cdnFile(let bytes):
if boxed {
buffer.appendInt32(-1449145777)
}
serializeBytes(bytes, buffer: buffer, boxed: false)
break
case .cdnFileReuploadNeeded(let requestToken):
if boxed {
buffer.appendInt32(-290921362)
}
serializeBytes(requestToken, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .cdnFile(let bytes):
return ("cdnFile", [("bytes", String(describing: bytes))])
case .cdnFileReuploadNeeded(let requestToken):
return ("cdnFileReuploadNeeded", [("requestToken", String(describing: requestToken))])
}
}
public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? {
var _1: Buffer?
_1 = parseBytes(reader)
let _c1 = _1 != nil
if _c1 {
return Api.upload.CdnFile.cdnFile(bytes: _1!)
}
else {
return nil
}
}
public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? {
var _1: Buffer?
_1 = parseBytes(reader)
let _c1 = _1 != nil
if _c1 {
return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,59 @@
public extension Api.upload {
enum CdnFile: TypeConstructorDescription {
case cdnFile(bytes: Buffer)
case cdnFileReuploadNeeded(requestToken: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .cdnFile(let bytes):
if boxed {
buffer.appendInt32(-1449145777)
}
serializeBytes(bytes, buffer: buffer, boxed: false)
break
case .cdnFileReuploadNeeded(let requestToken):
if boxed {
buffer.appendInt32(-290921362)
}
serializeBytes(requestToken, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .cdnFile(let bytes):
return ("cdnFile", [("bytes", String(describing: bytes))])
case .cdnFileReuploadNeeded(let requestToken):
return ("cdnFileReuploadNeeded", [("requestToken", String(describing: requestToken))])
}
}
public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? {
var _1: Buffer?
_1 = parseBytes(reader)
let _c1 = _1 != nil
if _c1 {
return Api.upload.CdnFile.cdnFile(bytes: _1!)
}
else {
return nil
}
}
public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? {
var _1: Buffer?
_1 = parseBytes(reader)
let _c1 = _1 != nil
if _c1 {
return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!)
}
else {
return nil
}
}
}
}
public extension Api.upload {
enum File: TypeConstructorDescription {
case file(type: Api.storage.FileType, mtime: Int32, bytes: Buffer)

View File

@ -344,12 +344,11 @@ public extension Api.functions.account {
}
}
public extension Api.functions.account {
static func getEmojiStatuses(flags: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.EmojiStatuses>) {
static func getDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.EmojiStatuses>) {
let buffer = Buffer()
buffer.appendInt32(644729392)
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(-696962170)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.getEmojiStatuses", parameters: [("flags", String(describing: flags)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in
return (FunctionDescription(name: "account.getDefaultEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in
let reader = BufferReader(buffer)
var result: Api.account.EmojiStatuses?
if let signature = reader.readInt32() {
@ -469,6 +468,21 @@ public extension Api.functions.account {
})
}
}
public extension Api.functions.account {
static func getRecentEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.EmojiStatuses>) {
let buffer = Buffer()
buffer.appendInt32(257392901)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.getRecentEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in
let reader = BufferReader(buffer)
var result: Api.account.EmojiStatuses?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses
}
return result
})
}
}
public extension Api.functions.account {
static func getSavedRingtones(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.SavedRingtones>) {
let buffer = Buffer()
@ -3392,6 +3406,21 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func clearRecentReactions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1644236876)
return (FunctionDescription(name: "messages.clearRecentReactions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.messages {
static func clearRecentStickers(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
@ -4645,6 +4674,22 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func getRecentReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Reactions>) {
let buffer = Buffer()
buffer.appendInt32(960896434)
serializeInt32(limit, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getRecentReactions", parameters: [("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in
let reader = BufferReader(buffer)
var result: Api.messages.Reactions?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.Reactions
}
return result
})
}
}
public extension Api.functions.messages {
static func getRecentStickers(flags: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.RecentStickers>) {
let buffer = Buffer()
@ -4853,6 +4898,22 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func getTopReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Reactions>) {
let buffer = Buffer()
buffer.appendInt32(-1149164102)
serializeInt32(limit, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getTopReactions", parameters: [("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in
let reader = BufferReader(buffer)
var result: Api.messages.Reactions?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.Reactions
}
return result
})
}
}
public extension Api.functions.messages {
static func getUnreadMentions(peer: Api.InputPeer, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
let buffer = Buffer()

View File

@ -1177,6 +1177,8 @@ public class Account {
self.managedOperationsDisposable.add(managedAllPremiumStickers(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedRecentStatusEmoji(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedFeaturedStatusEmoji(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedRecentReactions(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedTopReactions(postbox: self.postbox, network: self.network).start())
if !supplementary {
let mediaBox = postbox.mediaBox

View File

@ -12,11 +12,9 @@ private func hashForIds(_ ids: [Int64]) -> Int64 {
return finalizeInt64Hash(acc)
}
private func managedRecentMedia(postbox: Postbox, network: Network, collectionId: Int32, reverseHashOrder: Bool, forceFetch: Bool, fetch: @escaping (Int64) -> Signal<[OrderedItemListEntry]?, NoError>) -> Signal<Void, NoError> {
private func managedRecentMedia(postbox: Postbox, network: Network, collectionId: Int32, extractItemId: @escaping (MemoryBuffer) -> Int64?, reverseHashOrder: Bool, forceFetch: Bool, fetch: @escaping (Int64) -> Signal<[OrderedItemListEntry]?, NoError>) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> in
var itemIds = transaction.getOrderedListItemIds(collectionId: collectionId).map {
RecentMediaItemId($0).mediaId.id
}
var itemIds = transaction.getOrderedListItemIds(collectionId: collectionId).compactMap(extractItemId)
if reverseHashOrder {
itemIds.reverse()
}
@ -44,7 +42,7 @@ private func managedRecentMedia(postbox: Postbox, network: Network, collectionId
}
func managedRecentStickers(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentStickers, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentStickers, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getRecentStickers(flags: 0, hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
@ -67,7 +65,7 @@ func managedRecentStickers(postbox: Postbox, network: Network) -> Signal<Void, N
}
func managedRecentGifs(postbox: Postbox, network: Network, forceFetch: Bool = false) -> Signal<Void, NoError> {
return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentGifs, reverseHashOrder: false, forceFetch: forceFetch, fetch: { hash in
return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentGifs, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: forceFetch, fetch: { hash in
return network.request(Api.functions.messages.getSavedGifs(hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
@ -90,7 +88,7 @@ func managedRecentGifs(postbox: Postbox, network: Network, forceFetch: Bool = fa
}
func managedSavedStickers(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudSavedStickers, reverseHashOrder: true, forceFetch: false, fetch: { hash in
return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudSavedStickers, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: true, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getFavedStickers(hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
@ -132,7 +130,7 @@ func managedSavedStickers(postbox: Postbox, network: Network) -> Signal<Void, No
}
func managedGreetingStickers(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudGreetingStickers, reverseHashOrder: false, forceFetch: false, fetch: { hash in
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudGreetingStickers, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getStickers(emoticon: "👋⭐️", hash: 0))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
@ -156,7 +154,7 @@ func managedGreetingStickers(postbox: Postbox, network: Network) -> Signal<Void,
}
func managedPremiumStickers(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudPremiumStickers, reverseHashOrder: false, forceFetch: false, fetch: { hash in
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudPremiumStickers, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getStickers(emoticon: "⭐️⭐️", hash: 0))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
@ -180,7 +178,7 @@ func managedPremiumStickers(postbox: Postbox, network: Network) -> Signal<Void,
}
func managedAllPremiumStickers(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudAllPremiumStickers, reverseHashOrder: false, forceFetch: false, fetch: { hash in
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudAllPremiumStickers, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getStickers(emoticon: "📂⭐️", hash: 0))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
@ -204,8 +202,8 @@ func managedAllPremiumStickers(postbox: Postbox, network: Network) -> Signal<Voi
}
func managedRecentStatusEmoji(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentStatusEmoji, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.account.getEmojiStatuses(flags: 1 << 1, hash: hash))
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentStatusEmoji, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.account.getRecentEmojiStatuses(hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
switch result {
@ -234,8 +232,8 @@ func managedRecentStatusEmoji(postbox: Postbox, network: Network) -> Signal<Void
}
func managedFeaturedStatusEmoji(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudFeaturedStatusEmoji, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.account.getEmojiStatuses(flags: 1 << 0, hash: hash))
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudFeaturedStatusEmoji, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.account.getDefaultEmojiStatuses(hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
switch result {
@ -262,3 +260,105 @@ func managedFeaturedStatusEmoji(postbox: Postbox, network: Network) -> Signal<Vo
})
return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
func managedRecentReactions(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentReactions, extractItemId: { rawId in
switch RecentReactionItemId(rawId).id {
case .builtin:
return 0
case let .custom(fileId):
return fileId.id
}
}, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getRecentReactions(limit: 24, hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
switch result {
case .reactionsNotModified:
return .single(nil)
case let .reactions(_, reactions):
let parsedReactions = reactions.compactMap(MessageReaction.Reaction.init(apiReaction:))
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedReactions.compactMap { reaction -> Int64? in
switch reaction {
case .builtin:
return nil
case let .custom(fileId):
return fileId
}
})
|> map { files -> [OrderedItemListEntry] in
var items: [OrderedItemListEntry] = []
for reaction in parsedReactions {
let item: RecentReactionItem
switch reaction {
case let .builtin(value):
item = RecentReactionItem(.builtin(value))
case let .custom(fileId):
guard let file = files[fileId] else {
continue
}
item = RecentReactionItem(.custom(file))
}
if let entry = CodableEntry(item) {
items.append(OrderedItemListEntry(id: item.id.rawValue, contents: entry))
}
}
return items
}
}
}
})
return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
func managedTopReactions(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudTopReactions, extractItemId: { rawId in
switch RecentReactionItemId(rawId).id {
case .builtin:
return 0
case let .custom(fileId):
return fileId.id
}
}, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getTopReactions(limit: 24, hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
switch result {
case .reactionsNotModified:
return .single(nil)
case let .reactions(_, reactions):
let parsedReactions = reactions.compactMap(MessageReaction.Reaction.init(apiReaction:))
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedReactions.compactMap { reaction -> Int64? in
switch reaction {
case .builtin:
return nil
case let .custom(fileId):
return fileId
}
})
|> map { files -> [OrderedItemListEntry] in
var items: [OrderedItemListEntry] = []
for reaction in parsedReactions {
let item: RecentReactionItem
switch reaction {
case let .builtin(value):
item = RecentReactionItem(.builtin(value))
case let .custom(fileId):
guard let file = files[fileId] else {
continue
}
item = RecentReactionItem(.custom(file))
}
if let entry = CodableEntry(item) {
items.append(OrderedItemListEntry(id: item.id.rawValue, contents: entry))
}
}
return items
}
}
}
})
return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}

View File

@ -57,6 +57,31 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
}
}
for attribute in currentMessage.attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
for updatedReaction in reactions {
if !attribute.reactions.contains(where: { $0.value == updatedReaction.reaction }) {
let recentReactionItem: RecentReactionItem
switch updatedReaction {
case let .builtin(value):
recentReactionItem = RecentReactionItem(.builtin(value))
case let .custom(fileId, file):
if let file = file ?? (transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile) {
recentReactionItem = RecentReactionItem(.custom(file))
} else {
continue
}
}
if let entry = CodableEntry(recentReactionItem) {
let itemEntry = OrderedItemListEntry(id: recentReactionItem.id.rawValue, contents: entry)
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentReactions, item: itemEntry, removeTailIfCountExceeds: 50)
}
}
}
}
}
var mappedReactions = mappedReactions
let updatedReactions = mergedMessageReactions(attributes: attributes + [PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge)])?.reactions ?? []

View File

@ -70,6 +70,8 @@ public struct Namespaces {
public static let CloudAllPremiumStickers: Int32 = 16
public static let CloudRecentStatusEmoji: Int32 = 17
public static let CloudFeaturedStatusEmoji: Int32 = 18
public static let CloudRecentReactions: Int32 = 19
public static let CloudTopReactions: Int32 = 20
}
public struct CachedItemCollection {

View File

@ -152,3 +152,115 @@ public final class RecentEmojiItem: Codable, Equatable {
return lhs.content == rhs.content
}
}
public struct RecentReactionItemId {
public enum Id {
case custom(MediaId)
case builtin(String)
}
public let rawValue: MemoryBuffer
public let id: Id
public init(_ rawValue: MemoryBuffer) {
self.rawValue = rawValue
assert(rawValue.length >= 1)
var type: UInt8 = 0
memcpy(&type, rawValue.memory.advanced(by: 0), 1)
if type == 0 {
assert(rawValue.length == 1 + 4 + 8)
var mediaIdNamespace: Int32 = 0
var mediaIdId: Int64 = 0
memcpy(&mediaIdNamespace, rawValue.memory.advanced(by: 1), 4)
memcpy(&mediaIdId, rawValue.memory.advanced(by: 1 + 4), 8)
self.id = .custom(MediaId(namespace: mediaIdNamespace, id: mediaIdId))
} else if type == 1 {
var length: UInt16 = 0
assert(rawValue.length >= 1 + 2)
memcpy(&length, rawValue.memory.advanced(by: 1), 2)
assert(rawValue.length >= 1 + 2 + Int(length))
self.id = .builtin(String(data: Data(bytes: rawValue.memory.advanced(by: 1 + 2), count: Int(length)), encoding: .utf8) ?? ".")
} else {
assert(false)
self.id = .builtin(".")
}
}
public init(_ mediaId: MediaId) {
self.id = .custom(mediaId)
var mediaIdNamespace: Int32 = mediaId.namespace
var mediaIdId: Int64 = mediaId.id
self.rawValue = MemoryBuffer(memory: malloc(1 + 4 + 8)!, capacity: 1 + 4 + 8, length: 1 + 4 + 8, freeWhenDone: true)
var type: UInt8 = 0
memcpy(self.rawValue.memory.advanced(by: 0), &type, 1)
memcpy(self.rawValue.memory.advanced(by: 1), &mediaIdNamespace, 4)
memcpy(self.rawValue.memory.advanced(by: 1 + 4), &mediaIdId, 8)
}
public init(_ text: String) {
self.id = .builtin(text)
let data = text.data(using: .utf8) ?? Data()
var length: UInt16 = UInt16(data.count)
self.rawValue = MemoryBuffer(memory: malloc(1 + 2 + data.count)!, capacity: 1 + 2 + data.count, length: 1 + 2 + data.count, freeWhenDone: true)
var type: UInt8 = 1
memcpy(self.rawValue.memory.advanced(by: 0), &type, 1)
memcpy(self.rawValue.memory.advanced(by: 1), &length, 2)
data.withUnsafeBytes { bytes in
let _ = memcpy(self.rawValue.memory.advanced(by: 1 + 2), bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), bytes.count)
}
}
}
public final class RecentReactionItem: Codable, Equatable {
public enum Content: Equatable {
case custom(TelegramMediaFile)
case builtin(String)
}
public let content: Content
public var id: RecentReactionItemId {
switch self.content {
case let .builtin(value):
return RecentReactionItemId(value)
case let .custom(file):
return RecentReactionItemId(file.fileId)
}
}
public init(_ content: Content) {
self.content = content
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
if let mediaData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "m") {
self.content = .custom(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: mediaData.data))))
} else {
self.content = .builtin(try container.decode(String.self, forKey: "s"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
switch self.content {
case let .custom(file):
try container.encode(PostboxEncoder().encodeObjectToRawData(file), forKey: "m")
case let .builtin(string):
try container.encode(string, forKey: "s")
}
}
public static func ==(lhs: RecentReactionItem, rhs: RecentReactionItem) -> Bool {
return lhs.content == rhs.content
}
}

View File

@ -4455,7 +4455,7 @@ public final class EmojiPagerContentComponent: Component {
return hasPremium
}
public static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, isStatusSelection: Bool, isReactionSelection: Bool, reactionItems: [AvailableReactions.Reaction], areUnicodeEmojiEnabled: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?) -> Signal<EmojiPagerContentComponent, NoError> {
public static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, isStatusSelection: Bool, isReactionSelection: Bool, topReactionItems: [AvailableReactions.Reaction], areUnicodeEmojiEnabled: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?) -> Signal<EmojiPagerContentComponent, NoError> {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
@ -4468,14 +4468,24 @@ public final class EmojiPagerContentComponent: Component {
if isStatusSelection {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji)
} else if isReactionSelection {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions)
}
let availableReactions: Signal<AvailableReactions?, NoError>
if isReactionSelection {
availableReactions = context.engine.stickers.availableReactions()
} else {
availableReactions = .single(nil)
}
let emojiItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: true),
context.account.viewTracker.featuredEmojiPacks()
context.account.viewTracker.featuredEmojiPacks(),
availableReactions
)
|> map { view, hasPremium, featuredEmojiPacks -> EmojiPagerContentComponent in
|> map { view, hasPremium, featuredEmojiPacks, availableReactions -> EmojiPagerContentComponent in
struct ItemGroup {
var supergroupId: AnyHashable
var id: AnyHashable
@ -4493,6 +4503,7 @@ public final class EmojiPagerContentComponent: Component {
var recentEmoji: OrderedItemListView?
var featuredStatusEmoji: OrderedItemListView?
var recentStatusEmoji: OrderedItemListView?
var recentReactions: OrderedItemListView?
for orderedView in view.orderedItemListsViews {
if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji {
recentEmoji = orderedView
@ -4500,6 +4511,8 @@ public final class EmojiPagerContentComponent: Component {
featuredStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStatusEmoji {
recentStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentReactions {
recentReactions = orderedView
}
}
@ -4516,7 +4529,7 @@ public final class EmojiPagerContentComponent: Component {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: true, headerItem: nil, items: [resultItem]))
}
var existingIds = Set<MediaId>()
@ -4575,7 +4588,13 @@ public final class EmojiPagerContentComponent: Component {
}
}
} else if isReactionSelection {
for reactionItem in reactionItems {
var existingIds = Set<MessageReaction.Reaction>()
for reactionItem in topReactionItems {
if existingIds.contains(reactionItem.value) {
continue
}
existingIds.insert(reactionItem.value)
let animationFile = reactionItem.selectAnimation
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
let resultItem = EmojiPagerContentComponent.Item(
@ -4593,6 +4612,54 @@ public final class EmojiPagerContentComponent: Component {
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
}
}
if let recentReactions = recentReactions {
for item in recentReactions.items {
guard let item = item.contents.get(RecentReactionItem.self) else {
continue
}
let animationFile: TelegramMediaFile
switch item.content {
case let .builtin(value):
if existingIds.contains(.builtin(value)) {
continue
}
existingIds.insert(.builtin(value))
if let availableReactions = availableReactions, let availableReaction = availableReactions.reactions.first(where: { $0.value == .builtin(value) }) {
if let centerAnimation = availableReaction.centerAnimation {
animationFile = centerAnimation
} else {
continue
}
} else {
continue
}
case let .custom(file):
if existingIds.contains(.custom(file.fileId.id)) {
continue
}
existingIds.insert(.custom(file.fileId.id))
animationFile = file
}
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
}
}
}
}
if let recentEmoji = recentEmoji, !isReactionSelection, !isStatusSelection {

View File

@ -985,18 +985,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = combineLatest(queue: .mainQueue(),
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)),
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction, messageNode: node as? ChatMessageItemView),
strongSelf.context.engine.stickers.availableReactions(),
peerAllowedReactions(context: strongSelf.context, peerId: topMessage.id.peerId),
peerMessageAllowedReactions(context: strongSelf.context, message: topMessage),
topMessageReactions(context: strongSelf.context, message: topMessage),
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
).start(next: { peer, actions, availableReactions, allowedReactions, chatTextSelectionTips in
).start(next: { peer, actions, allowedReactions, topReactions, chatTextSelectionTips in
guard let strongSelf = self else {
return
}
var hasPremium = false
/*var hasPremium = false
if case let .user(user) = peer, user.isPremium {
hasPremium = true
}
}*/
var actions = actions
switch actions.content {
@ -1045,50 +1045,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
actions.animationCache = strongSelf.controllerInteraction?.presentationContext.animationCache
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
//let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
if canAddMessageReactions(message: topMessage), let availableReactions = availableReactions, let allowedReactions = allowedReactions {
var hasPremiumPlaceholder = false
filterReactions: for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if !reaction.isEnabled {
continue
}
switch allowedReactions {
case let .set(set):
if !set.contains(reaction.value) {
continue filterReactions
}
case .all:
break
}
if reaction.isPremium && !hasPremium {
hasPremiumPlaceholder = true
continue
}
actions.reactionItems.append(.reaction(ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation,
isCustom: false
)))
}
if hasPremiumPlaceholder && !premiumConfiguration.isPremiumDisabled {
if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty {
actions.reactionItems = topReactions.map(ReactionContextItem.reaction)
/*if hasPremiumPlaceholder && !premiumConfiguration.isPremiumDisabled {
actions.reactionItems.append(.premium)
}
}*/
if !actions.reactionItems.isEmpty {
let reactionItems: [AvailableReactions.Reaction] = actions.reactionItems.compactMap { item -> AvailableReactions.Reaction? in
@ -1118,12 +1081,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var allReactionsAreAvailable = false
switch allowedReactions {
case let .set(set):
if set == Set(availableReactions.reactions.filter(\.isEnabled).map(\.value)) {
allReactionsAreAvailable = true
} else {
allReactionsAreAvailable = false
}
case .set:
allReactionsAreAvailable = false
case .all:
allReactionsAreAvailable = true
}
@ -1145,7 +1104,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
isStandalone: false,
isStatusSelection: false,
isReactionSelection: true,
reactionItems: reactionItems,
topReactionItems: reactionItems,
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,
chatPeerId: strongSelf.chatLocation.peerId
@ -1312,6 +1271,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break
}*/
if removedReaction == nil, case .custom = chosenReaction {
if !strongSelf.presentationInterfaceState.isPremium {
controller?.premiumReactionsSelected?()
return
}
}
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
if item.message.id == message.id {
@ -1588,7 +1554,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let _ = (peerAllowedReactions(context: strongSelf.context, peerId: message.id.peerId)
let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message)
|> deliverOnMainQueue).start(next: { allowedReactions in
guard let strongSelf = self else {
return
@ -16981,12 +16947,16 @@ enum AllowedReactions {
case all
}
func peerAllowedReactions(context: AccountContext, peerId: PeerId) -> Signal<AllowedReactions?, NoError> {
func peerMessageAllowedReactions(context: AccountContext, message: Message) -> Signal<AllowedReactions?, NoError> {
return context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
TelegramEngine.EngineData.Item.Peer.AllowedReactions(id: peerId)
TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId),
TelegramEngine.EngineData.Item.Peer.AllowedReactions(id: message.id.peerId)
)
|> map { peer, allowedReactions -> AllowedReactions? in
if let effectiveReactions = message.effectiveReactions, effectiveReactions.count >= 11 {
return .set(Set(effectiveReactions.map(\.value)))
}
switch allowedReactions {
case .unknown:
return .all

View File

@ -97,7 +97,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
animationRenderer = MultiAnimationRendererImpl()
//}
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, reactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers]
@ -2018,7 +2018,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
let semaphore = DispatchSemaphore(value: 0)
var emojiComponent: EmojiPagerContentComponent?
let _ = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, reactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil).start(next: { value in
let _ = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil).start(next: { value in
emojiComponent = value
semaphore.signal()
})
@ -2033,7 +2033,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
gifs: nil,
availableGifSearchEmojies: []
),
updatedInputData: EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, reactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in
updatedInputData: EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in
return ChatEntityKeyboardInputNode.InputData(
emoji: emojiComponent,
stickers: nil,

View File

@ -1106,7 +1106,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
return lhs.count > rhs.count
}
}
}).prefix(10) {
}) {
let node: StatusReactionNode
var animateNode = true
if let current = strongSelf.reactionNodes[reaction.value] {

View File

@ -977,7 +977,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
interaction.openChat()
}))
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: presentationData.strings.ReportPeer_Report, color: .destructive, action: {
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: presentationData.strings.ReportPeer_ReportReaction, color: .destructive, action: {
interaction.openReport(.reaction(reactionSourceMessageId))
}))
} else if let _ = nearbyPeerDistance {
@ -1296,7 +1296,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
switch allowedReactions {
case .all:
//TODO:localize
label = "Enabled"
label = "All Reactions"
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -1462,7 +1462,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
switch allowedReactions {
case .all:
//TODO:localize
label = "Enabled"
label = "All Reactions"
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -1488,7 +1488,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
switch allowedReactions {
case .all:
//TODO:localize
label = "Enabled"
label = "All Reactions"
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -1602,7 +1602,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
switch allowedReactions {
case .all:
//TODO:localize
label = "Enabled"
label = "All Reactions"
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -3103,7 +3103,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
isStandalone: false,
isStatusSelection: true,
isReactionSelection: false,
reactionItems: [],
topReactionItems: [],
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,
chatPeerId: strongSelf.context.account.peerId

View File

@ -0,0 +1,140 @@
import Foundation
import SwiftSignalKit
import TelegramCore
import Postbox
import AccountContext
import ReactionSelectionNode
func topMessageReactions(context: AccountContext, message: Message) -> Signal<[ReactionItem], NoError> {
let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions)
let topReactions = context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> [RecentReactionItem] in
guard let view = views.views[viewKey] as? OrderedItemListView else {
return []
}
return view.items.compactMap { item -> RecentReactionItem? in
return item.contents.get(RecentReactionItem.self)
}
}
return combineLatest(
context.engine.stickers.availableReactions(),
peerMessageAllowedReactions(context: context, message: message),
topReactions
)
|> take(1)
|> map { availableReactions, allowedReactions, topReactions -> [ReactionItem] in
guard let availableReactions = availableReactions, let allowedReactions = allowedReactions else {
return []
}
var result: [ReactionItem] = []
var existingIds = Set<MessageReaction.Reaction>()
for topReaction in topReactions {
switch topReaction.content {
case let .builtin(value):
if let reaction = availableReactions.reactions.first(where: { $0.value == .builtin(value) }) {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if existingIds.contains(reaction.value) {
continue
}
existingIds.insert(reaction.value)
switch allowedReactions {
case let .set(set):
if !set.contains(reaction.value) {
continue
}
case .all:
break
}
result.append(ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation,
isCustom: false
))
} else {
continue
}
case let .custom(file):
switch allowedReactions {
case let .set(set):
if !set.contains(.custom(file.fileId.id)) {
continue
}
case .all:
break
}
if existingIds.contains(.custom(file.fileId.id)) {
continue
}
existingIds.insert(.custom(file.fileId.id))
result.append(ReactionItem(
reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)),
appearAnimation: file,
stillAnimation: file,
listAnimation: file,
largeListAnimation: file,
applicationAnimation: nil,
largeApplicationAnimation: nil,
isCustom: true
))
}
}
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if !reaction.isEnabled {
continue
}
switch allowedReactions {
case let .set(set):
if !set.contains(reaction.value) {
continue
}
case .all:
break
}
if existingIds.contains(reaction.value) {
continue
}
existingIds.insert(reaction.value)
result.append(ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation,
isCustom: false
))
}
return result
}
}