From b04eee8289363a4ce414250ccb95471a6acea1d1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 27 Nov 2025 19:55:24 +0400 Subject: [PATCH] Update API [skip ci] --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/AccountContext.swift | 1 + .../AccountContext/Sources/Premium.swift | 1 + submodules/TelegramApi/Sources/Api0.swift | 4 +- submodules/TelegramApi/Sources/Api15.swift | 68 +++++ submodules/TelegramApi/Sources/Api25.swift | 18 +- submodules/TelegramApi/Sources/Api39.swift | 44 ++- .../ApiUtils/StoreMessage_Telegram.swift | 2 +- .../ApiUtils/TelegramMediaAction.swift | 13 +- .../StandaloneSendMessage.swift | 2 +- .../Sources/State/ManagedRecentStickers.swift | 3 +- .../Sources/State/PendingMessageManager.swift | 4 +- .../Sources/State/Serialization.swift | 2 +- .../SyncCore_TelegramMediaAction.swift | 27 +- .../TelegramEngine/Messages/ForwardGame.swift | 2 +- .../TelegramEngine/Payments/StarGifts.swift | 39 ++- .../Payments/StarGiftsOffers.swift | 55 ++++ .../TelegramEngine/Payments/Stars.swift | 6 +- .../Payments/TelegramEnginePayments.swift | 8 + .../Sources/ServiceMessageStrings.swift | 6 +- .../ChatMessageGiftBubbleContentNode.swift | 4 +- .../Sources/GiftStoreScreen.swift | 2 - .../Sources/GiftViewScreen.swift | 159 +++++++++- .../Sources/ListActionItemComponent.swift | 29 ++ .../Sources/UserApperanceScreen.swift | 3 +- .../Sources/StarsPurchaseScreen.swift | 8 +- .../Sources/StarsWithdrawalScreen.swift | 276 +++++++++++++----- .../Sources/TabSelectorComponent.swift | 20 +- .../ChatInterfaceStateContextMenus.swift | 2 +- .../Sources/SharedAccountContext.swift | 2 + 30 files changed, 687 insertions(+), 125 deletions(-) create mode 100644 submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsOffers.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 44e63f3062..d19b9a7112 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15432,3 +15432,5 @@ Error: %8$@"; "LiveStream.ErrorMaxAllowedEmoji.Text_1" = "You can send up to %d emoji."; "LiveStream.ErrorMaxAllowedEmoji.Text_any" = "You can send up to %d emoji."; + +"Stars.Purchase.StarGiftOfferInfo" = "Buy Stars to make a gift buy offer."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index fa23c3e2f2..5b2029e260 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1178,6 +1178,7 @@ public enum StarsWithdrawalScreenSubject { case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, completion: (Int64) -> Void) case postSuggestion(channel: EnginePeer, isFromAdmin: Bool, current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void) case postSuggestionModification(current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void) + case starGiftOffer(peer: EnginePeer, gift: StarGift.UniqueGift, completion: (CurrencyAmount, Int32) -> Void) } public enum ChannelMembersSearchControllerMode { diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index a97e750a4f..51a2c3f06a 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -144,6 +144,7 @@ public enum StarsPurchasePurpose: Equatable { case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64) case buyStarGift(requiredStars: Int64) case removeOriginalDetailsStarGift(requiredStars: Int64) + case starGiftOffer(requiredStars: Int64) } public struct PremiumConfiguration { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index c21a005241..fc4b470207 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -621,6 +621,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[-614898352] = { return Api.MessageAction.parse_messageActionStarGift($0) } + dict[2000845012] = { return Api.MessageAction.parse_messageActionStarGiftPurchaseOffer($0) } + dict[1940760427] = { return Api.MessageAction.parse_messageActionStarGiftPurchaseOfferDeclined($0) } dict[-1787656893] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[747579941] = { return Api.MessageAction.parse_messageActionSuggestBirthday($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } @@ -979,7 +981,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) } dict[463097215] = { return Api.StarGift.parse_starGift($0) } - dict[-1329630181] = { return Api.StarGift.parse_starGiftUnique($0) } + dict[-1624264554] = { return Api.StarGift.parse_starGiftUnique($0) } dict[-753154979] = { return Api.StarGiftActiveAuctionState.parse_starGiftActiveAuctionState($0) } dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 40303517e8..a526ce3c8c 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -1070,6 +1070,8 @@ public extension Api { case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, prepaidUpgradeHash: String?, giftMsgId: Int32?, toId: Api.Peer?) + case messageActionStarGiftPurchaseOffer(flags: Int32, gift: Api.StarGift, price: Api.StarsAmount, expiresAt: Int32) + case messageActionStarGiftPurchaseOfferDeclined(flags: Int32, gift: Api.StarGift, price: Api.StarsAmount) case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?, dropOriginalDetailsStars: Int64?) case messageActionSuggestBirthday(birthday: Api.Birthday) case messageActionSuggestProfilePhoto(photo: Api.Photo) @@ -1477,6 +1479,23 @@ public extension Api { if Int(flags) & Int(1 << 15) != 0 {serializeInt32(giftMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 18) != 0 {toId!.serialize(buffer, true)} break + case .messageActionStarGiftPurchaseOffer(let flags, let gift, let price, let expiresAt): + if boxed { + buffer.appendInt32(2000845012) + } + serializeInt32(flags, buffer: buffer, boxed: false) + gift.serialize(buffer, true) + price.serialize(buffer, true) + serializeInt32(expiresAt, buffer: buffer, boxed: false) + break + case .messageActionStarGiftPurchaseOfferDeclined(let flags, let gift, let price): + if boxed { + buffer.appendInt32(1940760427) + } + serializeInt32(flags, buffer: buffer, boxed: false) + gift.serialize(buffer, true) + price.serialize(buffer, true) + break case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt, let dropOriginalDetailsStars): if boxed { buffer.appendInt32(-1787656893) @@ -1680,6 +1699,10 @@ public extension Api { 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 upgradeMsgId, let upgradeStars, let fromId, let peer, let savedId, let prepaidUpgradeHash, let giftMsgId, let toId): 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), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any), ("giftMsgId", giftMsgId as Any), ("toId", toId as Any)]) + case .messageActionStarGiftPurchaseOffer(let flags, let gift, let price, let expiresAt): + return ("messageActionStarGiftPurchaseOffer", [("flags", flags as Any), ("gift", gift as Any), ("price", price as Any), ("expiresAt", expiresAt as Any)]) + case .messageActionStarGiftPurchaseOfferDeclined(let flags, let gift, let price): + return ("messageActionStarGiftPurchaseOfferDeclined", [("flags", flags as Any), ("gift", gift as Any), ("price", price as Any)]) case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt, let dropOriginalDetailsStars): return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleAmount", resaleAmount as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("dropOriginalDetailsStars", dropOriginalDetailsStars as Any)]) case .messageActionSuggestBirthday(let birthday): @@ -2473,6 +2496,51 @@ public extension Api { return nil } } + public static func parse_messageActionStarGiftPurchaseOffer(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarGift? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _3: Api.StarsAmount? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionStarGiftPurchaseOffer(flags: _1!, gift: _2!, price: _3!, expiresAt: _4!) + } + else { + return nil + } + } + public static func parse_messageActionStarGiftPurchaseOfferDeclined(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarGift? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _3: Api.StarsAmount? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionStarGiftPurchaseOfferDeclined(flags: _1!, gift: _2!, price: _3!) + } + else { + return nil + } + } public static func parse_messageActionStarGiftUnique(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 5e4db299ba..4baea64b9d 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -289,7 +289,7 @@ public extension Api { public extension Api { enum StarGift: TypeConstructorDescription { case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?, auctionSlug: String?, giftsPerRound: Int32?) - case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?, peerColor: Api.PeerColor?, hostId: Api.Peer?) + case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?, peerColor: Api.PeerColor?, hostId: Api.Peer?, offerMinStars: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -317,9 +317,9 @@ public extension Api { if Int(flags) & Int(1 << 11) != 0 {serializeString(auctionSlug!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 11) != 0 {serializeInt32(giftsPerRound!, buffer: buffer, boxed: false)} break - case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor, let hostId): + case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor, let hostId, let offerMinStars): if boxed { - buffer.appendInt32(-1329630181) + buffer.appendInt32(-1624264554) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -349,6 +349,7 @@ public extension Api { if Int(flags) & Int(1 << 10) != 0 {themePeer!.serialize(buffer, true)} if Int(flags) & Int(1 << 11) != 0 {peerColor!.serialize(buffer, true)} if Int(flags) & Int(1 << 12) != 0 {hostId!.serialize(buffer, true)} + if Int(flags) & Int(1 << 13) != 0 {serializeInt32(offerMinStars!, buffer: buffer, boxed: false)} break } } @@ -357,8 +358,8 @@ public extension Api { switch self { case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains, let lockedUntilDate, let auctionSlug, let giftsPerRound): return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any), ("lockedUntilDate", lockedUntilDate as Any), ("auctionSlug", auctionSlug as Any), ("giftsPerRound", giftsPerRound as Any)]) - case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor, let hostId): - return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any), ("peerColor", peerColor as Any), ("hostId", hostId as Any)]) + case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor, let hostId, let offerMinStars): + return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any), ("peerColor", peerColor as Any), ("hostId", hostId as Any), ("offerMinStars", offerMinStars as Any)]) } } @@ -486,6 +487,8 @@ public extension Api { if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() { _20 = Api.parse(reader, signature: signature) as? Api.Peer } } + var _21: Int32? + if Int(_1!) & Int(1 << 13) != 0 {_21 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -506,8 +509,9 @@ public extension Api { let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil let _c19 = (Int(_1!) & Int(1 << 11) == 0) || _19 != nil let _c20 = (Int(_1!) & Int(1 << 12) == 0) || _20 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 { - return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18, peerColor: _19, hostId: _20) + let _c21 = (Int(_1!) & Int(1 << 13) == 0) || _21 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 { + return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18, peerColor: _19, hostId: _20, offerMinStars: _21) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index f9ccc43e45..a5ed9e4f95 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -5830,9 +5830,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1104419550) + buffer.appendInt32(326126204) serializeInt32(flags, buffer: buffer, boxed: false) fromPeer.serialize(buffer, true) buffer.appendInt32(481674261) @@ -5852,10 +5852,11 @@ public extension Api.functions.messages { if Int(flags) & Int(1 << 24) != 0 {serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} + if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 20) != 0 {serializeInt32(videoTimestamp!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 23) != 0 {suggestedPost!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("replyTo", String(describing: replyTo)), ("scheduleDate", String(describing: scheduleDate)), ("scheduleRepeatPeriod", String(describing: scheduleRepeatPeriod)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("videoTimestamp", String(describing: videoTimestamp)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("replyTo", String(describing: replyTo)), ("scheduleDate", String(describing: scheduleDate)), ("scheduleRepeatPeriod", String(describing: scheduleRepeatPeriod)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("videoTimestamp", String(describing: videoTimestamp)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -10225,6 +10226,22 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func resolveStarGiftOffer(flags: Int32, offerMsgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-372344804) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(offerMsgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.resolveStarGiftOffer", parameters: [("flags", String(describing: flags)), ("offerMsgId", String(describing: offerMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.payments { static func saveStarGift(flags: Int32, stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -10262,6 +10279,27 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func sendStarGiftOffer(flags: Int32, peer: Api.InputPeer, slug: String, price: Api.StarsAmount, duration: Int32, randomId: Int64, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1883739327) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + price.serialize(buffer, true) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "payments.sendStarGiftOffer", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("slug", String(describing: slug)), ("price", String(describing: price)), ("duration", String(describing: duration)), ("randomId", String(describing: randomId)), ("allowPaidStars", String(describing: allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.payments { static func sendStarsForm(formId: Int64, invoice: Api.InputInvoice) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index b3790e80e2..f32fbbe7b9 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -232,7 +232,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon, .messageActionSuggestedPostSuccess, .messageActionSuggestedPostRefund, .messageActionSuggestBirthday: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon, .messageActionSuggestedPostSuccess, .messageActionSuggestedPostRefund, .messageActionSuggestBirthday, .messageActionStarGiftPurchaseOffer, .messageActionStarGiftPurchaseOfferDeclined: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 8d2c483acf..9d11d920a3 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -200,7 +200,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe guard let gift = StarGift(apiStarGift: apiGift) else { 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, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0)) + 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, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0)) case let .messageActionPaidMessagesRefunded(count, stars): return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars)) case let .messageActionPaidMessagesPrice(flags, stars): @@ -274,6 +274,17 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .suggestedPostRefund(TelegramMediaActionType.SuggestedPostRefund(isUserInitiated: (flags & (1 << 0)) != 0))) case let .messageActionSuggestBirthday(birthday): return TelegramMediaAction(action: .suggestedBirthday(TelegramBirthday(apiBirthday: birthday))) + + case let .messageActionStarGiftPurchaseOffer(flags, apiGift, price, expiresAt): + guard let gift = StarGift(apiStarGift: apiGift) else { + return nil + } + return TelegramMediaAction(action: .starGiftPurchaseOffer(gift: gift, amount: CurrencyAmount(apiAmount: price), expireDate: expiresAt, isAccepted: (flags & (1 << 0)) != 0, isDeclined: (flags & (1 << 1)) != 0)) + case let .messageActionStarGiftPurchaseOfferDeclined(flags, apiGift, price): + guard let gift = StarGift(apiStarGift: apiGift) else { + return nil + } + return TelegramMediaAction(action: .starGiftPurchaseOfferDeclined(gift: gift, amount: CurrencyAmount(apiAmount: price), hasExpired: (flags & (1 << 0)) != 0)) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 387b022a43..c4a52df96a 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -499,7 +499,7 @@ private func sendUploadedMessageContent( } if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) diff --git a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift index bbca2809d5..0413196f78 100644 --- a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift +++ b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift @@ -363,7 +363,8 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne flags: [], themePeerId: nil, peerColor: nil, - hostPeerId: nil + hostPeerId: nil, + minOfferStars: nil ) if let entry = CodableEntry(RecentStarGiftItem(gift)) { items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry)) diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 42c4920826..cbdf0a3a1b 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -1133,7 +1133,7 @@ public final class PendingMessageManager { } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id) - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) } else { assertionFailure() sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) @@ -1831,7 +1831,7 @@ public final class PendingMessageManager { } if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 69094986ec..0a8680497f 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 219 + return 220 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 549e5dd77a..82dcbc27a4 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -256,7 +256,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { 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 starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool, isAuctionAcquired: Bool, toPeerId: EnginePeer.Id?) - case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool) + case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool, fromOffer: Bool) case paidMessagesRefunded(count: Int32, stars: Int64) case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool) case conferenceCall(ConferenceCall) @@ -267,6 +267,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case suggestedPostSuccess(amount: CurrencyAmount) case suggestedPostRefund(SuggestedPostRefund) case suggestedBirthday(TelegramBirthday) + case starGiftPurchaseOffer(gift: StarGift, amount: CurrencyAmount, expireDate: Int32, isAccepted: Bool, isDeclined: Bool) + case starGiftPurchaseOfferDeclined(gift: StarGift, amount: CurrencyAmount, hasExpired: Bool) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -402,7 +404,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else if let stars = decoder.decodeOptionalInt64ForKey("resaleStars") { resaleAmount = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars) } - 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), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false)) + 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), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false), fromOffer: decoder.decodeBoolForKey("fromOffer", orElse: false)) case 46: self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0)) case 47: @@ -434,6 +436,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .suggestedPostRefund(decoder.decodeCodable(SuggestedPostRefund.self, forKey: "s") ?? SuggestedPostRefund(isUserInitiated: true)) case 55: self = .suggestedBirthday(decoder.decodeCodable(TelegramBirthday.self, forKey: "birthday") ?? TelegramBirthday(day: 1, month: 1, year: nil)) + case 56: + self = .starGiftPurchaseOffer(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount")!, expireDate: decoder.decodeInt32ForKey("expireDate", orElse: 0), isAccepted: decoder.decodeBoolForKey("isAccepted", orElse: false), isDeclined: decoder.decodeBoolForKey("isDeclined", orElse: false)) + case 57: + self = .starGiftPurchaseOfferDeclined(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amount")!, hasExpired: decoder.decodeBoolForKey("hasExpired", orElse: false)) default: self = .unknown } @@ -792,7 +798,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "toPeerId") } - case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned): + case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned, fromOffer): encoder.encodeInt32(45, forKey: "_rawValue") encoder.encodeObject(gift, forKey: "gift") encoder.encodeBool(isUpgrade, forKey: "isUpgrade") @@ -846,6 +852,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeNil(forKey: "dropOriginalDetailsStars") } encoder.encodeBool(assigned, forKey: "assigned") + encoder.encodeBool(fromOffer, forKey: "fromOffer") case let .paidMessagesRefunded(count, stars): encoder.encodeInt32(46, forKey: "_rawValue") encoder.encodeInt32(count, forKey: "count") @@ -899,6 +906,18 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case let .suggestedBirthday(birthday): encoder.encodeInt32(55, forKey: "_rawValue") encoder.encodeCodable(birthday, forKey: "birthday") + case let .starGiftPurchaseOffer(gift, amount, expireDate, isAccepted, isDeclined): + encoder.encodeInt32(56, forKey: "_rawValue") + encoder.encodeObject(gift, forKey: "gift") + encoder.encodeCodable(amount, forKey: "amount") + encoder.encodeInt32(expireDate, forKey: "expireDate") + encoder.encodeBool(isAccepted, forKey: "isAccepted") + encoder.encodeBool(isDeclined, forKey: "isDeclined") + case let .starGiftPurchaseOfferDeclined(gift, amount, hasExpired): + encoder.encodeInt32(57, forKey: "_rawValue") + encoder.encodeObject(gift, forKey: "gift") + encoder.encodeCodable(amount, forKey: "amount") + encoder.encodeBool(hasExpired, forKey: "hasExpired") } } @@ -941,7 +960,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { peerIds.append(toPeerId) } return peerIds - case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _): + case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _, _): var peerIds: [PeerId] = [] if let peerId { peerIds.append(peerId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift index 584f8775a8..95e6913650 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift @@ -14,7 +14,7 @@ func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to p flags |= (1 << 13) } - return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: threadId.flatMap { Int32(clamping: $0) }, replyTo: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: nil, allowPaidStars: nil, suggestedPost: nil)) + return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: threadId.flatMap { Int32(clamping: $0) }, replyTo: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, videoTimestamp: nil, allowPaidStars: nil, suggestedPost: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 66b92fb194..dc7ce90d2a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -346,6 +346,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { case themePeerId case peerColor case hostPeerId + case minOfferStars } public struct Flags: OptionSet { @@ -635,8 +636,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding { public let themePeerId: EnginePeer.Id? public let peerColor: PeerCollectibleColor? public let hostPeerId: EnginePeer.Id? + public let minOfferStars: Int64? - public init(id: Int64, giftId: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?, valueAmount: Int64?, valueCurrency: String?, flags: Flags, themePeerId: EnginePeer.Id?, peerColor: PeerCollectibleColor?, hostPeerId: EnginePeer.Id?) { + public init(id: Int64, giftId: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?, valueAmount: Int64?, valueCurrency: String?, flags: Flags, themePeerId: EnginePeer.Id?, peerColor: PeerCollectibleColor?, hostPeerId: EnginePeer.Id?, minOfferStars: Int64?) { self.id = id self.giftId = giftId self.title = title @@ -655,6 +657,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.themePeerId = themePeerId self.peerColor = peerColor self.hostPeerId = hostPeerId + self.minOfferStars = minOfferStars } public init(from decoder: Decoder) throws { @@ -691,6 +694,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.themePeerId = try container.decodeIfPresent(Int64.self, forKey: .themePeerId).flatMap { EnginePeer.Id($0) } self.peerColor = try container.decodeIfPresent(PeerCollectibleColor.self, forKey: .peerColor) self.hostPeerId = try container.decodeIfPresent(Int64.self, forKey: .hostPeerId).flatMap { EnginePeer.Id($0) } + self.minOfferStars = try container.decodeIfPresent(Int64.self, forKey: .minOfferStars) } public init(decoder: PostboxDecoder) { @@ -726,6 +730,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.themePeerId = decoder.decodeOptionalInt64ForKey(CodingKeys.themePeerId.rawValue).flatMap { EnginePeer.Id($0) } self.peerColor = decoder.decodeCodable(PeerCollectibleColor.self, forKey: CodingKeys.peerColor.rawValue) self.hostPeerId = decoder.decodeOptionalInt64ForKey(CodingKeys.hostPeerId.rawValue).flatMap { EnginePeer.Id($0) } + self.minOfferStars = decoder.decodeOptionalInt64ForKey(CodingKeys.minOfferStars.rawValue) } public func encode(to encoder: Encoder) throws { @@ -755,6 +760,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { try container.encodeIfPresent(self.themePeerId?.toInt64(), forKey: .themePeerId) try container.encodeIfPresent(self.peerColor, forKey: .peerColor) try container.encodeIfPresent(self.hostPeerId?.toInt64(), forKey: .hostPeerId) + try container.encodeIfPresent(self.minOfferStars, forKey: .minOfferStars) } public func encode(_ encoder: PostboxEncoder) { @@ -812,6 +818,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding { } else { encoder.encodeNil(forKey: CodingKeys.hostPeerId.rawValue) } + if let minOfferStars = self.minOfferStars { + encoder.encodeInt64(minOfferStars, forKey: CodingKeys.minOfferStars.rawValue) + } else { + encoder.encodeNil(forKey: CodingKeys.minOfferStars.rawValue) + } } public func withResellAmounts(_ resellAmounts: [CurrencyAmount]?) -> UniqueGift { @@ -833,7 +844,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding { flags: self.flags, themePeerId: self.themePeerId, peerColor: self.peerColor, - hostPeerId: self.hostPeerId + hostPeerId: self.hostPeerId, + minOfferStars: self.minOfferStars ) } @@ -856,7 +868,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding { flags: self.flags, themePeerId: self.themePeerId, peerColor: self.peerColor, - hostPeerId: self.hostPeerId + hostPeerId: self.hostPeerId, + minOfferStars: self.minOfferStars ) } @@ -879,7 +892,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding { flags: self.flags, themePeerId: themePeerId, peerColor: self.peerColor, - hostPeerId: self.hostPeerId + hostPeerId: self.hostPeerId, + minOfferStars: self.minOfferStars ) } @@ -902,7 +916,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding { flags: self.flags, themePeerId: self.themePeerId, peerColor: self.peerColor, - hostPeerId: self.hostPeerId + hostPeerId: self.hostPeerId, + minOfferStars: self.minOfferStars ) } } @@ -1027,7 +1042,7 @@ extension StarGift { return nil } self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId, perUserLimit: perUserLimit, lockedUntilDate: lockedUntilDate, auctionSlug: auctionSlug, auctionGiftsPerRound: giftsPerRound)) - case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer, peerColor, hostPeerId): + case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer, peerColor, hostPeerId, minOfferStars): let owner: StarGift.UniqueGift.Owner if let ownerAddress { owner = .address(ownerAddress) @@ -1059,7 +1074,7 @@ extension StarGift { break } - self = .unique(StarGift.UniqueGift(id: id, giftId: giftId, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellAmounts: resellAmounts, resellForTonOnly: (apiFlags & (1 << 7)) != 0, releasedBy: releasedBy?.peerId, valueAmount: valueAmount, valueCurrency: valueCurrency, flags: flags, themePeerId: themePeer?.peerId, peerColor: peerCollectibleColor, hostPeerId: hostPeerId?.peerId)) + self = .unique(StarGift.UniqueGift(id: id, giftId: giftId, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellAmounts: resellAmounts, resellForTonOnly: (apiFlags & (1 << 7)) != 0, releasedBy: releasedBy?.peerId, valueAmount: valueAmount, valueCurrency: valueCurrency, flags: flags, themePeerId: themePeer?.peerId, peerColor: peerCollectibleColor, hostPeerId: hostPeerId?.peerId, minOfferStars: minOfferStars.flatMap { Int64($0) })) } } } @@ -1270,7 +1285,7 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) } var media = currentMessage.media - if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned) = action.action, case let .unique(uniqueGift) = gift { + if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer) = action.action, case let .unique(uniqueGift) = gift { let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo } media = [ TelegramMediaAction( @@ -1290,7 +1305,9 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: nil, - assigned: assigned) + assigned: assigned, + fromOffer: fromOffer + ) ) ] } @@ -1395,7 +1412,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star case let .updateNewMessage(message, _, _): if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) { for media in message.media { - if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _) = action.action, case let .Id(messageId) = message.id { + if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id { let reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) @@ -1422,7 +1439,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star collectionIds: nil, prepaidUpgradeHash: nil, upgradeSeparate: false, - dropOriginalDetailsStars: dropOriginalDetailsStars + dropOriginalDetailsStars: dropOriginalDetailsStars )) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsOffers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsOffers.swift new file mode 100644 index 0000000000..acb147757a --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsOffers.swift @@ -0,0 +1,55 @@ +import Foundation +import Postbox +import MtProtoKit +import SwiftSignalKit +import TelegramApi + +public enum ResolveStarGiftOfferError { + case generic +} + +func _internal_resolveStarGiftOffer(account: Account, messageId: EngineMessage.Id, accept: Bool) -> Signal { + var flags: Int32 = 0 + if !accept { + flags |= (1 << 0) + } + return account.network.request(Api.functions.payments.resolveStarGiftOffer(flags: flags, offerMsgId: messageId.id)) + |> mapError { _ -> ResolveStarGiftOfferError in + return .generic + } + |> mapToSignal { updates -> Signal in + account.stateManager.addUpdates(updates) + return .complete() + } + |> ignoreValues +} + + +public enum SendStarGiftOfferError { + case generic +} + +func _internal_sendStarGiftOffer(account: Account, peerId: EnginePeer.Id, slug: String, amount: CurrencyAmount, duration: Int32, allowPaidStars: Int64?) -> Signal { + var flags: Int32 = 0 + if let _ = allowPaidStars { + flags |= (1 << 0) + } + return account.postbox.transaction { transaction in + return transaction.getPeer(peerId).flatMap(apiInputPeer) + } + |> castError(SendStarGiftOfferError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .fail(.generic) + } + return account.network.request(Api.functions.payments.sendStarGiftOffer(flags: flags, peer: inputPeer, slug: slug, price: amount.apiAmount, duration: duration, randomId: Int64.random(in: .min ..< .max), allowPaidStars: allowPaidStars)) + |> mapError { _ -> SendStarGiftOfferError in + return .generic + } + |> mapToSignal { updates -> Signal in + account.stateManager.addUpdates(updates) + return .complete() + } + } + |> ignoreValues +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 4bddd073cc..902808ae31 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -735,6 +735,9 @@ private extension StarsContext.State.Transaction { if (apiFlags & (1 << 28)) != 0 { flags.insert(.isStarGiftAuctionBid) } + if (apiFlags & (1 << 29)) != 0 { + flags.insert(.isStarGiftOffer) + } let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? [] @@ -793,6 +796,7 @@ public final class StarsContext { public static let isStarGiftDropOriginalDetails = Flags(rawValue: 1 << 12) public static let isStarGiftAuctionBid = Flags(rawValue: 1 << 13) public static let isLiveStreamPaidMessage = Flags(rawValue: 1 << 14) + public static let isStarGiftOffer = Flags(rawValue: 1 << 15) } public enum Peer: Equatable { @@ -1640,7 +1644,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid: receiptMessageId = nil } - } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _) = action.action, case let .Id(messageId) = message.id { + } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id { let reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index f92f22385b..df269f3a72 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -180,6 +180,14 @@ public extension TelegramEngine { public func getStarsTransaction(reference: StarsTransactionReference) -> Signal { return _internal_getStarsTransaction(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, transactionReference: reference) } + + public func resolveStarGiftOffer(messageId: EngineMessage.Id, accept: Bool) -> Signal { + return _internal_resolveStarGiftOffer(account: self.account, messageId: messageId, accept: accept) + } + + public func sendStarGiftOffer(peerId: EnginePeer.Id, slug: String, amount: CurrencyAmount, duration: Int32, allowPaidStars: Int64?) -> Signal { + return _internal_sendStarGiftOffer(account: self.account, peerId: peerId, slug: slug, amount: amount, duration: duration, allowPaidStars: allowPaidStars) + } } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 3855648936..8c972d6962 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1262,7 +1262,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes) } } - case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned): + case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned, _): if case let .unique(gift) = gift { if !forAdditionalServiceMessage && !"".isEmpty { attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor) @@ -1616,6 +1616,10 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, price)._tuple, body: bodyAttributes, argumentAttributes: attributes) } } + case .starGiftPurchaseOffer: + attributedString = nil + case .starGiftPurchaseOfferDeclined: + attributedString = nil case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 4aef351ad9..b65bd9c04a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -282,7 +282,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { switch action.action { case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): releasedBy = gift.releasedBy - case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): releasedBy = gift.releasedBy default: break @@ -686,7 +686,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } } - case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _): + case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _, _): if case let .unique(uniqueGift) = gift { isStarGift = true diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift index fa59cfe9e3..f8fcc34ce1 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift @@ -167,8 +167,6 @@ final class GiftStoreScreenComponent: Component { return } - //, self.state?.starGiftsState?.dataState != .loading - let availableWidth = self.scrollView.bounds.width let availableHeight = self.scrollView.bounds.height let contentOffset = self.scrollView.contentOffset.y diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 57e43cc4c2..ccb673df45 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -720,8 +720,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let dropOriginalDetailsImpl { signal = dropOriginalDetailsImpl(reference) } else { - signal = (context.engine.payments.dropStarGiftOriginalDetails(reference: reference) - |> deliverOnMainQueue) + signal = context.engine.payments.dropStarGiftOriginalDetails(reference: reference) } self.upgradeDisposable = (signal @@ -738,7 +737,7 @@ private final class GiftViewSheetContent: CombinedComponent { let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo } self.subject = .profileGift(peerId, gift.withGift(.unique(uniqueGift.withAttributes(updatedAttributes)))) case let .message(message): - if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned) = action.action, case let .unique(uniqueGift) = gift { + if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer) = action.action, case let .unique(uniqueGift) = gift { let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo } let updatedMedia: [Media] = [ TelegramMediaAction( @@ -758,7 +757,9 @@ private final class GiftViewSheetContent: CombinedComponent { canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: nil, - assigned: assigned) + assigned: assigned, + fromOffer: fromOffer + ) ) ] @@ -1323,7 +1324,7 @@ private final class GiftViewSheetContent: CombinedComponent { } } - func viewUpgradedGift(messageId: EngineMessage.Id) { + func viewUpgradedGift(messageId: EngineMessage.Id, delay: Bool) { guard let controller = self.getController(), let navigationController = controller.navigationController as? NavigationController else { return } @@ -1334,7 +1335,17 @@ private final class GiftViewSheetContent: CombinedComponent { guard let self, let navigationController, let peer else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), keepStack: .always, useExisting: true, purposefulAction: {}, peekData: nil, forceAnimatedScroll: true)) + let action = { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), keepStack: .always, useExisting: true, purposefulAction: {}, peekData: nil, forceAnimatedScroll: true)) + } + + if delay { + Queue.mainQueue().after(0.3) { + action() + } + } else { + action() + } }) } @@ -1425,6 +1436,17 @@ private final class GiftViewSheetContent: CombinedComponent { self?.shareGift() }))) + + //TODO:localize + if case let .unique(uniqueGift) = arguments.gift, case let .peerId(ownerPeerId) = uniqueGift.owner, ownerPeerId != self.context.account.peerId { + items.append(.action(ContextMenuActionItem(text: "Offer to Buy", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: nil) + + self?.openGiftBuyOffer() + }))) + } if gift.flags.contains(.isThemeAvailable) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_View_Context_SetAsTheme, icon: { theme in @@ -2310,6 +2332,118 @@ private final class GiftViewSheetContent: CombinedComponent { controller.switchToNextUpgradable() } } + + func openGiftBuyOffer() { + guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, case let .peerId(ownerPeerId) = uniqueGift.owner, let controller = self.getController() else { + return + } + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: ownerPeerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + let buyController = self.context.sharedContext.makeStarsWithdrawalScreen(context: self.context, subject: .starGiftOffer(peer: peer, gift: uniqueGift, completion: { [weak self] amount, duration in + guard let self else { + return + } + self.commitGiftBuyOffer(peer: peer, price: amount, duration: duration) + })) + controller.push(buyController) + }) + } + + func commitGiftBuyOffer(peer: EnginePeer, price: CurrencyAmount, duration: Int32) { + guard let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { + return + } + + let context = self.context + let proceed = { [weak self, weak starsContext] in + guard let self else { + return + } + self.upgradeDisposable = (context.engine.payments.sendStarGiftOffer(peerId: peer.id, slug: uniqueGift.slug, amount: price, duration: duration, allowPaidStars: nil) + |> deliverOnMainQueue).start(error: { _ in + }, completed: { [weak self, weak starsContext] in + guard let self else { + return + } + Queue.mainQueue().after(0.5) { + starsContext?.load(force: true) + } + self.openPeer(peer, dismiss: true) + }) + } + + if price.currency == .stars, starsState.balance < price.amount { + let _ = (self.starsTopUpOptionsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] options in + guard let self, let controller = self.getController() else { + return + } + let purchaseController = context.sharedContext.makeStarsPurchaseScreen( + context: context, + starsContext: starsContext, + options: options ?? [], + purpose: .starGiftOffer(requiredStars: price.amount.value), + targetPeerId: nil, + customTheme: nil, + completion: { [weak self, weak starsContext] stars in + guard let self, let starsContext else { + return + } + self.inProgress = true + self.updated() + + starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) + let _ = (starsContext.onUpdate + |> deliverOnMainQueue).start(next: { [weak self] in + guard let self else { + return + } + Queue.mainQueue().after(0.1, { [weak self] in + guard let self, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { + return + } + if starsState.balance < price.amount { + self.inProgress = false + self.updated() + + self.commitGiftBuyOffer(peer: peer, price: price, duration: duration) + } else { + proceed() + } + }); + }) + } + ) + controller.push(purchaseController) + }) + } else if price.currency == .ton, let tonState = context.tonContext?.currentState, tonState.balance < price.amount { + guard let controller = self.getController() else { + return + } + let needed = price.amount - tonState.balance + var fragmentUrl = "https://fragment.com/ads/topup" + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { + fragmentUrl = value + } + controller.push(BalanceNeededScreen( + context: self.context, + amount: needed, + buttonAction: { [weak self] in + guard let self else { + return + } + self.context.sharedContext.applicationBindings.openUrl(fragmentUrl) + } + )) + } else { + proceed() + } + } } func makeState() -> State { @@ -4735,7 +4869,14 @@ private final class GiftViewSheetContent: CombinedComponent { transition: .spring(duration: 0.2) ) } else if upgraded, let arguments = subject.arguments, let upgradeMessageIdId = arguments.upgradeMessageId, let originalMessageId = arguments.messageId, !arguments.upgradeSeparate { - let upgradeMessageId = MessageId(peerId: originalMessageId.peerId, namespace: originalMessageId.namespace, id: upgradeMessageIdId) + var delay = false + var peerId: EnginePeer.Id = originalMessageId.peerId + if peerId.isTelegramNotifications { + peerId = component.context.account.peerId + delay = true + } + + let upgradeMessageId = MessageId(peerId: peerId, namespace: originalMessageId.namespace, id: upgradeMessageIdId) let buttonTitle = strings.Gift_View_ViewUpgraded buttonChild = button.update( component: ButtonComponent( @@ -4748,7 +4889,7 @@ private final class GiftViewSheetContent: CombinedComponent { displaysProgress: false, action: { [weak state] in state?.dismiss(animated: true) - state?.viewUpgradedGift(messageId: upgradeMessageId) + state?.viewUpgradedGift(messageId: upgradeMessageId, delay: delay) }), availableSize: buttonSize, transition: context.transition @@ -5216,7 +5357,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { let fromPeerId = senderId ?? message.author?.id return (message.id.peerId, fromPeerId, message.author?.debugDisplayTitle, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate, nil, toPeerId) - case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _): + case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _): var reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift index d9abc93708..05b33fcc04 100644 --- a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift +++ b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift @@ -70,6 +70,7 @@ public final class ListActionItemComponent: Component { case arrow case toggle(Toggle) case activity + case expandArrows case custom(CustomAccessory) } @@ -373,6 +374,8 @@ public final class ListActionItemComponent: Component { } case .arrow: contentRightInset = 30.0 + case .expandArrows: + contentRightInset = 36.0 case .toggle: contentRightInset = 76.0 case .activity: @@ -645,6 +648,32 @@ public final class ListActionItemComponent: Component { } } + if case .expandArrows = component.accessory { + let arrowView: UIImageView + var arrowTransition = transition + if let current = self.arrowView { + arrowView = current + if themeUpdated { + arrowView.image = PresentationResourcesItemList.disclosureOptionArrowsImage(component.theme) + } + } else { + arrowTransition = arrowTransition.withAnimation(.none) + arrowView = UIImageView(image: PresentationResourcesItemList.disclosureOptionArrowsImage(component.theme)) + self.arrowView = arrowView + self.addSubview(arrowView) + } + + if let image = arrowView.image { + let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size) + arrowTransition.setFrame(view: arrowView, frame: arrowFrame) + } + } else { + if let arrowView = self.arrowView { + self.arrowView = nil + arrowView.removeFromSuperview() + } + } + if case let .toggle(toggle) = component.accessory { switch toggle.style { case .regular: diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index 20d3361b50..be6a5431ad 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -777,7 +777,8 @@ final class UserAppearanceScreenComponent: Component { flags: [], themePeerId: nil, peerColor: nil, - hostPeerId: nil + hostPeerId: nil, + minOfferStars: nil ) signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate) } else { diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index d30e7eac64..b0a69117a4 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -253,6 +253,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { textString = strings.Stars_Purchase_BuyStarGiftInfo case .removeOriginalDetailsStarGift: textString = strings.Stars_Purchase_RemoveOriginalDetailsStarGiftInfo + case .starGiftOffer: + textString = strings.Stars_Purchase_StarGiftOfferInfo } let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { contents in @@ -843,7 +845,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { titleText = strings.Stars_Purchase_GetStars case .gift: titleText = strings.Stars_Purchase_GiftStars - case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars), let .buyStarGift(requiredStars), let .removeOriginalDetailsStarGift(requiredStars): + case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars), let .buyStarGift(requiredStars), let .removeOriginalDetailsStarGift(requiredStars), let .starGiftOffer(requiredStars): titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) } @@ -1312,6 +1314,10 @@ private extension StarsPurchasePurpose { return requiredStars case let .buyStarGift(requiredStars): return requiredStars + case let .removeOriginalDetailsStarGift(requiredStars): + return requiredStars + case let .starGiftOffer(requiredStars): + return requiredStars default: return nil } diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 3a3a2ee0b3..a1ebcb086f 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -61,6 +61,7 @@ private final class SheetContent: CombinedComponent { let currencyToggle = Child(TabSelectorComponent.self) let amountSection = Child(ListSectionComponent.self) let amountAdditionalLabel = Child(MultilineTextComponent.self) + let periodSection = Child(ListSectionComponent.self) let timestampSection = Child(ListSectionComponent.self) let onlyTonSection = Child(ListSectionComponent.self) let button = Child(ButtonComponent.self) @@ -177,7 +178,7 @@ private final class SheetContent: CombinedComponent { if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero { let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + amountLabel = "~\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" } case .reaction: titleString = environment.strings.Stars_SendStars_Title @@ -231,10 +232,33 @@ private final class SheetContent: CombinedComponent { switch state.currency { case .stars: let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + amountLabel = "~\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" case .ton: let usdRate = Double(tonUsdRate) / 1000.0 / 1000000.0 - amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + amountLabel = "~\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + } + } + case .starGiftOffer: + titleString = "Offer to Buy" + switch state.currency { + case .stars: + amountTitle = "STARS TO OFFER" + minAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMinStarsAmount, nanos: 0) + case .ton: + amountTitle = "TON TO OFFER" + minAmount = StarsAmount(value: 0, nanos: 0) + } + maxAmount = nil + amountPlaceholder = "Price" + + if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let tonUsdRate = withdrawConfiguration.tonUsdRate, let amount = state.amount, amount > StarsAmount.zero { + switch state.currency { + case .stars: + let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 + amountLabel = "~\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + case .ton: + let usdRate = Double(tonUsdRate) / 1000.0 / 1000000.0 + amountLabel = "~\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" } } } @@ -309,8 +333,12 @@ private final class SheetContent: CombinedComponent { tonBalanceValue = tonBalance } - if case let .suggestedPost(mode, _, _, _) = component.mode { - var displayCurrencySelector = false + let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int) + let starsTitle: String + let tonTitle: String + var displayCurrencySelector = false + switch component.mode { + case let .suggestedPost(mode, _, _, _): switch mode { case let .sender(_, isFromAdmin): if isFromAdmin { @@ -320,74 +348,74 @@ private final class SheetContent: CombinedComponent { displayCurrencySelector = true } } + starsTitle = environment.strings.Chat_PostSuggestion_Suggest_OfferStars + tonTitle = environment.strings.Chat_PostSuggestion_Suggest_OfferTon case .admin: displayCurrencySelector = true + starsTitle = environment.strings.Chat_PostSuggestion_Suggest_RequestStars + tonTitle = environment.strings.Chat_PostSuggestion_Suggest_RequestTon } - - if displayCurrencySelector { - let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int) - let starsTitle: String - let tonTitle: String - switch mode { - case .sender: - starsTitle = environment.strings.Chat_PostSuggestion_Suggest_OfferStars - tonTitle = environment.strings.Chat_PostSuggestion_Suggest_OfferTon - case .admin: - starsTitle = environment.strings.Chat_PostSuggestion_Suggest_RequestStars - tonTitle = environment.strings.Chat_PostSuggestion_Suggest_RequestTon - } - - let currencyToggle = currencyToggle.update( - component: TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: theme.list.itemSecondaryTextColor, - selection: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), - simple: true - ), - theme: theme, - customLayout: TabSelectorComponent.CustomLayout( - font: Font.medium(14.0), - spacing: 10.0 - ), - items: [ - TabSelectorComponent.Item( - id: AnyHashable(0), - content: .component(AnyComponent(CurrencyTabItemComponent(icon: .stars, title: starsTitle, theme: theme))) - ), - TabSelectorComponent.Item( - id: AnyHashable(1), - content: .component(AnyComponent(CurrencyTabItemComponent(icon: .ton, title: tonTitle, theme: theme))) - ) - ], - selectedId: selectedId, - setSelectedId: { [weak state] id in - guard let state else { - return - } - - let currency: CurrencyAmount.Currency - if id == AnyHashable(0) { - currency = .stars - } else { - currency = .ton - } - if state.currency != currency { - state.currency = currency - state.amount = nil - } - state.updated(transition: .spring(duration: 0.4)) - } + case .starGiftOffer: + displayCurrencySelector = true + starsTitle = "Offer Stars" + tonTitle = "Offer TON" + default: + starsTitle = "" + tonTitle = "" + } + + if displayCurrencySelector { + let currencyToggle = currencyToggle.update( + component: TabSelectorComponent( + colors: TabSelectorComponent.Colors( + foreground: theme.list.itemSecondaryTextColor, + selection: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), + simple: true ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0), - transition: context.transition - ) - contentSize.height -= 17.0 - let currencyToggleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - currencyToggle.size.width) * 0.5), y: contentSize.height), size: currencyToggle.size) - context.add(currencyToggle - .position(currencyToggle.size.centered(in: currencyToggleFrame).center)) - - contentSize.height += currencyToggle.size.height + 29.0 - } + theme: theme, + style: .glass, + customLayout: TabSelectorComponent.CustomLayout( + font: Font.medium(14.0), + spacing: 10.0 + ), + items: [ + TabSelectorComponent.Item( + id: AnyHashable(0), + content: .component(AnyComponent(CurrencyTabItemComponent(icon: .stars, title: starsTitle, theme: theme))) + ), + TabSelectorComponent.Item( + id: AnyHashable(1), + content: .component(AnyComponent(CurrencyTabItemComponent(icon: .ton, title: tonTitle, theme: theme))) + ) + ], + selectedId: selectedId, + setSelectedId: { [weak state] id in + guard let state else { + return + } + + let currency: CurrencyAmount.Currency + if id == AnyHashable(0) { + currency = .stars + } else { + currency = .ton + } + if state.currency != currency { + state.currency = currency + state.amount = nil + } + state.updated(transition: .spring(duration: 0.4)) + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0), + transition: context.transition + ) + contentSize.height -= 17.0 + let currencyToggleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - currencyToggle.size.width) * 0.5), y: contentSize.height), size: currencyToggle.size) + context.add(currencyToggle + .position(currencyToggle.size.centered(in: currencyToggleFrame).center)) + + contentSize.height += currencyToggle.size.height + 29.0 } let amountFont = Font.regular(13.0) @@ -446,7 +474,7 @@ private final class SheetContent: CombinedComponent { if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate { let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - amountRightLabel = "≈\(formatTonUsdValue(Int64(starsValue), divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + amountRightLabel = "~\(formatTonUsdValue(Int64(starsValue), divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" } } else { amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo("\(resaleConfiguration.starGiftCommissionStarsPermille / 10)%").string, attributes: amountMarkdownAttributes, textAlignment: .natural)) @@ -458,7 +486,7 @@ private final class SheetContent: CombinedComponent { amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo(tonString).string, attributes: amountMarkdownAttributes, textAlignment: .natural)) if let tonUsdRate = withdrawConfiguration.tonUsdRate { - amountRightLabel = "≈\(formatTonUsdValue(tonValue, divide: true, rate: tonUsdRate, dateTimeFormat: environment.dateTimeFormat))" + amountRightLabel = "~\(formatTonUsdValue(tonValue, divide: true, rate: tonUsdRate, dateTimeFormat: environment.dateTimeFormat))" } } else { amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo("\(resaleConfiguration.starGiftCommissionTonPermille / 10)%").string, attributes: amountMarkdownAttributes, textAlignment: .natural)) @@ -519,6 +547,20 @@ private final class SheetContent: CombinedComponent { maximumNumberOfLines: 0 )) } + case let .starGiftOffer(_, uniqueGift, _): + let giftTitle = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))" + let string: String + switch state.currency { + case .stars: + string = "Enter the number of Stars you'd like to offer for **\(giftTitle)**." + case .ton: + string = "Enter the number of TON you'd like to offer for **\(giftTitle)**." + } + let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(string, attributes: amountMarkdownAttributes, textAlignment: .natural)) + amountFooter = AnyComponent(MultilineTextComponent( + text: .plain(amountInfoString), + maximumNumberOfLines: 0 + )) default: amountFooter = nil } @@ -595,12 +637,14 @@ private final class SheetContent: CombinedComponent { let onlyTonSection = onlyTonSection.update( component: ListSectionComponent( theme: theme, + style: .glass, header: nil, footer: onlyTonFooter, items: [AnyComponentWithIdentity( id: "switch", component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -686,12 +730,14 @@ private final class SheetContent: CombinedComponent { let timestampSection = timestampSection.update( component: ListSectionComponent( theme: theme, + style: .glass, header: nil, footer: timestampFooter, items: [AnyComponentWithIdentity( id: "timestamp", component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -743,6 +789,71 @@ private final class SheetContent: CombinedComponent { .cornerRadius(10.0) ) contentSize.height += timestampSection.size.height + } else if case let .starGiftOffer(peer, _, _) = component.mode { + contentSize.height += 24.0 + + let peerName = peer.compactDisplayTitle + + let footerString: String + switch state.currency { + case .stars: + footerString = "Choose how long **\(peerName)** can accept your offer. When the time expires, your Stars will be automatically refunded." + case .ton: + footerString = "Choose how long **\(peerName)** can accept your offer. When the time expires, your TON will be automatically refunded." + } + + let periodFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(footerString, attributes: amountMarkdownAttributes, textAlignment: .natural)) + let periodFooter = AnyComponent(MultilineTextComponent( + text: .plain(periodFooterString), + maximumNumberOfLines: 0 + )) + + let periodSection = periodSection.update( + component: ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: periodFooter, + items: [AnyComponentWithIdentity( + id: "timestamp", + component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Offer Duration", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "48 Hours", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )))), + accessory: .expandArrows, + action: { _ in + + } + )) + )] + ), + environment: {}, + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), + transition: context.transition + ) + context.add(periodSection + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + periodSection.size.height / 2.0)) + .clipsToBounds(true) + .cornerRadius(10.0) + ) + contentSize.height += periodSection.size.height } contentSize.height += 32.0 @@ -789,6 +900,22 @@ private final class SheetContent: CombinedComponent { case .admin: buttonString = environment.strings.Chat_PostSuggestion_Suggest_UpdateButton } + } else if case .starGiftOffer = component.mode { + if let amount = state.amount { + let currencySymbol: String + let currencyAmount: String + switch state.currency { + case .stars: + currencySymbol = "#" + currencyAmount = presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator) + case .ton: + currencySymbol = "$" + currencyAmount = formatTonAmountText(amount.value, dateTimeFormat: environment.dateTimeFormat, maxDecimalPositions: nil) + } + buttonString = "Offer \(currencySymbol) \(currencyAmount)" + } else { + buttonString = "Offer" + } } else if let amount = state.amount { buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" } else { @@ -913,8 +1040,9 @@ private final class SheetContent: CombinedComponent { } } } - completion(CurrencyAmount(amount: amount, currency: state.currency), state.timestamp) + case let .starGiftOffer(_, _, completion): + completion(CurrencyAmount(amount: amount, currency: state.currency), state.duration) } controller.dismissAnimated() @@ -952,6 +1080,7 @@ private final class SheetContent: CombinedComponent { fileprivate var amount: StarsAmount? fileprivate var currency: CurrencyAmount.Currency = .stars fileprivate var timestamp: Int32? + fileprivate var duration: Int32 = 120 fileprivate var starsBalance: StarsAmount? private var starsStateDisposable: Disposable? @@ -986,6 +1115,8 @@ private final class SheetContent: CombinedComponent { currency = initialValue.currency amount = initialValue.amount self.timestamp = initialTimestamp + case .starGiftOffer: + amount = nil } self.currency = currency @@ -1178,6 +1309,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { case starGiftResell(StarGift.UniqueGift, Bool, completion: (CurrencyAmount) -> Void) case paidMessages(current: Int64, minValue: Int64, fractionAfterCommission: Int, kind: StarsWithdrawalScreenSubject.PaidMessageKind, completion: (Int64) -> Void) case suggestedPost(mode: SuggestedPostMode, price: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void) + case starGiftOffer(peer: EnginePeer, gift: StarGift.UniqueGift, completion: (CurrencyAmount, Int32) -> Void) } private let context: AccountContext diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index 621e2e4c80..7d93cb30f9 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -10,6 +10,11 @@ import AccountContext import TelegramPresentationData public final class TabSelectorComponent: Component { + public enum Style { + case glass + case legacy + } + public final class TransitionHint { public let scrollToEnd: Bool @@ -124,6 +129,7 @@ public final class TabSelectorComponent: Component { public let context: AccountContext? public let colors: Colors public let theme: PresentationTheme + public let style: Style public let customLayout: CustomLayout? public let items: [Item] public let selectedId: AnyHashable? @@ -135,6 +141,7 @@ public final class TabSelectorComponent: Component { context: AccountContext? = nil, colors: Colors, theme: PresentationTheme, + style: Style = .legacy, customLayout: CustomLayout? = nil, items: [Item], selectedId: AnyHashable?, @@ -145,6 +152,7 @@ public final class TabSelectorComponent: Component { self.context = context self.colors = colors self.theme = theme + self.style = style self.customLayout = customLayout self.items = items self.selectedId = selectedId @@ -163,6 +171,9 @@ public final class TabSelectorComponent: Component { if lhs.theme !== rhs.theme { return false } + if lhs.style != rhs.style { + return false + } if lhs.customLayout != rhs.customLayout { return false } @@ -519,7 +530,14 @@ public final class TabSelectorComponent: Component { self.reorderRecognizer?.isEnabled = component.reorderItem != nil - let baseHeight: CGFloat = 28.0 + let baseHeight: CGFloat + switch component.style { + case .glass: + baseHeight = 32.0 + case .legacy: + baseHeight = 28.0 + } + var verticalInset: CGFloat = 0.0 if let customLayout = component.customLayout { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 96c927354b..81195abb78 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1131,7 +1131,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState let sendGiftTitle: String var isIncoming = message.effectivelyIncoming(context.account.peerId) for media in message.media { - if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action { + if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action { if isUpgrade && message.author?.id == context.account.peerId { isIncoming = true } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 716f336389..0a6d10e448 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3805,6 +3805,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mode = .suggestedPost(mode: .sender(channel: channel, isFromAdmin: isFromAdmin), price: current, timestamp: timestamp, completion: completion) case let .postSuggestionModification(current, timestamp, completion): mode = .suggestedPost(mode: .admin, price: current, timestamp: timestamp, completion: completion) + case let .starGiftOffer(peer, gift, completion): + mode = .starGiftOffer(peer: peer, gift: gift, completion: completion) } return StarsWithdrawScreen(context: context, mode: mode) }