Update API [skip ci]

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

View File

@ -13651,6 +13651,21 @@ Sorry for the inconvenience.";
"Camera.OpenChat" = "Open Chat";
"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";

View File

@ -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
)

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -464,6 +464,21 @@ public extension Api.functions.account {
})
}
}
public extension Api.functions.account {
static func getCollectibleEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.EmojiStatuses>) {
let buffer = Buffer()
buffer.appendInt32(779830595)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.getCollectibleEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in
let reader = BufferReader(buffer)
var result: Api.account.EmojiStatuses?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses
}
return result
})
}
}
public extension Api.functions.account {
static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.ConnectedBots>) {
let buffer = Buffer()

View File

@ -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

View File

@ -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)

View File

@ -229,7 +229,7 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
}
|> mapToSignal { validatedResource -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> 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<PendingMessageUploadedContentResult, PendingMessageUploadError> 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<PendingMessageUploadedContentResult, PendingMessageUploadError> 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 {

View File

@ -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):

View File

@ -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<StandaloneUploadMediaEvent, StandaloneUploadMediaError> 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))))

View File

@ -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())

View File

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

View File

@ -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! {

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

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

View File

@ -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)

View File

@ -1405,7 +1405,7 @@ func _internal_deleteBotPreviews(account: Account, peerId: PeerId, language: Str
inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
inputMedia.append(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: []) }
}

View File

@ -2615,7 +2615,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
inputMedia.append(.inputMediaPhoto(flags: 0, id: .inputPhoto(id: resource.photoId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), ttlSeconds: nil))
inputMedia.append(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))
}
}

View File

@ -274,7 +274,7 @@ public enum UpdatePeerEmojiStatusError {
func _internal_updatePeerEmojiStatus(account: Account, peerId: PeerId, fileId: Int64?, expirationDate: Int32?) -> Signal<Never, UpdatePeerEmojiStatusError> {
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
}

View File

@ -83,11 +83,11 @@ func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResour
attributes.append(.documentAttributeVideo(flags: 0, duration: duration, w: dimensions.width, h: dimensions.height, preloadPrefixSize: nil, videoStartTs: nil, videoCodec: nil))
}
attributes.append(.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<UploadStickerStatus, UploadStickerError> 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) }) {

View File

@ -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))
}
}
}

View File

@ -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)
}
}

View File

@ -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):

View File

@ -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))

View File

@ -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()

View File

@ -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,

View File

@ -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",
],

View File

@ -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<Empty>()
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)
}
}

View File

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

View File

@ -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<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
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<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
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<Empty>.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<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: "badge",
component: AnyComponent(ParagraphComponent(
title: strings.Gift_Wear_Badge_Title,
titleColor: textColor,
text: strings.Gift_Wear_Badge_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/Collectible/Badge",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "design",
component: AnyComponent(ParagraphComponent(
title: strings.Gift_Wear_Design_Title,
titleColor: textColor,
text: strings.Gift_Wear_Design_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/BoostPerk/CoverColor",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "proof",
component: AnyComponent(ParagraphComponent(
title: strings.Gift_Wear_Proof_Title,
titleColor: textColor,
text: strings.Gift_Wear_Proof_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/Collectible/Proof",
iconColor: linkColor
))
)
)
let perksSideInset = sideInset + 16.0
let wearPerks = wearPerks.update(
component: List(items),
availableSize: CGSize(width: context.availableSize.width - perksSideInset * 2.0, height: 10000.0),
transition: context.transition
)
context.add(wearPerks
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + wearPerks.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += wearPerks.size.height
originY += 16.0
} else if showUpgradePreview {
let title: String
let 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<Empty>
if let _ = subject.arguments?.transferStars {
if peer.id == component.context.account.peerId, peer.isPremium {
let animationContent: EmojiStatusComponent.Content
var color: UIColor?
if state.pendingWear {
var fileId: Int64?
for attribute in uniqueGift.attributes {
if case let .model(_, file, _) = attribute {
fileId = file.fileId.id
}
if case let .backdrop(_, innerColor, _, _, _, _) = attribute {
color = UIColor(rgb: UInt32(bitPattern: innerColor))
}
}
if let fileId {
animationContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: tableLinkColor, loopMode: .count(2))
} else {
animationContent = .premium(color: tableLinkColor)
}
} else if let emojiStatus = peer.emojiStatus, !state.pendingTakeOff {
animationContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: tableLinkColor, loopMode: .count(2))
if case let .starGift(id, _, _, _, _, innerColor, _, _, _) = emojiStatus.content {
color = UIColor(rgb: UInt32(bitPattern: innerColor))
if id == uniqueGift.id {
isWearing = true
state.pendingWear = false
}
}
} else {
animationContent = .premium(color: tableLinkColor)
state.pendingTakeOff = false
}
ownerComponent = AnyComponent(
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<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
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<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
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<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)?
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<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
self.avatarNode.frame = CGRect(origin: .zero, size: availableSize)
self.avatarNode.setPeer(
context: component.context,
theme: component.theme,
peer: component.peer,
synchronousLoad: true
)
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -102,6 +102,7 @@ public final class PeerInfoCoverComponent: Component {
public let avatarCenter: CGPoint
public let 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<Empty>, 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

View File

@ -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))

View File

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

View File

@ -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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -84,7 +84,7 @@ final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
let _ = ApplicationSpecificNotice.setDisplayedPeerVerification(accountManager: self.context.sharedContext.accountManager, peerId: peer.id).start()
}
let 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

View File

@ -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,