Update API [skip ci]

This commit is contained in:
Ilya Laktyushin 2024-12-27 09:16:41 +04:00
parent d64208df43
commit e3ee1dde7e
31 changed files with 1041 additions and 459 deletions

View File

@ -13469,15 +13469,21 @@ Sorry for the inconvenience.";
"Gift.View.Status.NonUnique" = "Non-Unique"; "Gift.View.Status.NonUnique" = "Non-Unique";
"Gift.View.Status.Upgrade" = "upgrade"; "Gift.View.Status.Upgrade" = "upgrade";
"Gift.View.DisplayedInfoHide" = "The gift is visible on your Page. [Hide >]()"; "Gift.View.DisplayedInfoHide" = "The gift is visible on your Page. [Hide >]()";
"Gift.View.HiddenInfoShow" = "This gift is hidden. Only you can see it. [Show >]()";
"Gift.Upgrade.Title" = "Upgrade Gift"; "Gift.Upgrade.Title" = "Upgrade Gift";
"Gift.Upgrade.IncludeTitle" = "Make Unique";
"Gift.Upgrade.Description" = "Turn your gift into a unique collectible that you can transfer or auction."; "Gift.Upgrade.Description" = "Turn your gift into a unique collectible that you can transfer or auction.";
"Gift.Upgrade.IncludeDescription" = "Let %@ turn your gift into a unique collectible.";
"Gift.Upgrade.Unique.Title" = "Unique"; "Gift.Upgrade.Unique.Title" = "Unique";
"Gift.Upgrade.Unique.Description" = "Get a unique number, model backdrop and and symbol for your gift."; "Gift.Upgrade.Unique.Description" = "Get a unique number, model, backdrop, and symbol for your gift.";
"Gift.Upgrade.Unique.IncludeDescription" = "The recipient will get a unique number, model, backdrop, and symbol for the gift.";
"Gift.Upgrade.Transferable.Title" = "Transferable"; "Gift.Upgrade.Transferable.Title" = "Transferable";
"Gift.Upgrade.Transferable.Description" = "Send your upgraded gift to any of your friends on Telegram."; "Gift.Upgrade.Transferable.Description" = "Send your upgraded gift to any of your friends on Telegram.";
"Gift.Upgrade.Transferable.IncludeDescription" = "The recipient will be able to send the gift to anyone Telegram.";
"Gift.Upgrade.Tradable.Title" = "Tradable"; "Gift.Upgrade.Tradable.Title" = "Tradable";
"Gift.Upgrade.Tradable.Description" = "Sell or auction your gift on third-party NFT marketplaces."; "Gift.Upgrade.Tradable.Description" = "Sell or auction your gift on third-party NFT marketplaces.";
"Gift.Upgrade.Tradable.IncludeDescription" = "The recipient will be able to auction the gift on third-party NFT marketplaces.";
"Gift.Upgrade.Soon" = "SOON"; "Gift.Upgrade.Soon" = "SOON";
"Gift.Upgrade.AddName" = "Add sender's name"; "Gift.Upgrade.AddName" = "Add sender's name";
"Gift.Upgrade.AddNameAndComment" = "Add sender's name and comment"; "Gift.Upgrade.AddNameAndComment" = "Add sender's name and comment";
@ -13525,6 +13531,7 @@ Sorry for the inconvenience.";
"Gift.Transfer.Confirmation.Transfer" = "Transfer for"; "Gift.Transfer.Confirmation.Transfer" = "Transfer for";
"Gift.Transfer.Confirmation.TransferFree" = "Transfer"; "Gift.Transfer.Confirmation.TransferFree" = "Transfer";
"Gift.View.UpgradeForFree" = "Upgrade for Free";
"Gift.View.KeepUpgradeOrConvertDescription" = "You can keep this gift, upgrade it, or sell it for %@. [More About Stars >]()"; "Gift.View.KeepUpgradeOrConvertDescription" = "You can keep this gift, upgrade it, or sell it for %@. [More About Stars >]()";
"PeerInfo.VerificationInfo.Bot" = "This bot is verified as official by the representatives of Telegram."; "PeerInfo.VerificationInfo.Bot" = "This bot is verified as official by the representatives of Telegram.";
@ -13537,8 +13544,21 @@ Sorry for the inconvenience.";
"Gift.Send.Upgrade" = "Make Unique for %@"; "Gift.Send.Upgrade" = "Make Unique for %@";
"Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()"; "Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()";
"Notification.StarGift.Unpack" = "Unpack";
"Notification.StarGift.Model" = "Model";
"Notification.StarGift.Backdrop" = "Backdrop";
"Notification.StarGift.Symbol" = "Symbol";
"Notification.StarGift.Gift" = "gift";
"Notification.StarsGift.Upgrade" = "%@ turned the gift from you to a unique collectible"; "Notification.StarsGift.Upgrade" = "%@ turned the gift from you to a unique collectible";
"Notification.StarsGift.UpgradeYou" = "You turned the gift from %@ to a unique collectible"; "Notification.StarsGift.UpgradeYou" = "You turned the gift from %@ to a unique collectible";
"Notification.StarsGift.Transfer" = "%@ transferred you a unique collectible"; "Notification.StarsGift.Transfer" = "%@ transferred you a unique collectible";
"Notification.StarsGift.TransferYou" = "You transferred a unique collectible"; "Notification.StarsGift.TransferYou" = "You transferred a unique collectible";
"Notification.StarGift.Subtitle.Refunded" = "This gift cannot be converted to Stars because the payment related to it was refunded.";
"Notification.StarGift.Subtitle.Downgraded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
"Gift.View.KeepOrUpgradeDescription" = "You can keep this gift or upgrade it.";

View File

@ -3107,7 +3107,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
var titleLeftOffset: CGFloat = 0.0 var titleLeftOffset: CGFloat = 0.0
if let currentVerifiedIconContent { if let currentVerifiedIconContent {
if titleLeftOffset.isZero, currentVerifiedIconContent != .none { if titleLeftOffset.isZero, case .animation = currentVerifiedIconContent {
titleLeftOffset += 20.0 titleLeftOffset += 20.0
} }
@ -3127,10 +3127,6 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
if let currentCredibilityIconContent { if let currentCredibilityIconContent {
if titleLeftOffset.isZero, case .verified = currentCredibilityIconContent {
titleLeftOffset += 20.0
}
if titleIconsWidth.isZero { if titleIconsWidth.isZero {
titleIconsWidth += 4.0 titleIconsWidth += 4.0
} else { } else {
@ -4500,11 +4496,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
) )
strongSelf.credibilityIconComponent = credibilityIconComponent strongSelf.credibilityIconComponent = credibilityIconComponent
var iconOrigin: CGFloat = nextTitleIconOrigin let iconOrigin: CGFloat = nextTitleIconOrigin
let containerSize = CGSize(width: 20.0, height: 20.0) let containerSize = CGSize(width: 20.0, height: 20.0)
if case .verified = currentCredibilityIconContent {
iconOrigin = contentRect.origin.x
}
let iconSize = credibilityIconView.update( let iconSize = credibilityIconView.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(credibilityIconComponent), component: AnyComponent(credibilityIconComponent),
@ -4512,10 +4505,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
containerSize: containerSize containerSize: containerSize
) )
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: iconOrigin, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - iconSize.height / 2.0) - UIScreenPixel), size: iconSize)) transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: iconOrigin, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - iconSize.height / 2.0) - UIScreenPixel), size: iconSize))
if case .verified = currentCredibilityIconContent { nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0
} else {
nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0
}
} else if let credibilityIconView = strongSelf.credibilityIconView { } else if let credibilityIconView = strongSelf.credibilityIconView {
strongSelf.credibilityIconView = nil strongSelf.credibilityIconView = nil
credibilityIconView.removeFromSuperview() credibilityIconView.removeFromSuperview()
@ -4541,7 +4531,12 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
) )
strongSelf.verifiedIconComponent = verifiedIconComponent strongSelf.verifiedIconComponent = verifiedIconComponent
let iconOrigin = contentRect.origin.x let iconOrigin: CGFloat
if case .animation = currentVerifiedIconContent {
iconOrigin = contentRect.origin.x
} else {
iconOrigin = nextTitleIconOrigin
}
let containerSize = CGSize(width: 16.0, height: 16.0) let containerSize = CGSize(width: 16.0, height: 16.0)
let iconSize = verifiedIconView.update( let iconSize = verifiedIconView.update(

View File

@ -582,77 +582,6 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? {
view.cornerRadius = layer.cornerRadius view.cornerRadius = layer.cornerRadius
view.backgroundColor = layer.backgroundColor view.backgroundColor = layer.backgroundColor
view.layerTintColor = layer.layerTintColor view.layerTintColor = layer.layerTintColor
/*
open var path: CGPath?
/* The color to fill the path, or nil for no fill. Defaults to opaque
* black. Animatable. */
open var fillColor: CGColor?
/* The fill rule used when filling the path. Options are `non-zero' and
* `even-odd'. Defaults to `non-zero'. */
open var fillRule: CAShapeLayerFillRule
/* The color to fill the path's stroked outline, or nil for no stroking.
* Defaults to nil. Animatable. */
open var strokeColor: CGColor?
/* These values define the subregion of the path used to draw the
* stroked outline. The values must be in the range [0,1] with zero
* representing the start of the path and one the end. Values in
* between zero and one are interpolated linearly along the path
* length. strokeStart defaults to zero and strokeEnd to one. Both are
* animatable. */
open var strokeStart: CGFloat
open var strokeEnd: CGFloat
/* The line width used when stroking the path. Defaults to one.
* Animatable. */
open var lineWidth: CGFloat
/* The miter limit used when stroking the path. Defaults to ten.
* Animatable. */
open var miterLimit: CGFloat
/* The cap style used when stroking the path. Options are `butt', `round'
* and `square'. Defaults to `butt'. */
open var lineCap: CAShapeLayerLineCap
/* The join style used when stroking the path. Options are `miter', `round'
* and `bevel'. Defaults to `miter'. */
open var lineJoin: CAShapeLayerLineJoin
/* The phase of the dashing pattern applied when creating the stroke.
* Defaults to zero. Animatable. */
open var lineDashPhase: CGFloat
/* The dash pattern (an array of NSNumbers) applied when creating the
* stroked version of the path. Defaults to nil. */
open var lineDashPattern: [NSNumber]?
*/
view.path = layer.path view.path = layer.path
view.fillColor = layer.fillColor view.fillColor = layer.fillColor
view.fillRule = layer.fillRule view.fillRule = layer.fillRule
@ -666,6 +595,40 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? {
view.lineDashPhase = layer.lineDashPhase view.lineDashPhase = layer.lineDashPhase
view.lineDashPattern = layer.lineDashPattern view.lineDashPattern = layer.lineDashPattern
if let sublayers = layer.sublayers {
for sublayer in sublayers {
let subtree = makeLayerSubtreeSnapshot(layer: sublayer)
if let subtree = subtree {
subtree.transform = sublayer.transform
subtree.position = sublayer.position
subtree.bounds = sublayer.bounds
subtree.anchorPoint = sublayer.anchorPoint
view.addSublayer(subtree)
} else {
return nil
}
}
}
return view
} else if let layer = layer as? CAGradientLayer {
let view = CAGradientLayer()
view.isHidden = layer.isHidden
view.opacity = layer.opacity
view.contents = layer.contents
view.contentsRect = layer.contentsRect
view.contentsScale = layer.contentsScale
view.contentsCenter = layer.contentsCenter
view.contentsGravity = layer.contentsGravity
view.masksToBounds = layer.masksToBounds
view.cornerRadius = layer.cornerRadius
view.backgroundColor = layer.backgroundColor
view.layerTintColor = layer.layerTintColor
view.colors = layer.colors
view.locations = layer.locations
view.startPoint = layer.startPoint
view.endPoint = layer.endPoint
view.type = layer.type
if let sublayers = layer.sublayers { if let sublayers = layer.sublayers {
for sublayer in sublayers { for sublayer in sublayers {
let subtree = makeLayerSubtreeSnapshot(layer: sublayer) let subtree = makeLayerSubtreeSnapshot(layer: sublayer)

View File

@ -47,6 +47,7 @@ final class DrawingMetalView: MTKView {
super.init(frame: CGRect(origin: .zero, size: size), device: device) super.init(frame: CGRect(origin: .zero, size: size), device: device)
self.drawableSize = self.size self.drawableSize = self.size
self.colorPixelFormat = .bgra8Unorm
self.autoResizeDrawable = false self.autoResizeDrawable = false
self.isOpaque = false self.isOpaque = false
self.contentScaleFactor = 1.0 self.contentScaleFactor = 1.0
@ -123,7 +124,7 @@ final class DrawingMetalView: MTKView {
let pipelineDescription = MTLRenderPipelineDescriptor() let pipelineDescription = MTLRenderPipelineDescriptor()
pipelineDescription.vertexFunction = vertexFunction pipelineDescription.vertexFunction = vertexFunction
pipelineDescription.fragmentFunction = fragmentFunction pipelineDescription.fragmentFunction = fragmentFunction
pipelineDescription.colorAttachments[0].pixelFormat = colorPixelFormat pipelineDescription.colorAttachments[0].pixelFormat = self.colorPixelFormat
do { do {
self.pipelineState = try self.device?.makeRenderPipelineState(descriptor: pipelineDescription) self.pipelineState = try self.device?.makeRenderPipelineState(descriptor: pipelineDescription)
@ -250,6 +251,7 @@ private class Drawable {
attachment?.texture = self.texture?.texture attachment?.texture = self.texture?.texture
attachment?.loadAction = .load attachment?.loadAction = .load
attachment?.storeAction = .store attachment?.storeAction = .store
attachment?.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
self.updateBuffer(with: size) self.updateBuffer(with: size)
} }
@ -288,7 +290,6 @@ private class Drawable {
return commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) return commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
} }
internal func commit(wait: Bool = false) { internal func commit(wait: Bool = false) {
self.commandBuffer?.commit() self.commandBuffer?.commit()
if wait { if wait {
@ -673,9 +674,9 @@ final class Texture {
origin: MTLOrigin(x: 0, y: 0, z: 0), origin: MTLOrigin(x: 0, y: 0, z: 0),
size: MTLSize(width: self.width, height: self.height, depth: 1) size: MTLSize(width: self.width, height: self.height, depth: 1)
) )
let data = Data(capacity: Int(self.bytesPerRow * self.height)) let zeroData = [UInt8](repeating: 0, count: self.bytesPerRow * self.height)
if let bytes = data.withUnsafeBytes({ $0.baseAddress }) { zeroData.withUnsafeBytes { bytes in
self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: self.bytesPerRow) self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes.baseAddress!, bytesPerRow: self.bytesPerRow)
} }
} }

View File

@ -583,7 +583,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) }
dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) }
dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
dict[1785072017] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[-655036249] = { return Api.MessageAction.parse_messageActionStarGift($0) }
dict[638024601] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[638024601] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) }
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }

View File

@ -373,7 +373,7 @@ public extension Api {
case messageActionSetChatTheme(emoticon: String) case messageActionSetChatTheme(emoticon: String)
case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper)
case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?)
case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeStars: Int64?) case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?)
case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?) case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?)
case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionSuggestProfilePhoto(photo: Api.Photo)
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
@ -720,14 +720,15 @@ public extension Api {
serializeInt32(period, buffer: buffer, boxed: false) serializeInt32(period, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)}
break break
case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeStars): case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeMsgId, let upgradeStars):
if boxed { if boxed {
buffer.appendInt32(1785072017) buffer.appendInt32(-655036249)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
gift.serialize(buffer, true) gift.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 5) != 0 {serializeInt32(upgradeMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 8) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)}
break break
case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars): case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars):
@ -864,8 +865,8 @@ public extension Api {
return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)]) return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)])
case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom):
return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)])
case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeStars): case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeMsgId, let upgradeStars):
return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeStars", upgradeStars as Any)]) return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeMsgId", upgradeMsgId as Any), ("upgradeStars", upgradeStars as Any)])
case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars): case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars):
return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any)]) return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any)])
case .messageActionSuggestProfilePhoto(let photo): case .messageActionSuggestProfilePhoto(let photo):
@ -1528,15 +1529,18 @@ public extension Api {
} } } }
var _4: Int64? var _4: Int64?
if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() } if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() }
var _5: Int64? var _5: Int32?
if Int(_1!) & Int(1 << 8) != 0 {_5 = reader.readInt64() } if Int(_1!) & Int(1 << 5) != 0 {_5 = reader.readInt32() }
var _6: Int64?
if Int(_1!) & Int(1 << 8) != 0 {_6 = reader.readInt64() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 { let _c6 = (Int(_1!) & Int(1 << 8) == 0) || _6 != nil
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeStars: _5) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeMsgId: _5, upgradeStars: _6)
} }
else { else {
return nil return nil

View File

@ -9463,6 +9463,25 @@ public extension Api.functions.payments {
}) })
} }
} }
public extension Api.functions.payments {
static func getUserStarGift(msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.UserStarGifts>) {
let buffer = Buffer()
buffer.appendInt32(-1258101595)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(msgId.count))
for item in msgId {
serializeInt32(item, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "payments.getUserStarGift", parameters: [("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UserStarGifts? in
let reader = BufferReader(buffer)
var result: Api.payments.UserStarGifts?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.payments.UserStarGifts
}
return result
})
}
}
public extension Api.functions.payments { public extension Api.functions.payments {
static func getUserStarGifts(userId: Api.InputUser, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.UserStarGifts>) { static func getUserStarGifts(userId: Api.InputUser, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.UserStarGifts>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -132,7 +132,6 @@ enum AccountStateMutationOperation {
case UpdateStarsBalance(peerId: PeerId, balance: Api.StarsAmount) case UpdateStarsBalance(peerId: PeerId, balance: Api.StarsAmount)
case UpdateStarsRevenueStatus(peerId: PeerId, status: StarsRevenueStats.Balances) case UpdateStarsRevenueStatus(peerId: PeerId, status: StarsRevenueStats.Balances)
case UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: Bool) case UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: Bool)
case UpdateUpgradedStarGift(from: Api.UserStarGift, to: Api.UserStarGift)
} }
struct HoleFromPreviousState { struct HoleFromPreviousState {
@ -703,13 +702,9 @@ struct AccountMutableState {
self.addOperation(.UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: isAnonymous)) self.addOperation(.UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: isAnonymous))
} }
mutating func updateUpgradedStarGift(from: Api.UserStarGift, to: Api.UserStarGift) {
self.addOperation(.UpdateUpgradedStarGift(from: from, to: to))
}
mutating func addOperation(_ operation: AccountStateMutationOperation) { mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault, .UpdateUpgradedStarGift: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault:
break break
case let .AddMessages(messages, location): case let .AddMessages(messages, location):
for message in messages { for message in messages {
@ -856,7 +851,6 @@ struct AccountReplayedFinalState {
let updatedRevenueBalances: [PeerId: RevenueStats.Balances] let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
let updatedStarsBalance: [PeerId: StarsAmount] let updatedStarsBalance: [PeerId: StarsAmount]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let updatedUpgradedStarGifts:[(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)]
let sentScheduledMessageIds: Set<MessageId> let sentScheduledMessageIds: Set<MessageId>
} }
@ -888,13 +882,12 @@ struct AccountFinalStateEvents {
let updatedRevenueBalances: [PeerId: RevenueStats.Balances] let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
let updatedStarsBalance: [PeerId: StarsAmount] let updatedStarsBalance: [PeerId: StarsAmount]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let updatedUpgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)]
var isEmpty: Bool { var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.updatedUpgradedStarGifts.isEmpty return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty
} }
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), updatedUpgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)] = []) { init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set()) {
self.addedIncomingMessageIds = addedIncomingMessageIds self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds
@ -921,7 +914,6 @@ struct AccountFinalStateEvents {
self.updatedRevenueBalances = updatedRevenueBalances self.updatedRevenueBalances = updatedRevenueBalances
self.updatedStarsBalance = updatedStarsBalance self.updatedStarsBalance = updatedStarsBalance
self.updatedStarsRevenueStatus = updatedStarsRevenueStatus self.updatedStarsRevenueStatus = updatedStarsRevenueStatus
self.updatedUpgradedStarGifts = updatedUpgradedStarGifts
self.sentScheduledMessageIds = sentScheduledMessageIds self.sentScheduledMessageIds = sentScheduledMessageIds
} }
@ -952,7 +944,6 @@ struct AccountFinalStateEvents {
self.updatedRevenueBalances = state.updatedRevenueBalances self.updatedRevenueBalances = state.updatedRevenueBalances
self.updatedStarsBalance = state.updatedStarsBalance self.updatedStarsBalance = state.updatedStarsBalance
self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus
self.updatedUpgradedStarGifts = state.updatedUpgradedStarGifts
self.sentScheduledMessageIds = state.sentScheduledMessageIds self.sentScheduledMessageIds = state.sentScheduledMessageIds
} }
@ -986,6 +977,6 @@ struct AccountFinalStateEvents {
var sentScheduledMessageIds = self.sentScheduledMessageIds var sentScheduledMessageIds = self.sentScheduledMessageIds
sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds) sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds)
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds, updatedUpgradedStarGifts: self.updatedUpgradedStarGifts + other.updatedUpgradedStarGifts) return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds)
} }
} }

View File

@ -171,7 +171,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId)) return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId))
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId): case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId))) return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
case let .messageActionStarGift(flags, apiGift, message, convertStars, upgradeStars): case let .messageActionStarGift(flags, apiGift, message, convertStars, _, upgradeStars):
let text: String? let text: String?
let entities: [MessageTextEntity]? let entities: [MessageTextEntity]?
switch message { switch message {
@ -185,12 +185,12 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
guard let gift = StarGift(apiStarGift: apiGift) else { guard let gift = StarGift(apiStarGift: apiGift) else {
return nil return nil
} }
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, upgradeStars: upgradeStars)) return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0))
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars): case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars):
guard let gift = StarGift(apiStarGift: apiGift) else { guard let gift = StarGift(apiStarGift: apiGift) else {
return nil return nil
} }
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars)) return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0))
} }
} }

View File

@ -3282,7 +3282,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddQuickReplyMessages: OptimizeAddMessagesState? var currentAddQuickReplyMessages: OptimizeAddMessagesState?
for operation in operations { for operation in operations {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault, .UpdateUpgradedStarGift: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
} }
@ -3421,7 +3421,6 @@ func replayFinalState(
var updatedStarsBalance: [PeerId: StarsAmount] = [:] var updatedStarsBalance: [PeerId: StarsAmount] = [:]
var updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:] var updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:]
var updatedStarsReactionsAreAnonymousByDefault: Bool? var updatedStarsReactionsAreAnonymousByDefault: Bool?
var updatedUpgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)] = []
var holesFromPreviousStateMessageIds: [MessageId] = [] var holesFromPreviousStateMessageIds: [MessageId] = []
var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:]
@ -4856,10 +4855,6 @@ func replayFinalState(
updatedStarsRevenueStatus[peerId] = status updatedStarsRevenueStatus[peerId] = status
case let .UpdateStarsReactionsAreAnonymousByDefault(value): case let .UpdateStarsReactionsAreAnonymousByDefault(value):
updatedStarsReactionsAreAnonymousByDefault = value updatedStarsReactionsAreAnonymousByDefault = value
case let .UpdateUpgradedStarGift(from, to):
if let fromGift = ProfileGiftsContext.State.StarGift(apiUserStarGift: from, transaction: transaction), let toGift = ProfileGiftsContext.State.StarGift(apiUserStarGift: to, transaction: transaction) {
updatedUpgradedStarGifts.append((fromGift, toGift))
}
} }
} }
@ -5381,7 +5376,6 @@ func replayFinalState(
updatedRevenueBalances: updatedRevenueBalances, updatedRevenueBalances: updatedRevenueBalances,
updatedStarsBalance: updatedStarsBalance, updatedStarsBalance: updatedStarsBalance,
updatedStarsRevenueStatus: updatedStarsRevenueStatus, updatedStarsRevenueStatus: updatedStarsRevenueStatus,
updatedUpgradedStarGifts: updatedUpgradedStarGifts,
sentScheduledMessageIds: finalState.state.sentScheduledMessageIds sentScheduledMessageIds: finalState.state.sentScheduledMessageIds
) )
} }

View File

@ -56,10 +56,6 @@ private final class UpdatedStarsRevenueStatusSubscriberContext {
let subscribers = Bag<([PeerId: StarsRevenueStats.Balances]) -> Void>() let subscribers = Bag<([PeerId: StarsRevenueStats.Balances]) -> Void>()
} }
private final class UpgradedStarGiftsSubscriberContext {
let subscribers = Bag<([(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)]) -> Void>()
}
public enum DeletedMessageId: Hashable { public enum DeletedMessageId: Hashable {
case global(Int32) case global(Int32)
case messageId(MessageId) case messageId(MessageId)
@ -350,7 +346,6 @@ public final class AccountStateManager {
private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext() private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext()
private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext() private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext()
private var updatedStarsRevenueStatusContext = UpdatedStarsRevenueStatusSubscriberContext() private var updatedStarsRevenueStatusContext = UpdatedStarsRevenueStatusSubscriberContext()
private var upgradedStarGiftsContext = UpgradedStarGiftsSubscriberContext()
private let delayNotificatonsUntil = Atomic<Int32?>(value: nil) private let delayNotificatonsUntil = Atomic<Int32?>(value: nil)
private let appliedMaxMessageIdPromise = Promise<Int32?>(nil) private let appliedMaxMessageIdPromise = Promise<Int32?>(nil)
@ -1113,9 +1108,6 @@ public final class AccountStateManager {
if !events.updatedStarsRevenueStatus.isEmpty { if !events.updatedStarsRevenueStatus.isEmpty {
strongSelf.notifyUpdatedStarsRevenueStatus(events.updatedStarsRevenueStatus) strongSelf.notifyUpdatedStarsRevenueStatus(events.updatedStarsRevenueStatus)
} }
if !events.updatedUpgradedStarGifts.isEmpty {
strongSelf.notifyUpgradedStarGifts(events.updatedUpgradedStarGifts)
}
if !events.updatedCalls.isEmpty { if !events.updatedCalls.isEmpty {
for call in events.updatedCalls { for call in events.updatedCalls {
strongSelf.callSessionManager?.updateSession(call, completion: { _ in }) strongSelf.callSessionManager?.updateSession(call, completion: { _ in })
@ -1775,33 +1767,6 @@ public final class AccountStateManager {
} }
} }
public func upgradedStarGifts() -> Signal<[(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)], NoError> {
let queue = self.queue
return Signal { [weak self] subscriber in
let disposable = MetaDisposable()
queue.async {
if let strongSelf = self {
let index = strongSelf.upgradedStarGiftsContext.subscribers.add({ upgradedGifts in
subscriber.putNext(upgradedGifts)
})
disposable.set(ActionDisposable {
if let strongSelf = self {
strongSelf.upgradedStarGiftsContext.subscribers.remove(index)
}
})
}
}
return disposable
}
}
private func notifyUpgradedStarGifts(_ upgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)]) {
for subscriber in self.upgradedStarGiftsContext.subscribers.copyItems() {
subscriber(upgradedStarGifts)
}
}
func notifyDeletedMessages(messageIds: [MessageId]) { func notifyDeletedMessages(messageIds: [MessageId]) {
self.deletedMessagesPipe.putNext(messageIds.map { .messageId($0) }) self.deletedMessagesPipe.putNext(messageIds.map { .messageId($0) })
} }
@ -2143,12 +2108,6 @@ public final class AccountStateManager {
} }
} }
public func upgradedStarGifts() -> Signal<[(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)], NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.upgradedStarGifts().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
func addCustomOperation<T, E>(_ f: Signal<T, E>) -> Signal<T, E> { func addCustomOperation<T, E>(_ f: Signal<T, E>) -> Signal<T, E> {
return self.impl.signalWith { impl, subscriber in return self.impl.signalWith { impl, subscriber in
return impl.addCustomOperation(f).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) return impl.addCustomOperation(f).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)

View File

@ -137,6 +137,7 @@ public struct Namespaces {
public static let recommendedApps: Int8 = 40 public static let recommendedApps: Int8 = 40
public static let starsReactionDefaultToPrivate: Int8 = 41 public static let starsReactionDefaultToPrivate: Int8 = 41
public static let cachedPremiumGiftCodeOptions: Int8 = 42 public static let cachedPremiumGiftCodeOptions: Int8 = 42
public static let cachedProfileGifts: Int8 = 43
} }
public struct UnorderedItemList { public struct UnorderedItemList {

View File

@ -130,8 +130,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String) case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String)
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?) case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, upgradeStars: Int64?) case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool)
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?) case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool)
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -253,9 +253,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} }
self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId) self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId)
case 44: case 44:
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars")) self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false))
case 45: case 45:
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars")) self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false))
default: default:
self = .unknown self = .unknown
} }
@ -548,7 +548,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "giveawayMsgId") encoder.encodeNil(forKey: "giveawayMsgId")
} }
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, upgradeStars): case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded):
encoder.encodeInt32(44, forKey: "_rawValue") encoder.encodeInt32(44, forKey: "_rawValue")
encoder.encodeObject(gift, forKey: "gift") encoder.encodeObject(gift, forKey: "gift")
if let convertStars { if let convertStars {
@ -567,12 +567,14 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
encoder.encodeBool(savedToProfile, forKey: "savedToProfile") encoder.encodeBool(savedToProfile, forKey: "savedToProfile")
encoder.encodeBool(converted, forKey: "converted") encoder.encodeBool(converted, forKey: "converted")
encoder.encodeBool(upgraded, forKey: "upgraded") encoder.encodeBool(upgraded, forKey: "upgraded")
encoder.encodeBool(canUpgrade, forKey: "canUpgrade")
if let upgradeStars { if let upgradeStars {
encoder.encodeInt64(upgradeStars, forKey: "upgradeStars") encoder.encodeInt64(upgradeStars, forKey: "upgradeStars")
} else { } else {
encoder.encodeNil(forKey: "upgradeStars") encoder.encodeNil(forKey: "upgradeStars")
} }
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars): encoder.encodeBool(isRefunded, forKey: "isRefunded")
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded):
encoder.encodeInt32(45, forKey: "_rawValue") encoder.encodeInt32(45, forKey: "_rawValue")
encoder.encodeObject(gift, forKey: "gift") encoder.encodeObject(gift, forKey: "gift")
encoder.encodeBool(isUpgrade, forKey: "isUpgrade") encoder.encodeBool(isUpgrade, forKey: "isUpgrade")
@ -588,6 +590,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "transferStars") encoder.encodeNil(forKey: "transferStars")
} }
encoder.encodeBool(isRefunded, forKey: "isRefunded")
} }
} }

View File

@ -707,8 +707,17 @@ public enum UpgradeStarGiftError {
case generic case generic
} }
func _internal_upgradeStarGift(account: Account, prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> { func _internal_upgradeStarGift(account: Account, formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
if prepaid { if let formId {
let source: BotPaymentInvoiceSource = .starGiftUpgrade(keepOriginalInfo: keepOriginalInfo, messageId: messageId)
return _internal_sendStarsPaymentForm(account: account, formId: formId, source: source)
|> mapError { _ -> UpgradeStarGiftError in
return .generic
}
|> mapToSignal { _ in
return .complete()
}
} else {
var flags: Int32 = 0 var flags: Int32 = 0
if keepOriginalInfo { if keepOriginalInfo {
flags |= (1 << 0) flags |= (1 << 0)
@ -719,57 +728,35 @@ func _internal_upgradeStarGift(account: Account, prepaid: Bool, messageId: Engin
} }
|> mapToSignal { updates in |> mapToSignal { updates in
account.stateManager.addUpdates(updates) account.stateManager.addUpdates(updates)
for update in updates.allUpdates {
return account.stateManager.upgradedStarGifts() switch update {
|> castError(UpgradeStarGiftError.self) case let .updateNewMessage(message, _, _):
|> take(until: { updates in if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
for update in updates { for media in message.media {
if update.0.messageId == messageId { if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _) = action.action, case let .Id(messageId) = message.id {
return .init(passthrough: true, complete: true) return .single(ProfileGiftsContext.State.StarGift(
} gift: gift,
} fromPeer: nil,
return .init(passthrough: false, complete: false) date: message.timestamp,
}) text: nil,
|> mapToSignal { updates in entities: nil,
for update in updates { messageId: messageId,
if update.0.messageId == messageId { nameHidden: false,
return .single(update.1) savedToProfile: savedToProfile,
} convertStars: nil,
} canUpgrade: false,
return .complete() canExportDate: canExportDate,
} upgradeStars: nil,
} transferStars: transferStars
} else { ))
let source: BotPaymentInvoiceSource = .starGiftUpgrade(keepOriginalInfo: keepOriginalInfo, messageId: messageId) }
return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil)
|> mapError { _ -> UpgradeStarGiftError in
return .generic
}
|> mapToSignal { paymentForm in
return _internal_sendStarsPaymentForm(account: account, formId: paymentForm.id, source: source)
|> mapError { _ -> UpgradeStarGiftError in
return .generic
}
|> mapToSignal { _ in
return account.stateManager.upgradedStarGifts()
|> castError(UpgradeStarGiftError.self)
|> take(until: { updates in
for update in updates {
if update.0.messageId == messageId {
return .init(passthrough: true, complete: true)
} }
} }
return .init(passthrough: false, complete: false) default:
}) break
|> mapToSignal { updates in
for update in updates {
if update.0.messageId == messageId {
return .single(update.1)
}
}
return .complete()
} }
} }
return .fail(.generic)
} }
} }
} }
@ -791,7 +778,49 @@ func _internal_starGiftUpgradePreview(account: Account, giftId: Int64) -> Signal
} }
} }
private var cachedAccountGifts: [EnginePeer.Id: [ProfileGiftsContext.State.StarGift]] = [:] private final class CachedProfileGifts: Codable {
enum CodingKeys: String, CodingKey {
case gifts
case count
}
var gifts: [ProfileGiftsContext.State.StarGift]
let count: Int32
init(gifts: [ProfileGiftsContext.State.StarGift], count: Int32) {
self.gifts = gifts
self.count = count
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.gifts = try container.decode([ProfileGiftsContext.State.StarGift].self, forKey: .gifts)
self.count = try container.decode(Int32.self, forKey: .count)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.gifts, forKey: .gifts)
try container.encode(self.count, forKey: .count)
}
func render(transaction: Transaction) {
for i in 0 ..< self.gifts.count {
let gift = self.gifts[i]
if gift.fromPeer == nil, let fromPeerId = gift._fromPeerId, let peer = transaction.getPeer(fromPeerId) {
self.gifts[i] = gift.withFromPeer(EnginePeer(peer))
}
}
}
}
private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 8)
cacheKey.setInt64(0, value: peerId.toInt64())
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedProfileGifts, key: cacheKey)
}
private final class ProfileGiftsContextImpl { private final class ProfileGiftsContextImpl {
private let queue: Queue private let queue: Queue
@ -799,6 +828,7 @@ private final class ProfileGiftsContextImpl {
private let peerId: PeerId private let peerId: PeerId
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
private let cacheDisposable = MetaDisposable()
private let actionDisposable = MetaDisposable() private let actionDisposable = MetaDisposable()
private var gifts: [ProfileGiftsContext.State.StarGift] = [] private var gifts: [ProfileGiftsContext.State.StarGift] = []
@ -821,22 +851,37 @@ private final class ProfileGiftsContextImpl {
deinit { deinit {
self.disposable.dispose() self.disposable.dispose()
self.cacheDisposable.dispose()
self.actionDisposable.dispose() self.actionDisposable.dispose()
} }
func loadMore() { func loadMore() {
let peerId = self.peerId
let accountPeerId = self.account.peerId
let network = self.account.network
let postbox = self.account.postbox
if case let .ready(true, initialNextOffset) = self.dataState { if case let .ready(true, initialNextOffset) = self.dataState {
if self.gifts.isEmpty, self.peerId == self.account.peerId, let cachedGifts = cachedAccountGifts[self.peerId] { if self.gifts.isEmpty, initialNextOffset == nil {
self.gifts = cachedGifts self.cacheDisposable.set((self.account.postbox.transaction { transaction -> CachedProfileGifts? in
let cachedGifts = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedProfileGifts.self)
cachedGifts?.render(transaction: transaction)
return cachedGifts
} |> deliverOn(self.queue)).start(next: { [weak self] cachedGifts in
guard let self, let cachedGifts else {
return
}
if case .loading = self.dataState {
self.gifts = cachedGifts.gifts
self.count = cachedGifts.count
self.pushState()
}
}))
} }
self.dataState = .loading self.dataState = .loading
self.pushState() self.pushState()
let peerId = self.peerId
let accountPeerId = self.account.peerId
let network = self.account.network
let postbox = self.account.postbox
let signal: Signal<([ProfileGiftsContext.State.StarGift], Int32, String?), NoError> = self.account.postbox.transaction { transaction -> Api.InputUser? in let signal: Signal<([ProfileGiftsContext.State.StarGift], Int32, String?), NoError> = self.account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser) return transaction.getPeer(peerId).flatMap(apiInputUser)
} }
@ -871,9 +916,14 @@ private final class ProfileGiftsContextImpl {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if initialNextOffset == nil, strongSelf.peerId == strongSelf.account.peerId { if initialNextOffset == nil {
cachedAccountGifts[strongSelf.peerId] = gifts
strongSelf.gifts = gifts strongSelf.gifts = gifts
strongSelf.cacheDisposable.set(strongSelf.account.postbox.transaction { transaction in
if let entry = CodableEntry(CachedProfileGifts(gifts: gifts, count: count)) {
transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry)
}
}.start())
} else { } else {
for gift in gifts { for gift in gifts {
strongSelf.gifts.append(gift) strongSelf.gifts.append(gift)
@ -920,9 +970,14 @@ private final class ProfileGiftsContextImpl {
self.pushState() self.pushState()
} }
func upgradeStarGift(prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) {
self.actionDisposable.set( self.actionDisposable.set(
_internal_upgradeStarGift(account: self.account, prepaid: prepaid, messageId: messageId, keepOriginalInfo: keepOriginalInfo).startStrict() _internal_upgradeStarGift(account: self.account, formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo).startStrict(next: { [weak self] result in
guard let self else {
return
}
let _ = self
})
) )
self.pushState() self.pushState()
} }
@ -935,7 +990,23 @@ private final class ProfileGiftsContextImpl {
public final class ProfileGiftsContext { public final class ProfileGiftsContext {
public struct State: Equatable { public struct State: Equatable {
public struct StarGift: Equatable { public struct StarGift: Equatable, Codable {
enum CodingKeys: String, CodingKey {
case gift
case fromPeerId
case date
case text
case entities
case messageId
case nameHidden
case savedToProfile
case convertStars
case canUpgrade
case canExportDate
case upgradeStars
case transferStars
}
public let gift: TelegramCore.StarGift public let gift: TelegramCore.StarGift
public let fromPeer: EnginePeer? public let fromPeer: EnginePeer?
public let date: Int32 public let date: Int32
@ -950,6 +1021,76 @@ public final class ProfileGiftsContext {
public let upgradeStars: Int64? public let upgradeStars: Int64?
public let transferStars: Int64? public let transferStars: Int64?
fileprivate let _fromPeerId: EnginePeer.Id?
public init (
gift: TelegramCore.StarGift,
fromPeer: EnginePeer?,
date: Int32,
text: String?,
entities: [MessageTextEntity]?,
messageId: EngineMessage.Id?,
nameHidden: Bool,
savedToProfile: Bool,
convertStars: Int64?,
canUpgrade: Bool,
canExportDate: Int32?,
upgradeStars: Int64?,
transferStars: Int64?
) {
self.gift = gift
self.fromPeer = fromPeer
self._fromPeerId = fromPeer?.id
self.date = date
self.text = text
self.entities = entities
self.messageId = messageId
self.nameHidden = nameHidden
self.savedToProfile = savedToProfile
self.convertStars = convertStars
self.canUpgrade = canUpgrade
self.canExportDate = canExportDate
self.upgradeStars = upgradeStars
self.transferStars = transferStars
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.gift = try container.decode(TelegramCore.StarGift.self, forKey: .gift)
self.fromPeer = nil
self._fromPeerId = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .fromPeerId)
self.date = try container.decode(Int32.self, forKey: .date)
self.text = try container.decodeIfPresent(String.self, forKey: .text)
self.entities = try container.decodeIfPresent([MessageTextEntity].self, forKey: .entities)
self.messageId = try container.decodeIfPresent(EngineMessage.Id.self, forKey: .messageId)
self.nameHidden = try container.decode(Bool.self, forKey: .nameHidden)
self.savedToProfile = try container.decode(Bool.self, forKey: .savedToProfile)
self.convertStars = try container.decodeIfPresent(Int64.self, forKey: .convertStars)
self.canUpgrade = try container.decode(Bool.self, forKey: .canUpgrade)
self.canExportDate = try container.decodeIfPresent(Int32.self, forKey: .canExportDate)
self.upgradeStars = try container.decodeIfPresent(Int64.self, forKey: .upgradeStars)
self.transferStars = try container.decodeIfPresent(Int64.self, forKey: .transferStars)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.gift, forKey: .gift)
try container.encodeIfPresent(self.fromPeer?.id, forKey: .fromPeerId)
try container.encode(self.date, forKey: .date)
try container.encodeIfPresent(self.text, forKey: .text)
try container.encodeIfPresent(self.entities, forKey: .entities)
try container.encodeIfPresent(self.messageId, forKey: .messageId)
try container.encode(self.nameHidden, forKey: .nameHidden)
try container.encode(self.savedToProfile, forKey: .savedToProfile)
try container.encodeIfPresent(self.convertStars, forKey: .convertStars)
try container.encode(self.canUpgrade, forKey: .canUpgrade)
try container.encodeIfPresent(self.canExportDate, forKey: .canExportDate)
try container.encodeIfPresent(self.upgradeStars, forKey: .upgradeStars)
try container.encodeIfPresent(self.transferStars, forKey: .transferStars)
}
public func withSavedToProfile(_ savedToProfile: Bool) -> StarGift { public func withSavedToProfile(_ savedToProfile: Bool) -> StarGift {
return StarGift( return StarGift(
gift: self.gift, gift: self.gift,
@ -967,6 +1108,24 @@ public final class ProfileGiftsContext {
transferStars: self.transferStars transferStars: self.transferStars
) )
} }
fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift {
return StarGift(
gift: self.gift,
fromPeer: fromPeer,
date: self.date,
text: self.text,
entities: self.entities,
messageId: self.messageId,
nameHidden: self.nameHidden,
savedToProfile: savedToProfile,
convertStars: self.convertStars,
canUpgrade: self.canUpgrade,
canExportDate: self.canExportDate,
upgradeStars: self.upgradeStars,
transferStars: self.transferStars
)
}
} }
public enum DataState: Equatable { public enum DataState: Equatable {
@ -1027,9 +1186,9 @@ public final class ProfileGiftsContext {
} }
} }
public func upgradeStarGift(prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { public func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) {
self.impl.with { impl in self.impl.with { impl in
impl.upgradeStarGift(prepaid: prepaid, messageId: messageId, keepOriginalInfo: keepOriginalInfo) impl.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo)
} }
} }
@ -1055,6 +1214,7 @@ extension ProfileGiftsContext.State.StarGift {
} else { } else {
self.fromPeer = nil self.fromPeer = nil
} }
self._fromPeerId = self.fromPeer?.id
self.date = date self.date = date
if let message { if let message {
@ -1081,7 +1241,7 @@ extension ProfileGiftsContext.State.StarGift {
self.nameHidden = (flags & (1 << 0)) != 0 self.nameHidden = (flags & (1 << 0)) != 0
self.savedToProfile = (flags & (1 << 5)) == 0 self.savedToProfile = (flags & (1 << 5)) == 0
self.convertStars = convertStars self.convertStars = convertStars
self.canUpgrade = (flags & (1 << 6)) != 0 self.canUpgrade = (flags & (1 << 10)) != 0
self.canExportDate = canExportDate self.canExportDate = canExportDate
self.upgradeStars = upgradeStars self.upgradeStars = upgradeStars
self.transferStars = transferStars self.transferStars = transferStars

View File

@ -125,8 +125,8 @@ public extension TelegramEngine {
return _internal_transferStarGift(account: self.account, prepaid: prepaid, messageId: messageId, peerId: peerId) return _internal_transferStarGift(account: self.account, prepaid: prepaid, messageId: messageId, peerId: peerId)
} }
public func upgradeStarGift(prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> { public func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
return _internal_upgradeStarGift(account: self.account, prepaid: prepaid, messageId: messageId, keepOriginalInfo: keepOriginalInfo) return _internal_upgradeStarGift(account: self.account, formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo)
} }
public func starGiftUpgradePreview(giftId: Int64) -> Signal<[StarGift.UniqueGift.Attribute], NoError> { public func starGiftUpgradePreview(giftId: Int64) -> Signal<[StarGift.UniqueGift.Attribute], NoError> {

View File

@ -1066,7 +1066,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = mutableString attributedString = mutableString
case .prizeStars: case .prizeStars:
attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor)
case let .starGift(gift, _, text, entities, _, _, _, _, _): case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _):
if !forAdditionalServiceMessage { if !forAdditionalServiceMessage {
if let text { if let text {
let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage())) let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage()))
@ -1075,7 +1075,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = NSAttributedString(string: strings.Notification_Gift, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_Gift, font: titleFont, textColor: primaryTextColor)
} }
} else if case let .generic(gift) = gift { } else if case let .generic(gift) = gift {
let starsPrice = strings.Notification_StarsGift_Stars(Int32(gift.price)) var finalPrice = gift.price
if let upgradeStars {
finalPrice += upgradeStars
}
let starsPrice = strings.Notification_StarsGift_Stars(Int32(finalPrice))
var authorName = compactAuthorName var authorName = compactAuthorName
var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)] var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 { if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 {
@ -1090,7 +1094,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes) attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
} }
} }
case let .starGiftUnique(gift, isUpgrade, isTransferred, _, _, _): case let .starGiftUnique(gift, isUpgrade, isTransferred, _, _, _, _):
if case let .unique(gift) = gift { if case let .unique(gift) = gift {
if !forAdditionalServiceMessage { if !forAdditionalServiceMessage {
attributedString = NSAttributedString(string: "\(gift.title) #\(gift.number)", font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: "\(gift.title) #\(gift.number)", font: titleFont, textColor: primaryTextColor)

View File

@ -32,6 +32,7 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
"//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/InvisibleInkDustNode", "//submodules/InvisibleInkDustNode",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -3,6 +3,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import ComponentFlow
import TelegramCore import TelegramCore
import AccountContext import AccountContext
import TelegramPresentationData import TelegramPresentationData
@ -22,6 +23,7 @@ import ChatMessageBubbleContentNode
import ChatMessageItemCommon import ChatMessageItemCommon
import TextNodeWithEntities import TextNodeWithEntities
import InvisibleInkDustNode import InvisibleInkDustNode
import PeerInfoCoverComponent
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? { private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? {
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false, forAdditionalServiceMessage: true) return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false, forAdditionalServiceMessage: true)
@ -33,6 +35,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let backgroundMaskNode: ASImageNode private let backgroundMaskNode: ASImageNode
private var linkHighlightingNode: LinkHighlightingNode? private var linkHighlightingNode: LinkHighlightingNode?
private let patternView = ComponentView<Empty>()
private let mediaBackgroundMaskNode: ASImageNode private let mediaBackgroundMaskNode: ASImageNode
private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? private var mediaBackgroundContent: WallpaperBubbleBackgroundNode?
private let titleNode: TextNode private let titleNode: TextNode
@ -43,6 +46,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let placeholderNode: StickerShimmerEffectNode private let placeholderNode: StickerShimmerEffectNode
private let animationNode: AnimatedStickerNode private let animationNode: AnimatedStickerNode
private let modelTitleTextNode: TextNode
private let modelValueTextNode: TextNode
private let backdropTitleTextNode: TextNode
private let backdropValueTextNode: TextNode
private let symbolTitleTextNode: TextNode
private let symbolValueTextNode: TextNode
private let ribbonBackgroundNode: ASImageNode private let ribbonBackgroundNode: ASImageNode
private let ribbonTextNode: TextNode private let ribbonTextNode: TextNode
@ -50,6 +60,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let buttonNode: HighlightTrackingButtonNode private let buttonNode: HighlightTrackingButtonNode
private let buttonStarsNode: PremiumStarsNode private let buttonStarsNode: PremiumStarsNode
private let buttonTitleNode: TextNode private let buttonTitleNode: TextNode
private let buttonIconNode: ASImageNode
private let moreTextNode: TextNode private let moreTextNode: TextNode
@ -120,6 +131,25 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.textClippingNode = ASDisplayNode() self.textClippingNode = ASDisplayNode()
self.textClippingNode.clipsToBounds = true self.textClippingNode.clipsToBounds = true
self.modelTitleTextNode = TextNode()
self.modelTitleTextNode.isUserInteractionEnabled = false
self.modelTitleTextNode.displaysAsynchronously = false
self.modelValueTextNode = TextNode()
self.modelValueTextNode.isUserInteractionEnabled = false
self.modelValueTextNode.displaysAsynchronously = false
self.backdropTitleTextNode = TextNode()
self.backdropTitleTextNode.isUserInteractionEnabled = false
self.backdropTitleTextNode.displaysAsynchronously = false
self.backdropValueTextNode = TextNode()
self.backdropValueTextNode.isUserInteractionEnabled = false
self.backdropValueTextNode.displaysAsynchronously = false
self.symbolTitleTextNode = TextNode()
self.symbolTitleTextNode.isUserInteractionEnabled = false
self.symbolTitleTextNode.displaysAsynchronously = false
self.symbolValueTextNode = TextNode()
self.symbolValueTextNode.isUserInteractionEnabled = false
self.symbolValueTextNode.displaysAsynchronously = false
self.buttonNode = HighlightTrackingButtonNode() self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 17.0 self.buttonNode.cornerRadius = 17.0
@ -136,6 +166,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.buttonTitleNode.isUserInteractionEnabled = false self.buttonTitleNode.isUserInteractionEnabled = false
self.buttonTitleNode.displaysAsynchronously = false self.buttonTitleNode.displaysAsynchronously = false
self.buttonIconNode = ASImageNode()
self.buttonIconNode.displaysAsynchronously = false
self.buttonIconNode.isUserInteractionEnabled = false
self.ribbonBackgroundNode = ASImageNode() self.ribbonBackgroundNode = ASImageNode()
self.ribbonBackgroundNode.displaysAsynchronously = false self.ribbonBackgroundNode.displaysAsynchronously = false
@ -161,6 +195,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.addSubnode(self.buttonNode) self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.buttonStarsNode) self.buttonNode.addSubnode(self.buttonStarsNode)
self.buttonNode.addSubnode(self.buttonTitleNode) self.buttonNode.addSubnode(self.buttonTitleNode)
self.buttonNode.addSubnode(self.buttonIconNode)
self.addSubnode(self.ribbonBackgroundNode) self.addSubnode(self.ribbonBackgroundNode)
self.addSubnode(self.ribbonTextNode) self.addSubnode(self.ribbonTextNode)
@ -293,6 +328,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let makeMeasureTextLayout = TextNode.asyncLayout(nil) let makeMeasureTextLayout = TextNode.asyncLayout(nil)
let makeMoreTextLayout = TextNode.asyncLayout(self.moreTextNode) let makeMoreTextLayout = TextNode.asyncLayout(self.moreTextNode)
let makeModelTitleLayout = TextNode.asyncLayout(self.modelTitleTextNode)
let makeModelValueLayout = TextNode.asyncLayout(self.modelValueTextNode)
let makeBackdropTitleLayout = TextNode.asyncLayout(self.backdropTitleTextNode)
let makeBackdropValueLayout = TextNode.asyncLayout(self.backdropValueTextNode)
let makeSymbolTitleLayout = TextNode.asyncLayout(self.symbolTitleTextNode)
let makeSymbolValueLayout = TextNode.asyncLayout(self.symbolValueTextNode)
let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage
let currentIsExpanded = self.isExpanded let currentIsExpanded = self.isExpanded
@ -303,7 +345,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
var giftSize = CGSize(width: 220.0, height: 240.0) var giftSize = CGSize(width: 220.0, height: 240.0)
let incoming = item.message.effectivelyIncoming(item.context.account.peerId) let incoming: Bool
if item.message.id.peerId == item.context.account.peerId && item.message.forwardInfo == nil {
incoming = true
} else {
incoming = item.message.effectivelyIncoming(item.context.account.peerId)
}
let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId) let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId)
@ -314,12 +361,26 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
var animationFile: TelegramMediaFile? var animationFile: TelegramMediaFile?
var title = item.presentationData.strings.Notification_PremiumGift_Title var title = item.presentationData.strings.Notification_PremiumGift_Title
var text = "" var text = ""
var subtitleColor = primaryTextColor
var entities: [MessageTextEntity] = [] var entities: [MessageTextEntity] = []
var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View
var buttonIcon: String?
var ribbonTitle = "" var ribbonTitle = ""
var hasServiceMessage = true var hasServiceMessage = true
var textSpacing: CGFloat = 0.0 var textSpacing: CGFloat = 0.0
var isStarGift = false var isStarGift = false
var modelTitle: String?
var modelValue: String?
var backdropTitle: String?
var backdropValue: String?
var symbolTitle: String?
var symbolValue: String?
var uniqueBackgroundColor: UIColor?
var uniqueSecondBackgroundColor: UIColor?
var uniquePatternColor: UIColor?
var uniquePatternFile: TelegramMediaFile?
for media in item.message.media { for media in item.message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
switch action.action { switch action.action {
@ -406,7 +467,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
hasServiceMessage = false hasServiceMessage = false
} }
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, _, _): case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, _, upgradeStars, isRefunded):
if case let .generic(gift) = gift { if case let .generic(gift) = gift {
isStarGift = true isStarGift = true
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
@ -415,7 +476,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
text = giftText text = giftText
entities = giftEntities ?? [] entities = giftEntities ?? []
} else { } else {
if incoming { if isRefunded {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Refunded
} else if incoming {
if converted { if converted {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Converted(item.presentationData.strings.Notification_StarGift_Subtitle_Converted_Stars(Int32(convertStars ?? 0))).string text = item.presentationData.strings.Notification_StarGift_Subtitle_Converted(item.presentationData.strings.Notification_StarGift_Subtitle_Converted_Stars(Int32(convertStars ?? 0))).string
} else if savedToProfile { } else if savedToProfile {
@ -461,26 +524,49 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
ribbonTitle = item.presentationData.strings.Notification_StarGift_OneOf(availabilityString).string ribbonTitle = item.presentationData.strings.Notification_StarGift_OneOf(availabilityString).string
} }
if incoming { if incoming, let upgradeStars, upgradeStars > 0, !upgraded {
buttonTitle = item.presentationData.strings.Notification_StarGift_View buttonTitle = item.presentationData.strings.Notification_StarGift_Unpack
buttonIcon = "Premium/GiftUnpack"
} else { } else {
buttonTitle = "" buttonTitle = item.presentationData.strings.Notification_StarGift_View
} }
} }
case let .starGiftUnique(gift, _, _, _, _, _): case let .starGiftUnique(gift, _, _, _, _, _, isRefunded):
if case let .unique(uniqueGift) = gift { if case let .unique(uniqueGift) = gift {
isStarGift = true isStarGift = true
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string title = item.presentationData.strings.Notification_StarGift_Title(authorName).string
text = "**\(uniqueGift.title) #\(uniqueGift.number)**"
ribbonTitle = item.presentationData.strings.Notification_StarGift_Gift
buttonTitle = item.presentationData.strings.Notification_StarGift_View
modelTitle = item.presentationData.strings.Notification_StarGift_Model
backdropTitle = item.presentationData.strings.Notification_StarGift_Backdrop
symbolTitle = item.presentationData.strings.Notification_StarGift_Symbol
for attribute in uniqueGift.attributes { for attribute in uniqueGift.attributes {
if case let .model(_, file, _) = attribute { switch attribute {
case let .model(name, file, _):
modelValue = name
animationFile = file animationFile = file
case let .backdrop(name, innerColor, outerColor, patternColor, _, _):
uniqueBackgroundColor = UIColor(rgb: UInt32(bitPattern: outerColor))
uniqueSecondBackgroundColor = UIColor(rgb: UInt32(bitPattern: innerColor))
uniquePatternColor = UIColor(rgb: UInt32(bitPattern: patternColor))
backdropValue = name
subtitleColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
case let .pattern(name, file, _):
symbolValue = name
uniquePatternFile = file
default:
break break
} }
} }
//TODO:localize } else if isRefunded, case let .generic(gift) = gift {
ribbonTitle = "gift" isStarGift = true
buttonTitle = item.presentationData.strings.Notification_StarGift_View let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string
text = item.presentationData.strings.Notification_StarGift_Subtitle_Refunded
animationFile = gift.file
} }
default: default:
break break
@ -510,9 +596,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.monospace(13.0), blockQuoteFont: Font.regular(13.0), message: nil) attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.monospace(13.0), blockQuoteFont: Font.regular(13.0), message: nil)
} else { } else {
attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes( attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: subtitleColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: subtitleColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: subtitleColor),
linkAttribute: { url in linkAttribute: { url in
return ("URL", url) return ("URL", url)
} }
@ -534,11 +620,56 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
let infoConstrainedSize = CGSize(width: (giftSize.width - 32.0) * 0.7, height: CGFloat.greatestFiniteMagnitude)
let modelTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let modelTitle {
modelTitleLayoutAndApply = makeModelTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: modelTitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
} else {
modelTitleLayoutAndApply = nil
}
let modelValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let modelValue {
modelValueLayoutAndApply = makeModelValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: modelValue, font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
} else {
modelValueLayoutAndApply = nil
}
let backdropTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let backdropTitle {
backdropTitleLayoutAndApply = makeBackdropTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: backdropTitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
} else {
backdropTitleLayoutAndApply = nil
}
let backdropValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let backdropValue {
backdropValueLayoutAndApply = makeBackdropValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: backdropValue, font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
} else {
backdropValueLayoutAndApply = nil
}
let symbolTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let symbolTitle {
symbolTitleLayoutAndApply = makeSymbolTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: symbolTitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
} else {
symbolTitleLayoutAndApply = nil
}
let symbolValueLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let symbolValue {
symbolValueLayoutAndApply = makeSymbolValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: symbolValue, font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
} else {
symbolValueLayoutAndApply = nil
}
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
giftSize.height = titleLayout.size.height + textSpacing + clippedTextHeight + 164.0 giftSize.height = titleLayout.size.height + textSpacing + clippedTextHeight + 164.0
if let _ = modelTitle {
giftSize.height += 70.0
}
if !buttonTitle.isEmpty { if !buttonTitle.isEmpty {
giftSize.height += 48.0 giftSize.height += 48.0
} }
@ -614,6 +745,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.buttonNode.isHidden = buttonTitle.isEmpty strongSelf.buttonNode.isHidden = buttonTitle.isEmpty
strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty
strongSelf.buttonIconNode.isHidden = buttonIcon == nil
if strongSelf.item == nil { if strongSelf.item == nil {
strongSelf.animationNode.started = { [weak self] in strongSelf.animationNode.started = { [weak self] in
@ -677,7 +809,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame strongSelf.titleNode.frame = titleFrame
let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight)) let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight))
let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size) let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size)
strongSelf.subtitleNode.textNode.frame = subtitleFrame strongSelf.subtitleNode.textNode.frame = subtitleFrame
@ -727,19 +859,97 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.dustNode = nil strongSelf.dustNode = nil
} }
let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) var middleX = mediaBackgroundFrame.width / 2.0
strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((buttonSize.width - buttonTitleLayout.size.width) / 2.0), y: 8.0), size: buttonTitleLayout.size) if let (modelValueLayout, _) = modelValueLayoutAndApply, let (backdropValueLayout, _) = backdropValueLayoutAndApply, let (symbolValueLayout, _) = symbolValueLayoutAndApply {
let maxWidth = max(modelValueLayout.size.width, max(backdropValueLayout.size.width, symbolValueLayout.size.width))
middleX = min(mediaBackgroundFrame.width - maxWidth - 16.0, middleX)
}
animation.animator.updateFrame(layer: strongSelf.buttonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: clippingTextFrame.maxY + 10.0), size: buttonSize), completion: nil) let titleMaxX: CGFloat = mediaBackgroundFrame.minX + middleX - 2.0
let valueMinX: CGFloat = mediaBackgroundFrame.minX + middleX + 3.0
if let (modelTitleLayout, modelTitleApply) = modelTitleLayoutAndApply {
if strongSelf.modelTitleTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.modelTitleTextNode)
}
let _ = modelTitleApply()
strongSelf.modelTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - modelTitleLayout.size.width, y: clippingTextFrame.maxY + 10.0), size: modelTitleLayout.size)
}
if let (modelValueLayout, modelValueApply) = modelValueLayoutAndApply {
if strongSelf.modelValueTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.modelValueTextNode)
}
let _ = modelValueApply()
strongSelf.modelValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 10.0), size: modelValueLayout.size)
}
if let (backdropTitleLayout, backdropTitleApply) = backdropTitleLayoutAndApply {
if strongSelf.backdropTitleTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.backdropTitleTextNode)
}
let _ = backdropTitleApply()
strongSelf.backdropTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - backdropTitleLayout.size.width, y: clippingTextFrame.maxY + 32.0), size: backdropTitleLayout.size)
}
if let (backdropValueLayout, backdropValueApply) = backdropValueLayoutAndApply {
if strongSelf.backdropValueTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.backdropValueTextNode)
}
let _ = backdropValueApply()
strongSelf.backdropValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 32.0), size: backdropValueLayout.size)
}
if let (symbolTitleLayout, symbolTitleApply) = symbolTitleLayoutAndApply {
if strongSelf.symbolTitleTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.symbolTitleTextNode)
}
let _ = symbolTitleApply()
strongSelf.symbolTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - symbolTitleLayout.size.width, y: clippingTextFrame.maxY + 54.0), size: symbolTitleLayout.size)
}
if let (symbolValueLayout, symbolValueApply) = symbolValueLayoutAndApply {
if strongSelf.symbolValueTextNode.supernode == nil {
strongSelf.addSubnode(strongSelf.symbolValueTextNode)
}
let _ = symbolValueApply()
strongSelf.symbolValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 54.0), size: symbolValueLayout.size)
}
var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0)
var buttonOriginY = clippingTextFrame.maxY + 10.0
if modelTitleLayoutAndApply != nil {
buttonOriginY = clippingTextFrame.maxY + 80.0
}
if let buttonIcon {
buttonSize.width += 15.0
if strongSelf.buttonIconNode.image == nil {
strongSelf.buttonIconNode.image = generateTintedImage(image: UIImage(bundleImageName: buttonIcon), color: .white)
}
}
strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 8.0), size: buttonTitleLayout.size)
strongSelf.buttonIconNode.frame = CGRect(origin: CGPoint(x: buttonSize.width - 30.0, y: 9.0), size: CGSize(width: 14.0, height: 14.0))
animation.animator.updateFrame(layer: strongSelf.buttonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: buttonOriginY), size: buttonSize), completion: nil)
strongSelf.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize) strongSelf.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize)
if ribbonTextLayout.size.width > 0.0 { if ribbonTextLayout.size.width > 0.0 {
if strongSelf.ribbonBackgroundNode.image == nil { if strongSelf.ribbonBackgroundNode.image == nil {
let ribbonImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/GiftRibbon"), color: overlayColor) if let uniqueBackgroundColor {
strongSelf.ribbonBackgroundNode.image = ribbonImage let colors = [
uniqueBackgroundColor.withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89),
uniqueBackgroundColor.withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
]
strongSelf.ribbonBackgroundNode.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: colors, direction: .mirroredDiagonal)
} else {
strongSelf.ribbonBackgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/GiftRibbon"), color: overlayColor)
}
} }
if let ribbonImage = strongSelf.ribbonBackgroundNode.image { if let ribbonImage = strongSelf.ribbonBackgroundNode.image {
let ribbonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.maxX - ribbonImage.size.width + 2.0, y: mediaBackgroundFrame.minY - 2.0), size: ribbonImage.size) var ribbonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.maxX - ribbonImage.size.width + 2.0, y: mediaBackgroundFrame.minY - 2.0), size: ribbonImage.size)
if let _ = uniqueBackgroundColor {
ribbonFrame = ribbonFrame.offsetBy(dx: -4.0, dy: 4.0)
}
strongSelf.ribbonBackgroundNode.frame = ribbonFrame strongSelf.ribbonBackgroundNode.frame = ribbonFrame
strongSelf.ribbonTextNode.transform = CATransform3DMakeRotation(.pi / 4.0, 0.0, 0.0, 1.0) strongSelf.ribbonTextNode.transform = CATransform3DMakeRotation(.pi / 4.0, 0.0, 0.0, 1.0)
@ -757,7 +967,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
if let backgroundContent = strongSelf.mediaBackgroundContent { if let backgroundContent = strongSelf.mediaBackgroundContent {
if ribbonTextLayout.size.width > 0.0 { if ribbonTextLayout.size.width > 0.0, uniqueBackgroundColor == nil {
let backgroundMaskFrame = mediaBackgroundFrame.insetBy(dx: -2.0, dy: -2.0) let backgroundMaskFrame = mediaBackgroundFrame.insetBy(dx: -2.0, dy: -2.0)
backgroundContent.frame = backgroundMaskFrame backgroundContent.frame = backgroundMaskFrame
animation.animator.updateFrame(layer: backgroundContent.layer, frame: backgroundMaskFrame, completion: nil) animation.animator.updateFrame(layer: backgroundContent.layer, frame: backgroundMaskFrame, completion: nil)
@ -787,6 +997,36 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
if let uniqueBackgroundColor, let uniqueSecondBackgroundColor, let uniquePatternColor, let uniquePatternFile {
let patternInset: CGFloat = 4.0
let patternSize = CGSize(width: mediaBackgroundFrame.width - patternInset * 2.0, height: mediaBackgroundFrame.height - patternInset * 2.0)
let files: [Int64: TelegramMediaFile] = [uniquePatternFile.fileId.id: uniquePatternFile]
let _ = strongSelf.patternView.update(
transition: .immediate,
component: AnyComponent(PeerInfoCoverComponent(
context: item.context,
subject: .custom(uniqueBackgroundColor, uniqueSecondBackgroundColor, uniquePatternColor, uniquePatternFile.fileId.id),
files: files,
isDark: false,
avatarCenter: CGPoint(x: patternSize.width / 2.0, y: 106.0),
avatarScale: 1.0,
defaultHeight: patternSize.height,
avatarTransitionFraction: 0.0,
patternTransitionFraction: 0.0
)),
environment: {},
containerSize: patternSize
)
if let backgroundView = strongSelf.patternView.view {
if backgroundView.superview == nil {
backgroundView.layer.cornerRadius = 20.0
backgroundView.clipsToBounds = true
strongSelf.view.insertSubview(backgroundView, belowSubview: strongSelf.titleNode.view)
}
backgroundView.frame = CGRect(origin: .zero, size: patternSize).offsetBy(dx: mediaBackgroundFrame.minX + patternInset, dy: mediaBackgroundFrame.minY + patternInset)
}
}
let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0)
if let (offset, image) = backgroundMaskImage { if let (offset, image) = backgroundMaskImage {
if strongSelf.backgroundNode == nil { if strongSelf.backgroundNode == nil {

View File

@ -909,7 +909,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
var titleFrame: CGRect var titleFrame: CGRect
if size.height > 40.0 { if size.height > 40.0 {
var titleInsets: UIEdgeInsets = .zero var titleInsets: UIEdgeInsets = .zero
if verifiedIconWidth > 0.0 { if case .emojiStatus = self.titleVerifiedIcon, verifiedIconWidth > 0.0 {
titleInsets.left = verifiedIconWidth + 2.0 titleInsets.left = verifiedIconWidth + 2.0
} }
@ -951,7 +951,18 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
var nextIconX: CGFloat = titleFrame.width var nextIconX: CGFloat = titleFrame.width
self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) var verifiedIconX: CGFloat
if case .emojiStatus = self.titleVerifiedIcon {
verifiedIconX = 0.0
} else {
verifiedIconX = nextIconX - titleVerifiedSize.width
}
self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: verifiedIconX, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize)
if case .emojiStatus = self.titleVerifiedIcon {
} else {
nextIconX -= titleVerifiedSize.width
}
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
nextIconX -= titleCredibilitySize.width nextIconX -= titleCredibilitySize.width

View File

@ -26,6 +26,7 @@ swift_library(
"//submodules/Components/ViewControllerComponent", "//submodules/Components/ViewControllerComponent",
"//submodules/Components/BundleIconComponent", "//submodules/Components/BundleIconComponent",
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/Components/BalancedTextComponent", "//submodules/Components/BalancedTextComponent",
"//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/ListActionItemComponent",
@ -45,6 +46,7 @@ swift_library(
"//submodules/InAppPurchaseManager", "//submodules/InAppPurchaseManager",
"//submodules/Components/BlurredBackgroundComponent", "//submodules/Components/BlurredBackgroundComponent",
"//submodules/ProgressNavigationButtonNode", "//submodules/ProgressNavigationButtonNode",
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -143,6 +143,9 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
if lhs.entities != rhs.entities { if lhs.entities != rhs.entities {
return false return false
} }
if lhs.includeUpgrade != rhs.includeUpgrade {
return false
}
return true return true
} }
} }
@ -224,7 +227,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
case let .starGift(gift): case let .starGift(gift):
media = [ media = [
TelegramMediaAction( TelegramMediaAction(
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, upgradeStars: item.includeUpgrade ? 0 : gift.upgradeStars) action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: true, upgradeStars: item.includeUpgrade ? gift.upgradeStars : 0, isRefunded: false)
) )
] ]
} }

View File

@ -13,6 +13,7 @@ import AccountContext
import ComponentFlow import ComponentFlow
import ViewControllerComponent import ViewControllerComponent
import MultilineTextComponent import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import BalancedTextComponent import BalancedTextComponent
import ListSectionComponent import ListSectionComponent
import ListActionItemComponent import ListActionItemComponent
@ -32,6 +33,7 @@ import InAppPurchaseManager
import BlurredBackgroundComponent import BlurredBackgroundComponent
import ProgressNavigationButtonNode import ProgressNavigationButtonNode
import Markdown import Markdown
import GiftViewScreen
final class GiftSetupScreenComponent: Component { final class GiftSetupScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -131,6 +133,7 @@ final class GiftSetupScreenComponent: Component {
} }
} }
private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil) private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil)
private let previewPromise = Promise<[StarGift.UniqueGift.Attribute]?>(nil)
private var cachedChevronImage: (UIImage, PresentationTheme)? private var cachedChevronImage: (UIImage, PresentationTheme)?
@ -578,7 +581,7 @@ final class GiftSetupScreenComponent: Component {
} }
) )
if case .starGift = component.subject { if case let .starGift(gift) = component.subject {
self.optionsDisposable = (component.context.engine.payments.starsTopUpOptions() self.optionsDisposable = (component.context.engine.payments.starsTopUpOptions()
|> deliverOnMainQueue).start(next: { [weak self] options in |> deliverOnMainQueue).start(next: { [weak self] options in
guard let self else { guard let self else {
@ -586,6 +589,13 @@ final class GiftSetupScreenComponent: Component {
} }
self.options = options self.options = options
}) })
if let _ = gift.upgradeStars {
self.previewPromise.set(
component.context.engine.payments.starGiftUpgradePreview(giftId: gift.id)
|> map(Optional.init)
)
}
} }
} }
@ -845,6 +855,13 @@ final class GiftSetupScreenComponent: Component {
upgradeFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: upgradeFooterText.string)) upgradeFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: upgradeFooterText.string))
} }
let upgradeAttributedText = NSMutableAttributedString(string: environment.strings.Gift_Send_Upgrade("#\(upgradeStars)").string, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor)
let range = (upgradeAttributedText.string as NSString).range(of: "#")
if range.location != NSNotFound {
upgradeAttributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
upgradeAttributedText.addAttribute(.baselineOffset, value: 1.0, range: range)
}
let upgradeSectionSize = self.upgradeSection.update( let upgradeSectionSize = self.upgradeSection.update(
transition: transition, transition: transition,
component: AnyComponent(ListSectionComponent( component: AnyComponent(ListSectionComponent(
@ -852,20 +869,47 @@ final class GiftSetupScreenComponent: Component {
header: nil, header: nil,
footer: AnyComponent(MultilineTextComponent( footer: AnyComponent(MultilineTextComponent(
text: .plain(upgradeFooterText), text: .plain(upgradeFooterText),
maximumNumberOfLines: 0 maximumNumberOfLines: 0,
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak self] _, _ in
guard let self else {
return
}
let _ = (self.previewPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] attributes in
guard let self, let component = self.component, let controller = self.environment?.controller(), let attributes else {
return
}
let previewController = GiftViewScreen(
context: component.context,
subject: .upgradePreview(attributes, peerName)
)
controller.push(previewController)
})
}
)), )),
items: [ items: [
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
theme: environment.theme, theme: environment.theme,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(
text: .plain(NSAttributedString( MultilineTextWithEntitiesComponent(
string: environment.strings.Gift_Send_Upgrade("\(upgradeStars)").string, context: component.context,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize), animationCache: component.context.animationCache,
textColor: environment.theme.list.itemPrimaryTextColor animationRenderer: component.context.animationRenderer,
)), placeholderColor: environment.theme.list.mediaPlaceholderColor,
maximumNumberOfLines: 1 text: .plain(upgradeAttributedText)
))), )
)),
], alignment: .left, spacing: 2.0)), ], alignment: .left, spacing: 2.0)),
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.includeUpgrade, action: { [weak self] _ in accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.includeUpgrade, action: { [weak self] _ in
guard let self else { guard let self else {
@ -982,7 +1026,11 @@ final class GiftSetupScreenComponent: Component {
let amountString = product.price let amountString = product.price
buttonString = "\(environment.strings.Gift_Send_Send) \(amountString)" buttonString = "\(environment.strings.Gift_Send_Send) \(amountString)"
case let .starGift(starGift): case let .starGift(starGift):
let amountString = presentationStringsFormattedNumber(Int32(starGift.price), presentationData.dateTimeFormat.groupingSeparator) var finalPrice: Int64 = starGift.price
if self.includeUpgrade, let upgradePrice = starGift.upgradeStars {
finalPrice += upgradePrice
}
let amountString = presentationStringsFormattedNumber(Int32(finalPrice), presentationData.dateTimeFormat.groupingSeparator)
buttonString = "\(environment.strings.Gift_Send_Send) # \(amountString)" buttonString = "\(environment.strings.Gift_Send_Send) # \(amountString)"
if let availability = starGift.availability, availability.remains == 0 { if let availability = starGift.availability, availability.remains == 0 {
buttonIsEnabled = false buttonIsEnabled = false

View File

@ -14,6 +14,13 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
final class GiftCompositionComponent: Component { final class GiftCompositionComponent: Component {
public class ExternalState {
public fileprivate(set) var previewPatternColor: UIColor?
public init() {
self.previewPatternColor = nil
}
}
enum Subject: Equatable { enum Subject: Equatable {
case generic(TelegramMediaFile) case generic(TelegramMediaFile)
case unique(StarGift.UniqueGift) case unique(StarGift.UniqueGift)
@ -23,15 +30,21 @@ final class GiftCompositionComponent: Component {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let subject: Subject let subject: Subject
let externalState: ExternalState
let requestUpdate: () -> Void
init( init(
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
subject: Subject subject: Subject,
externalState: ExternalState,
requestUpdate: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.subject = subject self.subject = subject
self.externalState = externalState
self.requestUpdate = requestUpdate
} }
static func ==(lhs: GiftCompositionComponent, rhs: GiftCompositionComponent) -> Bool { static func ==(lhs: GiftCompositionComponent, rhs: GiftCompositionComponent) -> Bool {
@ -194,12 +207,18 @@ final class GiftCompositionComponent: Component {
} }
} }
component.externalState.previewPatternColor = secondBackgroundColor
var animateTransition = false var animateTransition = false
if self.animatePreviewTransition { if self.animatePreviewTransition {
animateTransition = true animateTransition = true
self.animatePreviewTransition = false self.animatePreviewTransition = false
} else if let previousComponent, case .preview = previousComponent.subject, case .unique = component.subject { } else if let previousComponent, case .preview = previousComponent.subject, case .unique = component.subject {
animateTransition = true animateTransition = true
} else if let previousComponent, case .generic = previousComponent.subject, case .preview = component.subject {
animateTransition = true
} else if let previousComponent, case .preview = previousComponent.subject, case .generic = component.subject {
animateTransition = true
} }
if let backgroundColor { if let backgroundColor {
@ -235,7 +254,7 @@ final class GiftCompositionComponent: Component {
backgroundTransition.setFrame(view: backgroundView, frame: CGRect(origin: .zero, size: availableSize)) backgroundTransition.setFrame(view: backgroundView, frame: CGRect(origin: .zero, size: availableSize))
} }
} else if let backgroundView = self.background.view, backgroundView.superview != nil { } else if let backgroundView = self.background.view, backgroundView.superview != nil {
backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, completion: { _ in backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
backgroundView.removeFromSuperview() backgroundView.removeFromSuperview()
}) })
} }
@ -264,7 +283,6 @@ final class GiftCompositionComponent: Component {
if let startFromIndex { if let startFromIndex {
animationNode.play(firstFrame: false, fromIndex: startFromIndex) animationNode.play(firstFrame: false, fromIndex: startFromIndex)
//animationNode.seekTo(.frameIndex(startFromIndex))
} else { } else {
animationNode.playLoop() animationNode.playLoop()
} }
@ -276,31 +294,6 @@ final class GiftCompositionComponent: Component {
} }
} }
} }
// if self.animationLayer == nil, let animationFile {
// let emoji = ChatTextInputTextCustomEmojiAttribute(
// interactivelySelectedFromPackId: nil,
// fileId: animationFile.fileId.id,
// file: animationFile
// )
//
// let animationLayer = InlineStickerItemLayer(
// context: .account(component.context),
// userLocation: .other,
// attemptSynchronousLoad: false,
// emoji: emoji,
// file: animationFile,
// cache: component.context.animationCache,
// renderer: component.context.animationRenderer,
// unique: true,
// placeholderColor: component.theme.list.mediaPlaceholderColor,
// pointSize: CGSize(width: iconSize.width * 1.2, height: iconSize.height * 1.2),
// loopCount: 1
// )
// animationLayer.isVisibleForAnimations = true
// self.animationLayer = animationLayer
// self.layer.addSublayer(animationLayer)
// }
if let animationNode = self.animationNode { if let animationNode = self.animationNode {
transition.setFrame(layer: animationNode.layer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: 25.0), size: iconSize)) transition.setFrame(layer: animationNode.layer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: 25.0), size: iconSize))
} }

View File

@ -51,7 +51,7 @@ private final class GiftViewSheetContent: CombinedComponent {
init( init(
context: AccountContext, context: AccountContext,
subject: GiftViewScreen.Subject, subject: GiftViewScreen.Subject,
cancel: @escaping (Bool) -> Void, cancel: @escaping (Bool) -> Void,
openPeer: @escaping (EnginePeer) -> Void, openPeer: @escaping (EnginePeer) -> Void,
updateSavedToProfile: @escaping (Bool) -> Void, updateSavedToProfile: @escaping (Bool) -> Void,
convertToStars: @escaping () -> Void, convertToStars: @escaping () -> Void,
@ -108,11 +108,13 @@ private final class GiftViewSheetContent: CombinedComponent {
var inProgress = false var inProgress = false
var inUpgradePreview = false var inUpgradePreview = false
var upgradeForm: BotPaymentForm?
var upgradeDisposable: Disposable?
var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]? var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]?
var sampleDisposable: Disposable? let sampleDisposable = DisposableSet()
var keepOriginalInfo = false var keepOriginalInfo = false
var upgradeDisposable: Disposable?
private var optionsDisposable: Disposable? private var optionsDisposable: Disposable?
private(set) var options: [StarsTopUpOption] = [] { private(set) var options: [StarsTopUpOption] = [] {
@ -151,15 +153,40 @@ private final class GiftViewSheetContent: CombinedComponent {
break break
} }
} }
} else if case let .generic(gift) = arguments.gift, let _ = arguments.upgradeStars { } else if case let .generic(gift) = arguments.gift {
self.sampleDisposable = (context.engine.payments.starGiftUpgradePreview(giftId: gift.id) if arguments.canUpgrade || arguments.upgradeStars != nil {
|> deliverOnMainQueue).start(next: { [weak self] attributes in self.sampleDisposable.add((context.engine.payments.starGiftUpgradePreview(giftId: gift.id)
guard let self else { |> deliverOnMainQueue).start(next: { [weak self] attributes in
return guard let self else {
return
}
self.sampleGiftAttributes = attributes
for attribute in attributes {
switch attribute {
case let .model(_, file, _):
self.sampleDisposable.add(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
case let .pattern(_, file, _):
self.sampleDisposable.add(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
default:
break
}
}
self.updated()
}))
if arguments.upgradeStars == nil, let messageId = arguments.messageId {
self.upgradeDisposable = (context.engine.payments.fetchBotPaymentForm(source: .starGiftUpgrade(keepOriginalInfo: false, messageId: messageId), themeParams: nil)
|> deliverOnMainQueue).start(next: { [weak self] paymentForm in
guard let self else {
return
}
self.upgradeForm = paymentForm
self.updated()
})
} }
self.sampleGiftAttributes = attributes }
self.updated()
})
} }
self.disposable = combineLatest(queue: Queue.mainQueue(), self.disposable = combineLatest(queue: Queue.mainQueue(),
context.engine.data.get(EngineDataMap( context.engine.data.get(EngineDataMap(
@ -209,7 +236,7 @@ private final class GiftViewSheetContent: CombinedComponent {
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
self.sampleDisposable?.dispose() self.sampleDisposable.dispose()
self.upgradeDisposable?.dispose() self.upgradeDisposable?.dispose()
} }
@ -217,20 +244,22 @@ private final class GiftViewSheetContent: CombinedComponent {
guard let _ = self.subject.arguments?.upgradeStars else { guard let _ = self.subject.arguments?.upgradeStars else {
return return
} }
self.context.starsContext?.load(force: false)
self.inUpgradePreview = true self.inUpgradePreview = true
self.updated(transition: .spring(duration: 0.4)) self.updated(transition: .spring(duration: 0.4))
} }
func commitUpgrade() { func commitUpgrade() {
guard let arguments = self.subject.arguments, let messageId = arguments.messageId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState, let upgradeStars = arguments.upgradeStars else { guard let arguments = self.subject.arguments, let messageId = arguments.messageId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
return return
} }
let peerId = arguments.peerId let peerId = arguments.peerId
let proceed: (Bool) -> Void = { prepaid in let proceed: (Int64?) -> Void = { formId in
self.inProgress = true self.inProgress = true
self.updated() self.updated()
let _ = (self.context.engine.payments.upgradeStarGift(prepaid: prepaid, messageId: messageId, keepOriginalInfo: self.keepOriginalInfo) let _ = (self.context.engine.payments.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: self.keepOriginalInfo)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
guard let self, let controller = self.getController() as? GiftViewScreen else { guard let self, let controller = self.getController() as? GiftViewScreen else {
return return
@ -245,8 +274,10 @@ private final class GiftViewSheetContent: CombinedComponent {
}) })
} }
if upgradeStars > 0 { if let upgradeStars = arguments.upgradeStars, upgradeStars > 0 {
if starsState.balance < StarsAmount(value: upgradeStars, nanos: 0) { proceed(nil)
} else if let upgradeForm = self.upgradeForm, let price = upgradeForm.invoice.prices.first?.amount {
if starsState.balance < StarsAmount(value: price, nanos: 0) {
let _ = (self.optionsPromise.get() let _ = (self.optionsPromise.get()
|> filter { $0 != nil } |> filter { $0 != nil }
|> take(1) |> take(1)
@ -258,21 +289,19 @@ private final class GiftViewSheetContent: CombinedComponent {
context: self.context, context: self.context,
starsContext: starsContext, starsContext: starsContext,
options: options ?? [], options: options ?? [],
purpose: .upgradeStarGift(requiredStars: upgradeStars), purpose: .upgradeStarGift(requiredStars: price),
completion: { [weak starsContext] stars in completion: { [weak starsContext] stars in
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0)) starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
Queue.mainQueue().after(2.0) { Queue.mainQueue().after(2.0) {
proceed(false) proceed(upgradeForm.id)
} }
} }
) )
controller.push(purchaseController) controller.push(purchaseController)
}) })
} else { } else {
proceed(false) proceed(upgradeForm.id)
} }
} else {
proceed(true)
} }
} }
} }
@ -298,6 +327,8 @@ private final class GiftViewSheetContent: CombinedComponent {
let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: []) let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: [])
let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
return { context in return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
@ -326,6 +357,8 @@ private final class GiftViewSheetContent: CombinedComponent {
var date: Int32? var date: Int32?
var soldOut = false var soldOut = false
var nameHidden = false var nameHidden = false
var upgraded = false
var canUpgrade = false
var upgradeStars: Int64? var upgradeStars: Int64?
var uniqueGift: StarGift.UniqueGift? var uniqueGift: StarGift.UniqueGift?
if case let .soldOutGift(gift) = subject { if case let .soldOutGift(gift) = subject {
@ -351,6 +384,8 @@ private final class GiftViewSheetContent: CombinedComponent {
converted = arguments.converted converted = arguments.converted
giftId = gift.id giftId = gift.id
date = arguments.date date = arguments.date
upgraded = arguments.upgraded
canUpgrade = arguments.canUpgrade
upgradeStars = arguments.upgradeStars upgradeStars = arguments.upgradeStars
case let .unique(gift): case let .unique(gift):
stars = 0 stars = 0
@ -387,11 +422,27 @@ private final class GiftViewSheetContent: CombinedComponent {
state.cachedOverlayCloseImage = closeOverlayImage state.cachedOverlayCloseImage = closeOverlayImage
} }
var showUpgradePreview = false
if state.inUpgradePreview, let _ = state.sampleGiftAttributes {
showUpgradePreview = true
} else if case .upgradePreview = component.subject {
showUpgradePreview = true
}
let cancel = component.cancel
let closeButton = closeButton.update( let closeButton = closeButton.update(
component: Button( component: Button(
content: AnyComponent(Image(image: state.inUpgradePreview || uniqueGift != nil ? closeOverlayImage : closeImage)), content: AnyComponent(Image(image: showUpgradePreview || uniqueGift != nil ? closeOverlayImage : closeImage)),
action: { [weak component] in action: { [weak state] in
component?.cancel(true) guard let state else {
return
}
if state.inUpgradePreview {
state.inUpgradePreview = false
state.updated(transition: .spring(duration: 0.4))
} else {
cancel(true)
}
} }
), ),
availableSize: CGSize(width: 30.0, height: 30.0), availableSize: CGSize(width: 30.0, height: 30.0),
@ -405,9 +456,12 @@ private final class GiftViewSheetContent: CombinedComponent {
if let uniqueGift { if let uniqueGift {
animationHeight = 240.0 animationHeight = 240.0
animationSubject = .unique(uniqueGift) animationSubject = .unique(uniqueGift)
} else if state.inUpgradePreview, let sampleGiftAttributes = state.sampleGiftAttributes { } else if state.inUpgradePreview, let attributes = state.sampleGiftAttributes {
animationHeight = 258.0 animationHeight = 258.0
animationSubject = .preview(sampleGiftAttributes) animationSubject = .preview(attributes)
} else if case let .upgradePreview(attributes, _) = component.subject {
animationHeight = 258.0
animationSubject = .preview(attributes)
} else if let animationFile { } else if let animationFile {
animationHeight = 210.0 animationHeight = 210.0
animationSubject = .generic(animationFile) animationSubject = .generic(animationFile)
@ -420,7 +474,11 @@ private final class GiftViewSheetContent: CombinedComponent {
component: GiftCompositionComponent( component: GiftCompositionComponent(
context: component.context, context: component.context,
theme: environment.theme, theme: environment.theme,
subject: animationSubject subject: animationSubject,
externalState: giftCompositionExternalState,
requestUpdate: { [weak state] in
state?.updated()
}
), ),
availableSize: CGSize(width: context.availableSize.width, height: animationHeight), availableSize: CGSize(width: context.availableSize.width, height: animationHeight),
transition: .immediate transition: .immediate
@ -431,12 +489,30 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
originY += animationHeight originY += animationHeight
if showUpgradePreview {
let title: String
let description: String
let uniqueText: String
let transferableText: String
let tradableText: String
if case let .upgradePreview(_, name) = component.subject {
title = environment.strings.Gift_Upgrade_IncludeTitle
description = environment.strings.Gift_Upgrade_IncludeDescription(name).string
uniqueText = strings.Gift_Upgrade_Unique_IncludeDescription
transferableText = strings.Gift_Upgrade_Transferable_IncludeDescription
tradableText = strings.Gift_Upgrade_Tradable_IncludeDescription
} else {
title = environment.strings.Gift_Upgrade_Title
description = environment.strings.Gift_Upgrade_Description
uniqueText = strings.Gift_Upgrade_Unique_Description
transferableText = strings.Gift_Upgrade_Transferable_Description
tradableText = strings.Gift_Upgrade_Tradable_Description
}
if state.inUpgradePreview, let _ = state.sampleGiftAttributes {
let upgradeTitle = upgradeTitle.update( let upgradeTitle = upgradeTitle.update(
component: MultilineTextComponent( component: MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: environment.strings.Gift_Upgrade_Title, string: title,
font: Font.bold(20.0), font: Font.bold(20.0),
textColor: .white, textColor: .white,
paragraphAlignment: .center paragraphAlignment: .center
@ -447,18 +523,18 @@ private final class GiftViewSheetContent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate transition: .immediate
) )
context.add(upgradeTitle let descriptionColor: UIColor
.position(CGPoint(x: context.availableSize.width / 2.0, y: 191.0)) if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
.appear(.default(alpha: true)) descriptionColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
.disappear(.default(alpha: true)) } else {
) descriptionColor = UIColor.white.withAlphaComponent(0.6)
}
let upgradeDescription = upgradeDescription.update( let upgradeDescription = upgradeDescription.update(
component: BalancedTextComponent( component: BalancedTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: environment.strings.Gift_Upgrade_Description, string: description,
font: Font.regular(13.0), font: Font.regular(13.0),
textColor: UIColor.white.withAlphaComponent(0.6), textColor: descriptionColor,
paragraphAlignment: .center paragraphAlignment: .center
)), )),
horizontalAlignment: .center, horizontalAlignment: .center,
@ -468,8 +544,18 @@ private final class GiftViewSheetContent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate transition: .immediate
) )
let spacing: CGFloat = 6.0
let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height
context.add(upgradeTitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0)))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
context.add(upgradeDescription context.add(upgradeDescription
.position(CGPoint(x: context.availableSize.width / 2.0, y: 208.0 + upgradeDescription.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0)))
.appear(.default(alpha: true)) .appear(.default(alpha: true))
.disappear(.default(alpha: true)) .disappear(.default(alpha: true))
) )
@ -486,7 +572,7 @@ private final class GiftViewSheetContent: CombinedComponent {
component: AnyComponent(ParagraphComponent( component: AnyComponent(ParagraphComponent(
title: strings.Gift_Upgrade_Unique_Title, title: strings.Gift_Upgrade_Unique_Title,
titleColor: textColor, titleColor: textColor,
text: strings.Gift_Upgrade_Unique_Description, text: uniqueText,
textColor: secondaryTextColor, textColor: secondaryTextColor,
accentColor: linkColor, accentColor: linkColor,
iconName: "Premium/Collectible/Unique", iconName: "Premium/Collectible/Unique",
@ -500,7 +586,7 @@ private final class GiftViewSheetContent: CombinedComponent {
component: AnyComponent(ParagraphComponent( component: AnyComponent(ParagraphComponent(
title: strings.Gift_Upgrade_Transferable_Title, title: strings.Gift_Upgrade_Transferable_Title,
titleColor: textColor, titleColor: textColor,
text: strings.Gift_Upgrade_Transferable_Description, text: transferableText,
textColor: secondaryTextColor, textColor: secondaryTextColor,
accentColor: linkColor, accentColor: linkColor,
iconName: "Premium/Collectible/Transferable", iconName: "Premium/Collectible/Transferable",
@ -514,7 +600,7 @@ private final class GiftViewSheetContent: CombinedComponent {
component: AnyComponent(ParagraphComponent( component: AnyComponent(ParagraphComponent(
title: strings.Gift_Upgrade_Tradable_Title, title: strings.Gift_Upgrade_Tradable_Title,
titleColor: textColor, titleColor: textColor,
text: strings.Gift_Upgrade_Tradable_Description, text: tradableText,
textColor: secondaryTextColor, textColor: secondaryTextColor,
accentColor: linkColor, accentColor: linkColor,
iconName: "Premium/Collectible/Tradable", iconName: "Premium/Collectible/Tradable",
@ -538,55 +624,59 @@ private final class GiftViewSheetContent: CombinedComponent {
originY += upgradePerks.size.height originY += upgradePerks.size.height
originY += 16.0 originY += 16.0
let checkTheme = CheckComponent.Theme( if case .upgradePreview = component.subject {
backgroundColor: theme.list.itemCheckColors.fillColor,
strokeColor: theme.list.itemCheckColors.foregroundColor,
borderColor: theme.list.itemCheckColors.strokeColor,
overlayBorder: false,
hasInset: false,
hasShadow: false
)
let keepInfoText: String
if let nameHidden = subject.arguments?.nameHidden, nameHidden {
keepInfoText = strings.Gift_Upgrade_AddMyName
} else { } else {
keepInfoText = text != nil ? strings.Gift_Upgrade_AddNameAndComment : strings.Gift_Upgrade_AddName let checkTheme = CheckComponent.Theme(
backgroundColor: theme.list.itemCheckColors.fillColor,
strokeColor: theme.list.itemCheckColors.foregroundColor,
borderColor: theme.list.itemCheckColors.strokeColor,
overlayBorder: false,
hasInset: false,
hasShadow: false
)
let keepInfoText: String
if let nameHidden = subject.arguments?.nameHidden, nameHidden {
keepInfoText = strings.Gift_Upgrade_AddMyName
} else {
keepInfoText = text != nil ? strings.Gift_Upgrade_AddNameAndComment : strings.Gift_Upgrade_AddName
}
let upgradeKeepName = upgradeKeepName.update(
component: PlainButtonComponent(
content: AnyComponent(HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
theme: checkTheme,
size: CGSize(width: 18.0, height: 18.0),
selected: state.keepOriginalInfo
))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: keepInfoText, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor))
)))
],
spacing: 10.0
)),
effectAlignment: .center,
action: { [weak state] in
guard let state else {
return
}
state.keepOriginalInfo = !state.keepOriginalInfo
state.updated(transition: .easeInOut(duration: 0.2))
},
animateAlpha: false,
animateScale: false
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 1000.0),
transition: context.transition
)
context.add(upgradeKeepName
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + upgradeKeepName.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += upgradeKeepName.size.height
originY += 18.0
} }
let upgradeKeepName = upgradeKeepName.update(
component: PlainButtonComponent(
content: AnyComponent(HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
theme: checkTheme,
size: CGSize(width: 18.0, height: 18.0),
selected: state.keepOriginalInfo
))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: keepInfoText, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor))
)))
],
spacing: 10.0
)),
effectAlignment: .center,
action: { [weak state] in
guard let state else {
return
}
state.keepOriginalInfo = !state.keepOriginalInfo
state.updated(transition: .easeInOut(duration: 0.2))
},
animateAlpha: false,
animateScale: false
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 1000.0),
transition: context.transition
)
context.add(upgradeKeepName
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + upgradeKeepName.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += upgradeKeepName.size.height
originY += 18.0
} else { } else {
var descriptionText: String var descriptionText: String
if let uniqueGift { if let uniqueGift {
@ -595,12 +685,18 @@ private final class GiftViewSheetContent: CombinedComponent {
} else if soldOut { } else if soldOut {
descriptionText = strings.Gift_View_UnavailableDescription descriptionText = strings.Gift_View_UnavailableDescription
} else if incoming { } else if incoming {
if let convertStars { if let convertStars, !upgraded {
if !converted { if !converted {
descriptionText = strings.Gift_View_KeepUpgradeOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string if canUpgrade || upgradeStars != nil {
descriptionText = strings.Gift_View_KeepUpgradeOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string
} else {
descriptionText = strings.Gift_View_KeepOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string
}
} else { } else {
descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(convertStars))).string descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(convertStars))).string
} }
} else if (canUpgrade || upgradeStars != nil) && !upgraded {
descriptionText = strings.Gift_View_KeepOrUpgradeDescription
} else { } else {
descriptionText = strings.Gift_View_BotDescription descriptionText = strings.Gift_View_BotDescription
} }
@ -660,17 +756,13 @@ private final class GiftViewSheetContent: CombinedComponent {
let textFont: UIFont let textFont: UIFont
let textColor: UIColor let textColor: UIColor
if let uniqueGift { if let _ = uniqueGift {
textFont = Font.regular(13.0) textFont = Font.regular(13.0)
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
var giftTextColor: UIColor? textColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
for attribute in uniqueGift.attributes { } else {
if case let .backdrop(_, _, _, _, textColor, _) = attribute { textColor = UIColor.white.withAlphaComponent(0.6)
giftTextColor = UIColor(rgb: UInt32(bitPattern: textColor))
break
}
} }
textColor = giftTextColor ?? UIColor.white.withAlphaComponent(0.6)
} else { } else {
textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0) textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0)
textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor
@ -1043,7 +1135,11 @@ private final class GiftViewSheetContent: CombinedComponent {
)) ))
} }
let valueString = "⭐️\(presentationStringsFormattedNumber(abs(Int32(stars)), dateTimeFormat.groupingSeparator))" var finalStars = stars
if let upgradeStars, upgradeStars > 0 {
finalStars += upgradeStars
}
let valueString = "⭐️\(presentationStringsFormattedNumber(abs(Int32(finalStars)), dateTimeFormat.groupingSeparator))"
let valueAttributedString = NSMutableAttributedString(string: valueString, font: tableFont, textColor: tableTextColor) let valueAttributedString = NSMutableAttributedString(string: valueString, font: tableFont, textColor: tableTextColor)
let range = (valueAttributedString.string as NSString).range(of: "⭐️") let range = (valueAttributedString.string as NSString).range(of: "⭐️")
if range.location != NSNotFound { if range.location != NSNotFound {
@ -1115,7 +1211,7 @@ private final class GiftViewSheetContent: CombinedComponent {
)) ))
} }
if !soldOut && upgradeStars != nil { if !soldOut && canUpgrade {
var items: [AnyComponentWithIdentity<Empty>] = [] var items: [AnyComponentWithIdentity<Empty>] = []
items.append( items.append(
AnyComponentWithIdentity( AnyComponentWithIdentity(
@ -1190,7 +1286,14 @@ private final class GiftViewSheetContent: CombinedComponent {
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme { if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme) state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
} }
let descriptionText = savedToProfile ? strings.Gift_View_DisplayedInfoHide : strings.Gift_View_HiddenInfo let descriptionText: String
if savedToProfile {
descriptionText = strings.Gift_View_DisplayedInfoHide
} else if let upgradeStars, upgradeStars > 0 && !upgraded {
descriptionText = strings.Gift_View_HiddenInfoShow
} else {
descriptionText = strings.Gift_View_HiddenInfo
}
let textFont = Font.regular(13.0) let textFont = Font.regular(13.0)
let textColor = theme.list.itemSecondaryTextColor let textColor = theme.list.itemSecondaryTextColor
@ -1219,7 +1322,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
}, },
tapAction: { _, _ in tapAction: { _, _ in
component.updateSavedToProfile(false) component.updateSavedToProfile(!savedToProfile)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(1.0, {
component.cancel(false) component.cancel(false)
}) })
@ -1239,11 +1342,15 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
let buttonChild: _UpdatedChildComponent let buttonChild: _UpdatedChildComponent
if state.inUpgradePreview, let upgradeStars = subject.arguments?.upgradeStars { if state.inUpgradePreview {
if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme {
state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme) state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme)
} }
let buttonTitle = upgradeStars > 0 ? "\(strings.Gift_Upgrade_Upgrade) # \(upgradeStars)" : strings.Gift_Upgrade_Confirm var upgradeString = strings.Gift_Upgrade_Upgrade
if let upgradeForm = state.upgradeForm, let price = upgradeForm.invoice.prices.first?.amount {
upgradeString += " # \(price)"
}
let buttonTitle = subject.arguments?.upgradeStars != nil ? strings.Gift_Upgrade_Confirm : upgradeString
let buttonAttributedString = NSMutableAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let buttonAttributedString = NSMutableAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
if let range = buttonAttributedString.string.range(of: "#"), let starImage = state.cachedStarImage?.0 { if let range = buttonAttributedString.string.range(of: "#"), let starImage = state.cachedStarImage?.0 {
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
@ -1259,7 +1366,7 @@ private final class GiftViewSheetContent: CombinedComponent {
cornerRadius: 10.0 cornerRadius: 10.0
), ),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable(0), id: AnyHashable("upgrade"),
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
), ),
isEnabled: true, isEnabled: true,
@ -1270,7 +1377,8 @@ private final class GiftViewSheetContent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition transition: context.transition
) )
} else if incoming && !converted && !savedToProfile { } else if incoming && !converted && !upgraded, let upgradeStars, upgradeStars > 0 {
let buttonTitle = strings.Gift_View_UpgradeForFree
buttonChild = button.update( buttonChild = button.update(
component: ButtonComponent( component: ButtonComponent(
background: ButtonComponent.Background( background: ButtonComponent.Background(
@ -1280,8 +1388,36 @@ private final class GiftViewSheetContent: CombinedComponent {
cornerRadius: 10.0 cornerRadius: 10.0
), ),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable(0), id: AnyHashable("freeUpgrade"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) component: AnyComponent(HStack([
AnyComponentWithIdentity(id: 0, component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(BundleIconComponent(name: "Premium/GiftUpgrade", tintColor: theme.list.itemCheckColors.foregroundColor)))
], spacing: 6.0))
),
isEnabled: true,
displaysProgress: state.inProgress,
action: { [weak state] in
state?.requestUpgradePreview()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
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
),
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))))
), ),
isEnabled: true, isEnabled: true,
displaysProgress: state.inProgress, displaysProgress: state.inProgress,
@ -1301,7 +1437,7 @@ private final class GiftViewSheetContent: CombinedComponent {
cornerRadius: 10.0 cornerRadius: 10.0
), ),
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: AnyHashable(0), id: AnyHashable("ok"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Common_OK, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Common_OK, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
), ),
isEnabled: true, isEnabled: true,
@ -1486,24 +1622,31 @@ public class GiftViewScreen: ViewControllerComponentContainer {
case message(EngineMessage) case message(EngineMessage)
case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift) case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift)
case soldOutGift(StarGift.Gift) case soldOutGift(StarGift.Gift)
case upgradePreview([StarGift.UniqueGift.Attribute], String)
var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?)? { var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?)? {
switch self { switch self {
case let .message(message): case let .message(message):
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction {
switch action.action { switch action.action {
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, _, upgradeStars): case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, _):
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, converted, false, upgradeStars, nil, nil) return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, nil, nil)
case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars): case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _):
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, message.timestamp, nil, nil, nil, false, savedToProfile, false, false, nil, transferStars, canExportDate) return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, message.timestamp, nil, nil, nil, false, savedToProfile, false, true, false, nil, transferStars, canExportDate)
default: default:
return nil return nil
} }
} }
case let .profileGift(peerId, gift): case let .profileGift(peerId, gift):
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate) var upgraded = false
if case .unique = gift.gift {
upgraded = true
}
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, upgraded, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate)
case .soldOutGift: case .soldOutGift:
return nil return nil
case .upgradePreview:
return nil
} }
return nil return nil
} }
@ -1772,6 +1915,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
guard let self else { guard let self else {
return return
} }
self.dismissAllTooltips()
guard let sourceView = self.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: self.view) else { guard let sourceView = self.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: self.view) else {
return return
} }
@ -1818,13 +1963,16 @@ public class GiftViewScreen: ViewControllerComponentContainer {
fileprivate func dismissAllTooltips() { fileprivate func dismissAllTooltips() {
self.window?.forEachController({ controller in self.window?.forEachController({ controller in
if let controller = controller as? TooltipScreen { if let controller = controller as? TooltipScreen {
controller.dismiss() controller.dismiss(inPlace: false)
} }
if let controller = controller as? UndoOverlayController { if let controller = controller as? UndoOverlayController {
controller.dismiss() controller.dismiss()
} }
}) })
self.forEachController({ controller in self.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
if let controller = controller as? UndoOverlayController { if let controller = controller as? UndoOverlayController {
controller.dismiss() controller.dismiss()
} }

View File

@ -222,29 +222,24 @@ public final class PeerInfoCoverComponent: Component {
} }
public func animateTransition() { public func animateTransition() {
if let snapshotLayer = self.backgroundView.layer.snapshotContentTree() { if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() {
self.layer.insertSublayer(snapshotLayer, above: self.backgroundGradientLayer) gradientSnapshotLayer.frame = self.backgroundGradientLayer.frame
if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() { self.layer.insertSublayer(gradientSnapshotLayer, above: self.backgroundGradientLayer)
self.layer.insertSublayer(gradientSnapshotLayer, above: snapshotLayer) gradientSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
snapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in gradientSnapshotLayer.removeFromSuperlayer()
snapshotLayer.removeFromSuperlayer() })
})
gradientSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
gradientSnapshotLayer.removeFromSuperlayer()
})
}
} }
for layer in self.avatarPatternContentLayers { for layer in self.avatarPatternContentLayers {
if let snapshot = layer.snapshotContentTree() { if let _ = layer.contents, let snapshot = layer.snapshotContentTree() {
layer.superlayer?.addSublayer(snapshot) layer.superlayer?.addSublayer(snapshot)
snapshot.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in snapshot.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
snapshot.removeFromSuperlayer() snapshot.removeFromSuperlayer()
}) })
layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
} }
layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
} }
let values: [NSNumber] = [1.0, 1.08, 1.0] let values: [NSNumber] = [1.0, 1.08, 1.0]
self.avatarBackgroundPatternContentsLayer.animateKeyframes(values: values, duration: 0.25, keyPath: "transform.scale") self.avatarBackgroundPatternContentsLayer.animateKeyframes(values: values, duration: 0.25, keyPath: "sublayerTransform.scale")
} }
private func loadPatternFromFile() { private func loadPatternFromFile() {
@ -298,7 +293,10 @@ public final class PeerInfoCoverComponent: Component {
} }
func update(component: PeerInfoCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize { func update(component: PeerInfoCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
if self.component?.subject?.fileId != component.subject?.fileId { let previousComponent = self.component
self.component = component
if previousComponent?.subject?.fileId != component.subject?.fileId {
if let fileId = component.subject?.fileId, fileId != 0 { if let fileId = component.subject?.fileId, fileId != 0 {
if self.patternContentsTarget == nil { if self.patternContentsTarget == nil {
self.patternContentsTarget = PatternContentsTarget(imageUpdated: { [weak self] hadContents in self.patternContentsTarget = PatternContentsTarget(imageUpdated: { [weak self] hadContents in
@ -338,7 +336,6 @@ public final class PeerInfoCoverComponent: Component {
} }
} }
self.component = component
self.state = state self.state = state
let backgroundColor: UIColor let backgroundColor: UIColor

View File

@ -1210,9 +1210,15 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
case .gifts: case .gifts:
title = presentationData.strings.PeerInfo_PaneGifts title = presentationData.strings.PeerInfo_PaneGifts
icons = data?.profileGiftsContext?.currentState?.gifts.prefix(3).compactMap { gift in icons = data?.profileGiftsContext?.currentState?.gifts.prefix(3).compactMap { gift in
if case let .generic(gift) = gift.gift { switch gift.gift {
case let .generic(gift):
return gift.file return gift.file
} else { case let .unique(gift):
for attribute in gift.attributes {
if case let .model(_, file, _) = attribute {
return file
}
}
return nil return nil
} }
} ?? [] } ?? []

View File

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

View File

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

View File

@ -2289,18 +2289,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
var currentBirthdays: [EnginePeer.Id: TelegramBirthday]? var currentBirthdays: [EnginePeer.Id: TelegramBirthday]?
if case let .starGiftTransfer(birthdays, _, _, _, _) = source { if case let .starGiftTransfer(birthdays, _, _, _, _) = source {
if let birthdays, !birthdays.isEmpty { mode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: false)
mode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: true)
currentBirthdays = birthdays
} else {
mode = .starsGifting(birthdays: nil, hasActions: false, showSelf: true)
}
}
if case let .chatList(birthdays) = source, let birthdays, !birthdays.isEmpty {
mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: false)
currentBirthdays = birthdays currentBirthdays = birthdays
} else if case let .settings(birthdays) = source, let birthdays, !birthdays.isEmpty { } else if case let .chatList(birthdays) = source {
mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: false) mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true)
currentBirthdays = birthdays
} else if case let .settings(birthdays) = source {
mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true)
currentBirthdays = birthdays currentBirthdays = birthdays
} else { } else {
mode = .starsGifting(birthdays: nil, hasActions: true, showSelf: false) mode = .starsGifting(birthdays: nil, hasActions: true, showSelf: false)