diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 81aa77bc65..44ca74299d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13651,6 +13651,21 @@ Sorry for the inconvenience."; "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"; "PeerInfo.PaneRecommendedBots" = "Similar Bots"; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 88eab2bab7..5146d1deee 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2145,6 +2145,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var currentCredibilityIconContent: EmojiStatusComponent.Content? var currentVerifiedIconContent: EmojiStatusComponent.Content? var currentStatusIconContent: EmojiStatusComponent.Content? + var currentStatusIconParticleColor: UIColor? var currentSecretIconImage: UIImage? var currentForwardedIcon: 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()) } 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)) + if case let .starGift(_, _, _, _, _, innerColor, _, _, _) = emojiStatus.content { + currentStatusIconParticleColor = UIColor(rgb: UInt32(bitPattern: innerColor)) + } } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { 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()) } 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)) + if case let .starGift(_, _, _, _, _, innerColor, _, _, _) = emojiStatus.content { + currentStatusIconParticleColor = UIColor(rgb: UInt32(bitPattern: innerColor)) + } } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor) } @@ -4543,12 +4550,13 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.statusIconView = statusIconView strongSelf.mainContentContainerNode.view.addSubview(statusIconView) } - + let statusIconComponent = EmojiStatusComponent( context: item.context, animationCache: item.interaction.animationCache, animationRenderer: item.interaction.animationRenderer, content: currentStatusIconContent, + particleColor: currentStatusIconParticleColor, isVisibleForAnimations: strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji, action: nil ) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 1d79f7b5d0..39e076cb80 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -267,9 +267,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1275374751] = { return Api.EmojiLanguage.parse_emojiLanguage($0) } dict[2048790993] = { return Api.EmojiList.parse_emojiList($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[-97474361] = { return Api.EmojiStatus.parse_emojiStatusUntil($0) } + dict[118758847] = { return Api.EmojiStatus.parse_inputEmojiStatusCollectible($0) } dict[-1519029347] = { return Api.EmojiURL.parse_emojiURL($0) } dict[1643173063] = { return Api.EncryptedChat.parse_encryptedChat($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[-122978821] = { return Api.InputMedia.parse_inputMediaContact($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[-1771768449] = { return Api.InputMedia.parse_inputMediaEmpty($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[261416433] = { return Api.InputMedia.parse_inputMediaPoll($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[-1052959727] = { return Api.InputMedia.parse_inputMediaVenue($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[1882335561] = { return Api.MessageMedia.parse_messageMediaContact($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[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) } dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) } diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index ecc50324a1..b5828fb64d 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -428,7 +428,7 @@ public extension Api { indirect enum InputMedia: TypeConstructorDescription { case inputMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: 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 inputMediaEmpty case inputMediaGame(id: Api.InputGame) @@ -440,7 +440,7 @@ public extension Api { case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?) case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?) 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 inputMediaVenue(geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) case inputMediaWebPage(flags: Int32, url: String) @@ -462,12 +462,13 @@ public extension Api { } serializeString(emoticon, buffer: buffer, boxed: false) 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 { - buffer.appendInt32(860303448) + buffer.appendInt32(1946579745) } serializeInt32(flags, buffer: buffer, boxed: false) 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 << 1) != 0 {serializeString(query!, buffer: buffer, boxed: false)} break @@ -576,9 +577,9 @@ public extension Api { peer.serialize(buffer, true) serializeInt32(id, buffer: buffer, boxed: false) 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 { - buffer.appendInt32(1530447553) + buffer.appendInt32(-264125395) } serializeInt32(flags, buffer: buffer, boxed: false) file.serialize(buffer, true) @@ -594,6 +595,7 @@ public extension Api { for item in stickers! { 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)} break 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)]) case .inputMediaDice(let emoticon): return ("inputMediaDice", [("emoticon", emoticon as Any)]) - case .inputMediaDocument(let flags, let id, let ttlSeconds, let query): - return ("inputMediaDocument", [("flags", flags as Any), ("id", id as Any), ("ttlSeconds", ttlSeconds as Any), ("query", query as Any)]) + case .inputMediaDocument(let flags, let id, let videoCover, let ttlSeconds, let query): + 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): return ("inputMediaDocumentExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)]) 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)]) case .inputMediaStory(let peer, let id): 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): - 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)]) + 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), ("videoCover", videoCover as Any), ("ttlSeconds", ttlSeconds as Any)]) 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)]) 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() { _2 = Api.parse(reader, signature: signature) as? Api.InputDocument } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } + var _3: Api.InputPhoto? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.InputPhoto + } } + 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 _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, ttlSeconds: _3, query: _4) + let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputMedia.inputMediaDocument(flags: _1!, id: _2!, videoCover: _3, ttlSeconds: _4, query: _5) } else { return nil @@ -965,17 +972,22 @@ public extension Api { if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) } } - var _7: Int32? - if Int(_1!) & Int(1 << 1) != 0 {_7 = reader.readInt32() } + var _7: Api.InputPhoto? + 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 _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.InputMedia.inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, ttlSeconds: _7) + let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil + 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 { return nil diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 77e5198937..22fdd7dbed 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -710,7 +710,7 @@ public extension Api { indirect enum MessageMedia: TypeConstructorDescription { case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) 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 messageMediaGame(game: Api.Game) case messageMediaGeo(geo: Api.GeoPoint) @@ -745,9 +745,9 @@ public extension Api { serializeInt32(value, buffer: buffer, boxed: false) serializeString(emoticon, buffer: buffer, boxed: false) 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 { - buffer.appendInt32(-581497899) + buffer.appendInt32(1838230743) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {document!.serialize(buffer, true)} @@ -756,6 +756,7 @@ public extension Api { for item in altDocuments! { 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)} break 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)]) case .messageMediaDice(let value, let emoticon): return ("messageMediaDice", [("value", value as Any), ("emoticon", emoticon as Any)]) - case .messageMediaDocument(let flags, let document, let altDocuments, let ttlSeconds): - return ("messageMediaDocument", [("flags", flags as Any), ("document", document as Any), ("altDocuments", altDocuments as Any), ("ttlSeconds", ttlSeconds as Any)]) + 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), ("coverPhoto", coverPhoto as Any), ("ttlSeconds", ttlSeconds as Any)]) case .messageMediaEmpty: return ("messageMediaEmpty", []) case .messageMediaGame(let game): @@ -990,14 +991,19 @@ public extension Api { if Int(_1!) & Int(1 << 5) != 0 {if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } + var _4: Api.Photo? + 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 _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, ttlSeconds: _4) + let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageMedia.messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, coverPhoto: _4, ttlSeconds: _5) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 98b621dcef..3a8468e0ac 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -464,6 +464,21 @@ public extension Api.functions.account { }) } } +public extension Api.functions.account { + static func getCollectibleEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + 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 { static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index 90cfa4cde6..68e237c7af 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -796,17 +796,36 @@ public extension Api { } public extension Api { 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 emojiStatusUntil(documentId: Int64, until: Int32) + case inputEmojiStatusCollectible(flags: Int32, collectibleId: Int64, until: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .emojiStatus(let documentId): + case .emojiStatus(let flags, let documentId, let until): if boxed { - buffer.appendInt32(-1835310691) + buffer.appendInt32(-402717046) } + serializeInt32(flags, 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 case .emojiStatusEmpty: if boxed { @@ -814,33 +833,83 @@ public extension Api { } break - case .emojiStatusUntil(let documentId, let until): + case .inputEmojiStatusCollectible(let flags, let collectibleId, let until): if boxed { - buffer.appendInt32(-97474361) + buffer.appendInt32(118758847) } - serializeInt64(documentId, buffer: buffer, boxed: false) - serializeInt32(until, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(collectibleId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(until!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .emojiStatus(let documentId): - return ("emojiStatus", [("documentId", documentId as Any)]) + case .emojiStatus(let flags, let documentId, let until): + 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: return ("emojiStatusEmpty", []) - case .emojiStatusUntil(let documentId, let until): - return ("emojiStatusUntil", [("documentId", documentId as Any), ("until", until as Any)]) + case .inputEmojiStatusCollectible(let flags, let collectibleId, let until): + return ("inputEmojiStatusCollectible", [("flags", flags as Any), ("collectibleId", collectibleId as Any), ("until", until as Any)]) } } public static func parse_emojiStatus(_ reader: BufferReader) -> EmojiStatus? { - var _1: Int64? - _1 = reader.readInt64() + var _1: Int32? + _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 - if _c1 { - return Api.EmojiStatus.emojiStatus(documentId: _1!) + let _c2 = _2 != nil + 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 { return nil @@ -849,15 +918,18 @@ public extension Api { public static func parse_emojiStatusEmpty(_ reader: BufferReader) -> EmojiStatus? { return Api.EmojiStatus.emojiStatusEmpty } - public static func parse_emojiStatusUntil(_ reader: BufferReader) -> EmojiStatus? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_inputEmojiStatusCollectible(_ reader: BufferReader) -> EmojiStatus? { + var _1: Int32? + _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 _c2 = _2 != nil - if _c1 && _c2 { - return Api.EmojiStatus.emojiStatusUntil(documentId: _1!, until: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.EmojiStatus.inputEmojiStatusCollectible(flags: _1!, collectibleId: _2!, until: _3) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 49c4cdc791..89aea20e89 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -350,7 +350,8 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI 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) 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 mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments) { return (mediaFile, ttlSeconds, (flags & (1 << 3)) != 0, (flags & (1 << 4)) != 0, nil) diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 8346d55cb0..58ee46b27e 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -229,7 +229,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post } |> mapToSignal { validatedResource -> Signal in 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 { 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 { 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))) |> 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 @@ -1049,11 +1049,11 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } 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 { - 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)) } @@ -1064,11 +1064,11 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili |> mapError { _ -> PendingMessageUploadError in } |> mapToSignal { inputPeer -> Signal in 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 } |> mapToSignal { result -> Signal in 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 { var flags: Int32 = 0 var ttlSeconds: Int32? @@ -1080,7 +1080,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili 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 { return .single(result) } else { diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 2daf73906d..064f8d9991 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -702,7 +702,7 @@ private func uploadedFile(account: Account, data: Data, mimeType: String, attrib |> map { next -> UploadMediaEvent in switch next { 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: preconditionFailure() case let .progress(progress): diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift index 2f99126412..2d195b87a2 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift @@ -158,11 +158,11 @@ public func standaloneUploadedFile(postbox: Postbox, network: Network, peerId: P if let _ = thumbnailFile { 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 } |> mapToSignal { media -> Signal in switch media { - case let .messageMediaDocument(_, document, altDocuments, _): + case let .messageMediaDocument(_, document, altDocuments, _, _): if let document = document { if let mediaFile = telegramMediaFileFromApiDocument(document, altDocuments: altDocuments) { return .single(.result(.media(.standalone(media: mediaFile)))) diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index bcc226f1d8..ba23451233 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -108,6 +108,7 @@ final class AccountTaskManager { 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(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(managedGroupPhotoEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedBackgroundIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) diff --git a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift index 8974f82500..5a06921c6b 100644 --- a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift +++ b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift @@ -213,11 +213,11 @@ func managedRecentStatusEmoji(postbox: Postbox, network: Network) -> Signal map { files -> [OrderedItemListEntry] in var items: [OrderedItemListEntry] = [] for status in parsedStatuses { - guard let file = files[status.fileId] else { + guard let fileId = status.emojiFileId, let file = files[fileId] else { continue } if let entry = CodableEntry(RecentMediaItem(file)) { @@ -243,11 +243,11 @@ func managedFeaturedStatusEmoji(postbox: Postbox, network: Network) -> Signal map { files -> [OrderedItemListEntry] in var items: [OrderedItemListEntry] = [] for status in parsedStatuses { - guard let file = files[status.fileId] else { + guard let fileId = status.emojiFileId, let file = files[fileId] else { continue } if let entry = CodableEntry(RecentMediaItem(file)) { @@ -273,11 +273,11 @@ func managedFeaturedChannelStatusEmoji(postbox: Postbox, network: Network) -> Si case let .emojiStatuses(_, statuses): 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 var items: [OrderedItemListEntry] = [] for status in parsedStatuses { - guard let file = files[status.fileId] else { + guard let fileId = status.emojiFileId, let file = files[fileId] else { continue } 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 } +func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal { + 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 { 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)) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 15c02dcff4..40f0173b41 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 197 + return 198 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 47ef304788..bd2b2f20f0 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -52,7 +52,7 @@ extension Api.MessageMedia { } else { return nil } - case let .messageMediaDocument(_, document, _, _): + case let .messageMediaDocument(_, document, _, _, _): if let document = document { return collectPreCachedResources(for: document) } @@ -626,14 +626,14 @@ extension Api.EncryptedMessage { extension Api.InputMedia { func withUpdatedStickers(_ stickers: [Api.InputDocument]?) -> Api.InputMedia { 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 attributes = attributes if let _ = stickers { flags |= (1 << 0) 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): var flags = flags if let _ = stickers { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index f91c7088ea..c1d130f18b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -260,11 +260,62 @@ public enum PeerNameColor: Hashable { } 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 init(fileId: Int64, expirationDate: Int32?) { - self.fileId = fileId + public init(content: Content, expirationDate: Int32?) { + self.content = content self.expirationDate = expirationDate } } @@ -272,15 +323,52 @@ public struct PeerEmojiStatus: Equatable, Codable { extension PeerEmojiStatus { init?(apiStatus: Api.EmojiStatus) { switch apiStatus { - case let .emojiStatus(documentId): - self.init(fileId: documentId, expirationDate: nil) - case let .emojiStatusUntil(documentId, until): - self.init(fileId: documentId, expirationDate: until) - case .emojiStatusEmpty: + case let .emojiStatus(_, documentId, until): + self.init(content: .emoji(fileId: documentId), expirationDate: until) + case let .emojiStatusCollectible(_, collectibleId, documentId, title, slug, patternDocumentId, centerColor, edgeColor, patternColor, textColor, 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, .inputEmojiStatusCollectible: 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 var rawValue: Int32 diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 9b26ac2eb2..2ce6c8d17a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -95,6 +95,7 @@ public struct Namespaces { public static let CloudFeaturedChannelStatusEmoji: Int32 = 27 public static let CloudDisabledChannelStatusEmoji: Int32 = 28 public static let CloudDefaultTagReactions: Int32 = 29 + public static let CloudUniqueStarGifts: Int32 = 30 } public struct CachedItemCollection { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentMediaItem.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentMediaItem.swift index c8a0ad4f8c..9bdd6e3078 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentMediaItem.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentMediaItem.swift @@ -284,3 +284,47 @@ public final class RecentReactionItem: Codable, Equatable { 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 + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift index d5450424a5..fc635a43f0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift @@ -186,22 +186,26 @@ public final class TelegramChannel: Peer, Equatable { } public var associatedMediaIds: [MediaId]? { - if let emojiStatus = self.emojiStatus, let backgroundEmojiId = self.backgroundEmojiId { - return [ - MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId), - MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) - ] - } else if let emojiStatus = self.emojiStatus { - return [ - MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId) - ] - } else if let backgroundEmojiId = self.backgroundEmojiId { - return [ - MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) - ] - } else { + var mediaIds: [MediaId] = [] + if let emojiStatus = self.emojiStatus { + switch emojiStatus.content { + case let .emoji(fileId): + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) + case let .starGift(_, fileId, _, _, patternFileId, _, _, _, _): + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: patternFileId)) + } + } + if let backgroundEmojiId = self.backgroundEmojiId { + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)) + } + if let profileBackgroundEmojiId = self.profileBackgroundEmojiId { + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: profileBackgroundEmojiId)) + } + guard !mediaIds.isEmpty else { return nil } + return mediaIds } public let associatedPeerId: PeerId? = nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift index ed06240571..4ffd92962b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift @@ -174,22 +174,26 @@ public final class TelegramUser: Peer, Equatable { } public var associatedMediaIds: [MediaId]? { - if let emojiStatus = self.emojiStatus, let backgroundEmojiId = self.backgroundEmojiId { - return [ - MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId), - MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) - ] - } else if let emojiStatus = self.emojiStatus { - return [ - MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId) - ] - } else if let backgroundEmojiId = self.backgroundEmojiId { - return [ - MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId) - ] - } else { + var mediaIds: [MediaId] = [] + if let emojiStatus = self.emojiStatus { + switch emojiStatus.content { + case let .emoji(fileId): + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) + case let .starGift(_, fileId, _, _, patternFileId, _, _, _, _): + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: patternFileId)) + } + } + if let backgroundEmojiId = self.backgroundEmojiId { + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)) + } + if let profileBackgroundEmojiId = self.profileBackgroundEmojiId { + mediaIds.append(MediaId(namespace: Namespaces.Media.CloudFile, id: profileBackgroundEmojiId)) + } + guard !mediaIds.isEmpty else { return nil } + return mediaIds } public let associatedPeerId: PeerId? = nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index 02aa66b673..61fd06f150 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -75,15 +75,75 @@ public extension TelegramEngine { return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: true) } + public func setStarGiftStatus(starGift: StarGift.UniqueGift, expirationDate: Int32?) -> Signal { + 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 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 { let peerId = self.account.peerId let remoteApply = self.account.network.request(Api.functions.account.updateEmojiStatus(emojiStatus: file.flatMap({ file in - if let expirationDate = expirationDate { - return Api.EmojiStatus.emojiStatusUntil(documentId: file.fileId.id, until: expirationDate) - } else { - return Api.EmojiStatus.emojiStatus(documentId: file.fileId.id) + var flags: Int32 = 0 + if let _ = expirationDate { + flags |= (1 << 0) } + return Api.EmojiStatus.emojiStatus(flags: flags, documentId: file.fileId.id, until: expirationDate) }) ?? Api.EmojiStatus.emojiStatusEmpty)) |> `catch` { _ -> Signal in return .single(.boolFalse) @@ -101,7 +161,7 @@ public extension TelegramEngine { } 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 }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/HistoryImport/TelegramEngineHistoryImport.swift b/submodules/TelegramCore/Sources/TelegramEngine/HistoryImport/TelegramEngineHistoryImport.swift index 8d48f8789e..9a044ea5aa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/HistoryImport/TelegramEngineHistoryImport.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/HistoryImport/TelegramEngineHistoryImport.swift @@ -153,7 +153,7 @@ public extension TelegramEngine { default: 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): return .single(value) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 2336fd44b7..b498a4667d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -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(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 { - 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 { @@ -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(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 { - 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 @@ -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 { inputMedia = media } 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 } else { inputMedia = nil @@ -2099,7 +2099,7 @@ extension Stories.StoredItem { var parsedAlternativeMedia: [Media] = [] switch media { - case let .messageMediaDocument(_, _, altDocuments, _): + case let .messageMediaDocument(_, _, altDocuments, _, _): if let altDocuments { parsedAlternativeMedia = altDocuments.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index b6e2c1fd7f..9502d36aea 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -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(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 { - 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)) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift index c4ebfb1334..bbb1851742 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift @@ -274,7 +274,7 @@ public enum UpdatePeerEmojiStatusError { func _internal_updatePeerEmojiStatus(account: Account, peerId: PeerId, fileId: Int64?, expirationDate: Int32?) -> Signal { return account.postbox.transaction { transaction -> Api.InputChannel? in let updatedStatus = fileId.flatMap { - PeerEmojiStatus(fileId: $0, expirationDate: expirationDate) + PeerEmojiStatus(content: .emoji(fileId: $0), expirationDate: expirationDate) } if let peer = transaction.getPeer(peerId) as? TelegramChannel { 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 if let fileId = fileId { - if let expirationDate = expirationDate { - mappedStatus = .emojiStatusUntil(documentId: fileId, until: expirationDate) - } else { - mappedStatus = .emojiStatus(documentId: fileId) + var flags: Int32 = 0 + if let _ = expirationDate { + flags |= (1 << 0) } + mappedStatus = .emojiStatus(flags: flags, documentId: fileId, until: expirationDate) } else { mappedStatus = .emojiStatusEmpty } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index f8a8e133b1..e8b8a2ef60 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -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(.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 } |> mapToSignal { media -> Signal in 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 { 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) }) { diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 9e4b73525b..b9fad025fe 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -284,7 +284,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { titleCredibilityIcon = .verified } if let verificationIconFileId = peer.verificationIconFileId { - titleVerifiedIcon = .emojiStatus(PeerEmojiStatus(fileId: verificationIconFileId, expirationDate: nil)) + titleVerifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil)) } } } diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index b8a2912289..c5d3a8ab92 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -59,22 +59,26 @@ public final class EmojiStatusComponent: Component { public let animationCache: AnimationCache public let animationRenderer: MultiAnimationRenderer public let content: Content + public let particleColor: UIColor? public let size: CGSize? public let isVisibleForAnimations: Bool public let useSharedAnimation: Bool public let action: (() -> Void)? public let emojiFileUpdated: ((TelegramMediaFile?) -> Void)? + public let tag: AnyObject? public convenience init( context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, content: Content, + particleColor: UIColor? = nil, size: CGSize? = nil, isVisibleForAnimations: Bool, useSharedAnimation: Bool = false, action: (() -> Void)?, - emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil + emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil, + tag: AnyObject? = nil ) { self.init( postbox: context.account.postbox, @@ -85,11 +89,13 @@ public final class EmojiStatusComponent: Component { animationCache: animationCache, animationRenderer: animationRenderer, content: content, + particleColor: particleColor, size: size, isVisibleForAnimations: isVisibleForAnimations, useSharedAnimation: useSharedAnimation, action: action, - emojiFileUpdated: emojiFileUpdated + emojiFileUpdated: emojiFileUpdated, + tag: tag ) } @@ -100,11 +106,13 @@ public final class EmojiStatusComponent: Component { animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, content: Content, + particleColor: UIColor? = nil, size: CGSize? = nil, isVisibleForAnimations: Bool, useSharedAnimation: Bool = false, action: (() -> Void)?, - emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil + emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil, + tag: AnyObject? = nil ) { self.postbox = postbox self.energyUsageSettings = energyUsageSettings @@ -112,11 +120,13 @@ public final class EmojiStatusComponent: Component { self.animationCache = animationCache self.animationRenderer = animationRenderer self.content = content + self.particleColor = particleColor self.size = size self.isVisibleForAnimations = isVisibleForAnimations self.useSharedAnimation = useSharedAnimation self.action = action self.emojiFileUpdated = emojiFileUpdated + self.tag = tag } public func withVisibleForAnimations(_ isVisibleForAnimations: Bool) -> EmojiStatusComponent { @@ -127,11 +137,13 @@ public final class EmojiStatusComponent: Component { animationCache: self.animationCache, animationRenderer: self.animationRenderer, content: self.content, + particleColor: self.particleColor, size: self.size, isVisibleForAnimations: isVisibleForAnimations, useSharedAnimation: self.useSharedAnimation, 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 { return false } + if lhs.particleColor != rhs.particleColor { + return false + } if lhs.size != rhs.size { return false } @@ -160,10 +175,23 @@ public final class EmojiStatusComponent: Component { if lhs.useSharedAnimation != rhs.useSharedAnimation { return false } + if lhs.tag !== rhs.tag { + return false + } 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 { let path: String let coloredComposition: Animation? @@ -195,6 +223,7 @@ public final class EmojiStatusComponent: Component { private weak var state: EmptyComponentState? private var component: EmojiStatusComponent? + private var starsLayer: StarsEffectLayer? private var iconView: UIImageView? private var animationLayer: InlineStickerItemLayer? private var lottieAnimationView: AnimationView? @@ -255,6 +284,24 @@ public final class EmojiStatusComponent: Component { 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 if self.component?.content != component.content { switch component.content { @@ -662,3 +709,57 @@ public func topicIconColors(for color: Int32) -> ([UInt32], [UInt32]) { 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) + } +} diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 9b437cf096..d08905b75b 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -1336,8 +1336,13 @@ public final class EmojiStatusSelectionController: ViewController { switch controller.mode { case .statusSelection: - let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile, expirationDate: nil) - |> deliverOnMainQueue).start() + 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) + |> deliverOnMainQueue).start() + } case let .backgroundSelection(completion): completion(item?.itemFile) case let .customStatusSelection(completion): diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift index 2932b20a14..43ff78156c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift @@ -53,6 +53,7 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { private var iconLayer: SimpleLayer? private var tintIconLayer: SimpleLayer? + private(set) var underlyingContentLayer: SimpleLayer? private(set) var tintContentLayer: SimpleLayer? private var badge: Badge? @@ -93,6 +94,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { if let mirrorLayer = self.tintContentLayer { mirrorLayer.position = value } + if let mirrorLayer = self.underlyingContentLayer { + mirrorLayer.position = value + } super.position = value } } @@ -104,6 +108,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { if let mirrorLayer = self.tintContentLayer { mirrorLayer.bounds = value } + if let mirrorLayer = self.underlyingContentLayer { + mirrorLayer.bounds = value + } super.bounds = value } } @@ -112,7 +119,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { if let mirrorLayer = self.tintContentLayer { mirrorLayer.add(animation, forKey: key) } - + if let mirrorLayer = self.underlyingContentLayer { + mirrorLayer.add(animation, forKey: key) + } super.add(animation, forKey: key) } @@ -120,7 +129,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { if let mirrorLayer = self.tintContentLayer { mirrorLayer.removeAllAnimations() } - + if let mirrorLayer = self.underlyingContentLayer { + mirrorLayer.removeAllAnimations() + } super.removeAllAnimations() } @@ -128,7 +139,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { if let mirrorLayer = self.tintContentLayer { mirrorLayer.removeAnimation(forKey: forKey) } - + if let mirrorLayer = self.underlyingContentLayer { + mirrorLayer.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): let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 6fd0351253..c18b142b68 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -40,6 +40,7 @@ public final class EntityKeyboardAnimationData: Equatable { public enum Id: Hashable { case file(MediaId) case stickerPackThumbnail(ItemCollectionId) + case gift(String) } public enum ItemType { @@ -66,8 +67,9 @@ public final class EntityKeyboardAnimationData: Equatable { public let immediateThumbnailData: Data? public let isReaction: 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.type = type self.resource = resource @@ -75,6 +77,7 @@ public final class EntityKeyboardAnimationData: Equatable { self.immediateThumbnailData = immediateThumbnailData self.isReaction = isReaction self.isTemplate = isTemplate + self.particleColor = particleColor } 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) } + 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 { if lhs === rhs { return true @@ -335,6 +357,7 @@ public final class EmojiPagerContentComponent: Component { case animation(EntityKeyboardAnimationData.Id) case staticEmoji(String) case icon(Icon) + case starGift(String) } public enum Icon: Equatable, Hashable { @@ -379,6 +402,7 @@ public final class EmojiPagerContentComponent: Component { public let animationData: EntityKeyboardAnimationData? public let content: ItemContent public let itemFile: TelegramMediaFile? + public let itemGift: StarGift.UniqueGift? public let subgroupId: Int32? public let icon: Icon public let tintMode: TintMode @@ -387,6 +411,7 @@ public final class EmojiPagerContentComponent: Component { animationData: EntityKeyboardAnimationData?, content: ItemContent, itemFile: TelegramMediaFile?, + itemGift: StarGift.UniqueGift? = nil, subgroupId: Int32?, icon: Icon, tintMode: TintMode @@ -394,6 +419,7 @@ public final class EmojiPagerContentComponent: Component { self.animationData = animationData self.content = content self.itemFile = itemFile + self.itemGift = itemGift self.subgroupId = subgroupId self.icon = icon self.tintMode = tintMode @@ -412,6 +438,9 @@ public final class EmojiPagerContentComponent: Component { if lhs.itemFile?.fileId != rhs.itemFile?.fileId { return false } + if lhs.itemGift?.id != rhs.itemGift?.id { + return false + } if lhs.subgroupId != rhs.subgroupId { return false } @@ -3461,6 +3490,9 @@ public final class EmojiPagerContentComponent: Component { ) self.visibleItemLayers[itemId] = itemLayer + if let underlyingContentLayer = itemLayer.underlyingContentLayer { + self.scrollView.layer.addSublayer(underlyingContentLayer) + } self.scrollView.layer.addSublayer(itemLayer) if let tintContentLayer = itemLayer.tintContentLayer { self.mirrorContentScrollView.layer.addSublayer(tintContentLayer) @@ -3692,6 +3724,7 @@ public final class EmojiPagerContentComponent: Component { itemLayer.opacity = 0.0 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?.underlyingContentLayer?.removeFromSuperlayer() itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer() }) @@ -3712,6 +3745,7 @@ public final class EmojiPagerContentComponent: Component { } } else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId { transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in + itemLayer?.underlyingContentLayer?.removeFromSuperlayer() itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer() }) @@ -3726,6 +3760,7 @@ public final class EmojiPagerContentComponent: Component { itemLayer.opacity = 0.0 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?.underlyingContentLayer?.removeFromSuperlayer() itemLayer?.tintContentLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer() }) @@ -3746,6 +3781,7 @@ public final class EmojiPagerContentComponent: Component { } } } else { + itemLayer.underlyingContentLayer?.removeFromSuperlayer() itemLayer.tintContentLayer?.removeFromSuperlayer() itemLayer.removeFromSuperlayer() diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 512b446dcc..cb7e670d8e 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -126,6 +126,7 @@ public extension EmojiPagerContentComponent { if case .status = subject { orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji) + orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudUniqueStarGifts) iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false) |> map { result -> [TelegramMediaFile] in @@ -343,8 +344,11 @@ public extension EmojiPagerContentComponent { var featuredAvatarEmoji: OrderedItemListView? var featuredBackgroundIconEmoji: OrderedItemListView? var defaultTagReactions: OrderedItemListView? + var uniqueGifts: OrderedItemListView? 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 } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedStatusEmoji { 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 { let resultItem = EmojiPagerContentComponent.Item( animationData: nil, diff --git a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD index 518a36b234..ab8dc02d74 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD @@ -25,6 +25,7 @@ swift_library( "//submodules/TextFormat", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent", + "//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode", ], diff --git a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift index 2c157867b2..dff5e42cb3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift @@ -12,6 +12,7 @@ import TextFormat import PeerInfoCoverComponent import AnimatedStickerNode import TelegramAnimatedStickerNode +import EmojiStatusComponent public final class GiftCompositionComponent: Component { public class ExternalState { @@ -30,6 +31,9 @@ public final class GiftCompositionComponent: Component { let context: AccountContext let theme: PresentationTheme let subject: Subject + let animationOffset: CGPoint? + let animationScale: CGFloat? + let displayAnimationStars: Bool let externalState: ExternalState? let requestUpdate: () -> Void @@ -37,12 +41,18 @@ public final class GiftCompositionComponent: Component { context: AccountContext, theme: PresentationTheme, subject: Subject, + animationOffset: CGPoint? = nil, + animationScale: CGFloat? = nil, + displayAnimationStars: Bool = false, externalState: ExternalState? = nil, requestUpdate: @escaping () -> Void = {} ) { self.context = context self.theme = theme self.subject = subject + self.animationOffset = animationOffset + self.animationScale = animationScale + self.displayAnimationStars = displayAnimationStars self.externalState = externalState self.requestUpdate = requestUpdate } @@ -57,6 +67,15 @@ public final class GiftCompositionComponent: Component { if lhs.subject != rhs.subject { 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 } @@ -64,6 +83,8 @@ public final class GiftCompositionComponent: Component { private var component: GiftCompositionComponent? private weak var componentState: EmptyComponentState? + private var starsLayer: StarsEffectLayer? + private let background = ComponentView() private var animationNode: AnimatedStickerNode? @@ -256,6 +277,12 @@ public final class GiftCompositionComponent: Component { if animateTransition, let backgroundView = self.background.view as? PeerInfoCoverComponent.View { 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( transition: backgroundTransition, component: AnyComponent(PeerInfoCoverComponent( @@ -263,9 +290,10 @@ public final class GiftCompositionComponent: Component { subject: .custom(backgroundColor, secondBackgroundColor, patternColor, patternFile?.fileId.id), files: files, isDark: false, - avatarCenter: CGPoint(x: availableSize.width / 2.0, y: 104.0), + avatarCenter: avatarCenter, avatarScale: 1.0, - defaultHeight: availableSize.height, + defaultHeight: 300.0, + gradientOnTop: true, avatarTransitionFraction: 0.0, patternTransitionFraction: 0.0 )), @@ -292,17 +320,20 @@ public final class GiftCompositionComponent: Component { let iconSize = CGSize(width: 136.0, height: 136.0) var startFromIndex: Int? + var animationTransition = transition if animateTransition, let disappearingAnimationNode = self.animationNode { self.animationNode = nil startFromIndex = disappearingAnimationNode.currentFrameIndex disappearingAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in disappearingAnimationNode.view.removeFromSuperview() }) + animationTransition = .immediate } if let file = animationFile { let animationNode: AnimatedStickerNode if self.animationNode == nil { + animationTransition = .immediate animationNode = DefaultAnimatedStickerNodeImpl() animationNode.isUserInteractionEnabled = false self.animationNode = animationNode @@ -333,9 +364,41 @@ public final class GiftCompositionComponent: Component { } } 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 } } @@ -348,3 +411,56 @@ public final class GiftCompositionComponent: Component { 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) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index 6b06e7e5d7..4eec76a393 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -43,6 +43,7 @@ swift_library( "//submodules/TooltipUI", "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", "//submodules/MoreButtonNode", + "//submodules/TelegramUI/Components/EmojiStatusComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 2662ca11d3..bac312a9b3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -22,6 +22,7 @@ import TextFormat import TelegramStringFormatting import StarsAvatarComponent import EmojiTextAttachmentView +import EmojiStatusComponent import UndoUI import ConfettiEffect import PlainButtonComponent @@ -34,6 +35,7 @@ import ContextUI private let modelButtonTag = GenericComponentViewTag() private let backdropButtonTag = GenericComponentViewTag() private let symbolButtonTag = GenericComponentViewTag() +private let statusTag = GenericComponentViewTag() private final class GiftViewSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -49,7 +51,8 @@ private final class GiftViewSheetContent: CombinedComponent { let openMyGifts: () -> Void let transferGift: () -> Void let upgradeGift: ((Int64?, Bool) -> Signal) - let showAttributeInfo: (Any, Float) -> Void + let shareGift: () -> Void + let showAttributeInfo: (Any, String) -> Void let viewUpgraded: (EngineMessage.Id) -> Void let openMore: (ASDisplayNode, ContextGesture?) -> Void let getController: () -> ViewController? @@ -66,7 +69,8 @@ private final class GiftViewSheetContent: CombinedComponent { openMyGifts: @escaping () -> Void, transferGift: @escaping () -> Void, upgradeGift: @escaping ((Int64?, Bool) -> Signal), - showAttributeInfo: @escaping (Any, Float) -> Void, + shareGift: @escaping () -> Void, + showAttributeInfo: @escaping (Any, String) -> Void, viewUpgraded: @escaping (EngineMessage.Id) -> Void, openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void, getController: @escaping () -> ViewController? @@ -82,6 +86,7 @@ private final class GiftViewSheetContent: CombinedComponent { self.openMyGifts = openMyGifts self.transferGift = transferGift self.upgradeGift = upgradeGift + self.shareGift = shareGift self.showAttributeInfo = showAttributeInfo self.viewUpgraded = viewUpgraded self.openMore = openMore @@ -123,6 +128,10 @@ private final class GiftViewSheetContent: CombinedComponent { var upgradeFormDisposable: Disposable? var upgradeDisposable: Disposable? + var inWearPreview = false + var pendingWear = false + var pendingTakeOff = false + var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]? let sampleDisposable = DisposableSet() @@ -135,13 +144,7 @@ private final class GiftViewSheetContent: CombinedComponent { } } private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil) - - var mockFiles: [TelegramMediaFile] = [] - var mockIconFiles: [TelegramMediaFile] = [] - var upgradedMockId: Int = 0 - var upgradedMockBackgroundColor: UIColor = .white - var upgradedMockIcon: TelegramMediaFile? - + init( context: AccountContext, subject: GiftViewScreen.Subject, @@ -278,6 +281,11 @@ private final class GiftViewSheetContent: CombinedComponent { self.updated(transition: .spring(duration: 0.4)) } + func requestWearPreview() { + self.inWearPreview = true + self.updated(transition: .spring(duration: 0.4)) + } + func commitUpgrade() { guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { return @@ -343,6 +351,18 @@ private final class GiftViewSheetContent: CombinedComponent { let animation = Child(GiftCompositionComponent.self) let title = 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.self) + let hiddenText = Child(MultilineTextComponent.self) let table = Child(TableComponent.self) let additionalText = Child(MultilineTextComponent.self) @@ -456,12 +476,15 @@ private final class GiftViewSheetContent: CombinedComponent { component: ButtonsComponent( theme: theme, isOverlay: showUpgradePreview || uniqueGift != nil, - showMoreButton: uniqueGift != nil, + showMoreButton: uniqueGift != nil && !state.inWearPreview, closePressed: { [weak state] in guard let state else { 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.updated(transition: .spring(duration: 0.4)) } else { @@ -477,46 +500,247 @@ private final class GiftViewSheetContent: CombinedComponent { ) var originY: CGFloat = 0.0 - - let animationHeight: CGFloat - let animationSubject: GiftCompositionComponent.Subject? + + let headerHeight: CGFloat + let headerSubject: GiftCompositionComponent.Subject? if let uniqueGift { - animationHeight = 240.0 - animationSubject = .unique(uniqueGift) + if state.inWearPreview { + headerHeight = 200.0 + } else if case let .peerId(peerId) = uniqueGift.owner, peerId == component.context.account.peerId { + headerHeight = 314.0 + } else { + headerHeight = 240.0 + } + headerSubject = .unique(uniqueGift) } else if state.inUpgradePreview, let attributes = state.sampleGiftAttributes { - animationHeight = 258.0 - animationSubject = .preview(attributes) + headerHeight = 258.0 + headerSubject = .preview(attributes) } else if case let .upgradePreview(attributes, _) = component.subject { - animationHeight = 258.0 - animationSubject = .preview(attributes) + headerHeight = 258.0 + headerSubject = .preview(attributes) } else if let animationFile { - animationHeight = 210.0 - animationSubject = .generic(animationFile) + headerHeight = 210.0 + headerSubject = .generic(animationFile) } else { - animationHeight = 210.0 - animationSubject = nil + headerHeight = 210.0 + headerSubject = nil } - if let animationSubject { + + 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( component: GiftCompositionComponent( context: component.context, theme: environment.theme, - subject: animationSubject, + subject: headerSubject, + animationOffset: animationOffset, + animationScale: animationScale, + displayAnimationStars: state.inWearPreview, externalState: giftCompositionExternalState, requestUpdate: { [weak state] in state?.updated() } ), - availableSize: CGSize(width: context.availableSize.width, height: animationHeight), - transition: .immediate + availableSize: CGSize(width: context.availableSize.width, height: headerHeight), + transition: context.transition ) 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 - - if showUpgradePreview { + originY += headerHeight + + 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] = [] + 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 description: 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), 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( component: BalancedTextComponent( text: .plain(NSAttributedString( string: description, font: Font.regular(13.0), - textColor: descriptionColor, + textColor: vibrantColor, paragraphAlignment: .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( component: List(items), availableSize: CGSize(width: context.availableSize.width - perksSideInset * 2.0, height: 10000.0), @@ -784,11 +1002,7 @@ private final class GiftViewSheetContent: CombinedComponent { let textColor: UIColor if let _ = uniqueGift { textFont = Font.regular(13.0) - if let previewPatternColor = giftCompositionExternalState.previewPatternColor { - 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) - } + textColor = vibrantColor } else { textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0) textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor @@ -883,13 +1097,46 @@ private final class GiftViewSheetContent: CombinedComponent { let tableLinkColor = theme.list.itemAccentColor var tableItems: [TableComponent.Item] = [] + var isWearing = state.pendingWear + if !soldOut { if let uniqueGift { switch uniqueGift.owner { case let .peerId(peerId): if let peer = state.peerMap[peerId] { let ownerComponent: AnyComponent - 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( HStack([ AnyComponentWithIdentity( @@ -913,21 +1160,21 @@ private final class GiftViewSheetContent: CombinedComponent { ), AnyComponentWithIdentity( id: AnyHashable(1), - component: AnyComponent(Button( - content: AnyComponent(ButtonContentComponent( - context: component.context, - text: strings.Gift_Unique_Transfer, - color: theme.list.itemAccentColor - )), + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: animationContent, + particleColor: color, + size: CGSize(width: 18.0, height: 18.0), + isVisibleForAnimations: true, action: { - component.transferGift() - Queue.mainQueue().after(1.0, { - component.cancel(false) - }) - } + + }, + tag: statusTag )) ) - ], spacing: 4.0) + ], spacing: 2.0) ) } else { ownerComponent = AnyComponent(Button( @@ -1052,6 +1299,93 @@ private final class GiftViewSheetContent: CombinedComponent { } 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 order: [StarGift.UniqueGift.Attribute.AttributeType] = [ @@ -1191,7 +1525,7 @@ private final class GiftViewSheetContent: CombinedComponent { color: theme.list.itemAccentColor )), action: { - showAttributeInfo(tag, percentage) + showAttributeInfo(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string) } ).tagged(tag)) )) @@ -1393,7 +1727,7 @@ private final class GiftViewSheetContent: CombinedComponent { originY += table.size.height + 23.0 } - if incoming && !converted && !upgraded && !showUpgradePreview { + if incoming && !converted && !upgraded && !showUpgradePreview && !state.inWearPreview { let linkColor = theme.actionSheet.controlAccentColor if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.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 } + 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 - 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 { 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( component: ButtonComponent( - background: ButtonComponent.Background( - color: theme.list.itemCheckColors.fillColor, - foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 - ), + background: buttonBackground, content: AnyComponentWithIdentity( id: AnyHashable("upgrade"), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) @@ -1485,7 +1847,7 @@ private final class GiftViewSheetContent: CombinedComponent { action: { [weak state] in state?.commitUpgrade() }), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: buttonSize, transition: context.transition ) } 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 buttonChild = button.update( component: ButtonComponent( - background: ButtonComponent.Background( - color: theme.list.itemCheckColors.fillColor, - foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 - ), + background: buttonBackground, content: AnyComponentWithIdentity( id: AnyHashable("button"), 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.viewUpgraded(upgradeMessageId) }), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: buttonSize, transition: context.transition ) } else if incoming && !converted && !upgraded, let upgradeStars, upgradeStars > 0 { let buttonTitle = strings.Gift_View_UpgradeForFree buttonChild = button.update( component: ButtonComponent( - background: ButtonComponent.Background( - color: theme.list.itemCheckColors.fillColor, - foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0, - isShimmering: true - ), + background: buttonBackground.withIsShimmering(true), content: AnyComponentWithIdentity( id: AnyHashable("freeUpgrade"), component: AnyComponent(HStack([ @@ -1546,19 +1897,14 @@ private final class GiftViewSheetContent: CombinedComponent { state?.requestUpgradePreview() } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: buttonSize, transition: context.transition ) } else if incoming && !converted && !savedToProfile { let buttonTitle = savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display buttonChild = button.update( component: ButtonComponent( - background: ButtonComponent.Background( - color: theme.list.itemCheckColors.fillColor, - foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 - ), + background: buttonBackground, content: AnyComponentWithIdentity( id: AnyHashable("button"), 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: { component.updateSavedToProfile(!savedToProfile) }), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: buttonSize, transition: context.transition ) } else { buttonChild = button.update( component: ButtonComponent( - background: ButtonComponent.Background( - color: theme.list.itemCheckColors.fillColor, - foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 - ), + background: buttonBackground, content: AnyComponentWithIdentity( 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)))) @@ -1589,7 +1930,7 @@ private final class GiftViewSheetContent: CombinedComponent { action: { component.cancel(true) }), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: buttonSize, transition: context.transition ) } @@ -1625,9 +1966,10 @@ private final class GiftViewSheetComponent: CombinedComponent { let openMyGifts: () -> Void let transferGift: () -> Void let upgradeGift: ((Int64?, Bool) -> Signal) + let shareGift: () -> Void let viewUpgraded: (EngineMessage.Id) -> Void let openMore: (ASDisplayNode, ContextGesture?) -> Void - let showAttributeInfo: (Any, Float) -> Void + let showAttributeInfo: (Any, String) -> Void init( context: AccountContext, @@ -1640,9 +1982,10 @@ private final class GiftViewSheetComponent: CombinedComponent { openMyGifts: @escaping () -> Void, transferGift: @escaping () -> Void, upgradeGift: @escaping ((Int64?, Bool) -> Signal), + shareGift: @escaping () -> Void, viewUpgraded: @escaping (EngineMessage.Id) -> Void, openMore: @escaping (ASDisplayNode, ContextGesture?) -> Void, - showAttributeInfo: @escaping (Any, Float) -> Void + showAttributeInfo: @escaping (Any, String) -> Void ) { self.context = context self.subject = subject @@ -1654,6 +1997,7 @@ private final class GiftViewSheetComponent: CombinedComponent { self.openMyGifts = openMyGifts self.transferGift = transferGift self.upgradeGift = upgradeGift + self.shareGift = shareGift self.viewUpgraded = viewUpgraded self.openMore = openMore self.showAttributeInfo = showAttributeInfo @@ -1704,6 +2048,7 @@ private final class GiftViewSheetComponent: CombinedComponent { openMyGifts: context.component.openMyGifts, transferGift: context.component.transferGift, upgradeGift: context.component.upgradeGift, + shareGift: context.component.shareGift, showAttributeInfo: context.component.showAttributeInfo, viewUpgraded: context.component.viewUpgraded, openMore: context.component.openMore, @@ -1845,10 +2190,10 @@ public class GiftViewScreen: ViewControllerComponentContainer { var sendGiftImpl: ((EnginePeer.Id) -> Void)? var openMyGiftsImpl: (() -> Void)? var transferGiftImpl: (() -> Void)? - var showAttributeInfoImpl: ((Any, Float) -> Void)? var upgradeGiftImpl: ((Int64?, Bool) -> Signal)? var shareGiftImpl: (() -> Void)? var openMoreImpl: ((ASDisplayNode, ContextGesture?) -> Void)? + var showAttributeInfoImpl: ((Any, String) -> Void)? var viewUpgradedImpl: ((EngineMessage.Id) -> Void)? super.init( @@ -1880,14 +2225,17 @@ public class GiftViewScreen: ViewControllerComponentContainer { upgradeGift: { formId, keepOriginalInfo in return upgradeGiftImpl?(formId, keepOriginalInfo) ?? .complete() }, + shareGift: { + shareGiftImpl?() + }, viewUpgraded: { messageId in viewUpgradedImpl?(messageId) }, openMore: { node, gesture in openMoreImpl?(node, gesture) }, - showAttributeInfo: { tag, rarity in - showAttributeInfoImpl?(tag, rarity) + showAttributeInfo: { tag, text in + showAttributeInfoImpl?(tag, text) } ), 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 { 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 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 }) self.present(controller, in: .current) @@ -3042,3 +3390,147 @@ private final class GiftViewContextReferenceContentSource: ContextReferenceConte 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, 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, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift index 49c8b45f2e..1a2253e243 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift @@ -102,6 +102,7 @@ public final class PeerInfoCoverComponent: Component { public let avatarCenter: CGPoint public let avatarScale: CGFloat public let defaultHeight: CGFloat + public let gradientOnTop: Bool public let avatarTransitionFraction: CGFloat public let patternTransitionFraction: CGFloat @@ -113,6 +114,7 @@ public final class PeerInfoCoverComponent: Component { avatarCenter: CGPoint, avatarScale: CGFloat, defaultHeight: CGFloat, + gradientOnTop: Bool = false, avatarTransitionFraction: CGFloat, patternTransitionFraction: CGFloat ) { @@ -123,6 +125,7 @@ public final class PeerInfoCoverComponent: Component { self.avatarCenter = avatarCenter self.avatarScale = avatarScale self.defaultHeight = defaultHeight + self.gradientOnTop = gradientOnTop self.avatarTransitionFraction = avatarTransitionFraction self.patternTransitionFraction = patternTransitionFraction } @@ -149,6 +152,9 @@ public final class PeerInfoCoverComponent: Component { if lhs.defaultHeight != rhs.defaultHeight { return false } + if lhs.gradientOnTop != rhs.gradientOnTop { + return false + } if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction { return false } @@ -166,6 +172,7 @@ public final class PeerInfoCoverComponent: Component { private let avatarBackgroundGradientLayer: SimpleGradientLayer private let backgroundPatternContainer: UIView + private var currentSize: CGSize? private var component: PeerInfoCoverComponent? private var state: EmptyComponentState? @@ -180,6 +187,7 @@ public final class PeerInfoCoverComponent: Component { self.backgroundGradientLayer = SimpleGradientLayer() self.avatarBackgroundGradientLayer = SimpleGradientLayer() + self.avatarBackgroundGradientLayer.opacity = 0.0 let baseAvatarGradientAlpha: CGFloat = 0.4 let numSteps = 6 self.avatarBackgroundGradientLayer.colors = (0 ..< numSteps).map { i in @@ -221,13 +229,41 @@ public final class PeerInfoCoverComponent: Component { 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() { if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() { - gradientSnapshotLayer.frame = self.backgroundGradientLayer.frame - self.layer.insertSublayer(gradientSnapshotLayer, above: self.backgroundGradientLayer) - gradientSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in - gradientSnapshotLayer.removeFromSuperlayer() + let backgroundSnapshotLayer = SimpleLayer() + backgroundSnapshotLayer.allowsGroupOpacity = true + backgroundSnapshotLayer.backgroundColor = self.backgroundView.backgroundColor?.cgColor + 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 { 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, transition: ComponentTransition) -> CGSize { let previousComponent = self.component self.component = component + self.currentSize = availableSize + + if case .custom = component.subject { + self.layer.allowsGroupOpacity = true + } if previousComponent?.subject?.fileId != component.subject?.fileId { if let fileId = component.subject?.fileId, fileId != 0 { @@ -349,18 +390,18 @@ public final class PeerInfoCoverComponent: Component { secondaryBackgroundColor = .clear } - self.backgroundView.backgroundColor = secondaryBackgroundColor - + let gradientWidth: CGFloat + let gradientHeight: CGFloat = component.defaultHeight if case .custom = component.subject { - if availableSize.width < availableSize.height { - self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.25) - } else { - self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5) - } + gradientWidth = gradientHeight + self.backgroundView.backgroundColor = backgroundColor + self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: component.avatarCenter.y / gradientHeight) self.backgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) self.backgroundGradientLayer.type = .radial self.backgroundGradientLayer.colors = [secondaryBackgroundColor.cgColor, backgroundColor.cgColor] } else { + gradientWidth = availableSize.width + self.backgroundView.backgroundColor = secondaryBackgroundColor self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0) self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0) self.backgroundGradientLayer.type = .axial @@ -368,8 +409,7 @@ public final class PeerInfoCoverComponent: Component { } self.backgroundGradientLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0) - let gradientHeight: CGFloat = component.defaultHeight - let backgroundGradientFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - gradientHeight), size: CGSize(width: availableSize.width, height: gradientHeight)) + 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)) if !transition.animation.isImmediate { let previousPosition = self.backgroundGradientLayer.position 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)) 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 baseRowDistance: CGFloat = 28.0 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index f36aeb857b..162eac20a0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -166,6 +166,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)? var displayStatusPremiumIntro: (() -> Void)? + var displayUniqueGiftInfo: ((UIView, String) -> Void)? var navigateToForum: (() -> Void)? @@ -408,6 +409,16 @@ final class PeerInfoHeaderNode: ASDisplayNode { 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) { if let peer = self.peer, peer.profileImageRepresentations.isEmpty && gallery { self.requestOpenAvatarForEditing?(false) @@ -554,7 +565,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { credibilityIcon = .verified } if let verificationIconFileId = peer.verificationIconFileId { - verifiedIcon = .emojiStatus(PeerEmojiStatus(fileId: verificationIconFileId, expirationDate: nil)) + verifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil)) } } @@ -805,7 +816,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { 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) } - + let iconSize = self.titleCredibilityIconView.update( transition: ComponentTransition(navigationTransition), component: AnyComponent(EmojiStatusComponent( @@ -857,6 +868,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.currentStatusIcon = statusIcon var currentEmojiStatus: PeerEmojiStatus? + var particleColor: UIColor? + let emojiRegularStatusContent: EmojiStatusComponent.Content let emojiExpandedStatusContent: EmojiStatusComponent.Content switch statusIcon { @@ -864,11 +877,15 @@ final class PeerInfoHeaderNode: ASDisplayNode { 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) 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: emojiRegularStatusContent = .none emojiExpandedStatusContent = .none } - + let iconSize = self.titleStatusIconView.update( transition: ComponentTransition(navigationTransition), component: AnyComponent(EmojiStatusComponent( @@ -876,13 +893,18 @@ final class PeerInfoHeaderNode: ASDisplayNode { animationCache: self.animationCache, animationRenderer: self.animationRenderer, content: emojiRegularStatusContent, + particleColor: particleColor, isVisibleForAnimations: true, useSharedAnimation: true, action: { [weak self] in guard let strongSelf = self else { return } - strongSelf.displayPremiumIntro?(strongSelf.titleStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false) + if let _ = particleColor { + strongSelf.invokeDisplayGiftInfo() + } else { + strongSelf.displayPremiumIntro?(strongSelf.titleStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false) + } }, emojiFileUpdated: { [weak self] emojiFile in guard let strongSelf = self else { @@ -932,13 +954,18 @@ final class PeerInfoHeaderNode: ASDisplayNode { animationCache: self.animationCache, animationRenderer: self.animationRenderer, content: emojiExpandedStatusContent, + particleColor: particleColor, isVisibleForAnimations: true, useSharedAnimation: true, action: { [weak self] in guard let strongSelf = self else { return } - strongSelf.displayPremiumIntro?(strongSelf.titleExpandedStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true) + if let _ = particleColor { + strongSelf.invokeDisplayGiftInfo() + } else { + strongSelf.displayPremiumIntro?(strongSelf.titleExpandedStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true) + } } )), environment: {}, @@ -2217,11 +2244,22 @@ final class PeerInfoHeaderNode: ASDisplayNode { 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( transition: ComponentTransition(transition), component: AnyComponent(PeerInfoCoverComponent( context: self.context, - subject: peer.flatMap { .peer(EnginePeer($0)) }, + subject: backgroundCoverSubject, files: [:], isDark: presentationData.theme.overallDarkAppearance, avatarCenter: apparentAvatarFrame.center, @@ -2233,9 +2271,25 @@ final class PeerInfoHeaderNode: ASDisplayNode { environment: {}, 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 { 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 { transition.updateFrameAdditive(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: -3.0, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 33d0fe32c0..6a3bdbfc21 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -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 guard let self else { return diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 412be719b1..39d7b106c6 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -727,7 +727,7 @@ final class ChannelAppearanceScreenComponent: Component { } if let result { - self.updatedPeerStatus = PeerEmojiStatus(fileId: result.fileId.id, expirationDate: timestamp) + self.updatedPeerStatus = PeerEmojiStatus(content: .emoji(fileId: result.fileId.id), expirationDate: timestamp) } else { self.updatedPeerStatus = .some(nil) } diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Badge.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Badge.imageset/Contents.json new file mode 100644 index 0000000000..f6de9f866a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Badge.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "nft_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Badge.imageset/nft_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Badge.imageset/nft_30.pdf new file mode 100644 index 0000000000..a0dc08c6f2 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Badge.imageset/nft_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Proof.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Proof.imageset/Contents.json new file mode 100644 index 0000000000..9085cfbf7f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Proof.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "badge_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Proof.imageset/badge_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Proof.imageset/badge_30.pdf new file mode 100644 index 0000000000..54ce7132d8 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Proof.imageset/badge_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Share.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Share.imageset/Contents.json new file mode 100644 index 0000000000..20dff6d937 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Share.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "share_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Share.imageset/share_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Share.imageset/share_30.pdf new file mode 100644 index 0000000000..179fe9efeb Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Share.imageset/share_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Transfer.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Transfer.imageset/Contents.json new file mode 100644 index 0000000000..cb6acaef48 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Transfer.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "transfer_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Transfer.imageset/transfer_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Transfer.imageset/transfer_30.pdf new file mode 100644 index 0000000000..46e40d11a4 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Transfer.imageset/transfer_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unwear.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unwear.imageset/Contents.json new file mode 100644 index 0000000000..bb86e1900a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unwear.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "wearoff_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unwear.imageset/wearoff_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unwear.imageset/wearoff_30.pdf new file mode 100644 index 0000000000..f40382dabd Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unwear.imageset/wearoff_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wear.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wear.imageset/Contents.json new file mode 100644 index 0000000000..c3eef99c2e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wear.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "wear_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wear.imageset/wear_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wear.imageset/wear_30.pdf new file mode 100644 index 0000000000..ec2544a255 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Collectible/Wear.imageset/wear_30.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift index 7f2935dbe8..80ec66650f 100644 --- a/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift @@ -84,7 +84,7 @@ final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode { 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 description = verification.description diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index 58efffaa42..cc1f641314 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -600,7 +600,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } } - let textSize: CGSize + var textSize: CGSize var isTextWithEntities = false switch self.text { @@ -630,6 +630,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode { environment: {}, 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 { textSize = self.textView.update( transition: .immediate,