Update API [skip ci]

This commit is contained in:
Ilya Laktyushin 2025-01-10 15:22:27 +04:00
parent b0511f146e
commit b6f8cce7ea
54 changed files with 1665 additions and 287 deletions

View File

@ -13651,6 +13651,21 @@ Sorry for the inconvenience.";
"Camera.OpenChat" = "Open Chat"; "Camera.OpenChat" = "Open Chat";
"Gift.Wear.Wear" = "Wear %@";
"Gift.Wear.GetBenefits" = "and get these benefits:";
"Gift.Wear.Badge.Title" = "Radiant Badge";
"Gift.Wear.Badge.Text" = "The glittering icon of this item will be displayed next to your name.";
"Gift.Wear.Design.Title" = "Unqiue Profile Design";
"Gift.Wear.Design.Text" = "Your profile page will get the color and the symbol of this item.";
"Gift.Wear.Proof.Title" = "Proof of Ownership";
"Gift.Wear.Proof.Text" = "Tapping the icon of this item next to your name will show its info and owner.";
"Gift.Wear.Start" = "Start Wearing";
"Gift.View.Header.Transfer" = "transfer";
"Gift.View.Header.Wear" = "wear";
"Gift.View.Header.TakeOff" = "take off";
"Gift.View.Header.Share" = "share";
"Conversation.AddToContactsLong" = "Add to Contacts"; "Conversation.AddToContactsLong" = "Add to Contacts";
"PeerInfo.PaneRecommendedBots" = "Similar Bots"; "PeerInfo.PaneRecommendedBots" = "Similar Bots";

View File

@ -2145,6 +2145,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var currentCredibilityIconContent: EmojiStatusComponent.Content? var currentCredibilityIconContent: EmojiStatusComponent.Content?
var currentVerifiedIconContent: EmojiStatusComponent.Content? var currentVerifiedIconContent: EmojiStatusComponent.Content?
var currentStatusIconContent: EmojiStatusComponent.Content? var currentStatusIconContent: EmojiStatusComponent.Content?
var currentStatusIconParticleColor: UIColor?
var currentSecretIconImage: UIImage? var currentSecretIconImage: UIImage?
var currentForwardedIcon: UIImage? var currentForwardedIcon: UIImage?
var currentStoryIcon: UIImage? var currentStoryIcon: UIImage?
@ -3102,6 +3103,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled { } else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
currentStatusIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) currentStatusIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
if case let .starGift(_, _, _, _, _, innerColor, _, _, _) = emojiStatus.content {
currentStatusIconParticleColor = UIColor(rgb: UInt32(bitPattern: innerColor))
}
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor) currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor)
} }
@ -3130,6 +3134,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased())
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled { } else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
currentStatusIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) currentStatusIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
if case let .starGift(_, _, _, _, _, innerColor, _, _, _) = emojiStatus.content {
currentStatusIconParticleColor = UIColor(rgb: UInt32(bitPattern: innerColor))
}
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor) currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor)
} }
@ -4549,6 +4556,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
animationCache: item.interaction.animationCache, animationCache: item.interaction.animationCache,
animationRenderer: item.interaction.animationRenderer, animationRenderer: item.interaction.animationRenderer,
content: currentStatusIconContent, content: currentStatusIconContent,
particleColor: currentStatusIconParticleColor,
isVisibleForAnimations: strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji, isVisibleForAnimations: strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji,
action: nil action: nil
) )

View File

@ -267,9 +267,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1275374751] = { return Api.EmojiLanguage.parse_emojiLanguage($0) } dict[-1275374751] = { return Api.EmojiLanguage.parse_emojiLanguage($0) }
dict[2048790993] = { return Api.EmojiList.parse_emojiList($0) } dict[2048790993] = { return Api.EmojiList.parse_emojiList($0) }
dict[1209970170] = { return Api.EmojiList.parse_emojiListNotModified($0) } dict[1209970170] = { return Api.EmojiList.parse_emojiListNotModified($0) }
dict[-1835310691] = { return Api.EmojiStatus.parse_emojiStatus($0) } dict[-402717046] = { return Api.EmojiStatus.parse_emojiStatus($0) }
dict[1904500795] = { return Api.EmojiStatus.parse_emojiStatusCollectible($0) }
dict[769727150] = { return Api.EmojiStatus.parse_emojiStatusEmpty($0) } dict[769727150] = { return Api.EmojiStatus.parse_emojiStatusEmpty($0) }
dict[-97474361] = { return Api.EmojiStatus.parse_emojiStatusUntil($0) } dict[118758847] = { return Api.EmojiStatus.parse_inputEmojiStatusCollectible($0) }
dict[-1519029347] = { return Api.EmojiURL.parse_emojiURL($0) } dict[-1519029347] = { return Api.EmojiURL.parse_emojiURL($0) }
dict[1643173063] = { return Api.EncryptedChat.parse_encryptedChat($0) } dict[1643173063] = { return Api.EncryptedChat.parse_encryptedChat($0) }
dict[505183301] = { return Api.EncryptedChat.parse_encryptedChatDiscarded($0) } dict[505183301] = { return Api.EncryptedChat.parse_encryptedChatDiscarded($0) }
@ -388,7 +389,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1710230755] = { return Api.InputInvoice.parse_inputInvoiceStars($0) } dict[1710230755] = { return Api.InputInvoice.parse_inputInvoiceStars($0) }
dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) } dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) }
dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) } dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) }
dict[860303448] = { return Api.InputMedia.parse_inputMediaDocument($0) } dict[1946579745] = { return Api.InputMedia.parse_inputMediaDocument($0) }
dict[-78455655] = { return Api.InputMedia.parse_inputMediaDocumentExternal($0) } dict[-78455655] = { return Api.InputMedia.parse_inputMediaDocumentExternal($0) }
dict[-1771768449] = { return Api.InputMedia.parse_inputMediaEmpty($0) } dict[-1771768449] = { return Api.InputMedia.parse_inputMediaEmpty($0) }
dict[-750828557] = { return Api.InputMedia.parse_inputMediaGame($0) } dict[-750828557] = { return Api.InputMedia.parse_inputMediaGame($0) }
@ -400,7 +401,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) } dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) }
dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) } dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) }
dict[-1979852936] = { return Api.InputMedia.parse_inputMediaStory($0) } dict[-1979852936] = { return Api.InputMedia.parse_inputMediaStory($0) }
dict[1530447553] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) } dict[-264125395] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) }
dict[505969924] = { return Api.InputMedia.parse_inputMediaUploadedPhoto($0) } dict[505969924] = { return Api.InputMedia.parse_inputMediaUploadedPhoto($0) }
dict[-1052959727] = { return Api.InputMedia.parse_inputMediaVenue($0) } dict[-1052959727] = { return Api.InputMedia.parse_inputMediaVenue($0) }
dict[-1038383031] = { return Api.InputMedia.parse_inputMediaWebPage($0) } dict[-1038383031] = { return Api.InputMedia.parse_inputMediaWebPage($0) }
@ -617,7 +618,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1313731771] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } dict[1313731771] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) }
dict[1882335561] = { return Api.MessageMedia.parse_messageMediaContact($0) } dict[1882335561] = { return Api.MessageMedia.parse_messageMediaContact($0) }
dict[1065280907] = { return Api.MessageMedia.parse_messageMediaDice($0) } dict[1065280907] = { return Api.MessageMedia.parse_messageMediaDice($0) }
dict[-581497899] = { return Api.MessageMedia.parse_messageMediaDocument($0) } dict[1838230743] = { return Api.MessageMedia.parse_messageMediaDocument($0) }
dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) } dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) }
dict[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) } dict[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) }
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) } dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }

View File

@ -428,7 +428,7 @@ public extension Api {
indirect enum InputMedia: TypeConstructorDescription { indirect enum InputMedia: TypeConstructorDescription {
case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String) case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String)
case inputMediaDice(emoticon: String) case inputMediaDice(emoticon: String)
case inputMediaDocument(flags: Int32, id: Api.InputDocument, ttlSeconds: Int32?, query: String?) case inputMediaDocument(flags: Int32, id: Api.InputDocument, videoCover: Api.InputPhoto?, ttlSeconds: Int32?, query: String?)
case inputMediaDocumentExternal(flags: Int32, url: String, ttlSeconds: Int32?) case inputMediaDocumentExternal(flags: Int32, url: String, ttlSeconds: Int32?)
case inputMediaEmpty case inputMediaEmpty
case inputMediaGame(id: Api.InputGame) case inputMediaGame(id: Api.InputGame)
@ -440,7 +440,7 @@ public extension Api {
case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?) case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?)
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?)
case inputMediaStory(peer: Api.InputPeer, id: Int32) case inputMediaStory(peer: Api.InputPeer, id: Int32)
case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, ttlSeconds: Int32?) case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, videoCover: Api.InputPhoto?, ttlSeconds: Int32?)
case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?) case inputMediaUploadedPhoto(flags: Int32, file: Api.InputFile, stickers: [Api.InputDocument]?, ttlSeconds: Int32?)
case inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) case inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String)
case inputMediaWebPage(flags: Int32, url: String) case inputMediaWebPage(flags: Int32, url: String)
@ -462,12 +462,13 @@ public extension Api {
} }
serializeString(emoticon, buffer: buffer, boxed: false) serializeString(emoticon, buffer: buffer, boxed: false)
break break
case .inputMediaDocument(let flags, let id, let ttlSeconds, let query): case .inputMediaDocument(let flags, let id, let videoCover, let ttlSeconds, let query):
if boxed { if boxed {
buffer.appendInt32(860303448) buffer.appendInt32(1946579745)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
id.serialize(buffer, true) id.serialize(buffer, true)
if Int(flags) & Int(1 << 3) != 0 {videoCover!.serialize(buffer, true)}
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(query!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeString(query!, buffer: buffer, boxed: false)}
break break
@ -576,9 +577,9 @@ public extension Api {
peer.serialize(buffer, true) peer.serialize(buffer, true)
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
break break
case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let ttlSeconds): case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let videoCover, let ttlSeconds):
if boxed { if boxed {
buffer.appendInt32(1530447553) buffer.appendInt32(-264125395)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
file.serialize(buffer, true) file.serialize(buffer, true)
@ -594,6 +595,7 @@ public extension Api {
for item in stickers! { for item in stickers! {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
if Int(flags) & Int(1 << 6) != 0 {videoCover!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)}
break break
case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds): case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds):
@ -636,8 +638,8 @@ public extension Api {
return ("inputMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any)]) return ("inputMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any)])
case .inputMediaDice(let emoticon): case .inputMediaDice(let emoticon):
return ("inputMediaDice", [("emoticon", emoticon as Any)]) return ("inputMediaDice", [("emoticon", emoticon as Any)])
case .inputMediaDocument(let flags, let id, let ttlSeconds, let query): case .inputMediaDocument(let flags, let id, let videoCover, let ttlSeconds, let query):
return ("inputMediaDocument", [("flags", flags as Any), ("id", id as Any), ("ttlSeconds", ttlSeconds as Any), ("query", query as Any)]) return ("inputMediaDocument", [("flags", flags as Any), ("id", id as Any), ("videoCover", videoCover as Any), ("ttlSeconds", ttlSeconds as Any), ("query", query as Any)])
case .inputMediaDocumentExternal(let flags, let url, let ttlSeconds): case .inputMediaDocumentExternal(let flags, let url, let ttlSeconds):
return ("inputMediaDocumentExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)]) return ("inputMediaDocumentExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)])
case .inputMediaEmpty: case .inputMediaEmpty:
@ -660,8 +662,8 @@ public extension Api {
return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)]) return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)])
case .inputMediaStory(let peer, let id): case .inputMediaStory(let peer, let id):
return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)]) return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)])
case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let ttlSeconds): case .inputMediaUploadedDocument(let flags, let file, let thumb, let mimeType, let attributes, let stickers, let videoCover, let ttlSeconds):
return ("inputMediaUploadedDocument", [("flags", flags as Any), ("file", file as Any), ("thumb", thumb as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)]) return ("inputMediaUploadedDocument", [("flags", flags as Any), ("file", file as Any), ("thumb", thumb as Any), ("mimeType", mimeType as Any), ("attributes", attributes as Any), ("stickers", stickers as Any), ("videoCover", videoCover as Any), ("ttlSeconds", ttlSeconds as Any)])
case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds): case .inputMediaUploadedPhoto(let flags, let file, let stickers, let ttlSeconds):
return ("inputMediaUploadedPhoto", [("flags", flags as Any), ("file", file as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)]) return ("inputMediaUploadedPhoto", [("flags", flags as Any), ("file", file as Any), ("stickers", stickers as Any), ("ttlSeconds", ttlSeconds as Any)])
case .inputMediaVenue(let geoPoint, let title, let address, let provider, let venueId, let venueType): case .inputMediaVenue(let geoPoint, let title, let address, let provider, let venueId, let venueType):
@ -709,16 +711,21 @@ public extension Api {
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.InputDocument _2 = Api.parse(reader, signature: signature) as? Api.InputDocument
} }
var _3: Int32? var _3: Api.InputPhoto?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
var _4: String? _3 = Api.parse(reader, signature: signature) as? Api.InputPhoto
if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } } }
var _4: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() }
var _5: String?
if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, ttlSeconds: _3, query: _4) if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, videoCover: _3, ttlSeconds: _4, query: _5)
} }
else { else {
return nil return nil
@ -965,17 +972,22 @@ public extension Api {
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self)
} } } }
var _7: Int32? var _7: Api.InputPhoto?
if Int(_1!) & Int(1 << 1) != 0 {_7 = reader.readInt32() } if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.InputPhoto
} }
var _8: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_8 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = _5 != nil let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil
return Api.InputMedia.inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, ttlSeconds: _7) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.InputMedia.inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, videoCover: _7, ttlSeconds: _8)
} }
else { else {
return nil return nil

View File

@ -710,7 +710,7 @@ public extension Api {
indirect enum MessageMedia: TypeConstructorDescription { indirect enum MessageMedia: TypeConstructorDescription {
case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64)
case messageMediaDice(value: Int32, emoticon: String) case messageMediaDice(value: Int32, emoticon: String)
case messageMediaDocument(flags: Int32, document: Api.Document?, altDocuments: [Api.Document]?, ttlSeconds: Int32?) case messageMediaDocument(flags: Int32, document: Api.Document?, altDocuments: [Api.Document]?, coverPhoto: Api.Photo?, ttlSeconds: Int32?)
case messageMediaEmpty case messageMediaEmpty
case messageMediaGame(game: Api.Game) case messageMediaGame(game: Api.Game)
case messageMediaGeo(geo: Api.GeoPoint) case messageMediaGeo(geo: Api.GeoPoint)
@ -745,9 +745,9 @@ public extension Api {
serializeInt32(value, buffer: buffer, boxed: false) serializeInt32(value, buffer: buffer, boxed: false)
serializeString(emoticon, buffer: buffer, boxed: false) serializeString(emoticon, buffer: buffer, boxed: false)
break break
case .messageMediaDocument(let flags, let document, let altDocuments, let ttlSeconds): case .messageMediaDocument(let flags, let document, let altDocuments, let coverPhoto, let ttlSeconds):
if boxed { if boxed {
buffer.appendInt32(-581497899) buffer.appendInt32(1838230743)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)}
@ -756,6 +756,7 @@ public extension Api {
for item in altDocuments! { for item in altDocuments! {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
if Int(flags) & Int(1 << 9) != 0 {coverPhoto!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(ttlSeconds!, buffer: buffer, boxed: false)}
break break
case .messageMediaEmpty: case .messageMediaEmpty:
@ -909,8 +910,8 @@ public extension Api {
return ("messageMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any), ("userId", userId as Any)]) return ("messageMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any), ("userId", userId as Any)])
case .messageMediaDice(let value, let emoticon): case .messageMediaDice(let value, let emoticon):
return ("messageMediaDice", [("value", value as Any), ("emoticon", emoticon as Any)]) return ("messageMediaDice", [("value", value as Any), ("emoticon", emoticon as Any)])
case .messageMediaDocument(let flags, let document, let altDocuments, let ttlSeconds): case .messageMediaDocument(let flags, let document, let altDocuments, let coverPhoto, let ttlSeconds):
return ("messageMediaDocument", [("flags", flags as Any), ("document", document as Any), ("altDocuments", altDocuments as Any), ("ttlSeconds", ttlSeconds as Any)]) return ("messageMediaDocument", [("flags", flags as Any), ("document", document as Any), ("altDocuments", altDocuments as Any), ("coverPhoto", coverPhoto as Any), ("ttlSeconds", ttlSeconds as Any)])
case .messageMediaEmpty: case .messageMediaEmpty:
return ("messageMediaEmpty", []) return ("messageMediaEmpty", [])
case .messageMediaGame(let game): case .messageMediaGame(let game):
@ -990,14 +991,19 @@ public extension Api {
if Int(_1!) & Int(1 << 5) != 0 {if let _ = reader.readInt32() { if Int(_1!) & Int(1 << 5) != 0 {if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
} } } }
var _4: Int32? var _4: Api.Photo?
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _5: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_5 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, ttlSeconds: _4) if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, coverPhoto: _4, ttlSeconds: _5)
} }
else { else {
return nil return nil

View File

@ -464,6 +464,21 @@ public extension Api.functions.account {
}) })
} }
} }
public extension Api.functions.account {
static func getCollectibleEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.EmojiStatuses>) {
let buffer = Buffer()
buffer.appendInt32(779830595)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.getCollectibleEmojiStatuses", 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 { public extension Api.functions.account {
static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.ConnectedBots>) { static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.ConnectedBots>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -796,17 +796,36 @@ public extension Api {
} }
public extension Api { public extension Api {
enum EmojiStatus: TypeConstructorDescription { enum EmojiStatus: TypeConstructorDescription {
case emojiStatus(documentId: Int64) case emojiStatus(flags: Int32, documentId: Int64, until: Int32?)
case emojiStatusCollectible(flags: Int32, collectibleId: Int64, documentId: Int64, title: String, slug: String, patternDocumentId: Int64, centerColor: Int32, edgeColor: Int32, patternColor: Int32, textColor: Int32, until: Int32?)
case emojiStatusEmpty case emojiStatusEmpty
case emojiStatusUntil(documentId: Int64, until: Int32) case inputEmojiStatusCollectible(flags: Int32, collectibleId: Int64, until: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .emojiStatus(let documentId): case .emojiStatus(let flags, let documentId, let until):
if boxed { if boxed {
buffer.appendInt32(-1835310691) buffer.appendInt32(-402717046)
} }
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(documentId, buffer: buffer, boxed: false) serializeInt64(documentId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(until!, buffer: buffer, boxed: false)}
break
case .emojiStatusCollectible(let flags, let collectibleId, let documentId, let title, let slug, let patternDocumentId, let centerColor, let edgeColor, let patternColor, let textColor, let until):
if boxed {
buffer.appendInt32(1904500795)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(collectibleId, buffer: buffer, boxed: false)
serializeInt64(documentId, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(slug, buffer: buffer, boxed: false)
serializeInt64(patternDocumentId, buffer: buffer, boxed: false)
serializeInt32(centerColor, buffer: buffer, boxed: false)
serializeInt32(edgeColor, buffer: buffer, boxed: false)
serializeInt32(patternColor, buffer: buffer, boxed: false)
serializeInt32(textColor, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(until!, buffer: buffer, boxed: false)}
break break
case .emojiStatusEmpty: case .emojiStatusEmpty:
if boxed { if boxed {
@ -814,33 +833,83 @@ public extension Api {
} }
break break
case .emojiStatusUntil(let documentId, let until): case .inputEmojiStatusCollectible(let flags, let collectibleId, let until):
if boxed { if boxed {
buffer.appendInt32(-97474361) buffer.appendInt32(118758847)
} }
serializeInt64(documentId, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(until, buffer: buffer, boxed: false) serializeInt64(collectibleId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(until!, buffer: buffer, boxed: false)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .emojiStatus(let documentId): case .emojiStatus(let flags, let documentId, let until):
return ("emojiStatus", [("documentId", documentId as Any)]) return ("emojiStatus", [("flags", flags as Any), ("documentId", documentId as Any), ("until", until as Any)])
case .emojiStatusCollectible(let flags, let collectibleId, let documentId, let title, let slug, let patternDocumentId, let centerColor, let edgeColor, let patternColor, let textColor, let until):
return ("emojiStatusCollectible", [("flags", flags as Any), ("collectibleId", collectibleId as Any), ("documentId", documentId as Any), ("title", title as Any), ("slug", slug as Any), ("patternDocumentId", patternDocumentId as Any), ("centerColor", centerColor as Any), ("edgeColor", edgeColor as Any), ("patternColor", patternColor as Any), ("textColor", textColor as Any), ("until", until as Any)])
case .emojiStatusEmpty: case .emojiStatusEmpty:
return ("emojiStatusEmpty", []) return ("emojiStatusEmpty", [])
case .emojiStatusUntil(let documentId, let until): case .inputEmojiStatusCollectible(let flags, let collectibleId, let until):
return ("emojiStatusUntil", [("documentId", documentId as Any), ("until", until as Any)]) return ("inputEmojiStatusCollectible", [("flags", flags as Any), ("collectibleId", collectibleId as Any), ("until", until as Any)])
} }
} }
public static func parse_emojiStatus(_ reader: BufferReader) -> EmojiStatus? { public static func parse_emojiStatus(_ reader: BufferReader) -> EmojiStatus? {
var _1: Int64? var _1: Int32?
_1 = reader.readInt64() _1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
if _c1 { let _c2 = _2 != nil
return Api.EmojiStatus.emojiStatus(documentId: _1!) let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.EmojiStatus.emojiStatus(flags: _1!, documentId: _2!, until: _3)
}
else {
return nil
}
}
public static func parse_emojiStatusCollectible(_ reader: BufferReader) -> EmojiStatus? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int64?
_3 = reader.readInt64()
var _4: String?
_4 = parseString(reader)
var _5: String?
_5 = parseString(reader)
var _6: Int64?
_6 = reader.readInt64()
var _7: Int32?
_7 = reader.readInt32()
var _8: Int32?
_8 = reader.readInt32()
var _9: Int32?
_9 = reader.readInt32()
var _10: Int32?
_10 = reader.readInt32()
var _11: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_11 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
let _c11 = (Int(_1!) & Int(1 << 0) == 0) || _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.EmojiStatus.emojiStatusCollectible(flags: _1!, collectibleId: _2!, documentId: _3!, title: _4!, slug: _5!, patternDocumentId: _6!, centerColor: _7!, edgeColor: _8!, patternColor: _9!, textColor: _10!, until: _11)
} }
else { else {
return nil return nil
@ -849,15 +918,18 @@ public extension Api {
public static func parse_emojiStatusEmpty(_ reader: BufferReader) -> EmojiStatus? { public static func parse_emojiStatusEmpty(_ reader: BufferReader) -> EmojiStatus? {
return Api.EmojiStatus.emojiStatusEmpty return Api.EmojiStatus.emojiStatusEmpty
} }
public static func parse_emojiStatusUntil(_ reader: BufferReader) -> EmojiStatus? { public static func parse_inputEmojiStatusCollectible(_ reader: BufferReader) -> EmojiStatus? {
var _1: Int64? var _1: Int32?
_1 = reader.readInt64() _1 = reader.readInt32()
var _2: Int32? var _2: Int64?
_2 = reader.readInt32() _2 = reader.readInt64()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
if _c1 && _c2 { let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
return Api.EmojiStatus.emojiStatusUntil(documentId: _1!, until: _2!) if _c1 && _c2 && _c3 {
return Api.EmojiStatus.inputEmojiStatusCollectible(flags: _1!, collectibleId: _2!, until: _3)
} }
else { else {
return nil return nil

View File

@ -350,7 +350,8 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
case let .messageMediaGeoLive(_, geo, heading, period, proximityNotificationRadius): case let .messageMediaGeoLive(_, geo, heading, period, proximityNotificationRadius):
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, liveProximityNotificationRadius: proximityNotificationRadius, heading: heading) let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, liveProximityNotificationRadius: proximityNotificationRadius, heading: heading)
return (mediaMap, nil, nil, nil, nil) return (mediaMap, nil, nil, nil, nil)
case let .messageMediaDocument(flags, document, altDocuments, ttlSeconds): case let .messageMediaDocument(flags, document, altDocuments, coverPhoto, ttlSeconds):
let _ = coverPhoto
if let document = document { if let document = document {
if let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments) { if let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments) {
return (mediaFile, ttlSeconds, (flags & (1 << 3)) != 0, (flags & (1 << 4)) != 0, nil) return (mediaFile, ttlSeconds, (flags & (1 << 3)) != 0, (flags & (1 << 4)) != 0, nil)

View File

@ -229,7 +229,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
} }
|> mapToSignal { validatedResource -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { validatedResource -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
if let validatedResource = validatedResource.updatedResource as? TelegramCloudMediaResourceWithFileReference, let reference = validatedResource.fileReference { if let validatedResource = validatedResource.updatedResource as? TelegramCloudMediaResourceWithFileReference, let reference = validatedResource.fileReference {
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: reference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil))) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: reference)), videoCover: nil, ttlSeconds: nil, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)))
} else { } else {
return .fail(.generic) return .fail(.generic)
} }
@ -246,7 +246,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
} }
} }
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil))) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), videoCover: nil, ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil)))
} }
} else { } else {
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, isPaid: false, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, file: file) return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, isPaid: false, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, file: file)
@ -828,7 +828,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0))) return .single(.progress(PendingMessageUploadedContentProgress(progress: 1.0)))
|> then( |> then(
.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil))) .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), videoCover: nil, ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)))
) )
} }
referenceKey = key referenceKey = key
@ -1049,11 +1049,11 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
} }
if ttlSeconds != nil { if ttlSeconds != nil {
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: referenceKey))) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, videoCover: nil, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: referenceKey)))
} }
if !isGrouped { if !isGrouped {
let resultInfo = PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: referenceKey) let resultInfo = PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, videoCover: nil, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: referenceKey)
return .single(.content(resultInfo)) return .single(.content(resultInfo))
} }
@ -1064,11 +1064,11 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|> mapError { _ -> PendingMessageUploadError in } |> mapError { _ -> PendingMessageUploadError in }
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
if let inputPeer = inputPeer { if let inputPeer = inputPeer {
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds))) return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, videoCover: nil, ttlSeconds: ttlSeconds)))
|> mapError { _ -> PendingMessageUploadError in return .generic } |> mapError { _ -> PendingMessageUploadError in return .generic }
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
switch result { switch result {
case let .messageMediaDocument(_, document, altDocuments, _): case let .messageMediaDocument(_, document, altDocuments, _, _):
if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
var flags: Int32 = 0 var flags: Int32 = 0
var ttlSeconds: Int32? var ttlSeconds: Int32?
@ -1080,7 +1080,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
flags |= (1 << 2) flags |= (1 << 2)
} }
let result: PendingMessageUploadedContentResult = .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)) let result: PendingMessageUploadedContentResult = .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), videoCover: nil, ttlSeconds: ttlSeconds, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil))
if let _ = ttlSeconds { if let _ = ttlSeconds {
return .single(result) return .single(result)
} else { } else {

View File

@ -702,7 +702,7 @@ private func uploadedFile(account: Account, data: Data, mimeType: String, attrib
|> map { next -> UploadMediaEvent in |> map { next -> UploadMediaEvent in
switch next { switch next {
case let .inputFile(inputFile): case let .inputFile(inputFile):
return .result(Api.InputMedia.inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, ttlSeconds: nil)) return .result(Api.InputMedia.inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, videoCover: nil, ttlSeconds: nil))
case .inputSecretFile: case .inputSecretFile:
preconditionFailure() preconditionFailure()
case let .progress(progress): case let .progress(progress):

View File

@ -158,11 +158,11 @@ public func standaloneUploadedFile(postbox: Postbox, network: Network, peerId: P
if let _ = thumbnailFile { if let _ = thumbnailFile {
flags |= 1 << 2 flags |= 1 << 2
} }
return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, ttlSeconds: nil))) return network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: mimeType, attributes: inputDocumentAttributesFromFileAttributes(attributes), stickers: nil, videoCover: nil, ttlSeconds: nil)))
|> mapError { _ -> StandaloneUploadMediaError in return .generic } |> mapError { _ -> StandaloneUploadMediaError in return .generic }
|> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in |> mapToSignal { media -> Signal<StandaloneUploadMediaEvent, StandaloneUploadMediaError> in
switch media { switch media {
case let .messageMediaDocument(_, document, altDocuments, _): case let .messageMediaDocument(_, document, altDocuments, _, _):
if let document = document { if let document = document {
if let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments) { if let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments) {
return .single(.result(.media(.standalone(media: mediaFile)))) return .single(.result(.media(.standalone(media: mediaFile))))

View File

@ -108,6 +108,7 @@ final class AccountTaskManager {
tasks.add(managedRecentStatusEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedRecentStatusEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedFeaturedStatusEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedFeaturedStatusEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedFeaturedChannelStatusEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedFeaturedChannelStatusEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedUniqueStarGifts(accountPeerId: self.accountPeerId, postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedProfilePhotoEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedProfilePhotoEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedGroupPhotoEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedGroupPhotoEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedBackgroundIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedBackgroundIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start())

View File

@ -213,11 +213,11 @@ func managedRecentStatusEmoji(postbox: Postbox, network: Network) -> Signal<Void
case let .emojiStatuses(_, statuses): case let .emojiStatuses(_, statuses):
let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:)) let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:))
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.map(\.fileId)) return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.compactMap(\.emojiFileId))
|> map { files -> [OrderedItemListEntry] in |> map { files -> [OrderedItemListEntry] in
var items: [OrderedItemListEntry] = [] var items: [OrderedItemListEntry] = []
for status in parsedStatuses { for status in parsedStatuses {
guard let file = files[status.fileId] else { guard let fileId = status.emojiFileId, let file = files[fileId] else {
continue continue
} }
if let entry = CodableEntry(RecentMediaItem(file)) { if let entry = CodableEntry(RecentMediaItem(file)) {
@ -243,11 +243,11 @@ func managedFeaturedStatusEmoji(postbox: Postbox, network: Network) -> Signal<Vo
case let .emojiStatuses(_, statuses): case let .emojiStatuses(_, statuses):
let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:)) let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:))
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.map(\.fileId)) return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.compactMap(\.emojiFileId))
|> map { files -> [OrderedItemListEntry] in |> map { files -> [OrderedItemListEntry] in
var items: [OrderedItemListEntry] = [] var items: [OrderedItemListEntry] = []
for status in parsedStatuses { for status in parsedStatuses {
guard let file = files[status.fileId] else { guard let fileId = status.emojiFileId, let file = files[fileId] else {
continue continue
} }
if let entry = CodableEntry(RecentMediaItem(file)) { if let entry = CodableEntry(RecentMediaItem(file)) {
@ -273,11 +273,11 @@ func managedFeaturedChannelStatusEmoji(postbox: Postbox, network: Network) -> Si
case let .emojiStatuses(_, statuses): case let .emojiStatuses(_, statuses):
let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:)) let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:))
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.map(\.fileId)) return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.compactMap(\.emojiFileId))
|> map { files -> [OrderedItemListEntry] in |> map { files -> [OrderedItemListEntry] in
var items: [OrderedItemListEntry] = [] var items: [OrderedItemListEntry] = []
for status in parsedStatuses { for status in parsedStatuses {
guard let file = files[status.fileId] else { guard let fileId = status.emojiFileId, let file = files[fileId] else {
continue continue
} }
if let entry = CodableEntry(RecentMediaItem(file)) { if let entry = CodableEntry(RecentMediaItem(file)) {
@ -292,6 +292,55 @@ func managedFeaturedChannelStatusEmoji(postbox: Postbox, network: Network) -> Si
return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart return (poll |> then(.complete() |> suspendAwareDelay(3.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
} }
func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudUniqueStarGifts, extractItemId: { RecentStarGiftItemId($0).id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.account.getCollectibleEmojiStatuses(hash: hash))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
switch result {
case .emojiStatusesNotModified:
return .single(nil)
case let .emojiStatuses(_, statuses):
let parsedStatuses = statuses.compactMap(PeerEmojiStatus.init(apiStatus:))
return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: parsedStatuses.flatMap(\.associatedFileIds))
|> map { files -> [OrderedItemListEntry] in
var items: [OrderedItemListEntry] = []
for status in parsedStatuses {
switch status.content {
case let .starGift(id, fileId, title, slug, patternFileId, innerColor, outerColor, patternColor, textColor):
let slugComponents = slug.components(separatedBy: "-")
if let file = files[fileId], let patternFile = files[patternFileId], let numberString = slugComponents.last, let number = Int32(numberString) {
let gift = StarGift.UniqueGift(
id: id,
title: title,
number: number,
slug: slug,
owner: .peerId(accountPeerId),
attributes: [
.model(name: "", file: file, rarity: 0),
.pattern(name: "", file: patternFile, rarity: 0),
.backdrop(name: "", innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor, rarity: 0)
],
availability: StarGift.UniqueGift.Availability(issued: 0, total: 0)
)
if let entry = CodableEntry(RecentStarGiftItem(gift)) {
items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry))
}
}
default:
break
}
}
return items
}
}
}
})
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
func managedProfilePhotoEmoji(postbox: Postbox, network: Network) -> Signal<Void, NoError> { func managedProfilePhotoEmoji(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudFeaturedProfilePhotoEmoji, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudFeaturedProfilePhotoEmoji, extractItemId: { RecentMediaItemId($0).mediaId.id }, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.account.getDefaultProfilePhotoEmojis(hash: hash)) return network.request(Api.functions.account.getDefaultProfilePhotoEmojis(hash: hash))

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 197 return 198
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -52,7 +52,7 @@ extension Api.MessageMedia {
} else { } else {
return nil return nil
} }
case let .messageMediaDocument(_, document, _, _): case let .messageMediaDocument(_, document, _, _, _):
if let document = document { if let document = document {
return collectPreCachedResources(for: document) return collectPreCachedResources(for: document)
} }
@ -626,14 +626,14 @@ extension Api.EncryptedMessage {
extension Api.InputMedia { extension Api.InputMedia {
func withUpdatedStickers(_ stickers: [Api.InputDocument]?) -> Api.InputMedia { func withUpdatedStickers(_ stickers: [Api.InputDocument]?) -> Api.InputMedia {
switch self { switch self {
case let .inputMediaUploadedDocument(flags, file, thumb, mimeType, attributes, _, ttlSeconds): case let .inputMediaUploadedDocument(flags, file, thumb, mimeType, attributes, _, videoCover, ttlSeconds):
var flags = flags var flags = flags
var attributes = attributes var attributes = attributes
if let _ = stickers { if let _ = stickers {
flags |= (1 << 0) flags |= (1 << 0)
attributes.append(.documentAttributeHasStickers) attributes.append(.documentAttributeHasStickers)
} }
return .inputMediaUploadedDocument(flags: flags, file: file, thumb: thumb, mimeType: mimeType, attributes: attributes, stickers: stickers, ttlSeconds: ttlSeconds) return .inputMediaUploadedDocument(flags: flags, file: file, thumb: thumb, mimeType: mimeType, attributes: attributes, stickers: stickers, videoCover: videoCover, ttlSeconds: ttlSeconds)
case let .inputMediaUploadedPhoto(flags, file, _, ttlSeconds): case let .inputMediaUploadedPhoto(flags, file, _, ttlSeconds):
var flags = flags var flags = flags
if let _ = stickers { if let _ = stickers {

View File

@ -260,11 +260,62 @@ public enum PeerNameColor: Hashable {
} }
public struct PeerEmojiStatus: Equatable, Codable { public struct PeerEmojiStatus: Equatable, Codable {
public var fileId: Int64 public enum Content: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case discriminator
case fileId
case id
case title
case slug
case patternFileId
case innerColor
case outerColor
case patternColor
case textColor
}
case emoji(fileId: Int64)
case starGift(id: Int64, fileId: Int64, title: String, slug: String, patternFileId: Int64, innerColor: Int32, outerColor: Int32, patternColor: Int32, textColor: Int32)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch try container.decode(Int32.self, forKey: .discriminator) {
case 0:
self = .emoji(fileId: try container.decode(Int64.self, forKey: .fileId))
case 1:
self = .starGift(id: try container.decode(Int64.self, forKey: .id), fileId: try container.decode(Int64.self, forKey: .fileId), title: try container.decode(String.self, forKey: .title), slug: try container.decode(String.self, forKey: .slug), patternFileId: try container.decode(Int64.self, forKey: .patternFileId), innerColor: try container.decode(Int32.self, forKey: .innerColor), outerColor: try container.decode(Int32.self, forKey: .outerColor), patternColor: try container.decode(Int32.self, forKey: .patternColor), textColor: try container.decode(Int32.self, forKey: .textColor))
default:
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "content"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .emoji(fileId):
try container.encode(0 as Int32, forKey: .discriminator)
try container.encode(fileId, forKey: .fileId)
case let .starGift(id, fileId, title, slug, patternFileId, innerColor, outerColor, patternColor, textColor):
try container.encode(1 as Int32, forKey: .discriminator)
try container.encode(id, forKey: .id)
try container.encode(fileId, forKey: .fileId)
try container.encode(title, forKey: .title)
try container.encode(slug, forKey: .slug)
try container.encode(patternFileId, forKey: .patternFileId)
try container.encode(innerColor, forKey: .innerColor)
try container.encode(outerColor, forKey: .outerColor)
try container.encode(patternColor, forKey: .patternColor)
try container.encode(textColor, forKey: .textColor)
}
}
}
public var content: Content
public var expirationDate: Int32? public var expirationDate: Int32?
public init(fileId: Int64, expirationDate: Int32?) { public init(content: Content, expirationDate: Int32?) {
self.fileId = fileId self.content = content
self.expirationDate = expirationDate self.expirationDate = expirationDate
} }
} }
@ -272,15 +323,52 @@ public struct PeerEmojiStatus: Equatable, Codable {
extension PeerEmojiStatus { extension PeerEmojiStatus {
init?(apiStatus: Api.EmojiStatus) { init?(apiStatus: Api.EmojiStatus) {
switch apiStatus { switch apiStatus {
case let .emojiStatus(documentId): case let .emojiStatus(_, documentId, until):
self.init(fileId: documentId, expirationDate: nil) self.init(content: .emoji(fileId: documentId), expirationDate: until)
case let .emojiStatusUntil(documentId, until): case let .emojiStatusCollectible(_, collectibleId, documentId, title, slug, patternDocumentId, centerColor, edgeColor, patternColor, textColor, until):
self.init(fileId: documentId, expirationDate: until) self.init(content: .starGift(id: collectibleId, fileId: documentId, title: title, slug: slug, patternFileId: patternDocumentId, innerColor: centerColor, outerColor: edgeColor, patternColor: patternColor, textColor: textColor), expirationDate: until)
case .emojiStatusEmpty: case .emojiStatusEmpty, .inputEmojiStatusCollectible:
return nil return nil
} }
} }
} }
extension PeerEmojiStatus {
var emojiFileId: Int64? {
switch self.content {
case let .emoji(fileId):
return fileId
default:
return nil
}
}
var associatedFileIds: [Int64] {
switch self.content {
case let .emoji(fileId):
return [fileId]
case let .starGift(_, fileId, _, _, patternFileId, _, _, _, _):
return [fileId, patternFileId]
}
}
public var fileId: Int64 {
switch self.content {
case let .emoji(fileId):
return fileId
case let .starGift(_, fileId, _, _, _, _, _, _, _):
return fileId
}
}
public var color: Int32? {
switch self.content {
case .emoji:
return nil
case let .starGift(_, _, _, _, _, innerColor, _, _, _):
return innerColor
}
}
}
public struct CachedUserFlags: OptionSet { public struct CachedUserFlags: OptionSet {
public var rawValue: Int32 public var rawValue: Int32

View File

@ -95,6 +95,7 @@ public struct Namespaces {
public static let CloudFeaturedChannelStatusEmoji: Int32 = 27 public static let CloudFeaturedChannelStatusEmoji: Int32 = 27
public static let CloudDisabledChannelStatusEmoji: Int32 = 28 public static let CloudDisabledChannelStatusEmoji: Int32 = 28
public static let CloudDefaultTagReactions: Int32 = 29 public static let CloudDefaultTagReactions: Int32 = 29
public static let CloudUniqueStarGifts: Int32 = 30
} }
public struct CachedItemCollection { public struct CachedItemCollection {

View File

@ -284,3 +284,47 @@ public final class RecentReactionItem: Codable, Equatable {
return lhs.content == rhs.content return lhs.content == rhs.content
} }
} }
public struct RecentStarGiftItemId {
public let rawValue: MemoryBuffer
public let id: Int64
public init(_ rawValue: MemoryBuffer) {
self.rawValue = rawValue
assert(rawValue.length == 8)
var id: Int64 = 0
memcpy(&id, rawValue.memory, 8)
self.id = id
}
public init(_ id: Int64) {
var id = id
self.id = id
self.rawValue = MemoryBuffer(memory: malloc(8)!, capacity: 8, length: 8, freeWhenDone: true)
memcpy(self.rawValue.memory, &id, 8)
}
}
public final class RecentStarGiftItem: Codable, Equatable {
public let starGift: StarGift.UniqueGift
public init(_ starGift: StarGift.UniqueGift) {
self.starGift = starGift
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.starGift = try container.decode(StarGift.UniqueGift.self, forKey: "g")
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.starGift, forKey: "g")
}
public static func ==(lhs: RecentStarGiftItem, rhs: RecentStarGiftItem) -> Bool {
return lhs.starGift == rhs.starGift
}
}

View File

@ -186,22 +186,26 @@ public final class TelegramChannel: Peer, Equatable {
} }
public var associatedMediaIds: [MediaId]? { public var associatedMediaIds: [MediaId]? {
if let emojiStatus = self.emojiStatus, let backgroundEmojiId = self.backgroundEmojiId { var mediaIds: [MediaId] = []
return [ if let emojiStatus = self.emojiStatus {
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId), switch emojiStatus.content {
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) case let .emoji(fileId):
] mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId))
} else if let emojiStatus = self.emojiStatus { case let .starGift(_, fileId, _, _, patternFileId, _, _, _, _):
return [ mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId))
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId) mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: patternFileId))
] }
} else if let backgroundEmojiId = self.backgroundEmojiId { }
return [ if let backgroundEmojiId = self.backgroundEmojiId {
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId))
] }
} else { if let profileBackgroundEmojiId = self.profileBackgroundEmojiId {
mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: profileBackgroundEmojiId))
}
guard !mediaIds.isEmpty else {
return nil return nil
} }
return mediaIds
} }
public let associatedPeerId: PeerId? = nil public let associatedPeerId: PeerId? = nil

View File

@ -174,22 +174,26 @@ public final class TelegramUser: Peer, Equatable {
} }
public var associatedMediaIds: [MediaId]? { public var associatedMediaIds: [MediaId]? {
if let emojiStatus = self.emojiStatus, let backgroundEmojiId = self.backgroundEmojiId { var mediaIds: [MediaId] = []
return [ if let emojiStatus = self.emojiStatus {
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId), switch emojiStatus.content {
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) case let .emoji(fileId):
] mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId))
} else if let emojiStatus = self.emojiStatus { case let .starGift(_, fileId, _, _, patternFileId, _, _, _, _):
return [ mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId))
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId) mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: patternFileId))
] }
} else if let backgroundEmojiId = self.backgroundEmojiId { }
return [ if let backgroundEmojiId = self.backgroundEmojiId {
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId))
] }
} else { if let profileBackgroundEmojiId = self.profileBackgroundEmojiId {
mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: profileBackgroundEmojiId))
}
guard !mediaIds.isEmpty else {
return nil return nil
} }
return mediaIds
} }
public let associatedPeerId: PeerId? = nil public let associatedPeerId: PeerId? = nil

View File

@ -75,15 +75,75 @@ public extension TelegramEngine {
return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: true) return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: true)
} }
public func setStarGiftStatus(starGift: StarGift.UniqueGift, expirationDate: Int32?) -> Signal<Never, NoError> {
let peerId = self.account.peerId
var flags: Int32 = 0
if let _ = expirationDate {
flags |= (1 << 0)
}
var file: TelegramMediaFile?
var patternFile: TelegramMediaFile?
var innerColor: Int32?
var outerColor: Int32?
var patternColor: Int32?
var textColor: Int32?
for attribute in starGift.attributes {
switch attribute {
case let .model(_, fileValue, _):
file = fileValue
case let .pattern(_, patternFileValue, _):
patternFile = patternFileValue
case let .backdrop(_, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
innerColor = innerColorValue
outerColor = outerColorValue
patternColor = patternColorValue
textColor = textColorValue
default:
break
}
}
let apiEmojiStatus: Api.EmojiStatus
var emojiStatus: PeerEmojiStatus?
if let file, let patternFile, let innerColor, let outerColor, let patternColor, let textColor {
apiEmojiStatus = .inputEmojiStatusCollectible(flags: flags, collectibleId: starGift.id, until: expirationDate)
emojiStatus = PeerEmojiStatus(content: .starGift(id: starGift.id, fileId: file.fileId.id, title: starGift.title, slug: starGift.slug, patternFileId: patternFile.fileId.id, innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor), expirationDate: expirationDate)
} else {
apiEmojiStatus = .emojiStatusEmpty
}
let remoteApply = self.account.network.request(Api.functions.account.updateEmojiStatus(emojiStatus: apiEmojiStatus))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
return self.account.postbox.transaction { transaction -> Void in
if let file, let patternFile {
transaction.storeMediaIfNotPresent(media: file)
transaction.storeMediaIfNotPresent(media: patternFile)
}
if let peer = transaction.getPeer(peerId) as? TelegramUser {
updatePeersCustom(transaction: transaction, peers: [
peer.withUpdatedEmojiStatus(emojiStatus)
], update: { _, updated in
updated
})
}
}
|> ignoreValues
|> then(remoteApply)
}
public func setEmojiStatus(file: TelegramMediaFile?, expirationDate: Int32?) -> Signal<Never, NoError> { public func setEmojiStatus(file: TelegramMediaFile?, expirationDate: Int32?) -> Signal<Never, NoError> {
let peerId = self.account.peerId let peerId = self.account.peerId
let remoteApply = self.account.network.request(Api.functions.account.updateEmojiStatus(emojiStatus: file.flatMap({ file in let remoteApply = self.account.network.request(Api.functions.account.updateEmojiStatus(emojiStatus: file.flatMap({ file in
if let expirationDate = expirationDate { var flags: Int32 = 0
return Api.EmojiStatus.emojiStatusUntil(documentId: file.fileId.id, until: expirationDate) if let _ = expirationDate {
} else { flags |= (1 << 0)
return Api.EmojiStatus.emojiStatus(documentId: file.fileId.id)
} }
return Api.EmojiStatus.emojiStatus(flags: flags, documentId: file.fileId.id, until: expirationDate)
}) ?? Api.EmojiStatus.emojiStatusEmpty)) }) ?? Api.EmojiStatus.emojiStatusEmpty))
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)
@ -101,7 +161,7 @@ public extension TelegramEngine {
} }
if let peer = transaction.getPeer(peerId) as? TelegramUser { if let peer = transaction.getPeer(peerId) as? TelegramUser {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(file.flatMap({ PeerEmojiStatus(fileId: $0.fileId.id, expirationDate: expirationDate) }))], update: { _, updated in updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(file.flatMap({ PeerEmojiStatus(content: .emoji(fileId: $0.fileId.id), expirationDate: expirationDate) }))], update: { _, updated in
updated updated
}) })
} }

View File

@ -153,7 +153,7 @@ public extension TelegramEngine {
default: default:
break break
} }
inputMedia = .inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: resolvedMimeType, attributes: attributes, stickers: nil, ttlSeconds: nil) inputMedia = .inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: resolvedMimeType, attributes: attributes, stickers: nil, videoCover: nil, ttlSeconds: nil)
} }
case let .progress(value): case let .progress(value):
return .single(value) return .single(value)

View File

@ -1405,7 +1405,7 @@ func _internal_deleteBotPreviews(account: Account, peerId: PeerId, language: Str
inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil)) inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
inputMedia.append(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil)) inputMedia.append(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
} else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { } else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource {
inputMedia.append(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: nil)) inputMedia.append(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), videoCover: nil, ttlSeconds: nil, query: nil))
} }
} }
if language == nil { if language == nil {
@ -1463,7 +1463,7 @@ func _internal_deleteBotPreviewsLanguage(account: Account, peerId: PeerId, langu
inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil)) inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
inputMedia.append(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil)) inputMedia.append(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
} else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { } else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource {
inputMedia.append(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: nil)) inputMedia.append(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), videoCover: nil, ttlSeconds: nil, query: nil))
} }
} }
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current -> CachedPeerData? in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current -> CachedPeerData? in
@ -1528,7 +1528,7 @@ func _internal_editStory(account: Account, peerId: PeerId, id: Int32, media: Eng
if let result = result, case let .content(uploadedContent) = result, case let .media(media, _) = uploadedContent.content { if let result = result, case let .content(uploadedContent) = result, case let .media(media, _) = uploadedContent.content {
inputMedia = media inputMedia = media
} else if case let .existing(media) = media, let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { } else if case let .existing(media) = media, let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource {
inputMedia = .inputMediaUploadedDocument(flags: 0, file: .inputFileStoryDocument(id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), thumb: nil, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: nil) inputMedia = .inputMediaUploadedDocument(flags: 0, file: .inputFileStoryDocument(id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))), thumb: nil, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, videoCover: nil, ttlSeconds: nil)
updatingCoverTime = true updatingCoverTime = true
} else { } else {
inputMedia = nil inputMedia = nil
@ -2099,7 +2099,7 @@ extension Stories.StoredItem {
var parsedAlternativeMedia: [Media] = [] var parsedAlternativeMedia: [Media] = []
switch media { switch media {
case let .messageMediaDocument(_, _, altDocuments, _): case let .messageMediaDocument(_, _, altDocuments, _, _):
if let altDocuments { if let altDocuments {
parsedAlternativeMedia = altDocuments.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) } parsedAlternativeMedia = altDocuments.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
} }

View File

@ -2615,7 +2615,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil)) inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
inputMedia.append(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil)) inputMedia.append(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
} else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { } else if let file = item as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource {
inputMedia.append(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: nil)) inputMedia.append(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), videoCover: nil, ttlSeconds: nil, query: nil))
} }
} }

View File

@ -274,7 +274,7 @@ public enum UpdatePeerEmojiStatusError {
func _internal_updatePeerEmojiStatus(account: Account, peerId: PeerId, fileId: Int64?, expirationDate: Int32?) -> Signal<Never, UpdatePeerEmojiStatusError> { func _internal_updatePeerEmojiStatus(account: Account, peerId: PeerId, fileId: Int64?, expirationDate: Int32?) -> Signal<Never, UpdatePeerEmojiStatusError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in return account.postbox.transaction { transaction -> Api.InputChannel? in
let updatedStatus = fileId.flatMap { let updatedStatus = fileId.flatMap {
PeerEmojiStatus(fileId: $0, expirationDate: expirationDate) PeerEmojiStatus(content: .emoji(fileId: $0), expirationDate: expirationDate)
} }
if let peer = transaction.getPeer(peerId) as? TelegramChannel { if let peer = transaction.getPeer(peerId) as? TelegramChannel {
updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(updatedStatus)], update: { _, updated in updated }) updatePeersCustom(transaction: transaction, peers: [peer.withUpdatedEmojiStatus(updatedStatus)], update: { _, updated in updated })
@ -289,11 +289,11 @@ func _internal_updatePeerEmojiStatus(account: Account, peerId: PeerId, fileId: I
} }
let mappedStatus: Api.EmojiStatus let mappedStatus: Api.EmojiStatus
if let fileId = fileId { if let fileId = fileId {
if let expirationDate = expirationDate { var flags: Int32 = 0
mappedStatus = .emojiStatusUntil(documentId: fileId, until: expirationDate) if let _ = expirationDate {
} else { flags |= (1 << 0)
mappedStatus = .emojiStatus(documentId: fileId)
} }
mappedStatus = .emojiStatus(flags: flags, documentId: fileId, until: expirationDate)
} else { } else {
mappedStatus = .emojiStatusEmpty mappedStatus = .emojiStatusEmpty
} }

View File

@ -83,11 +83,11 @@ func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResour
attributes.append(.documentAttributeVideo(flags: 0, duration: duration, w: dimensions.width, h: dimensions.height, preloadPrefixSize: nil, videoStartTs: nil, videoCodec: nil)) attributes.append(.documentAttributeVideo(flags: 0, duration: duration, w: dimensions.width, h: dimensions.height, preloadPrefixSize: nil, videoStartTs: nil, videoCodec: nil))
} }
attributes.append(.documentAttributeImageSize(w: dimensions.width, h: dimensions.height)) attributes.append(.documentAttributeImageSize(w: dimensions.width, h: dimensions.height))
return account.network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: file, thumb: thumbnailFile, mimeType: mimeType, attributes: attributes, stickers: nil, ttlSeconds: nil))) return account.network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: file, thumb: thumbnailFile, mimeType: mimeType, attributes: attributes, stickers: nil, videoCover: nil, ttlSeconds: nil)))
|> mapError { _ -> UploadStickerError in return .generic } |> mapError { _ -> UploadStickerError in return .generic }
|> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in |> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in
switch media { switch media {
case let .messageMediaDocument(_, document, altDocuments, _): case let .messageMediaDocument(_, document, altDocuments, _, _):
if let document = document, let file = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments), let uploadedResource = file.resource as? CloudDocumentMediaResource { if let document = document, let file = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments), let uploadedResource = file.resource as? CloudDocumentMediaResource {
account.postbox.mediaBox.copyResourceData(from: resource.id, to: uploadedResource.id, synchronous: true) account.postbox.mediaBox.copyResourceData(from: resource.id, to: uploadedResource.id, synchronous: true)
if let thumbnail, let previewRepresentation = file.previewRepresentations.first(where: { $0.dimensions == PixelDimensions(width: 320, height: 320) }) { if let thumbnail, let previewRepresentation = file.previewRepresentations.first(where: { $0.dimensions == PixelDimensions(width: 320, height: 320) }) {

View File

@ -284,7 +284,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
titleCredibilityIcon = .verified titleCredibilityIcon = .verified
} }
if let verificationIconFileId = peer.verificationIconFileId { if let verificationIconFileId = peer.verificationIconFileId {
titleVerifiedIcon = .emojiStatus(PeerEmojiStatus(fileId: verificationIconFileId, expirationDate: nil)) titleVerifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil))
} }
} }
} }

View File

@ -59,22 +59,26 @@ public final class EmojiStatusComponent: Component {
public let animationCache: AnimationCache public let animationCache: AnimationCache
public let animationRenderer: MultiAnimationRenderer public let animationRenderer: MultiAnimationRenderer
public let content: Content public let content: Content
public let particleColor: UIColor?
public let size: CGSize? public let size: CGSize?
public let isVisibleForAnimations: Bool public let isVisibleForAnimations: Bool
public let useSharedAnimation: Bool public let useSharedAnimation: Bool
public let action: (() -> Void)? public let action: (() -> Void)?
public let emojiFileUpdated: ((TelegramMediaFile?) -> Void)? public let emojiFileUpdated: ((TelegramMediaFile?) -> Void)?
public let tag: AnyObject?
public convenience init( public convenience init(
context: AccountContext, context: AccountContext,
animationCache: AnimationCache, animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer, animationRenderer: MultiAnimationRenderer,
content: Content, content: Content,
particleColor: UIColor? = nil,
size: CGSize? = nil, size: CGSize? = nil,
isVisibleForAnimations: Bool, isVisibleForAnimations: Bool,
useSharedAnimation: Bool = false, useSharedAnimation: Bool = false,
action: (() -> Void)?, action: (() -> Void)?,
emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil,
tag: AnyObject? = nil
) { ) {
self.init( self.init(
postbox: context.account.postbox, postbox: context.account.postbox,
@ -85,11 +89,13 @@ public final class EmojiStatusComponent: Component {
animationCache: animationCache, animationCache: animationCache,
animationRenderer: animationRenderer, animationRenderer: animationRenderer,
content: content, content: content,
particleColor: particleColor,
size: size, size: size,
isVisibleForAnimations: isVisibleForAnimations, isVisibleForAnimations: isVisibleForAnimations,
useSharedAnimation: useSharedAnimation, useSharedAnimation: useSharedAnimation,
action: action, action: action,
emojiFileUpdated: emojiFileUpdated emojiFileUpdated: emojiFileUpdated,
tag: tag
) )
} }
@ -100,11 +106,13 @@ public final class EmojiStatusComponent: Component {
animationCache: AnimationCache, animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer, animationRenderer: MultiAnimationRenderer,
content: Content, content: Content,
particleColor: UIColor? = nil,
size: CGSize? = nil, size: CGSize? = nil,
isVisibleForAnimations: Bool, isVisibleForAnimations: Bool,
useSharedAnimation: Bool = false, useSharedAnimation: Bool = false,
action: (() -> Void)?, action: (() -> Void)?,
emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil,
tag: AnyObject? = nil
) { ) {
self.postbox = postbox self.postbox = postbox
self.energyUsageSettings = energyUsageSettings self.energyUsageSettings = energyUsageSettings
@ -112,11 +120,13 @@ public final class EmojiStatusComponent: Component {
self.animationCache = animationCache self.animationCache = animationCache
self.animationRenderer = animationRenderer self.animationRenderer = animationRenderer
self.content = content self.content = content
self.particleColor = particleColor
self.size = size self.size = size
self.isVisibleForAnimations = isVisibleForAnimations self.isVisibleForAnimations = isVisibleForAnimations
self.useSharedAnimation = useSharedAnimation self.useSharedAnimation = useSharedAnimation
self.action = action self.action = action
self.emojiFileUpdated = emojiFileUpdated self.emojiFileUpdated = emojiFileUpdated
self.tag = tag
} }
public func withVisibleForAnimations(_ isVisibleForAnimations: Bool) -> EmojiStatusComponent { public func withVisibleForAnimations(_ isVisibleForAnimations: Bool) -> EmojiStatusComponent {
@ -127,11 +137,13 @@ public final class EmojiStatusComponent: Component {
animationCache: self.animationCache, animationCache: self.animationCache,
animationRenderer: self.animationRenderer, animationRenderer: self.animationRenderer,
content: self.content, content: self.content,
particleColor: self.particleColor,
size: self.size, size: self.size,
isVisibleForAnimations: isVisibleForAnimations, isVisibleForAnimations: isVisibleForAnimations,
useSharedAnimation: self.useSharedAnimation, useSharedAnimation: self.useSharedAnimation,
action: self.action, action: self.action,
emojiFileUpdated: self.emojiFileUpdated emojiFileUpdated: self.emojiFileUpdated,
tag: self.tag
) )
} }
@ -151,6 +163,9 @@ public final class EmojiStatusComponent: Component {
if lhs.content != rhs.content { if lhs.content != rhs.content {
return false return false
} }
if lhs.particleColor != rhs.particleColor {
return false
}
if lhs.size != rhs.size { if lhs.size != rhs.size {
return false return false
} }
@ -160,10 +175,23 @@ public final class EmojiStatusComponent: Component {
if lhs.useSharedAnimation != rhs.useSharedAnimation { if lhs.useSharedAnimation != rhs.useSharedAnimation {
return false return false
} }
if lhs.tag !== rhs.tag {
return false
}
return true return true
} }
public final class View: UIView { public final class View: UIView, ComponentTaggedView {
public func matches(tag: Any) -> Bool {
if let component = self.component, let componentTag = component.tag {
let tag = tag as AnyObject
if componentTag === tag {
return true
}
}
return false
}
private final class AnimationFileProperties { private final class AnimationFileProperties {
let path: String let path: String
let coloredComposition: Animation? let coloredComposition: Animation?
@ -195,6 +223,7 @@ public final class EmojiStatusComponent: Component {
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
private var component: EmojiStatusComponent? private var component: EmojiStatusComponent?
private var starsLayer: StarsEffectLayer?
private var iconView: UIImageView? private var iconView: UIImageView?
private var animationLayer: InlineStickerItemLayer? private var animationLayer: InlineStickerItemLayer?
private var lottieAnimationView: AnimationView? private var lottieAnimationView: AnimationView?
@ -255,6 +284,24 @@ public final class EmojiStatusComponent: Component {
self.isUserInteractionEnabled = component.action != nil self.isUserInteractionEnabled = component.action != nil
if let particleColor = component.particleColor {
let starsLayer: StarsEffectLayer
if let current = self.starsLayer {
starsLayer = current
} else {
starsLayer = StarsEffectLayer()
self.layer.insertSublayer(starsLayer, at: 0)
self.starsLayer = starsLayer
}
let side = floor(availableSize.width * 1.25)
let starsFrame = CGSize(width: side, height: side).centered(in: CGRect(origin: .zero, size: availableSize))
starsLayer.frame = starsFrame
starsLayer.update(color: particleColor, size: starsFrame.size)
} else if let starsLayer = self.starsLayer {
self.starsLayer = nil
starsLayer.removeFromSuperlayer()
}
//let previousContent = self.component?.content //let previousContent = self.component?.content
if self.component?.content != component.content { if self.component?.content != component.content {
switch component.content { switch component.content {
@ -662,3 +709,57 @@ public func topicIconColors(for color: Int32) -> ([UInt32], [UInt32]) {
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]) return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
} }
public final class StarsEffectLayer: SimpleLayer {
private let emitterLayer = CAEmitterLayer()
public override init() {
super.init()
self.addSublayer(self.emitterLayer)
}
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup(color: UIColor, size: CGSize) {
let emitter = CAEmitterCell()
emitter.name = "emitter"
emitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage
emitter.birthRate = 8.0
emitter.lifetime = 2.0
emitter.velocity = 0.1
emitter.scale = (size.width / 32.0) * 0.12
emitter.scaleRange = 0.02
emitter.alphaRange = 0.1
emitter.emissionRange = .pi * 2.0
let staticColors: [Any] = [
color.withAlphaComponent(0.0).cgColor,
color.withAlphaComponent(0.58).cgColor,
color.withAlphaComponent(0.58).cgColor,
color.withAlphaComponent(0.0).cgColor
]
let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife")
staticColorBehavior.setValue(staticColors, forKey: "colors")
emitter.setValue([staticColorBehavior], forKey: "emitterBehaviors")
self.emitterLayer.emitterCells = [emitter]
}
public func update(color: UIColor, size: CGSize) {
if self.emitterLayer.emitterCells == nil {
self.setup(color: color, size: size)
}
self.emitterLayer.seed = UInt32.random(in: .min ..< .max)
self.emitterLayer.emitterShape = .circle
self.emitterLayer.emitterSize = size
self.emitterLayer.emitterMode = .surface
self.emitterLayer.frame = CGRect(origin: .zero, size: size)
self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
}
}

View File

@ -1336,8 +1336,13 @@ public final class EmojiStatusSelectionController: ViewController {
switch controller.mode { switch controller.mode {
case .statusSelection: case .statusSelection:
if let gift = item?.itemGift {
let _ = (self.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: nil)
|> deliverOnMainQueue).start()
} else {
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile, expirationDate: nil) let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile, expirationDate: nil)
|> deliverOnMainQueue).start() |> deliverOnMainQueue).start()
}
case let .backgroundSelection(completion): case let .backgroundSelection(completion):
completion(item?.itemFile) completion(item?.itemFile)
case let .customStatusSelection(completion): case let .customStatusSelection(completion):

View File

@ -53,6 +53,7 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
private var iconLayer: SimpleLayer? private var iconLayer: SimpleLayer?
private var tintIconLayer: SimpleLayer? private var tintIconLayer: SimpleLayer?
private(set) var underlyingContentLayer: SimpleLayer?
private(set) var tintContentLayer: SimpleLayer? private(set) var tintContentLayer: SimpleLayer?
private var badge: Badge? private var badge: Badge?
@ -93,6 +94,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
if let mirrorLayer = self.tintContentLayer { if let mirrorLayer = self.tintContentLayer {
mirrorLayer.position = value mirrorLayer.position = value
} }
if let mirrorLayer = self.underlyingContentLayer {
mirrorLayer.position = value
}
super.position = value super.position = value
} }
} }
@ -104,6 +108,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
if let mirrorLayer = self.tintContentLayer { if let mirrorLayer = self.tintContentLayer {
mirrorLayer.bounds = value mirrorLayer.bounds = value
} }
if let mirrorLayer = self.underlyingContentLayer {
mirrorLayer.bounds = value
}
super.bounds = value super.bounds = value
} }
} }
@ -112,7 +119,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
if let mirrorLayer = self.tintContentLayer { if let mirrorLayer = self.tintContentLayer {
mirrorLayer.add(animation, forKey: key) mirrorLayer.add(animation, forKey: key)
} }
if let mirrorLayer = self.underlyingContentLayer {
mirrorLayer.add(animation, forKey: key)
}
super.add(animation, forKey: key) super.add(animation, forKey: key)
} }
@ -120,7 +129,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
if let mirrorLayer = self.tintContentLayer { if let mirrorLayer = self.tintContentLayer {
mirrorLayer.removeAllAnimations() mirrorLayer.removeAllAnimations()
} }
if let mirrorLayer = self.underlyingContentLayer {
mirrorLayer.removeAllAnimations()
}
super.removeAllAnimations() super.removeAllAnimations()
} }
@ -128,7 +139,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
if let mirrorLayer = self.tintContentLayer { if let mirrorLayer = self.tintContentLayer {
mirrorLayer.removeAnimation(forKey: forKey) mirrorLayer.removeAnimation(forKey: forKey)
} }
if let mirrorLayer = self.underlyingContentLayer {
mirrorLayer.removeAnimation(forKey: forKey)
}
super.removeAnimation(forKey: forKey) super.removeAnimation(forKey: forKey)
} }
@ -233,6 +246,16 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
} }
}) })
} }
if let particleColor = animationData.particleColor {
let underlyingContentLayer = SimpleLayer()
self.underlyingContentLayer = underlyingContentLayer
let starsLayer = StarsEffectLayer()
starsLayer.frame = CGRect(origin: CGPoint(x: -3.0, y: -3.0), size: CGSize(width: 42.0, height: 42.0))
starsLayer.update(color: particleColor, size: CGSize(width: 42.0, height: 42.0))
underlyingContentLayer.addSublayer(starsLayer)
}
case let .staticEmoji(staticEmoji): case let .staticEmoji(staticEmoji):
let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))

View File

@ -40,6 +40,7 @@ public final class EntityKeyboardAnimationData: Equatable {
public enum Id: Hashable { public enum Id: Hashable {
case file(MediaId) case file(MediaId)
case stickerPackThumbnail(ItemCollectionId) case stickerPackThumbnail(ItemCollectionId)
case gift(String)
} }
public enum ItemType { public enum ItemType {
@ -66,8 +67,9 @@ public final class EntityKeyboardAnimationData: Equatable {
public let immediateThumbnailData: Data? public let immediateThumbnailData: Data?
public let isReaction: Bool public let isReaction: Bool
public let isTemplate: Bool public let isTemplate: Bool
public let particleColor: UIColor?
public init(id: Id, type: ItemType, resource: MediaResourceReference, dimensions: CGSize, immediateThumbnailData: Data?, isReaction: Bool, isTemplate: Bool) { public init(id: Id, type: ItemType, resource: MediaResourceReference, dimensions: CGSize, immediateThumbnailData: Data?, isReaction: Bool, isTemplate: Bool, particleColor: UIColor? = nil) {
self.id = id self.id = id
self.type = type self.type = type
self.resource = resource self.resource = resource
@ -75,6 +77,7 @@ public final class EntityKeyboardAnimationData: Equatable {
self.immediateThumbnailData = immediateThumbnailData self.immediateThumbnailData = immediateThumbnailData
self.isReaction = isReaction self.isReaction = isReaction
self.isTemplate = isTemplate self.isTemplate = isTemplate
self.particleColor = particleColor
} }
public convenience init(file: TelegramMediaFile, isReaction: Bool = false, partialReference: PartialMediaReference? = nil) { public convenience init(file: TelegramMediaFile, isReaction: Bool = false, partialReference: PartialMediaReference? = nil) {
@ -97,6 +100,25 @@ public final class EntityKeyboardAnimationData: Equatable {
self.init(id: .file(file.fileId), type: type, resource: resourceReference, dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData, isReaction: isReaction, isTemplate: isTemplate) self.init(id: .file(file.fileId), type: type, resource: resourceReference, dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData, isReaction: isReaction, isTemplate: isTemplate)
} }
public convenience init?(gift: StarGift.UniqueGift) {
var file: TelegramMediaFile?
var color: UIColor?
for attribute in gift.attributes {
if case let .model(_, fileValue, _) = attribute {
file = fileValue
} else if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute {
color = UIColor(rgb: UInt32(bitPattern: innerColor))
let _ = outerColor
}
}
if let file, let color {
let resourceReference: MediaResourceReference = .standalone(resource: file.resource)
self.init(id: .gift(gift.slug), type: .lottie, resource: resourceReference, dimensions: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: file.immediateThumbnailData, isReaction: false, isTemplate: false, particleColor: color)
} else {
return nil
}
}
public static func ==(lhs: EntityKeyboardAnimationData, rhs: EntityKeyboardAnimationData) -> Bool { public static func ==(lhs: EntityKeyboardAnimationData, rhs: EntityKeyboardAnimationData) -> Bool {
if lhs === rhs { if lhs === rhs {
return true return true
@ -335,6 +357,7 @@ public final class EmojiPagerContentComponent: Component {
case animation(EntityKeyboardAnimationData.Id) case animation(EntityKeyboardAnimationData.Id)
case staticEmoji(String) case staticEmoji(String)
case icon(Icon) case icon(Icon)
case starGift(String)
} }
public enum Icon: Equatable, Hashable { public enum Icon: Equatable, Hashable {
@ -379,6 +402,7 @@ public final class EmojiPagerContentComponent: Component {
public let animationData: EntityKeyboardAnimationData? public let animationData: EntityKeyboardAnimationData?
public let content: ItemContent public let content: ItemContent
public let itemFile: TelegramMediaFile? public let itemFile: TelegramMediaFile?
public let itemGift: StarGift.UniqueGift?
public let subgroupId: Int32? public let subgroupId: Int32?
public let icon: Icon public let icon: Icon
public let tintMode: TintMode public let tintMode: TintMode
@ -387,6 +411,7 @@ public final class EmojiPagerContentComponent: Component {
animationData: EntityKeyboardAnimationData?, animationData: EntityKeyboardAnimationData?,
content: ItemContent, content: ItemContent,
itemFile: TelegramMediaFile?, itemFile: TelegramMediaFile?,
itemGift: StarGift.UniqueGift? = nil,
subgroupId: Int32?, subgroupId: Int32?,
icon: Icon, icon: Icon,
tintMode: TintMode tintMode: TintMode
@ -394,6 +419,7 @@ public final class EmojiPagerContentComponent: Component {
self.animationData = animationData self.animationData = animationData
self.content = content self.content = content
self.itemFile = itemFile self.itemFile = itemFile
self.itemGift = itemGift
self.subgroupId = subgroupId self.subgroupId = subgroupId
self.icon = icon self.icon = icon
self.tintMode = tintMode self.tintMode = tintMode
@ -412,6 +438,9 @@ public final class EmojiPagerContentComponent: Component {
if lhs.itemFile?.fileId != rhs.itemFile?.fileId { if lhs.itemFile?.fileId != rhs.itemFile?.fileId {
return false return false
} }
if lhs.itemGift?.id != rhs.itemGift?.id {
return false
}
if lhs.subgroupId != rhs.subgroupId { if lhs.subgroupId != rhs.subgroupId {
return false return false
} }
@ -3461,6 +3490,9 @@ public final class EmojiPagerContentComponent: Component {
) )
self.visibleItemLayers[itemId] = itemLayer self.visibleItemLayers[itemId] = itemLayer
if let underlyingContentLayer = itemLayer.underlyingContentLayer {
self.scrollView.layer.addSublayer(underlyingContentLayer)
}
self.scrollView.layer.addSublayer(itemLayer) self.scrollView.layer.addSublayer(itemLayer)
if let tintContentLayer = itemLayer.tintContentLayer { if let tintContentLayer = itemLayer.tintContentLayer {
self.mirrorContentScrollView.layer.addSublayer(tintContentLayer) self.mirrorContentScrollView.layer.addSublayer(tintContentLayer)
@ -3692,6 +3724,7 @@ public final class EmojiPagerContentComponent: Component {
itemLayer.opacity = 0.0 itemLayer.opacity = 0.0
itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.16) itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.16)
itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemLayer] _ in itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemLayer] _ in
itemLayer?.underlyingContentLayer?.removeFromSuperlayer()
itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.tintContentLayer?.removeFromSuperlayer()
itemLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer()
}) })
@ -3712,6 +3745,7 @@ public final class EmojiPagerContentComponent: Component {
} }
} else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId { } else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId {
transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in
itemLayer?.underlyingContentLayer?.removeFromSuperlayer()
itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.tintContentLayer?.removeFromSuperlayer()
itemLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer()
}) })
@ -3726,6 +3760,7 @@ public final class EmojiPagerContentComponent: Component {
itemLayer.opacity = 0.0 itemLayer.opacity = 0.0
itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2) itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak itemLayer] _ in itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak itemLayer] _ in
itemLayer?.underlyingContentLayer?.removeFromSuperlayer()
itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.tintContentLayer?.removeFromSuperlayer()
itemLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer()
}) })
@ -3746,6 +3781,7 @@ public final class EmojiPagerContentComponent: Component {
} }
} }
} else { } else {
itemLayer.underlyingContentLayer?.removeFromSuperlayer()
itemLayer.tintContentLayer?.removeFromSuperlayer() itemLayer.tintContentLayer?.removeFromSuperlayer()
itemLayer.removeFromSuperlayer() itemLayer.removeFromSuperlayer()

View File

@ -126,6 +126,7 @@ public extension EmojiPagerContentComponent {
if case .status = subject { if case .status = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudUniqueStarGifts)
iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false) iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false)
|> map { result -> [TelegramMediaFile] in |> map { result -> [TelegramMediaFile] in
@ -343,8 +344,11 @@ public extension EmojiPagerContentComponent {
var featuredAvatarEmoji: OrderedItemListView? var featuredAvatarEmoji: OrderedItemListView?
var featuredBackgroundIconEmoji: OrderedItemListView? var featuredBackgroundIconEmoji: OrderedItemListView?
var defaultTagReactions: OrderedItemListView? var defaultTagReactions: OrderedItemListView?
var uniqueGifts: OrderedItemListView?
for orderedView in view.orderedItemListsViews { for orderedView in view.orderedItemListsViews {
if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji { if orderedView.collectionId == Namespaces.OrderedItemList.CloudUniqueStarGifts {
uniqueGifts = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji {
recentEmoji = orderedView recentEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedStatusEmoji { } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedStatusEmoji {
featuredStatusEmoji = orderedView featuredStatusEmoji = orderedView
@ -608,6 +612,38 @@ public extension EmojiPagerContentComponent {
} }
} }
} }
if let uniqueGifts, !uniqueGifts.items.isEmpty {
//TODO:localize
let groupId = "collectible"
let groupIndex: Int
if let current = itemGroupIndexById[groupId] {
groupIndex = current
} else {
groupIndex = itemGroups.count
itemGroupIndexById[groupId] = groupIndex
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: "COLLECTIBLES".uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 2, isClearable: false, headerItem: nil, items: []))
}
for item in uniqueGifts.items {
guard let item = item.contents.get(RecentStarGiftItem.self) else {
continue
}
guard let animationData = EntityKeyboardAnimationData(gift: item.starGift) else {
continue
}
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: nil,
itemGift: item.starGift,
subgroupId: nil,
icon: .none,
tintMode: .none
)
itemGroups[groupIndex].items.append(resultItem)
}
}
} else if case .channelStatus = subject { } else if case .channelStatus = subject {
let resultItem = EmojiPagerContentComponent.Item( let resultItem = EmojiPagerContentComponent.Item(
animationData: nil, animationData: nil,

View File

@ -25,6 +25,7 @@ swift_library(
"//submodules/TextFormat", "//submodules/TextFormat",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/AnimatedStickerNode", "//submodules/AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode",
], ],

View File

@ -12,6 +12,7 @@ import TextFormat
import PeerInfoCoverComponent import PeerInfoCoverComponent
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import EmojiStatusComponent
public final class GiftCompositionComponent: Component { public final class GiftCompositionComponent: Component {
public class ExternalState { public class ExternalState {
@ -30,6 +31,9 @@ public final class GiftCompositionComponent: Component {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let subject: Subject let subject: Subject
let animationOffset: CGPoint?
let animationScale: CGFloat?
let displayAnimationStars: Bool
let externalState: ExternalState? let externalState: ExternalState?
let requestUpdate: () -> Void let requestUpdate: () -> Void
@ -37,12 +41,18 @@ public final class GiftCompositionComponent: Component {
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
subject: Subject, subject: Subject,
animationOffset: CGPoint? = nil,
animationScale: CGFloat? = nil,
displayAnimationStars: Bool = false,
externalState: ExternalState? = nil, externalState: ExternalState? = nil,
requestUpdate: @escaping () -> Void = {} requestUpdate: @escaping () -> Void = {}
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.subject = subject self.subject = subject
self.animationOffset = animationOffset
self.animationScale = animationScale
self.displayAnimationStars = displayAnimationStars
self.externalState = externalState self.externalState = externalState
self.requestUpdate = requestUpdate self.requestUpdate = requestUpdate
} }
@ -57,6 +67,15 @@ public final class GiftCompositionComponent: Component {
if lhs.subject != rhs.subject { if lhs.subject != rhs.subject {
return false return false
} }
if lhs.animationOffset != rhs.animationOffset {
return false
}
if lhs.animationScale != rhs.animationScale {
return false
}
if lhs.displayAnimationStars != rhs.displayAnimationStars {
return false
}
return true return true
} }
@ -64,6 +83,8 @@ public final class GiftCompositionComponent: Component {
private var component: GiftCompositionComponent? private var component: GiftCompositionComponent?
private weak var componentState: EmptyComponentState? private weak var componentState: EmptyComponentState?
private var starsLayer: StarsEffectLayer?
private let background = ComponentView<Empty>() private let background = ComponentView<Empty>()
private var animationNode: AnimatedStickerNode? private var animationNode: AnimatedStickerNode?
@ -256,6 +277,12 @@ public final class GiftCompositionComponent: Component {
if animateTransition, let backgroundView = self.background.view as? PeerInfoCoverComponent.View { if animateTransition, let backgroundView = self.background.view as? PeerInfoCoverComponent.View {
backgroundView.animateTransition() backgroundView.animateTransition()
} }
var avatarCenter = CGPoint(x: availableSize.width / 2.0, y: 104.0)
if let _ = component.animationScale {
avatarCenter = CGPoint(x: avatarCenter.x, y: 67.0)
}
let _ = self.background.update( let _ = self.background.update(
transition: backgroundTransition, transition: backgroundTransition,
component: AnyComponent(PeerInfoCoverComponent( component: AnyComponent(PeerInfoCoverComponent(
@ -263,9 +290,10 @@ public final class GiftCompositionComponent: Component {
subject: .custom(backgroundColor, secondBackgroundColor, patternColor, patternFile?.fileId.id), subject: .custom(backgroundColor, secondBackgroundColor, patternColor, patternFile?.fileId.id),
files: files, files: files,
isDark: false, isDark: false,
avatarCenter: CGPoint(x: availableSize.width / 2.0, y: 104.0), avatarCenter: avatarCenter,
avatarScale: 1.0, avatarScale: 1.0,
defaultHeight: availableSize.height, defaultHeight: 300.0,
gradientOnTop: true,
avatarTransitionFraction: 0.0, avatarTransitionFraction: 0.0,
patternTransitionFraction: 0.0 patternTransitionFraction: 0.0
)), )),
@ -292,17 +320,20 @@ public final class GiftCompositionComponent: Component {
let iconSize = CGSize(width: 136.0, height: 136.0) let iconSize = CGSize(width: 136.0, height: 136.0)
var startFromIndex: Int? var startFromIndex: Int?
var animationTransition = transition
if animateTransition, let disappearingAnimationNode = self.animationNode { if animateTransition, let disappearingAnimationNode = self.animationNode {
self.animationNode = nil self.animationNode = nil
startFromIndex = disappearingAnimationNode.currentFrameIndex startFromIndex = disappearingAnimationNode.currentFrameIndex
disappearingAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in disappearingAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
disappearingAnimationNode.view.removeFromSuperview() disappearingAnimationNode.view.removeFromSuperview()
}) })
animationTransition = .immediate
} }
if let file = animationFile { if let file = animationFile {
let animationNode: AnimatedStickerNode let animationNode: AnimatedStickerNode
if self.animationNode == nil { if self.animationNode == nil {
animationTransition = .immediate
animationNode = DefaultAnimatedStickerNodeImpl() animationNode = DefaultAnimatedStickerNodeImpl()
animationNode.isUserInteractionEnabled = false animationNode.isUserInteractionEnabled = false
self.animationNode = animationNode self.animationNode = animationNode
@ -333,7 +364,39 @@ public final class GiftCompositionComponent: Component {
} }
} }
if let animationNode = self.animationNode { if let animationNode = self.animationNode {
transition.setFrame(layer: animationNode.layer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: 20.0), size: iconSize)) let offset = component.animationOffset ?? .zero
var size = CGSize(width: iconSize.width, height: iconSize.height)
if let scale = component.animationScale {
size = CGSize(width: size.width * scale, height: size.height * scale)
}
let animationFrame = CGRect(origin: CGPoint(x: availableSize.width / 2.0 + offset.x - size.width / 2.0, y: 88.0 + offset.y - size.height / 2.0), size: size)
animationNode.layer.bounds = CGRect(origin: .zero, size: iconSize)
animationTransition.setPosition(layer: animationNode.layer, position: animationFrame.center)
animationTransition.setScale(layer: animationNode.layer, scale: size.width / iconSize.width)
if component.displayAnimationStars {
var starsTransition = transition
let starsLayer: StarsEffectLayer
if let current = self.starsLayer {
starsLayer = current
} else {
starsTransition = .immediate
starsLayer = StarsEffectLayer()
self.layer.insertSublayer(starsLayer, below: animationNode.layer)
self.starsLayer = starsLayer
starsLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
let starsSize = CGSize(width: 36.0, height: 36.0)
starsLayer.update(color: .white, size: starsSize)
starsLayer.bounds = CGRect(origin: .zero, size: starsSize)
starsTransition.setPosition(layer: starsLayer, position: animationFrame.center)
} else if let starsLayer = self.starsLayer {
self.starsLayer = nil
transition.setPosition(layer: starsLayer, position: animationFrame.center)
starsLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
starsLayer.removeFromSuperlayer()
})
}
} }
return availableSize return availableSize
@ -348,3 +411,56 @@ public final class GiftCompositionComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
} }
} }
private final class StarsEffectLayer: SimpleLayer {
private let emitterLayer = CAEmitterLayer()
override init() {
super.init()
self.addSublayer(self.emitterLayer)
}
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup(color: UIColor) {
let emitter = CAEmitterCell()
emitter.name = "emitter"
emitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage
emitter.birthRate = 8.0
emitter.lifetime = 2.0
emitter.velocity = 0.1
emitter.scale = 0.12
emitter.scaleRange = 0.02
emitter.alphaRange = 0.1
emitter.emissionRange = .pi * 2.0
let staticColors: [Any] = [
color.withAlphaComponent(0.0).cgColor,
color.withAlphaComponent(0.55).cgColor,
color.withAlphaComponent(0.55).cgColor,
color.withAlphaComponent(0.0).cgColor
]
let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife")
staticColorBehavior.setValue(staticColors, forKey: "colors")
emitter.setValue([staticColorBehavior], forKey: "emitterBehaviors")
self.emitterLayer.emitterCells = [emitter]
}
func update(color: UIColor, size: CGSize) {
if self.emitterLayer.emitterCells == nil {
self.setup(color: color)
}
self.emitterLayer.emitterShape = .circle
self.emitterLayer.emitterSize = size
self.emitterLayer.emitterMode = .surface
self.emitterLayer.frame = CGRect(origin: .zero, size: size)
self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
}
}

View File

@ -43,6 +43,7 @@ swift_library(
"//submodules/TooltipUI", "//submodules/TooltipUI",
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent", "//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
"//submodules/MoreButtonNode", "//submodules/MoreButtonNode",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -22,6 +22,7 @@ import TextFormat
import TelegramStringFormatting import TelegramStringFormatting
import StarsAvatarComponent import StarsAvatarComponent
import EmojiTextAttachmentView import EmojiTextAttachmentView
import EmojiStatusComponent
import UndoUI import UndoUI
import ConfettiEffect import ConfettiEffect
import PlainButtonComponent import PlainButtonComponent
@ -34,6 +35,7 @@ import ContextUI
private let modelButtonTag = GenericComponentViewTag() private let modelButtonTag = GenericComponentViewTag()
private let backdropButtonTag = GenericComponentViewTag() private let backdropButtonTag = GenericComponentViewTag()
private let symbolButtonTag = GenericComponentViewTag() private let symbolButtonTag = GenericComponentViewTag()
private let statusTag = GenericComponentViewTag()
private final class GiftViewSheetContent: CombinedComponent { private final class GiftViewSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -49,7 +51,8 @@ private final class GiftViewSheetContent: CombinedComponent {
let openMyGifts: () -> Void let openMyGifts: () -> Void
let transferGift: () -> Void let transferGift: () -> Void
let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>) let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
let showAttributeInfo: (Any, Float) -> Void let shareGift: () -> Void
let showAttributeInfo: (Any, String) -> Void
let viewUpgraded: (EngineMessage.Id) -> Void let viewUpgraded: (EngineMessage.Id) -> Void
let openMore: (ASDisplayNode, ContextGesture?) -> Void let openMore: (ASDisplayNode, ContextGesture?) -> Void
let getController: () -> ViewController? let getController: () -> ViewController?
@ -66,7 +69,8 @@ private final class GiftViewSheetContent: CombinedComponent {
openMyGifts: @escaping () -> Void, openMyGifts: @escaping () -> Void,
transferGift: @escaping () -> Void, transferGift: @escaping () -> Void,
upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>), upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
showAttributeInfo: @escaping (Any, Float) -> Void, shareGift: @escaping () -> Void,
showAttributeInfo: @escaping (Any, String) -> Void,
viewUpgraded: @escaping (EngineMessage.Id) -> Void, viewUpgraded: @escaping (EngineMessage.Id) -> Void,
openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void, openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void,
getController: @escaping () -> ViewController? getController: @escaping () -> ViewController?
@ -82,6 +86,7 @@ private final class GiftViewSheetContent: CombinedComponent {
self.openMyGifts = openMyGifts self.openMyGifts = openMyGifts
self.transferGift = transferGift self.transferGift = transferGift
self.upgradeGift = upgradeGift self.upgradeGift = upgradeGift
self.shareGift = shareGift
self.showAttributeInfo = showAttributeInfo self.showAttributeInfo = showAttributeInfo
self.viewUpgraded = viewUpgraded self.viewUpgraded = viewUpgraded
self.openMore = openMore self.openMore = openMore
@ -123,6 +128,10 @@ private final class GiftViewSheetContent: CombinedComponent {
var upgradeFormDisposable: Disposable? var upgradeFormDisposable: Disposable?
var upgradeDisposable: Disposable? var upgradeDisposable: Disposable?
var inWearPreview = false
var pendingWear = false
var pendingTakeOff = false
var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]? var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]?
let sampleDisposable = DisposableSet() let sampleDisposable = DisposableSet()
@ -136,12 +145,6 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil) private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil)
var mockFiles: [TelegramMediaFile] = []
var mockIconFiles: [TelegramMediaFile] = []
var upgradedMockId: Int = 0
var upgradedMockBackgroundColor: UIColor = .white
var upgradedMockIcon: TelegramMediaFile?
init( init(
context: AccountContext, context: AccountContext,
subject: GiftViewScreen.Subject, subject: GiftViewScreen.Subject,
@ -278,6 +281,11 @@ private final class GiftViewSheetContent: CombinedComponent {
self.updated(transition: .spring(duration: 0.4)) self.updated(transition: .spring(duration: 0.4))
} }
func requestWearPreview() {
self.inWearPreview = true
self.updated(transition: .spring(duration: 0.4))
}
func commitUpgrade() { func commitUpgrade() {
guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
return return
@ -343,6 +351,18 @@ private final class GiftViewSheetContent: CombinedComponent {
let animation = Child(GiftCompositionComponent.self) let animation = Child(GiftCompositionComponent.self)
let title = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self)
let description = Child(MultilineTextComponent.self) let description = Child(MultilineTextComponent.self)
let transferButton = Child(PlainButtonComponent.self)
let wearButton = Child(PlainButtonComponent.self)
let shareButton = Child(PlainButtonComponent.self)
let wearAvatar = Child(AvatarComponent.self)
let wearPeerName = Child(MultilineTextComponent.self)
let wearPeerStatus = Child(MultilineTextComponent.self)
let wearTitle = Child(MultilineTextComponent.self)
let wearDescription = Child(MultilineTextComponent.self)
let wearPerks = Child(List<Empty>.self)
let hiddenText = Child(MultilineTextComponent.self) let hiddenText = Child(MultilineTextComponent.self)
let table = Child(TableComponent.self) let table = Child(TableComponent.self)
let additionalText = Child(MultilineTextComponent.self) let additionalText = Child(MultilineTextComponent.self)
@ -456,12 +476,15 @@ private final class GiftViewSheetContent: CombinedComponent {
component: ButtonsComponent( component: ButtonsComponent(
theme: theme, theme: theme,
isOverlay: showUpgradePreview || uniqueGift != nil, isOverlay: showUpgradePreview || uniqueGift != nil,
showMoreButton: uniqueGift != nil, showMoreButton: uniqueGift != nil && !state.inWearPreview,
closePressed: { [weak state] in closePressed: { [weak state] in
guard let state else { guard let state else {
return return
} }
if state.inUpgradePreview { if state.inWearPreview {
state.inWearPreview = false
state.updated(transition: .spring(duration: 0.4))
} else if state.inUpgradePreview {
state.inUpgradePreview = false state.inUpgradePreview = false
state.updated(transition: .spring(duration: 0.4)) state.updated(transition: .spring(duration: 0.4))
} else { } else {
@ -478,45 +501,246 @@ private final class GiftViewSheetContent: CombinedComponent {
var originY: CGFloat = 0.0 var originY: CGFloat = 0.0
let animationHeight: CGFloat let headerHeight: CGFloat
let animationSubject: GiftCompositionComponent.Subject? let headerSubject: GiftCompositionComponent.Subject?
if let uniqueGift { if let uniqueGift {
animationHeight = 240.0 if state.inWearPreview {
animationSubject = .unique(uniqueGift) headerHeight = 200.0
} else if state.inUpgradePreview, let attributes = state.sampleGiftAttributes { } else if case let .peerId(peerId) = uniqueGift.owner, peerId == component.context.account.peerId {
animationHeight = 258.0 headerHeight = 314.0
animationSubject = .preview(attributes)
} else if case let .upgradePreview(attributes, _) = component.subject {
animationHeight = 258.0
animationSubject = .preview(attributes)
} else if let animationFile {
animationHeight = 210.0
animationSubject = .generic(animationFile)
} else { } else {
animationHeight = 210.0 headerHeight = 240.0
animationSubject = nil
} }
if let animationSubject { headerSubject = .unique(uniqueGift)
} else if state.inUpgradePreview, let attributes = state.sampleGiftAttributes {
headerHeight = 258.0
headerSubject = .preview(attributes)
} else if case let .upgradePreview(attributes, _) = component.subject {
headerHeight = 258.0
headerSubject = .preview(attributes)
} else if let animationFile {
headerHeight = 210.0
headerSubject = .generic(animationFile)
} else {
headerHeight = 210.0
headerSubject = nil
}
var wearPeerNameChild: _UpdatedChildComponent?
if state.inWearPreview, let uniqueGift {
var peerName = ""
if let accountPeer = state.peerMap[component.context.account.peerId] {
peerName = accountPeer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
wearPeerNameChild = wearPeerName.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: peerName,
font: Font.bold(28.0),
textColor: .white,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let wearTitle = wearTitle.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: strings.Gift_Wear_Wear("\(uniqueGift.title) #\(uniqueGift.number)").string,
font: Font.bold(24.0),
textColor: theme.actionSheet.primaryTextColor,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let wearDescription = wearDescription.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: strings.Gift_Wear_GetBenefits,
font: Font.regular(15.0),
textColor: theme.actionSheet.primaryTextColor,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
var titleOriginY = headerHeight + 18.0
context.add(wearTitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: titleOriginY + wearTitle.size.height))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
titleOriginY += wearTitle.size.height
titleOriginY += 18.0
context.add(wearDescription
.position(CGPoint(x: context.availableSize.width / 2.0, y: titleOriginY + wearDescription.size.height))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
}
var animationOffset: CGPoint?
var animationScale: CGFloat?
if let wearPeerNameChild {
animationOffset = CGPoint(x: wearPeerNameChild.size.width / 2.0 + 20.0 - 12.0, y: 56.0)
animationScale = 0.19
}
if let headerSubject {
let animation = animation.update( let animation = animation.update(
component: GiftCompositionComponent( component: GiftCompositionComponent(
context: component.context, context: component.context,
theme: environment.theme, theme: environment.theme,
subject: animationSubject, subject: headerSubject,
animationOffset: animationOffset,
animationScale: animationScale,
displayAnimationStars: state.inWearPreview,
externalState: giftCompositionExternalState, externalState: giftCompositionExternalState,
requestUpdate: { [weak state] in requestUpdate: { [weak state] in
state?.updated() state?.updated()
} }
), ),
availableSize: CGSize(width: context.availableSize.width, height: animationHeight), availableSize: CGSize(width: context.availableSize.width, height: headerHeight),
transition: .immediate transition: context.transition
) )
context.add(animation context.add(animation
.position(CGPoint(x: context.availableSize.width / 2.0, y: animationHeight / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight / 2.0))
) )
} }
originY += animationHeight originY += headerHeight
if showUpgradePreview { let vibrantColor: UIColor
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
vibrantColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
} else {
vibrantColor = UIColor.white.withAlphaComponent(0.6)
}
if let wearPeerNameChild, let accountPeer = state.peerMap[component.context.account.peerId] {
let wearAvatar = wearAvatar.update(
component: AvatarComponent(
context: component.context,
theme: theme,
peer: accountPeer
),
environment: {},
availableSize: CGSize(width: 100.0, height: 100.0),
transition: context.transition
)
context.add(wearAvatar
.position(CGPoint(x: context.availableSize.width / 2.0, y: 67.0))
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
let wearPeerStatus = wearPeerStatus.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: strings.Presence_online,
font: Font.regular(17.0),
textColor: vibrantColor,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 5,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(wearPeerNameChild
.position(CGPoint(x: context.availableSize.width / 2.0 - 12.0, y: 144.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
context.add(wearPeerStatus
.position(CGPoint(x: context.availableSize.width / 2.0, y: 174.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += 18.0
originY += 28.0
originY += 18.0
originY += 20.0
originY += 24.0
let textColor = theme.actionSheet.primaryTextColor
let secondaryTextColor = theme.actionSheet.secondaryTextColor
let linkColor = theme.actionSheet.controlAccentColor
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: "badge",
component: AnyComponent(ParagraphComponent(
title: strings.Gift_Wear_Badge_Title,
titleColor: textColor,
text: strings.Gift_Wear_Badge_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/Collectible/Badge",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "design",
component: AnyComponent(ParagraphComponent(
title: strings.Gift_Wear_Design_Title,
titleColor: textColor,
text: strings.Gift_Wear_Design_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/BoostPerk/CoverColor",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "proof",
component: AnyComponent(ParagraphComponent(
title: strings.Gift_Wear_Proof_Title,
titleColor: textColor,
text: strings.Gift_Wear_Proof_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/Collectible/Proof",
iconColor: linkColor
))
)
)
let perksSideInset = sideInset + 16.0
let wearPerks = wearPerks.update(
component: List(items),
availableSize: CGSize(width: context.availableSize.width - perksSideInset * 2.0, height: 10000.0),
transition: context.transition
)
context.add(wearPerks
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + wearPerks.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += wearPerks.size.height
originY += 16.0
} else if showUpgradePreview {
let title: String let title: String
let description: String let description: String
let uniqueText: String let uniqueText: String
@ -550,18 +774,12 @@ private final class GiftViewSheetContent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate transition: .immediate
) )
let descriptionColor: UIColor
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
descriptionColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
} else {
descriptionColor = UIColor.white.withAlphaComponent(0.6)
}
let upgradeDescription = upgradeDescription.update( let upgradeDescription = upgradeDescription.update(
component: BalancedTextComponent( component: BalancedTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: description, string: description,
font: Font.regular(13.0), font: Font.regular(13.0),
textColor: descriptionColor, textColor: vibrantColor,
paragraphAlignment: .center paragraphAlignment: .center
)), )),
horizontalAlignment: .center, horizontalAlignment: .center,
@ -637,7 +855,7 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
) )
let perksSideInset = sideInset + 12.0 let perksSideInset = sideInset + 16.0
let upgradePerks = upgradePerks.update( let upgradePerks = upgradePerks.update(
component: List(items), component: List(items),
availableSize: CGSize(width: context.availableSize.width - perksSideInset * 2.0, height: 10000.0), availableSize: CGSize(width: context.availableSize.width - perksSideInset * 2.0, height: 10000.0),
@ -784,11 +1002,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let textColor: UIColor let textColor: UIColor
if let _ = uniqueGift { if let _ = uniqueGift {
textFont = Font.regular(13.0) textFont = Font.regular(13.0)
if let previewPatternColor = giftCompositionExternalState.previewPatternColor { textColor = vibrantColor
textColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
} else {
textColor = UIColor.white.withAlphaComponent(0.6)
}
} else { } else {
textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0) textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0)
textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor
@ -883,13 +1097,46 @@ private final class GiftViewSheetContent: CombinedComponent {
let tableLinkColor = theme.list.itemAccentColor let tableLinkColor = theme.list.itemAccentColor
var tableItems: [TableComponent.Item] = [] var tableItems: [TableComponent.Item] = []
var isWearing = state.pendingWear
if !soldOut { if !soldOut {
if let uniqueGift { if let uniqueGift {
switch uniqueGift.owner { switch uniqueGift.owner {
case let .peerId(peerId): case let .peerId(peerId):
if let peer = state.peerMap[peerId] { if let peer = state.peerMap[peerId] {
let ownerComponent: AnyComponent<Empty> let ownerComponent: AnyComponent<Empty>
if let _ = subject.arguments?.transferStars { if peer.id == component.context.account.peerId, peer.isPremium {
let animationContent: EmojiStatusComponent.Content
var color: UIColor?
if state.pendingWear {
var fileId: Int64?
for attribute in uniqueGift.attributes {
if case let .model(_, file, _) = attribute {
fileId = file.fileId.id
}
if case let .backdrop(_, innerColor, _, _, _, _) = attribute {
color = UIColor(rgb: UInt32(bitPattern: innerColor))
}
}
if let fileId {
animationContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: tableLinkColor, loopMode: .count(2))
} else {
animationContent = .premium(color: tableLinkColor)
}
} else if let emojiStatus = peer.emojiStatus, !state.pendingTakeOff {
animationContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: tableLinkColor, loopMode: .count(2))
if case let .starGift(id, _, _, _, _, innerColor, _, _, _) = emojiStatus.content {
color = UIColor(rgb: UInt32(bitPattern: innerColor))
if id == uniqueGift.id {
isWearing = true
state.pendingWear = false
}
}
} else {
animationContent = .premium(color: tableLinkColor)
state.pendingTakeOff = false
}
ownerComponent = AnyComponent( ownerComponent = AnyComponent(
HStack([ HStack([
AnyComponentWithIdentity( AnyComponentWithIdentity(
@ -913,21 +1160,21 @@ private final class GiftViewSheetContent: CombinedComponent {
), ),
AnyComponentWithIdentity( AnyComponentWithIdentity(
id: AnyHashable(1), id: AnyHashable(1),
component: AnyComponent(Button( component: AnyComponent(EmojiStatusComponent(
content: AnyComponent(ButtonContentComponent(
context: component.context, context: component.context,
text: strings.Gift_Unique_Transfer, animationCache: component.context.animationCache,
color: theme.list.itemAccentColor animationRenderer: component.context.animationRenderer,
)), content: animationContent,
particleColor: color,
size: CGSize(width: 18.0, height: 18.0),
isVisibleForAnimations: true,
action: { action: {
component.transferGift()
Queue.mainQueue().after(1.0, { },
component.cancel(false) tag: statusTag
})
}
)) ))
) )
], spacing: 4.0) ], spacing: 2.0)
) )
} else { } else {
ownerComponent = AnyComponent(Button( ownerComponent = AnyComponent(Button(
@ -1052,6 +1299,93 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
if let uniqueGift { if let uniqueGift {
if case let .peerId(peerId) = uniqueGift.owner, peerId == component.context.account.peerId {
let buttonSpacing: CGFloat = 10.0
let buttonWidth = floor(context.availableSize.width - sideInset * 2.0 - buttonSpacing * 2.0) / 3.0
let buttonHeight: CGFloat = 58.0
let transferButton = transferButton.update(
component: PlainButtonComponent(
content: AnyComponent(
HeaderButtonComponent(
title: strings.Gift_View_Header_Transfer,
iconName: "Premium/Collectible/Transfer"
)
),
effectAlignment: .center,
action: {
component.transferGift()
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
),
environment: {},
availableSize: CGSize(width: buttonWidth, height: buttonHeight),
transition: context.transition
)
context.add(transferButton
.position(CGPoint(x: sideInset + buttonWidth / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0))
)
let controller = environment.controller
let wearButton = wearButton.update(
component: PlainButtonComponent(
content: AnyComponent(
HeaderButtonComponent(
title: isWearing ? strings.Gift_View_Header_TakeOff : strings.Gift_View_Header_Wear,
iconName: isWearing ? "Premium/Collectible/Unwear" : "Premium/Collectible/Wear"
)
),
effectAlignment: .center,
action: { [weak state] in
if let state {
if isWearing {
state.pendingTakeOff = true
state.pendingWear = false
state.updated(transition: .spring(duration: 0.4))
component.showAttributeInfo(statusTag, "You took off \(uniqueGift.title) #\(uniqueGift.number)")
} else {
if let controller = controller() as? GiftViewScreen {
controller.dismissAllTooltips()
}
state.requestWearPreview()
}
}
}
),
environment: {},
availableSize: CGSize(width: buttonWidth, height: buttonHeight),
transition: context.transition
)
context.add(wearButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0))
)
let shareButton = shareButton.update(
component: PlainButtonComponent(
content: AnyComponent(
HeaderButtonComponent(
title: strings.Gift_View_Header_Share,
iconName: "Premium/Collectible/Share"
)
),
effectAlignment: .center,
action: {
component.shareGift()
}
),
environment: {},
availableSize: CGSize(width: buttonWidth, height: buttonHeight),
transition: context.transition
)
context.add(shareButton
.position(CGPoint(x: context.availableSize.width - sideInset - buttonWidth / 2.0, y: headerHeight - buttonHeight / 2.0 - 16.0))
)
}
let showAttributeInfo = component.showAttributeInfo let showAttributeInfo = component.showAttributeInfo
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [ let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
@ -1191,7 +1525,7 @@ private final class GiftViewSheetContent: CombinedComponent {
color: theme.list.itemAccentColor color: theme.list.itemAccentColor
)), )),
action: { action: {
showAttributeInfo(tag, percentage) showAttributeInfo(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
} }
).tagged(tag)) ).tagged(tag))
)) ))
@ -1393,7 +1727,7 @@ private final class GiftViewSheetContent: CombinedComponent {
originY += table.size.height + 23.0 originY += table.size.height + 23.0
} }
if incoming && !converted && !upgraded && !showUpgradePreview { if incoming && !converted && !upgraded && !showUpgradePreview && !state.inWearPreview {
let linkColor = theme.actionSheet.controlAccentColor let linkColor = theme.actionSheet.controlAccentColor
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme { if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme) state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
@ -1452,8 +1786,41 @@ private final class GiftViewSheetContent: CombinedComponent {
originY += 16.0 originY += 16.0
} }
let buttonSize = CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0)
let buttonBackground = ButtonComponent.Background(
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
)
let buttonChild: _UpdatedChildComponent let buttonChild: _UpdatedChildComponent
if state.inUpgradePreview { if state.inWearPreview, let uniqueGift {
buttonChild = button.update(
component: ButtonComponent(
background: buttonBackground,
content: AnyComponentWithIdentity(
id: AnyHashable("wear"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Wear_Start, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
isEnabled: true,
displaysProgress: false,
action: { [weak state] in
if let state {
state.pendingWear = true
state.pendingTakeOff = false
state.inWearPreview = false
state.updated(transition: .spring(duration: 0.4))
let _ = component.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).start()
Queue.mainQueue().after(0.2) {
component.showAttributeInfo(statusTag, "You put on \(uniqueGift.title) #\(uniqueGift.number)")
}
}
}),
availableSize: buttonSize,
transition: context.transition
)
} else if state.inUpgradePreview {
if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme {
state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme) state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme)
} }
@ -1470,12 +1837,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: ButtonComponent.Background( background: buttonBackground,
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable("upgrade"), id: AnyHashable("upgrade"),
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
@ -1485,7 +1847,7 @@ private final class GiftViewSheetContent: CombinedComponent {
action: { [weak state] in action: { [weak state] in
state?.commitUpgrade() state?.commitUpgrade()
}), }),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), availableSize: buttonSize,
transition: context.transition transition: context.transition
) )
} else if upgraded, let upgradeMessageIdId = subject.arguments?.upgradeMessageId, let originalMessageId = subject.arguments?.messageId { } else if upgraded, let upgradeMessageIdId = subject.arguments?.upgradeMessageId, let originalMessageId = subject.arguments?.messageId {
@ -1493,12 +1855,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let buttonTitle = strings.Gift_View_ViewUpgraded let buttonTitle = strings.Gift_View_ViewUpgraded
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: ButtonComponent.Background( background: buttonBackground,
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable("button"), id: AnyHashable("button"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
@ -1509,20 +1866,14 @@ private final class GiftViewSheetContent: CombinedComponent {
component.cancel(true) component.cancel(true)
component.viewUpgraded(upgradeMessageId) component.viewUpgraded(upgradeMessageId)
}), }),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), availableSize: buttonSize,
transition: context.transition transition: context.transition
) )
} else if incoming && !converted && !upgraded, let upgradeStars, upgradeStars > 0 { } else if incoming && !converted && !upgraded, let upgradeStars, upgradeStars > 0 {
let buttonTitle = strings.Gift_View_UpgradeForFree let buttonTitle = strings.Gift_View_UpgradeForFree
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: ButtonComponent.Background( background: buttonBackground.withIsShimmering(true),
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0,
isShimmering: true
),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable("freeUpgrade"), id: AnyHashable("freeUpgrade"),
component: AnyComponent(HStack([ component: AnyComponent(HStack([
@ -1546,19 +1897,14 @@ private final class GiftViewSheetContent: CombinedComponent {
state?.requestUpgradePreview() state?.requestUpgradePreview()
} }
), ),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), availableSize: buttonSize,
transition: context.transition transition: context.transition
) )
} else if incoming && !converted && !savedToProfile { } else if incoming && !converted && !savedToProfile {
let buttonTitle = savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display let buttonTitle = savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: ButtonComponent.Background( background: buttonBackground,
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable("button"), id: AnyHashable("button"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
@ -1568,18 +1914,13 @@ private final class GiftViewSheetContent: CombinedComponent {
action: { action: {
component.updateSavedToProfile(!savedToProfile) component.updateSavedToProfile(!savedToProfile)
}), }),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), availableSize: buttonSize,
transition: context.transition transition: context.transition
) )
} else { } else {
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: ButtonComponent.Background( background: buttonBackground,
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable("ok"), id: AnyHashable("ok"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Common_OK, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Common_OK, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
@ -1589,7 +1930,7 @@ private final class GiftViewSheetContent: CombinedComponent {
action: { action: {
component.cancel(true) component.cancel(true)
}), }),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), availableSize: buttonSize,
transition: context.transition transition: context.transition
) )
} }
@ -1625,9 +1966,10 @@ private final class GiftViewSheetComponent: CombinedComponent {
let openMyGifts: () -> Void let openMyGifts: () -> Void
let transferGift: () -> Void let transferGift: () -> Void
let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>) let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
let shareGift: () -> Void
let viewUpgraded: (EngineMessage.Id) -> Void let viewUpgraded: (EngineMessage.Id) -> Void
let openMore: (ASDisplayNode, ContextGesture?) -> Void let openMore: (ASDisplayNode, ContextGesture?) -> Void
let showAttributeInfo: (Any, Float) -> Void let showAttributeInfo: (Any, String) -> Void
init( init(
context: AccountContext, context: AccountContext,
@ -1640,9 +1982,10 @@ private final class GiftViewSheetComponent: CombinedComponent {
openMyGifts: @escaping () -> Void, openMyGifts: @escaping () -> Void,
transferGift: @escaping () -> Void, transferGift: @escaping () -> Void,
upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>), upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
shareGift: @escaping () -> Void,
viewUpgraded: @escaping (EngineMessage.Id) -> Void, viewUpgraded: @escaping (EngineMessage.Id) -> Void,
openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void, openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void,
showAttributeInfo: @escaping (Any, Float) -> Void showAttributeInfo: @escaping (Any, String) -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -1654,6 +1997,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
self.openMyGifts = openMyGifts self.openMyGifts = openMyGifts
self.transferGift = transferGift self.transferGift = transferGift
self.upgradeGift = upgradeGift self.upgradeGift = upgradeGift
self.shareGift = shareGift
self.viewUpgraded = viewUpgraded self.viewUpgraded = viewUpgraded
self.openMore = openMore self.openMore = openMore
self.showAttributeInfo = showAttributeInfo self.showAttributeInfo = showAttributeInfo
@ -1704,6 +2048,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
openMyGifts: context.component.openMyGifts, openMyGifts: context.component.openMyGifts,
transferGift: context.component.transferGift, transferGift: context.component.transferGift,
upgradeGift: context.component.upgradeGift, upgradeGift: context.component.upgradeGift,
shareGift: context.component.shareGift,
showAttributeInfo: context.component.showAttributeInfo, showAttributeInfo: context.component.showAttributeInfo,
viewUpgraded: context.component.viewUpgraded, viewUpgraded: context.component.viewUpgraded,
openMore: context.component.openMore, openMore: context.component.openMore,
@ -1845,10 +2190,10 @@ public class GiftViewScreen: ViewControllerComponentContainer {
var sendGiftImpl: ((EnginePeer.Id) -> Void)? var sendGiftImpl: ((EnginePeer.Id) -> Void)?
var openMyGiftsImpl: (() -> Void)? var openMyGiftsImpl: (() -> Void)?
var transferGiftImpl: (() -> Void)? var transferGiftImpl: (() -> Void)?
var showAttributeInfoImpl: ((Any, Float) -> Void)?
var upgradeGiftImpl: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? var upgradeGiftImpl: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)?
var shareGiftImpl: (() -> Void)? var shareGiftImpl: (() -> Void)?
var openMoreImpl: ((ASDisplayNode, ContextGesture?) -> Void)? var openMoreImpl: ((ASDisplayNode, ContextGesture?) -> Void)?
var showAttributeInfoImpl: ((Any, String) -> Void)?
var viewUpgradedImpl: ((EngineMessage.Id) -> Void)? var viewUpgradedImpl: ((EngineMessage.Id) -> Void)?
super.init( super.init(
@ -1880,14 +2225,17 @@ public class GiftViewScreen: ViewControllerComponentContainer {
upgradeGift: { formId, keepOriginalInfo in upgradeGift: { formId, keepOriginalInfo in
return upgradeGiftImpl?(formId, keepOriginalInfo) ?? .complete() return upgradeGiftImpl?(formId, keepOriginalInfo) ?? .complete()
}, },
shareGift: {
shareGiftImpl?()
},
viewUpgraded: { messageId in viewUpgraded: { messageId in
viewUpgradedImpl?(messageId) viewUpgradedImpl?(messageId)
}, },
openMore: { node, gesture in openMore: { node, gesture in
openMoreImpl?(node, gesture) openMoreImpl?(node, gesture)
}, },
showAttributeInfo: { tag, rarity in showAttributeInfo: { tag, text in
showAttributeInfoImpl?(tag, rarity) showAttributeInfoImpl?(tag, text)
} }
), ),
navigationBarAppearance: .none, navigationBarAppearance: .none,
@ -2224,7 +2572,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
}) })
} }
showAttributeInfoImpl = { [weak self] tag, rarity in showAttributeInfoImpl = { [weak self] tag, text in
guard let self else { guard let self else {
return return
} }
@ -2235,7 +2583,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
} }
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize()) let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: presentationData.strings.Gift_Unique_AttributeDescription(formatPercentage(rarity)).string), location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in let controller = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .ignore return .ignore
}) })
self.present(controller, in: .current) self.present(controller, in: .current)
@ -3042,3 +3390,147 @@ private final class GiftViewContextReferenceContentSource: ContextReferenceConte
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
} }
} }
private final class HeaderButtonComponent: CombinedComponent {
let title: String
let iconName: String
public init(
title: String,
iconName: String
) {
self.title = title
self.iconName = iconName
}
static func ==(lhs: HeaderButtonComponent, rhs: HeaderButtonComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.iconName != rhs.iconName {
return false
}
return true
}
static var body: Body {
let background = Child(RoundedRectangle.self)
let title = Child(MultilineTextComponent.self)
let icon = Child(BundleIconComponent.self)
return { context in
let component = context.component
let background = background.update(
component: RoundedRectangle(
color: UIColor.white.withAlphaComponent(0.16),
cornerRadius: 10.0
),
availableSize: context.availableSize,
transition: .immediate
)
context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
let icon = icon.update(
component: BundleIconComponent(
name: component.iconName,
tintColor: UIColor.white
),
availableSize: context.availableSize,
transition: .immediate
)
context.add(icon
.position(CGPoint(x: context.availableSize.width / 2.0, y: 22.0))
)
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: component.title,
font: Font.regular(11.0),
textColor: UIColor.white,
paragraphAlignment: .natural
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 42.0))
)
return context.availableSize
}
}
}
private final class AvatarComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let peer: EnginePeer
init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer) {
self.context = context
self.theme = theme
self.peer = peer
}
static func ==(lhs: AvatarComponent, rhs: AvatarComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.peer != rhs.peer {
return false
}
return true
}
final class View: UIView {
private let avatarNode: AvatarNode
private var component: AvatarComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
super.init(frame: frame)
self.addSubnode(self.avatarNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
self.avatarNode.frame = CGRect(origin: .zero, size: availableSize)
self.avatarNode.setPeer(
context: component.context,
theme: component.theme,
peer: component.peer,
synchronousLoad: true
)
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -102,6 +102,7 @@ public final class PeerInfoCoverComponent: Component {
public let avatarCenter: CGPoint public let avatarCenter: CGPoint
public let avatarScale: CGFloat public let avatarScale: CGFloat
public let defaultHeight: CGFloat public let defaultHeight: CGFloat
public let gradientOnTop: Bool
public let avatarTransitionFraction: CGFloat public let avatarTransitionFraction: CGFloat
public let patternTransitionFraction: CGFloat public let patternTransitionFraction: CGFloat
@ -113,6 +114,7 @@ public final class PeerInfoCoverComponent: Component {
avatarCenter: CGPoint, avatarCenter: CGPoint,
avatarScale: CGFloat, avatarScale: CGFloat,
defaultHeight: CGFloat, defaultHeight: CGFloat,
gradientOnTop: Bool = false,
avatarTransitionFraction: CGFloat, avatarTransitionFraction: CGFloat,
patternTransitionFraction: CGFloat patternTransitionFraction: CGFloat
) { ) {
@ -123,6 +125,7 @@ public final class PeerInfoCoverComponent: Component {
self.avatarCenter = avatarCenter self.avatarCenter = avatarCenter
self.avatarScale = avatarScale self.avatarScale = avatarScale
self.defaultHeight = defaultHeight self.defaultHeight = defaultHeight
self.gradientOnTop = gradientOnTop
self.avatarTransitionFraction = avatarTransitionFraction self.avatarTransitionFraction = avatarTransitionFraction
self.patternTransitionFraction = patternTransitionFraction self.patternTransitionFraction = patternTransitionFraction
} }
@ -149,6 +152,9 @@ public final class PeerInfoCoverComponent: Component {
if lhs.defaultHeight != rhs.defaultHeight { if lhs.defaultHeight != rhs.defaultHeight {
return false return false
} }
if lhs.gradientOnTop != rhs.gradientOnTop {
return false
}
if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction { if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction {
return false return false
} }
@ -166,6 +172,7 @@ public final class PeerInfoCoverComponent: Component {
private let avatarBackgroundGradientLayer: SimpleGradientLayer private let avatarBackgroundGradientLayer: SimpleGradientLayer
private let backgroundPatternContainer: UIView private let backgroundPatternContainer: UIView
private var currentSize: CGSize?
private var component: PeerInfoCoverComponent? private var component: PeerInfoCoverComponent?
private var state: EmptyComponentState? private var state: EmptyComponentState?
@ -180,6 +187,7 @@ public final class PeerInfoCoverComponent: Component {
self.backgroundGradientLayer = SimpleGradientLayer() self.backgroundGradientLayer = SimpleGradientLayer()
self.avatarBackgroundGradientLayer = SimpleGradientLayer() self.avatarBackgroundGradientLayer = SimpleGradientLayer()
self.avatarBackgroundGradientLayer.opacity = 0.0
let baseAvatarGradientAlpha: CGFloat = 0.4 let baseAvatarGradientAlpha: CGFloat = 0.4
let numSteps = 6 let numSteps = 6
self.avatarBackgroundGradientLayer.colors = (0 ..< numSteps).map { i in self.avatarBackgroundGradientLayer.colors = (0 ..< numSteps).map { i in
@ -221,13 +229,41 @@ public final class PeerInfoCoverComponent: Component {
self.patternImageDisposable?.dispose() self.patternImageDisposable?.dispose()
} }
public func willAnimateIn() {
for layer in self.avatarPatternContentLayers {
layer.opacity = 0.0
}
}
public func animateIn() {
guard let _ = self.currentSize, let component = self.component else {
return
}
for layer in self.avatarPatternContentLayers {
layer.opacity = 1.0
layer.animatePosition(
from: component.avatarCenter,
to: layer.position,
duration: 0.4,
timingFunction: kCAMediaTimingFunctionSpring
)
}
}
public func animateTransition() { public func animateTransition() {
if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() { if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() {
gradientSnapshotLayer.frame = self.backgroundGradientLayer.frame let backgroundSnapshotLayer = SimpleLayer()
self.layer.insertSublayer(gradientSnapshotLayer, above: self.backgroundGradientLayer) backgroundSnapshotLayer.allowsGroupOpacity = true
gradientSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in backgroundSnapshotLayer.backgroundColor = self.backgroundView.backgroundColor?.cgColor
gradientSnapshotLayer.removeFromSuperlayer() backgroundSnapshotLayer.frame = self.backgroundView.frame
self.layer.insertSublayer(backgroundSnapshotLayer, above: self.backgroundGradientLayer)
backgroundSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
backgroundSnapshotLayer.removeFromSuperlayer()
}) })
gradientSnapshotLayer.frame = self.backgroundGradientLayer.convert(self.backgroundGradientLayer.bounds, to: self.backgroundView.layer)
backgroundSnapshotLayer.addSublayer(gradientSnapshotLayer)
} }
for layer in self.avatarPatternContentLayers { for layer in self.avatarPatternContentLayers {
if let _ = layer.contents, let snapshot = layer.snapshotContentTree() { if let _ = layer.contents, let snapshot = layer.snapshotContentTree() {
@ -295,6 +331,11 @@ public final class PeerInfoCoverComponent: Component {
func update(component: PeerInfoCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize { func update(component: PeerInfoCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let previousComponent = self.component let previousComponent = self.component
self.component = component self.component = component
self.currentSize = availableSize
if case .custom = component.subject {
self.layer.allowsGroupOpacity = true
}
if previousComponent?.subject?.fileId != component.subject?.fileId { if previousComponent?.subject?.fileId != component.subject?.fileId {
if let fileId = component.subject?.fileId, fileId != 0 { if let fileId = component.subject?.fileId, fileId != 0 {
@ -349,18 +390,18 @@ public final class PeerInfoCoverComponent: Component {
secondaryBackgroundColor = .clear secondaryBackgroundColor = .clear
} }
self.backgroundView.backgroundColor = secondaryBackgroundColor let gradientWidth: CGFloat
let gradientHeight: CGFloat = component.defaultHeight
if case .custom = component.subject { if case .custom = component.subject {
if availableSize.width < availableSize.height { gradientWidth = gradientHeight
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.25) self.backgroundView.backgroundColor = backgroundColor
} else { self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: component.avatarCenter.y / gradientHeight)
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
}
self.backgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) self.backgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.backgroundGradientLayer.type = .radial self.backgroundGradientLayer.type = .radial
self.backgroundGradientLayer.colors = [secondaryBackgroundColor.cgColor, backgroundColor.cgColor] self.backgroundGradientLayer.colors = [secondaryBackgroundColor.cgColor, backgroundColor.cgColor]
} else { } else {
gradientWidth = availableSize.width
self.backgroundView.backgroundColor = secondaryBackgroundColor
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0) self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0) self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
self.backgroundGradientLayer.type = .axial self.backgroundGradientLayer.type = .axial
@ -368,8 +409,7 @@ public final class PeerInfoCoverComponent: Component {
} }
self.backgroundGradientLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0) self.backgroundGradientLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
let gradientHeight: CGFloat = component.defaultHeight let backgroundGradientFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - gradientWidth) / 2.0), y: component.gradientOnTop ? 0.0 : availableSize.height - gradientHeight), size: CGSize(width: gradientWidth, height: gradientHeight))
let backgroundGradientFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - gradientHeight), size: CGSize(width: availableSize.width, height: gradientHeight))
if !transition.animation.isImmediate { if !transition.animation.isImmediate {
let previousPosition = self.backgroundGradientLayer.position let previousPosition = self.backgroundGradientLayer.position
let updatedPosition = CGPoint(x: backgroundGradientFrame.minX, y: backgroundGradientFrame.maxY) let updatedPosition = CGPoint(x: backgroundGradientFrame.minX, y: backgroundGradientFrame.maxY)
@ -426,11 +466,7 @@ public final class PeerInfoCoverComponent: Component {
let backgroundPatternContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height), size: CGSize(width: availableSize.width, height: 0.0)) let backgroundPatternContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height), size: CGSize(width: availableSize.width, height: 0.0))
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame) transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame)
// if component.peer?.id == component.context.account.peerId {
// transition.setAlpha(view: self.backgroundPatternContainer, alpha: 0.0)
// } else {
transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction) transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction)
// }
var baseDistance: CGFloat = 72.0 var baseDistance: CGFloat = 72.0
var baseRowDistance: CGFloat = 28.0 var baseRowDistance: CGFloat = 28.0

View File

@ -166,6 +166,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)? var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)?
var displayStatusPremiumIntro: (() -> Void)? var displayStatusPremiumIntro: (() -> Void)?
var displayUniqueGiftInfo: ((UIView, String) -> Void)?
var navigateToForum: (() -> Void)? var navigateToForum: (() -> Void)?
@ -408,6 +409,16 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, .never(), self.isAvatarExpanded) self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, .never(), self.isAvatarExpanded)
} }
func invokeDisplayGiftInfo() {
guard case let .emojiStatus(status) = self.currentStatusIcon, case let .starGift(_, _, title, slug, _, _, _, _, _) = status.content else {
return
}
let slugComponents = slug.components(separatedBy: "-")
if let numberString = slugComponents.last {
self.displayUniqueGiftInfo?(self.isAvatarExpanded ? self.titleExpandedStatusIconView : self.titleStatusIconView, "\(title) #\(numberString)")
}
}
func initiateAvatarExpansion(gallery: Bool, first: Bool) { func initiateAvatarExpansion(gallery: Bool, first: Bool) {
if let peer = self.peer, peer.profileImageRepresentations.isEmpty && gallery { if let peer = self.peer, peer.profileImageRepresentations.isEmpty && gallery {
self.requestOpenAvatarForEditing?(false) self.requestOpenAvatarForEditing?(false)
@ -554,7 +565,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
credibilityIcon = .verified credibilityIcon = .verified
} }
if let verificationIconFileId = peer.verificationIconFileId { if let verificationIconFileId = peer.verificationIconFileId {
verifiedIcon = .emojiStatus(PeerEmojiStatus(fileId: verificationIconFileId, expirationDate: nil)) verifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil))
} }
} }
@ -857,6 +868,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.currentStatusIcon = statusIcon self.currentStatusIcon = statusIcon
var currentEmojiStatus: PeerEmojiStatus? var currentEmojiStatus: PeerEmojiStatus?
var particleColor: UIColor?
let emojiRegularStatusContent: EmojiStatusComponent.Content let emojiRegularStatusContent: EmojiStatusComponent.Content
let emojiExpandedStatusContent: EmojiStatusComponent.Content let emojiExpandedStatusContent: EmojiStatusComponent.Content
switch statusIcon { switch statusIcon {
@ -864,6 +877,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
currentEmojiStatus = emojiStatus currentEmojiStatus = emojiStatus
emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor, themeColor: navigationContentsAccentColor, loopMode: .forever) emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: navigationContentsAccentColor, themeColor: navigationContentsAccentColor, loopMode: .forever) emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: navigationContentsAccentColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
if let _ = emojiStatus.color {
particleColor = UIColor.white
}
default: default:
emojiRegularStatusContent = .none emojiRegularStatusContent = .none
emojiExpandedStatusContent = .none emojiExpandedStatusContent = .none
@ -876,13 +893,18 @@ final class PeerInfoHeaderNode: ASDisplayNode {
animationCache: self.animationCache, animationCache: self.animationCache,
animationRenderer: self.animationRenderer, animationRenderer: self.animationRenderer,
content: emojiRegularStatusContent, content: emojiRegularStatusContent,
particleColor: particleColor,
isVisibleForAnimations: true, isVisibleForAnimations: true,
useSharedAnimation: true, useSharedAnimation: true,
action: { [weak self] in action: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if let _ = particleColor {
strongSelf.invokeDisplayGiftInfo()
} else {
strongSelf.displayPremiumIntro?(strongSelf.titleStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false) strongSelf.displayPremiumIntro?(strongSelf.titleStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false)
}
}, },
emojiFileUpdated: { [weak self] emojiFile in emojiFileUpdated: { [weak self] emojiFile in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -932,14 +954,19 @@ final class PeerInfoHeaderNode: ASDisplayNode {
animationCache: self.animationCache, animationCache: self.animationCache,
animationRenderer: self.animationRenderer, animationRenderer: self.animationRenderer,
content: emojiExpandedStatusContent, content: emojiExpandedStatusContent,
particleColor: particleColor,
isVisibleForAnimations: true, isVisibleForAnimations: true,
useSharedAnimation: true, useSharedAnimation: true,
action: { [weak self] in action: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if let _ = particleColor {
strongSelf.invokeDisplayGiftInfo()
} else {
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true) strongSelf.displayPremiumIntro?(strongSelf.titleExpandedStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true)
} }
}
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: 26.0, height: 26.0) containerSize: CGSize(width: 26.0, height: 26.0)
@ -2217,11 +2244,22 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateFrame(view: self.backgroundBannerView, frame: bannerFrame) transition.updateFrame(view: self.backgroundBannerView, frame: bannerFrame)
} }
let backgroundCoverSubject: PeerInfoCoverComponent.Subject?
var backgroundCoverAnimateIn = false
if let status = peer?.emojiStatus, case let .starGift(_, _, _, _, patternFileId, innerColor, outerColor, patternColor, _) = status.content {
backgroundCoverSubject = .custom(UIColor(rgb: UInt32(bitPattern: innerColor)), UIColor(rgb: UInt32(bitPattern: outerColor)), UIColor(rgb: UInt32(bitPattern: patternColor)), patternFileId)
backgroundCoverAnimateIn = true
} else if let peer {
backgroundCoverSubject = .peer(EnginePeer(peer))
} else {
backgroundCoverSubject = nil
}
let backgroundCoverSize = self.backgroundCover.update( let backgroundCoverSize = self.backgroundCover.update(
transition: ComponentTransition(transition), transition: ComponentTransition(transition),
component: AnyComponent(PeerInfoCoverComponent( component: AnyComponent(PeerInfoCoverComponent(
context: self.context, context: self.context,
subject: peer.flatMap { .peer(EnginePeer($0)) }, subject: backgroundCoverSubject,
files: [:], files: [:],
isDark: presentationData.theme.overallDarkAppearance, isDark: presentationData.theme.overallDarkAppearance,
avatarCenter: apparentAvatarFrame.center, avatarCenter: apparentAvatarFrame.center,
@ -2233,9 +2271,25 @@ final class PeerInfoHeaderNode: ASDisplayNode {
environment: {}, environment: {},
containerSize: CGSize(width: width + bannerInset * 2.0, height: apparentBackgroundHeight + bannerInset) containerSize: CGSize(width: width + bannerInset * 2.0, height: apparentBackgroundHeight + bannerInset)
) )
if let backgroundCoverView = self.backgroundCover.view { if let backgroundCoverView = self.backgroundCover.view as? PeerInfoCoverComponent.View {
if backgroundCoverView.superview == nil { if backgroundCoverView.superview == nil {
self.backgroundBannerView.addSubview(backgroundCoverView) self.backgroundBannerView.addSubview(backgroundCoverView)
if backgroundCoverAnimateIn {
if !self.isAvatarExpanded {
backgroundCoverView.willAnimateIn()
Queue.mainQueue().after(0.2) {
backgroundCoverView.animateIn()
}
Queue.mainQueue().after(0.44) {
self.invokeDisplayGiftInfo()
}
} else {
Queue.mainQueue().after(0.44) {
self.invokeDisplayGiftInfo()
}
}
}
} }
if additive { if additive {
transition.updateFrameAdditive(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: -3.0, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize)) transition.updateFrameAdditive(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: -3.0, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize))

View File

@ -4661,6 +4661,27 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}) })
} }
self.headerNode.displayUniqueGiftInfo = { [weak self] sourceView, text in
guard let self, let controller = self.controller else {
return
}
let sourceRect = sourceView.convert(sourceView.bounds, to: controller.view)
let tooltipController = TooltipScreen(
context: self.context,
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .attributedString(text: NSAttributedString(string: text, font: Font.semibold(11.0), textColor: .white)),
style: .customBlur(UIColor(rgb: self.headerNode.isAvatarExpanded ? 0x000000 : 0x92c8de, alpha: 0.65), -4.0),
arrowStyle: .small,
location: .point(sourceRect, .bottom),
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .dismiss(consume: false)
}
)
controller.present(tooltipController, in: .current)
}
self.headerNode.displayStatusPremiumIntro = { [weak self] in self.headerNode.displayStatusPremiumIntro = { [weak self] in
guard let self else { guard let self else {
return return

View File

@ -727,7 +727,7 @@ final class ChannelAppearanceScreenComponent: Component {
} }
if let result { if let result {
self.updatedPeerStatus = PeerEmojiStatus(fileId: result.fileId.id, expirationDate: timestamp) self.updatedPeerStatus = PeerEmojiStatus(content: .emoji(fileId: result.fileId.id), expirationDate: timestamp)
} else { } else {
self.updatedPeerStatus = .some(nil) self.updatedPeerStatus = .some(nil)
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "nft_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "badge_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "share_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "transfer_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "wearoff_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "wear_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -84,7 +84,7 @@ final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
let _ = ApplicationSpecificNotice.setDisplayedPeerVerification(accountManager: self.context.sharedContext.accountManager, peerId: peer.id).start() let _ = ApplicationSpecificNotice.setDisplayedPeerVerification(accountManager: self.context.sharedContext.accountManager, peerId: peer.id).start()
} }
let emojiStatus = PeerEmojiStatus(fileId: verification.iconFileId, expirationDate: nil) let emojiStatus = PeerEmojiStatus(content: .emoji(fileId: verification.iconFileId), expirationDate: nil)
let emojiStatusTextNode = self.emojiStatusTextNode let emojiStatusTextNode = self.emojiStatusTextNode
let description = verification.description let description = verification.description

View File

@ -600,7 +600,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
} }
} }
let textSize: CGSize var textSize: CGSize
var isTextWithEntities = false var isTextWithEntities = false
switch self.text { switch self.text {
@ -630,6 +630,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
environment: {}, environment: {},
containerSize: CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: 1000000.0) containerSize: CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: 1000000.0)
) )
if case let .customBlur(_, inset) = self.tooltipStyle, inset < 0.0 {
textSize.height -= 3.0
}
} else { } else {
textSize = self.textView.update( textSize = self.textView.update(
transition: .immediate, transition: .immediate,