From f3eee0c0212eef7a2fefe1a7cc6805bfc7812b49 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 16 Sep 2024 15:51:26 +0400 Subject: [PATCH 01/11] Star gifts API --- .../Sources/ContactSelectionController.swift | 6 +- .../Sources/BotCheckoutController.swift | 10 +- .../Sources/BotCheckoutControllerNode.swift | 11 +- submodules/TelegramApi/Sources/Api0.swift | 10 + submodules/TelegramApi/Sources/Api10.swift | 36 ++ submodules/TelegramApi/Sources/Api14.swift | 42 ++ submodules/TelegramApi/Sources/Api23.swift | 168 +++---- submodules/TelegramApi/Sources/Api24.swift | 474 ++++-------------- submodules/TelegramApi/Sources/Api25.swift | 364 ++++++++++++++ submodules/TelegramApi/Sources/Api33.swift | 240 ++++----- submodules/TelegramApi/Sources/Api34.swift | 340 ++++++------- submodules/TelegramApi/Sources/Api35.swift | 184 +++++++ submodules/TelegramApi/Sources/Api36.swift | 15 + .../ApiUtils/StoreMessage_Telegram.swift | 2 +- .../ApiUtils/TelegramMediaAction.swift | 12 + .../Sources/State/AccountTaskManager.swift | 1 + .../Sources/State/Serialization.swift | 2 +- .../SyncCore/SyncCore_Namespaces.swift | 7 + .../SyncCore_TelegramMediaAction.swift | 22 + .../Payments/BotPaymentForm.swift | 28 +- .../TelegramEngine/Payments/StarGifts.swift | 155 ++++++ .../TelegramEngine/Payments/Stars.swift | 2 + .../Payments/TelegramEnginePayments.swift | 11 + .../Sources/Utils/PeerUtils.swift | 9 + .../Sources/ServiceMessageStrings.swift | 8 + 25 files changed, 1333 insertions(+), 826 deletions(-) create mode 100644 submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift diff --git a/submodules/AccountContext/Sources/ContactSelectionController.swift b/submodules/AccountContext/Sources/ContactSelectionController.swift index c16d295605..243145a8b7 100644 --- a/submodules/AccountContext/Sources/ContactSelectionController.swift +++ b/submodules/AccountContext/Sources/ContactSelectionController.swift @@ -101,8 +101,10 @@ public final class ContactSelectionControllerParams { public let multipleSelection: Bool public let requirePhoneNumbers: Bool public let confirmation: (ContactListPeer) -> Signal + public let openProfile: ((EnginePeer) -> Void)? + public let sendMessage: ((EnginePeer) -> Void)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactSelectionControllerMode = .generic, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: Signal<[ContactListAdditionalOption], NoError> = .single([]), displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, multipleSelection: Bool = false, requirePhoneNumbers: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal = { _ in .single(true) }) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactSelectionControllerMode = .generic, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: Signal<[ContactListAdditionalOption], NoError> = .single([]), displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, multipleSelection: Bool = false, requirePhoneNumbers: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal = { _ in .single(true) }, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) { self.context = context self.updatedPresentationData = updatedPresentationData self.mode = mode @@ -114,5 +116,7 @@ public final class ContactSelectionControllerParams { self.multipleSelection = multipleSelection self.requirePhoneNumbers = requirePhoneNumbers self.confirmation = confirmation + self.openProfile = openProfile + self.sendMessage = sendMessage } } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 988398d999..dba9e62241 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -51,8 +51,14 @@ public final class BotCheckoutController: ViewController { return .generic } |> mapToSignal { paymentForm -> Signal in - return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: paymentForm.paymentBotId)) - |> castError(FetchError.self) + let botPeer: Signal + if let paymentBotId = paymentForm.paymentBotId { + botPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: paymentBotId)) + |> castError(FetchError.self) + } else { + botPeer = .single(nil) + } + return botPeer |> mapToSignal { botPeer -> Signal in if let current = paymentForm.savedInfo { return context.engine.payments.validateBotPaymentForm(saveInfo: true, source: source, formInfo: current) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index 35b4c1f46d..6392b0de64 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -1389,6 +1389,9 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz } let botPeerId = paymentForm.paymentBotId + guard let botPeerId else { + return + } let _ = (context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: botPeerId) ) @@ -1460,15 +1463,15 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz } } - if !liabilityNoticeAccepted { + if !liabilityNoticeAccepted, let paymentBotId = paymentForm.paymentBotId { let botPeer: Signal = self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: paymentForm.paymentBotId) + TelegramEngine.EngineData.Item.Peer.Peer(id: paymentBotId) ) let providerPeer: Signal = paymentForm.providerId.flatMap { self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: $0)) } ?? .single(nil) let _ = (combineLatest( - ApplicationSpecificNotice.getBotPaymentLiability(accountManager: self.context.sharedContext.accountManager, peerId: paymentForm.paymentBotId), + ApplicationSpecificNotice.getBotPaymentLiability(accountManager: self.context.sharedContext.accountManager, peerId: paymentBotId), botPeer, providerPeer ) @@ -1489,7 +1492,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.Checkout_LiabilityAlertTitle, text: paymentText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { if let strongSelf = self { - let _ = ApplicationSpecificNotice.setBotPaymentLiability(accountManager: strongSelf.context.sharedContext.accountManager, peerId: paymentForm.paymentBotId).start() + let _ = ApplicationSpecificNotice.setBotPaymentLiability(accountManager: strongSelf.context.sharedContext.accountManager, peerId: paymentBotId).start() strongSelf.pay(savedCredentialsToken: savedCredentialsToken, liabilityNoticeAccepted: true) } })]), nil) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 2f4860f756..9a5127b5e7 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -378,6 +378,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($0) } dict[-1734841331] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftCode($0) } dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) } + dict[-396206446] = { return Api.InputInvoice.parse_inputInvoiceStarGift($0) } dict[1710230755] = { return Api.InputInvoice.parse_inputInvoiceStars($0) } dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) } dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) } @@ -573,6 +574,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } + dict[-1878231146] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } @@ -888,6 +890,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) } dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } + dict[-1095743646] = { return Api.StarGift.parse_starGift($0) } dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) } dict[-1798404822] = { return Api.StarsGiveawayOption.parse_starsGiveawayOption($0) } dict[1411605001] = { return Api.StarsGiveawayWinnersOption.parse_starsGiveawayWinnersOption($0) } @@ -1333,12 +1336,15 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1130879648] = { return Api.payments.GiveawayInfo.parse_giveawayInfo($0) } dict[-512366993] = { return Api.payments.GiveawayInfo.parse_giveawayInfoResults($0) } dict[-1610250415] = { return Api.payments.PaymentForm.parse_paymentForm($0) } + dict[-1272590367] = { return Api.payments.PaymentForm.parse_paymentFormStarGift($0) } dict[2079764828] = { return Api.payments.PaymentForm.parse_paymentFormStars($0) } dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) } dict[-625215430] = { return Api.payments.PaymentReceipt.parse_paymentReceiptStars($0) } dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) } dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) } dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) } + dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) } + dict[-1551326360] = { return Api.payments.StarGifts.parse_starGiftsNotModified($0) } dict[961445665] = { return Api.payments.StarsRevenueAdsAccountUrl.parse_starsRevenueAdsAccountUrl($0) } dict[-919881925] = { return Api.payments.StarsRevenueStats.parse_starsRevenueStats($0) } dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) } @@ -2013,6 +2019,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.SponsoredMessageReportOption: _1.serialize(buffer, boxed) + case let _1 as Api.StarGift: + _1.serialize(buffer, boxed) case let _1 as Api.StarsGiftOption: _1.serialize(buffer, boxed) case let _1 as Api.StarsGiveawayOption: @@ -2385,6 +2393,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.payments.SavedInfo: _1.serialize(buffer, boxed) + case let _1 as Api.payments.StarGifts: + _1.serialize(buffer, boxed) case let _1 as Api.payments.StarsRevenueAdsAccountUrl: _1.serialize(buffer, boxed) case let _1 as Api.payments.StarsRevenueStats: diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index ed8285c263..d9d45a32ea 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -4,6 +4,7 @@ public extension Api { case inputInvoiceMessage(peer: Api.InputPeer, msgId: Int32) case inputInvoicePremiumGiftCode(purpose: Api.InputStorePaymentPurpose, option: Api.PremiumGiftCodeOption) case inputInvoiceSlug(slug: String) + case inputInvoiceStarGift(flags: Int32, peer: Api.InputPeer, giftId: Int64, message: Api.TextWithEntities?) case inputInvoiceStars(purpose: Api.InputStorePaymentPurpose) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -34,6 +35,15 @@ public extension Api { } serializeString(slug, buffer: buffer, boxed: false) break + case .inputInvoiceStarGift(let flags, let peer, let giftId, let message): + if boxed { + buffer.appendInt32(-396206446) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt64(giftId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} + break case .inputInvoiceStars(let purpose): if boxed { buffer.appendInt32(1710230755) @@ -53,6 +63,8 @@ public extension Api { return ("inputInvoicePremiumGiftCode", [("purpose", purpose as Any), ("option", option as Any)]) case .inputInvoiceSlug(let slug): return ("inputInvoiceSlug", [("slug", slug as Any)]) + case .inputInvoiceStarGift(let flags, let peer, let giftId, let message): + return ("inputInvoiceStarGift", [("flags", flags as Any), ("peer", peer as Any), ("giftId", giftId as Any), ("message", message as Any)]) case .inputInvoiceStars(let purpose): return ("inputInvoiceStars", [("purpose", purpose as Any)]) } @@ -114,6 +126,30 @@ public extension Api { return nil } } + public static func parse_inputInvoiceStarGift(_ reader: BufferReader) -> InputInvoice? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _3: Int64? + _3 = reader.readInt64() + var _4: Api.TextWithEntities? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputInvoice.inputInvoiceStarGift(flags: _1!, peer: _2!, giftId: _3!, message: _4) + } + else { + return nil + } + } public static func parse_inputInvoiceStars(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputStorePaymentPurpose? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index b7de916ed3..f2f22591bd 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -1009,6 +1009,7 @@ public extension Api { case messageActionSetChatTheme(emoticon: String) case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) + case messageActionStarGift(flags: Int32, starsAmount: Int64, giftId: Int64, limitedNumber: Int32?, limitedTotal: Int32?, message: Api.TextWithEntities?) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) @@ -1350,6 +1351,17 @@ public extension Api { serializeInt32(period, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} break + case .messageActionStarGift(let flags, let starsAmount, let giftId, let limitedNumber, let limitedTotal, let message): + if boxed { + buffer.appendInt32(-1878231146) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(starsAmount, buffer: buffer, boxed: false) + serializeInt64(giftId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(limitedNumber!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(limitedTotal!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} + break case .messageActionSuggestProfilePhoto(let photo): if boxed { buffer.appendInt32(1474192222) @@ -1475,6 +1487,8 @@ public extension Api { return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)]) case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) + case .messageActionStarGift(let flags, let starsAmount, let giftId, let limitedNumber, let limitedTotal, let message): + return ("messageActionStarGift", [("flags", flags as Any), ("starsAmount", starsAmount as Any), ("giftId", giftId as Any), ("limitedNumber", limitedNumber as Any), ("limitedTotal", limitedTotal as Any), ("message", message as Any)]) case .messageActionSuggestProfilePhoto(let photo): return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): @@ -2106,6 +2120,34 @@ public extension Api { return nil } } + public static func parse_messageActionStarGift(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } + var _6: Api.TextWithEntities? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MessageAction.messageActionStarGift(flags: _1!, starsAmount: _2!, giftId: _3!, limitedNumber: _4, limitedTotal: _5, message: _6) + } + else { + return nil + } + } public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { var _1: Api.Photo? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 6372cf2fed..33199a5d86 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -572,6 +572,64 @@ public extension Api { } } +public extension Api { + enum StarGift: TypeConstructorDescription { + case starGift(flags: Int32, id: Int64, document: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGift(let flags, let id, let document, let stars, let availabilityRemains, let availabilityTotal): + if boxed { + buffer.appendInt32(-1095743646) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + document.serialize(buffer, true) + serializeInt64(stars, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityRemains!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityTotal!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starGift(let flags, let id, let document, let stars, let availabilityRemains, let availabilityTotal): + return ("starGift", [("flags", flags as Any), ("id", id as Any), ("document", document as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any)]) + } + } + + public static func parse_starGift(_ reader: BufferReader) -> StarGift? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.Document? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Document + } + var _4: Int64? + _4 = reader.readInt64() + var _5: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } + var _6: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.StarGift.starGift(flags: _1!, id: _2!, document: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6) + } + else { + return nil + } + } + + } +} public extension Api { enum StarsGiftOption: TypeConstructorDescription { case starsGiftOption(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64) @@ -1040,113 +1098,3 @@ public extension Api { } } -public extension Api { - enum StarsTransactionPeer: TypeConstructorDescription { - case starsTransactionPeer(peer: Api.Peer) - case starsTransactionPeerAds - case starsTransactionPeerAppStore - case starsTransactionPeerFragment - case starsTransactionPeerPlayMarket - case starsTransactionPeerPremiumBot - case starsTransactionPeerUnsupported - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsTransactionPeer(let peer): - if boxed { - buffer.appendInt32(-670195363) - } - peer.serialize(buffer, true) - break - case .starsTransactionPeerAds: - if boxed { - buffer.appendInt32(1617438738) - } - - break - case .starsTransactionPeerAppStore: - if boxed { - buffer.appendInt32(-1269320843) - } - - break - case .starsTransactionPeerFragment: - if boxed { - buffer.appendInt32(-382740222) - } - - break - case .starsTransactionPeerPlayMarket: - if boxed { - buffer.appendInt32(2069236235) - } - - break - case .starsTransactionPeerPremiumBot: - if boxed { - buffer.appendInt32(621656824) - } - - break - case .starsTransactionPeerUnsupported: - if boxed { - buffer.appendInt32(-1779253276) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .starsTransactionPeer(let peer): - return ("starsTransactionPeer", [("peer", peer as Any)]) - case .starsTransactionPeerAds: - return ("starsTransactionPeerAds", []) - case .starsTransactionPeerAppStore: - return ("starsTransactionPeerAppStore", []) - case .starsTransactionPeerFragment: - return ("starsTransactionPeerFragment", []) - case .starsTransactionPeerPlayMarket: - return ("starsTransactionPeerPlayMarket", []) - case .starsTransactionPeerPremiumBot: - return ("starsTransactionPeerPremiumBot", []) - case .starsTransactionPeerUnsupported: - return ("starsTransactionPeerUnsupported", []) - } - } - - public static func parse_starsTransactionPeer(_ reader: BufferReader) -> StarsTransactionPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - if _c1 { - return Api.StarsTransactionPeer.starsTransactionPeer(peer: _1!) - } - else { - return nil - } - } - public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAds - } - public static func parse_starsTransactionPeerAppStore(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAppStore - } - public static func parse_starsTransactionPeerFragment(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerFragment - } - public static func parse_starsTransactionPeerPlayMarket(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerPlayMarket - } - public static func parse_starsTransactionPeerPremiumBot(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerPremiumBot - } - public static func parse_starsTransactionPeerUnsupported(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerUnsupported - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index f42e53ce09..a9a831d8b5 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -1,3 +1,113 @@ +public extension Api { + enum StarsTransactionPeer: TypeConstructorDescription { + case starsTransactionPeer(peer: Api.Peer) + case starsTransactionPeerAds + case starsTransactionPeerAppStore + case starsTransactionPeerFragment + case starsTransactionPeerPlayMarket + case starsTransactionPeerPremiumBot + case starsTransactionPeerUnsupported + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsTransactionPeer(let peer): + if boxed { + buffer.appendInt32(-670195363) + } + peer.serialize(buffer, true) + break + case .starsTransactionPeerAds: + if boxed { + buffer.appendInt32(1617438738) + } + + break + case .starsTransactionPeerAppStore: + if boxed { + buffer.appendInt32(-1269320843) + } + + break + case .starsTransactionPeerFragment: + if boxed { + buffer.appendInt32(-382740222) + } + + break + case .starsTransactionPeerPlayMarket: + if boxed { + buffer.appendInt32(2069236235) + } + + break + case .starsTransactionPeerPremiumBot: + if boxed { + buffer.appendInt32(621656824) + } + + break + case .starsTransactionPeerUnsupported: + if boxed { + buffer.appendInt32(-1779253276) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starsTransactionPeer(let peer): + return ("starsTransactionPeer", [("peer", peer as Any)]) + case .starsTransactionPeerAds: + return ("starsTransactionPeerAds", []) + case .starsTransactionPeerAppStore: + return ("starsTransactionPeerAppStore", []) + case .starsTransactionPeerFragment: + return ("starsTransactionPeerFragment", []) + case .starsTransactionPeerPlayMarket: + return ("starsTransactionPeerPlayMarket", []) + case .starsTransactionPeerPremiumBot: + return ("starsTransactionPeerPremiumBot", []) + case .starsTransactionPeerUnsupported: + return ("starsTransactionPeerUnsupported", []) + } + } + + public static func parse_starsTransactionPeer(_ reader: BufferReader) -> StarsTransactionPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + if _c1 { + return Api.StarsTransactionPeer.starsTransactionPeer(peer: _1!) + } + else { + return nil + } + } + public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAds + } + public static func parse_starsTransactionPeerAppStore(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAppStore + } + public static func parse_starsTransactionPeerFragment(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerFragment + } + public static func parse_starsTransactionPeerPlayMarket(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPlayMarket + } + public static func parse_starsTransactionPeerPremiumBot(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPremiumBot + } + public static func parse_starsTransactionPeerUnsupported(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerUnsupported + } + + } +} public extension Api { enum StatsAbsValueAndPrev: TypeConstructorDescription { case statsAbsValueAndPrev(current: Double, previous: Double) @@ -1056,367 +1166,3 @@ public extension Api { } } -public extension Api { - indirect enum StoryView: TypeConstructorDescription { - case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) - case storyViewPublicForward(flags: Int32, message: Api.Message) - case storyViewPublicRepost(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyView(let flags, let userId, let date, let reaction): - if boxed { - buffer.appendInt32(-1329730875) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(userId, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {reaction!.serialize(buffer, true)} - break - case .storyViewPublicForward(let flags, let message): - if boxed { - buffer.appendInt32(-1870436597) - } - serializeInt32(flags, buffer: buffer, boxed: false) - message.serialize(buffer, true) - break - case .storyViewPublicRepost(let flags, let peerId, let story): - if boxed { - buffer.appendInt32(-1116418231) - } - serializeInt32(flags, buffer: buffer, boxed: false) - peerId.serialize(buffer, true) - story.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyView(let flags, let userId, let date, let reaction): - return ("storyView", [("flags", flags as Any), ("userId", userId as Any), ("date", date as Any), ("reaction", reaction as Any)]) - case .storyViewPublicForward(let flags, let message): - return ("storyViewPublicForward", [("flags", flags as Any), ("message", message as Any)]) - case .storyViewPublicRepost(let flags, let peerId, let story): - return ("storyViewPublicRepost", [("flags", flags as Any), ("peerId", peerId as Any), ("story", story as Any)]) - } - } - - public static func parse_storyView(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Reaction? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Reaction - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) - } - else { - return nil - } - } - public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Message? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) - } - else { - return nil - } - } - public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.StoryItem? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StoryItem - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum StoryViews: TypeConstructorDescription { - case storyViews(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): - if boxed { - buffer.appendInt32(-1923523370) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(viewsCount, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(forwardsCount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reactions!.count)) - for item in reactions! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(reactionsCount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentViewers!.count)) - for item in recentViewers! { - serializeInt64(item, buffer: buffer, boxed: false) - }} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): - return ("storyViews", [("flags", flags as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactions", reactions as Any), ("reactionsCount", reactionsCount as Any), ("recentViewers", recentViewers as Any)]) - } - } - - public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } - var _4: [Api.ReactionCount]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) - } } - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } - var _6: [Int64]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) - } - else { - return nil - } - } - - } -} -public extension Api { - enum TextWithEntities: TypeConstructorDescription { - case textWithEntities(text: String, entities: [Api.MessageEntity]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .textWithEntities(let text, let entities): - if boxed { - buffer.appendInt32(1964978502) - } - serializeString(text, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .textWithEntities(let text, let entities): - return ("textWithEntities", [("text", text as Any), ("entities", entities as Any)]) - } - } - - public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum Theme: TypeConstructorDescription { - case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): - if boxed { - buffer.appendInt32(-1609668650) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeString(slug, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(settings!.count)) - for item in settings! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 6) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {serializeInt32(installsCount!, buffer: buffer, boxed: false)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): - return ("theme", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("slug", slug as Any), ("title", title as Any), ("document", document as Any), ("settings", settings as Any), ("emoticon", emoticon as Any), ("installsCount", installsCount as Any)]) - } - } - - public static func parse_theme(_ reader: BufferReader) -> Theme? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.Document? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Document - } } - var _7: [Api.ThemeSettings]? - if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self) - } } - var _8: String? - if Int(_1!) & Int(1 << 6) != 0 {_8 = parseString(reader) } - var _9: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9) - } - else { - return nil - } - } - - } -} -public extension Api { - enum ThemeSettings: TypeConstructorDescription { - case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): - if boxed { - buffer.appendInt32(-94849324) - } - serializeInt32(flags, buffer: buffer, boxed: false) - baseTheme.serialize(buffer, true) - serializeInt32(accentColor, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messageColors!.count)) - for item in messageColors! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): - return ("themeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any)]) - } - } - - public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.BaseTheme? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } - var _5: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - var _6: Api.WallPaper? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WallPaper - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 9e55315313..a758260b09 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -1,3 +1,367 @@ +public extension Api { + indirect enum StoryView: TypeConstructorDescription { + case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) + case storyViewPublicForward(flags: Int32, message: Api.Message) + case storyViewPublicRepost(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyView(let flags, let userId, let date, let reaction): + if boxed { + buffer.appendInt32(-1329730875) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {reaction!.serialize(buffer, true)} + break + case .storyViewPublicForward(let flags, let message): + if boxed { + buffer.appendInt32(-1870436597) + } + serializeInt32(flags, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + case .storyViewPublicRepost(let flags, let peerId, let story): + if boxed { + buffer.appendInt32(-1116418231) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peerId.serialize(buffer, true) + story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyView(let flags, let userId, let date, let reaction): + return ("storyView", [("flags", flags as Any), ("userId", userId as Any), ("date", date as Any), ("reaction", reaction as Any)]) + case .storyViewPublicForward(let flags, let message): + return ("storyViewPublicForward", [("flags", flags as Any), ("message", message as Any)]) + case .storyViewPublicRepost(let flags, let peerId, let story): + return ("storyViewPublicRepost", [("flags", flags as Any), ("peerId", peerId as Any), ("story", story as Any)]) + } + } + + public static func parse_storyView(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Reaction? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Reaction + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryView.storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4) + } + else { + return nil + } + } + public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Message? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StoryView.storyViewPublicForward(flags: _1!, message: _2!) + } + else { + return nil + } + } + public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.StoryItem? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StoryItem + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoryView.storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum StoryViews: TypeConstructorDescription { + case storyViews(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): + if boxed { + buffer.appendInt32(-1923523370) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(viewsCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeInt32(forwardsCount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reactions!.count)) + for item in reactions! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(reactionsCount!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(recentViewers!.count)) + for item in recentViewers! { + serializeInt64(item, buffer: buffer, boxed: false) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .storyViews(let flags, let viewsCount, let forwardsCount, let reactions, let reactionsCount, let recentViewers): + return ("storyViews", [("flags", flags as Any), ("viewsCount", viewsCount as Any), ("forwardsCount", forwardsCount as Any), ("reactions", reactions as Any), ("reactionsCount", reactionsCount as Any), ("recentViewers", recentViewers as Any)]) + } + } + + public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt32() } + var _4: [Api.ReactionCount]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + } } + var _5: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt32() } + var _6: [Int64]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.StoryViews.storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6) + } + else { + return nil + } + } + + } +} +public extension Api { + enum TextWithEntities: TypeConstructorDescription { + case textWithEntities(text: String, entities: [Api.MessageEntity]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .textWithEntities(let text, let entities): + if boxed { + buffer.appendInt32(1964978502) + } + serializeString(text, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities.count)) + for item in entities { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .textWithEntities(let text, let entities): + return ("textWithEntities", [("text", text as Any), ("entities", entities as Any)]) + } + } + + public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.TextWithEntities.textWithEntities(text: _1!, entities: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum Theme: TypeConstructorDescription { + case theme(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): + if boxed { + buffer.appendInt32(-1609668650) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(settings!.count)) + for item in settings! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 6) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt32(installsCount!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .theme(let flags, let id, let accessHash, let slug, let title, let document, let settings, let emoticon, let installsCount): + return ("theme", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("slug", slug as Any), ("title", title as Any), ("document", document as Any), ("settings", settings as Any), ("emoticon", emoticon as Any), ("installsCount", installsCount as Any)]) + } + } + + public static func parse_theme(_ reader: BufferReader) -> Theme? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.Document? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _7: [Api.ThemeSettings]? + if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self) + } } + var _8: String? + if Int(_1!) & Int(1 << 6) != 0 {_8 = parseString(reader) } + var _9: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_9 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.Theme.theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9) + } + else { + return nil + } + } + + } +} +public extension Api { + enum ThemeSettings: TypeConstructorDescription { + case themeSettings(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): + if boxed { + buffer.appendInt32(-94849324) + } + serializeInt32(flags, buffer: buffer, boxed: false) + baseTheme.serialize(buffer, true) + serializeInt32(accentColor, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(outboxAccentColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messageColors!.count)) + for item in messageColors! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + if Int(flags) & Int(1 << 1) != 0 {wallpaper!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .themeSettings(let flags, let baseTheme, let accentColor, let outboxAccentColor, let messageColors, let wallpaper): + return ("themeSettings", [("flags", flags as Any), ("baseTheme", baseTheme as Any), ("accentColor", accentColor as Any), ("outboxAccentColor", outboxAccentColor as Any), ("messageColors", messageColors as Any), ("wallpaper", wallpaper as Any)]) + } + } + + public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.BaseTheme? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_4 = reader.readInt32() } + var _5: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + var _6: Api.WallPaper? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WallPaper + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.ThemeSettings.themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6) + } + else { + return nil + } + } + + } +} public extension Api { enum Timezone: TypeConstructorDescription { case timezone(id: String, name: String, utcOffset: Int32) diff --git a/submodules/TelegramApi/Sources/Api33.swift b/submodules/TelegramApi/Sources/Api33.swift index c1f54eeea8..72d9f6b5cd 100644 --- a/submodules/TelegramApi/Sources/Api33.swift +++ b/submodules/TelegramApi/Sources/Api33.swift @@ -679,6 +679,7 @@ public extension Api.payments { public extension Api.payments { enum PaymentForm: TypeConstructorDescription { case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User]) + case paymentFormStarGift(formId: Int64, invoice: Api.Invoice) case paymentFormStars(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -715,6 +716,13 @@ public extension Api.payments { item.serialize(buffer, true) } break + case .paymentFormStarGift(let formId, let invoice): + if boxed { + buffer.appendInt32(-1272590367) + } + serializeInt64(formId, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + break case .paymentFormStars(let flags, let formId, let botId, let title, let description, let photo, let invoice, let users): if boxed { buffer.appendInt32(2079764828) @@ -739,6 +747,8 @@ public extension Api.payments { switch self { case .paymentForm(let flags, let formId, let botId, let title, let description, let photo, let invoice, let providerId, let url, let nativeProvider, let nativeParams, let additionalMethods, let savedInfo, let savedCredentials, let users): return ("paymentForm", [("flags", flags as Any), ("formId", formId as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("providerId", providerId as Any), ("url", url as Any), ("nativeProvider", nativeProvider as Any), ("nativeParams", nativeParams as Any), ("additionalMethods", additionalMethods as Any), ("savedInfo", savedInfo as Any), ("savedCredentials", savedCredentials as Any), ("users", users as Any)]) + case .paymentFormStarGift(let formId, let invoice): + return ("paymentFormStarGift", [("formId", formId as Any), ("invoice", invoice as Any)]) case .paymentFormStars(let flags, let formId, let botId, let title, let description, let photo, let invoice, let users): return ("paymentFormStars", [("flags", flags as Any), ("formId", formId as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("users", users as Any)]) } @@ -811,6 +821,22 @@ public extension Api.payments { return nil } } + public static func parse_paymentFormStarGift(_ reader: BufferReader) -> PaymentForm? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Invoice? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Invoice + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.payments.PaymentForm.paymentFormStarGift(formId: _1!, invoice: _2!) + } + else { + return nil + } + } public static func parse_paymentFormStars(_ reader: BufferReader) -> PaymentForm? { var _1: Int32? _1 = reader.readInt32() @@ -1128,6 +1154,64 @@ public extension Api.payments { } } +public extension Api.payments { + enum StarGifts: TypeConstructorDescription { + case starGifts(hash: Int32, gifts: [Api.StarGift]) + case starGiftsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGifts(let hash, let gifts): + if boxed { + buffer.appendInt32(-1877571094) + } + serializeInt32(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(gifts.count)) + for item in gifts { + item.serialize(buffer, true) + } + break + case .starGiftsNotModified: + if boxed { + buffer.appendInt32(-1551326360) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .starGifts(let hash, let gifts): + return ("starGifts", [("hash", hash as Any), ("gifts", gifts as Any)]) + case .starGiftsNotModified: + return ("starGiftsNotModified", []) + } + } + + public static func parse_starGifts(_ reader: BufferReader) -> StarGifts? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.StarGift]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!) + } + else { + return nil + } + } + public static func parse_starGiftsNotModified(_ reader: BufferReader) -> StarGifts? { + return Api.payments.StarGifts.starGiftsNotModified + } + + } +} public extension Api.payments { enum StarsRevenueAdsAccountUrl: TypeConstructorDescription { case starsRevenueAdsAccountUrl(url: String) @@ -1498,159 +1582,3 @@ public extension Api.phone { } } -public extension Api.phone { - enum GroupCallStreamChannels: TypeConstructorDescription { - case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .groupCallStreamChannels(let channels): - if boxed { - buffer.appendInt32(-790330702) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(channels.count)) - for item in channels { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .groupCallStreamChannels(let channels): - return ("groupCallStreamChannels", [("channels", channels as Any)]) - } - } - - public static func parse_groupCallStreamChannels(_ reader: BufferReader) -> GroupCallStreamChannels? { - var _1: [Api.GroupCallStreamChannel]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallStreamChannel.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(channels: _1!) - } - else { - return nil - } - } - - } -} -public extension Api.phone { - enum GroupCallStreamRtmpUrl: TypeConstructorDescription { - case groupCallStreamRtmpUrl(url: String, key: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .groupCallStreamRtmpUrl(let url, let key): - if boxed { - buffer.appendInt32(767505458) - } - serializeString(url, buffer: buffer, boxed: false) - serializeString(key, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .groupCallStreamRtmpUrl(let url, let key): - return ("groupCallStreamRtmpUrl", [("url", url as Any), ("key", key as Any)]) - } - } - - public static func parse_groupCallStreamRtmpUrl(_ reader: BufferReader) -> GroupCallStreamRtmpUrl? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(url: _1!, key: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.phone { - enum GroupParticipants: TypeConstructorDescription { - case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], nextOffset: String, chats: [Api.Chat], users: [Api.User], version: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): - if boxed { - buffer.appendInt32(-193506890) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(participants.count)) - for item in participants { - item.serialize(buffer, true) - } - serializeString(nextOffset, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - serializeInt32(version, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): - return ("groupParticipants", [("count", count as Any), ("participants", participants as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any), ("version", version as Any)]) - } - } - - public static func parse_groupParticipants(_ reader: BufferReader) -> GroupParticipants? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.GroupCallParticipant]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) - } - var _3: String? - _3 = parseString(reader) - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api34.swift b/submodules/TelegramApi/Sources/Api34.swift index 1e10a978c4..778db2ffd1 100644 --- a/submodules/TelegramApi/Sources/Api34.swift +++ b/submodules/TelegramApi/Sources/Api34.swift @@ -1,3 +1,159 @@ +public extension Api.phone { + enum GroupCallStreamChannels: TypeConstructorDescription { + case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallStreamChannels(let channels): + if boxed { + buffer.appendInt32(-790330702) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(channels.count)) + for item in channels { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCallStreamChannels(let channels): + return ("groupCallStreamChannels", [("channels", channels as Any)]) + } + } + + public static func parse_groupCallStreamChannels(_ reader: BufferReader) -> GroupCallStreamChannels? { + var _1: [Api.GroupCallStreamChannel]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallStreamChannel.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(channels: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.phone { + enum GroupCallStreamRtmpUrl: TypeConstructorDescription { + case groupCallStreamRtmpUrl(url: String, key: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallStreamRtmpUrl(let url, let key): + if boxed { + buffer.appendInt32(767505458) + } + serializeString(url, buffer: buffer, boxed: false) + serializeString(key, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCallStreamRtmpUrl(let url, let key): + return ("groupCallStreamRtmpUrl", [("url", url as Any), ("key", key as Any)]) + } + } + + public static func parse_groupCallStreamRtmpUrl(_ reader: BufferReader) -> GroupCallStreamRtmpUrl? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(url: _1!, key: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.phone { + enum GroupParticipants: TypeConstructorDescription { + case groupParticipants(count: Int32, participants: [Api.GroupCallParticipant], nextOffset: String, chats: [Api.Chat], users: [Api.User], version: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): + if boxed { + buffer.appendInt32(-193506890) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(participants.count)) + for item in participants { + item.serialize(buffer, true) + } + serializeString(nextOffset, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + serializeInt32(version, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupParticipants(let count, let participants, let nextOffset, let chats, let users, let version): + return ("groupParticipants", [("count", count as Any), ("participants", participants as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any), ("version", version as Any)]) + } + } + + public static func parse_groupParticipants(_ reader: BufferReader) -> GroupParticipants? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.GroupCallParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + } + var _3: String? + _3 = parseString(reader) + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.phone.GroupParticipants.groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!) + } + else { + return nil + } + } + + } +} public extension Api.phone { enum JoinAsPeers: TypeConstructorDescription { case joinAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) @@ -1352,187 +1508,3 @@ public extension Api.storage { } } -public extension Api.stories { - enum AllStories: TypeConstructorDescription { - case allStories(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) - case allStoriesNotModified(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): - if boxed { - buffer.appendInt32(1862033025) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - serializeString(state, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peerStories.count)) - for item in peerStories { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - stealthMode.serialize(buffer, true) - break - case .allStoriesNotModified(let flags, let state, let stealthMode): - if boxed { - buffer.appendInt32(291044926) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(state, buffer: buffer, boxed: false) - stealthMode.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): - return ("allStories", [("flags", flags as Any), ("count", count as Any), ("state", state as Any), ("peerStories", peerStories as Any), ("chats", chats as Any), ("users", users as Any), ("stealthMode", stealthMode as Any)]) - case .allStoriesNotModified(let flags, let state, let stealthMode): - return ("allStoriesNotModified", [("flags", flags as Any), ("state", state as Any), ("stealthMode", stealthMode as Any)]) - } - } - - public static func parse_allStories(_ reader: BufferReader) -> AllStories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: [Api.PeerStories]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _7: Api.StoriesStealthMode? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.stories.AllStories.allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!) - } - else { - return nil - } - } - public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.StoriesStealthMode? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.stories.AllStories.allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.stories { - enum FoundStories: TypeConstructorDescription { - case foundStories(flags: Int32, count: Int32, stories: [Api.FoundStory], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .foundStories(let flags, let count, let stories, let nextOffset, let chats, let users): - if boxed { - buffer.appendInt32(-488736969) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stories.count)) - for item in stories { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .foundStories(let flags, let count, let stories, let nextOffset, let chats, let users): - return ("foundStories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) - } - } - - public static func parse_foundStories(_ reader: BufferReader) -> FoundStories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.FoundStory]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FoundStory.self) - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.FoundStories.foundStories(flags: _1!, count: _2!, stories: _3!, nextOffset: _4, chats: _5!, users: _6!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api35.swift b/submodules/TelegramApi/Sources/Api35.swift index 0e9dfa8a85..969678e990 100644 --- a/submodules/TelegramApi/Sources/Api35.swift +++ b/submodules/TelegramApi/Sources/Api35.swift @@ -1,3 +1,187 @@ +public extension Api.stories { + enum AllStories: TypeConstructorDescription { + case allStories(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) + case allStoriesNotModified(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): + if boxed { + buffer.appendInt32(1862033025) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + serializeString(state, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peerStories.count)) + for item in peerStories { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + stealthMode.serialize(buffer, true) + break + case .allStoriesNotModified(let flags, let state, let stealthMode): + if boxed { + buffer.appendInt32(291044926) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(state, buffer: buffer, boxed: false) + stealthMode.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .allStories(let flags, let count, let state, let peerStories, let chats, let users, let stealthMode): + return ("allStories", [("flags", flags as Any), ("count", count as Any), ("state", state as Any), ("peerStories", peerStories as Any), ("chats", chats as Any), ("users", users as Any), ("stealthMode", stealthMode as Any)]) + case .allStoriesNotModified(let flags, let state, let stealthMode): + return ("allStoriesNotModified", [("flags", flags as Any), ("state", state as Any), ("stealthMode", stealthMode as Any)]) + } + } + + public static func parse_allStories(_ reader: BufferReader) -> AllStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: [Api.PeerStories]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _7: Api.StoriesStealthMode? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.stories.AllStories.allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!) + } + else { + return nil + } + } + public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.StoriesStealthMode? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.stories.AllStories.allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.stories { + enum FoundStories: TypeConstructorDescription { + case foundStories(flags: Int32, count: Int32, stories: [Api.FoundStory], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .foundStories(let flags, let count, let stories, let nextOffset, let chats, let users): + if boxed { + buffer.appendInt32(-488736969) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stories.count)) + for item in stories { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .foundStories(let flags, let count, let stories, let nextOffset, let chats, let users): + return ("foundStories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_foundStories(_ reader: BufferReader) -> FoundStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.FoundStory]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FoundStory.self) + } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stories.FoundStories.foundStories(flags: _1!, count: _2!, stories: _3!, nextOffset: _4, chats: _5!, users: _6!) + } + else { + return nil + } + } + + } +} public extension Api.stories { enum PeerStories: TypeConstructorDescription { case peerStories(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User]) diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index a45ef83846..831da74e11 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -8964,6 +8964,21 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func getStarGifts(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1000983152) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGifts", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGifts + } + return result + }) + } +} public extension Api.functions.payments { static func getStarsGiftOptions(flags: Int32, userId: Api.InputUser?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsGiftOption]>) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 663cf616b5..84f95b35cf 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -227,7 +227,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: + 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: 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 f1849166ef..a4712a1cf7 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -150,6 +150,18 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId)) case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId): return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId))) + case let .messageActionStarGift(flags, starsAmount, giftId, limitedNumber, limitedTotal, message): + let text: String? + let entities: [MessageTextEntity]? + switch message { + case let .textWithEntities(textValue, entitiesValue): + text = textValue + entities = messageTextEntitiesFromApiEntities(entitiesValue) + default: + text = nil + entities = nil + } + return TelegramMediaAction(action: .starGift(amount: starsAmount, giftId: giftId, nameHidden: (flags & (1 << 2)) != 0, limitNumber: limitedNumber, limitTotal: limitedTotal, text: text, entities: entities)) } } diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index a0f2ee88a9..bcc226f1d8 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -118,6 +118,7 @@ final class AccountTaskManager { tasks.add(managedDisabledChannelStatusIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(_internal_loadedStickerPack(postbox: self.stateManager.postbox, network: self.stateManager.network, reference: .iconTopicEmoji, forceActualized: true).start()) tasks.add(managedPeerColorUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) + tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index a50963f612..12c50ddd30 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 188 + return 189 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index ce6b31cf53..b8ec6c8e80 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -300,6 +300,7 @@ private enum PreferencesKeyValues: Int32 { case timezoneList = 38 case botBiometricsState = 39 case businessLinks = 40 + case starGifts = 41 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -524,6 +525,12 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.businessLinks.rawValue) return key } + + public static func starGifts() -> ValueBoxKey { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.starGifts.rawValue) + return key + } } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 8bee101314..33a20b4ea1 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -130,6 +130,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String) case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?) + case starGift(amount: Int64, giftId: Int64, nameHidden: Bool, limitNumber: Int32?, limitTotal: Int32?, text: String?, entities: [MessageTextEntity]?) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -250,6 +251,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { giveawayMessageId = MessageId(peerId: boostPeerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId) } self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId) + case 44: + self = .starGift(amount: decoder.decodeInt64ForKey("amount", orElse: 0), giftId: decoder.decodeInt64ForKey("giftId", orElse: 0), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), limitNumber: decoder.decodeOptionalInt32ForKey("limitNumber"), limitTotal: decoder.decodeOptionalInt32ForKey("limitTotal"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities")) default: self = .unknown } @@ -525,6 +528,25 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "giveawayMsgId") } + case let .starGift(amount, giftId, nameHidden, limitNumber, limitTotal, text, entities): + encoder.encodeInt32(44, forKey: "_rawValue") + encoder.encodeInt64(amount, forKey: "amount") + encoder.encodeInt64(giftId, forKey: "giftId") + encoder.encodeBool(nameHidden, forKey: "nameHidden") + if let limitNumber, let limitTotal { + encoder.encodeInt32(limitNumber, forKey: "limitNumber") + encoder.encodeInt32(limitTotal, forKey: "limitTotal") + } else { + encoder.encodeNil(forKey: "limitNumber") + encoder.encodeNil(forKey: "limitTotal") + } + if let text, let entities { + encoder.encodeString(text, forKey: "text") + encoder.encodeObjectArray(entities, forKey: "entities") + } else { + encoder.encodeNil(forKey: "text") + encoder.encodeNil(forKey: "entities") + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 6adeaca770..42cf15b8ea 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -13,6 +13,7 @@ public enum BotPaymentInvoiceSource { case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64) case starsChatSubscription(hash: String) case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32) + case starGift(hideName: Bool, peerId: EnginePeer.Id, giftId: Int64, text: String?, entities: [MessageTextEntity]?) } public struct BotPaymentInvoiceFields: OptionSet { @@ -130,7 +131,7 @@ public struct BotPaymentForm : Equatable { public let canSaveCredentials: Bool public let passwordMissing: Bool public let invoice: BotPaymentInvoice - public let paymentBotId: PeerId + public let paymentBotId: PeerId? public let providerId: PeerId? public let url: String? public let nativeProvider: BotPaymentNativeProvider? @@ -138,7 +139,7 @@ public struct BotPaymentForm : Equatable { public let savedCredentials: [BotPaymentSavedCredentials] public let additionalPaymentMethods: [BotPaymentMethod] - public init(id: Int64, canSaveCredentials: Bool, passwordMissing: Bool, invoice: BotPaymentInvoice, paymentBotId: PeerId, providerId: PeerId?, url: String?, nativeProvider: BotPaymentNativeProvider?, savedInfo: BotPaymentRequestedInfo?, savedCredentials: [BotPaymentSavedCredentials], additionalPaymentMethods: [BotPaymentMethod]) { + public init(id: Int64, canSaveCredentials: Bool, passwordMissing: Bool, invoice: BotPaymentInvoice, paymentBotId: PeerId?, providerId: PeerId?, url: String?, nativeProvider: BotPaymentNativeProvider?, savedInfo: BotPaymentRequestedInfo?, savedCredentials: [BotPaymentSavedCredentials], additionalPaymentMethods: [BotPaymentMethod]) { self.id = id self.canSaveCredentials = canSaveCredentials self.passwordMissing = passwordMissing @@ -345,6 +346,20 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv flags |= (1 << 4) } return .inputInvoiceStars(purpose: .inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users)) + case let .starGift(hideName, peerId, giftId, text, entities): + guard let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) else { + return nil + } + var flags: Int32 = 0 + if hideName { + flags |= (1 << 0) + } + var message: Api.TextWithEntities? + if let text, !text.isEmpty { + flags |= (1 << 1) + message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? []) + } + return .inputInvoiceStarGift(flags: flags, peer: apiPeer, giftId: giftId, message: message) } } @@ -382,6 +397,9 @@ func _internal_fetchBotPaymentInvoice(postbox: Postbox, network: Network, source case let .paymentFormStars(_, _, _, title, description, photo, invoice, _): let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) return TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, flags: [], version: TelegramMediaInvoice.lastVersion) + case let .paymentFormStarGift(_, invoice): + let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) + return TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: parsedInvoice.currency, totalAmount: parsedInvoice.prices.reduce(0, { $0 + $1.amount }), startParam: "", extendedMedia: nil, flags: [], version: TelegramMediaInvoice.lastVersion) } } |> mapError { _ -> BotPaymentFormRequestError in } @@ -452,6 +470,10 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) return BotPaymentForm(id: id, canSaveCredentials: false, passwordMissing: false, invoice: parsedInvoice, paymentBotId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), providerId: nil, url: nil, nativeProvider: nil, savedInfo: nil, savedCredentials: [], additionalPaymentMethods: []) + + case let .paymentFormStarGift(id, invoice): + let parsedInvoice = BotPaymentInvoice(apiInvoice: invoice) + return BotPaymentForm(id: id, canSaveCredentials: false, passwordMissing: false, invoice: parsedInvoice, paymentBotId: nil, providerId: nil, url: nil, nativeProvider: nil, savedInfo: nil, savedCredentials: [], additionalPaymentMethods: []) } } |> mapError { _ -> BotPaymentFormRequestError in } @@ -660,7 +682,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa receiptMessageId = id } } - case .giftCode, .stars, .starsGift, .starsChatSubscription: + case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift: receiptMessageId = nil } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift new file mode 100644 index 0000000000..679b1d222a --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -0,0 +1,155 @@ +import Foundation +import Postbox +import MtProtoKit +import SwiftSignalKit +import TelegramApi + +public final class StarGiftsList: Codable, Equatable { + public let items: [StarGift] + public let hashValue: Int32 + + public init(items: [StarGift], hashValue: Int32) { + self.items = items + self.hashValue = hashValue + } + + public static func ==(lhs: StarGiftsList, rhs: StarGiftsList) -> Bool { + if lhs === rhs { + return true + } + if lhs.items != rhs.items { + return false + } + if lhs.hashValue != rhs.hashValue { + return false + } + return true + } +} + +public struct StarGift: Equatable, Codable { + enum CodingKeys: String, CodingKey { + case id + case file + case price + case availability + } + + public struct Availability: Equatable, Codable { + enum CodingKeys: String, CodingKey { + case remains + case total + } + + public let remains: Int32 + public let total: Int32 + } + + public enum DecodingError: Error { + case generic + } + + public let id: Int64 + public let file: TelegramMediaFile + public let price: Int64 + public let availability: Availability? + + public init(id: Int64, file: TelegramMediaFile, price: Int64, availability: Availability?) { + self.id = id + self.file = file + self.price = price + self.availability = availability + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(Int64.self, forKey: .id) + + if let fileData = try container.decodeIfPresent(Data.self, forKey: .file), let file = PostboxDecoder(buffer: MemoryBuffer(data: fileData)).decodeRootObject() as? TelegramMediaFile { + self.file = file + } else { + throw DecodingError.generic + } + + self.price = try container.decode(Int64.self, forKey: .price) + self.availability = try container.decodeIfPresent(Availability.self, forKey: .availability) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.id, forKey: .id) + + let encoder = PostboxEncoder() + encoder.encodeRootObject(self.file) + let fileData = encoder.makeData() + try container.encode(fileData, forKey: .file) + + try container.encode(self.price, forKey: .price) + try container.encodeIfPresent(self.availability, forKey: .availability) + } +} + +extension StarGift { + init?(apiStarGift: Api.StarGift) { + switch apiStarGift { + case let .starGift(_, id, document, stars, availabilityRemains, availabilityTotal): + var availability: Availability? + if let availabilityRemains, let availabilityTotal { + availability = Availability(remains: availabilityRemains, total: availabilityTotal) + } + guard let file = telegramMediaFileFromApiDocument(document) else { + return nil + } + self.init(id: id, file: file, price: stars, availability: availability) + } + } +} + +func _internal_cachedStarGifts(postbox: Postbox) -> Signal { + let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.starGifts()])) + return postbox.combinedView(keys: [viewKey]) + |> map { views -> StarGiftsList? in + guard let view = views.views[viewKey] as? PreferencesView else { + return nil + } + guard let value = view.values[PreferencesKeys.starGifts()]?.get(StarGiftsList.self) else { + return nil + } + return value + } +} + +func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> Signal { + let updateSignal = _internal_cachedStarGifts(postbox: postbox) + |> take(1) + |> mapToSignal { list -> Signal in + return network.request(Api.functions.payments.getStarGifts(hash: list?.hashValue ?? 0)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + + return postbox.transaction { transaction in + switch result { + case let .starGifts(hash, gifts): + let starGiftsLists = StarGiftsList(items: gifts.compactMap { StarGift(apiStarGift: $0) }, hashValue: hash) + transaction.setPreferencesEntry(key: PreferencesKeys.starGifts(), value: PreferencesEntry(starGiftsLists)) + case .starGiftsNotModified: + break + } + } + |> ignoreValues + } + } + + return updateSignal +} + +func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal { + let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network) + return (poll |> then(.complete() |> suspendAwareDelay(2.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 4036abb1e6..4ca6d68b2b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1288,6 +1288,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot receiptMessageId = nil case .starsChatSubscription: receiptMessageId = nil + case .starGift: + receiptMessageId = nil } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index a36471d4c8..d3211bf067 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -101,5 +101,16 @@ public extension TelegramEngine { public func fulfillStarsSubscription(peerId: EnginePeer.Id, subscriptionId: String) -> Signal { return _internal_fulfillStarsSubscription(account: self.account, peerId: peerId, subscriptionId: subscriptionId) } + + public func cachedStarGifts() -> Signal<[StarGift]?, NoError> { + return _internal_cachedStarGifts(postbox: self.account.postbox) + |> map { starGiftsList in + return starGiftsList?.items + } + } + + public func keepStarGiftsUpdated() -> Signal { + return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network) + } } } diff --git a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift index d993fc3d4c..8b086c7903 100644 --- a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift @@ -430,6 +430,15 @@ public func isServicePeer(_ peer: Peer) -> Bool { } public extension PeerId { + var isTelegramNotifications: Bool { + if self.namespace == Namespaces.Peer.CloudUser { + if self.id._internalGetInt64Value() == 777000 { + return true + } + } + return false + } + var isReplies: Bool { if self.namespace == Namespaces.Peer.CloudUser { if self.id._internalGetInt64Value() == 708513 || self.id._internalGetInt64Value() == 1271266957 { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 6e8bc0a898..d2d9eea5c7 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1051,6 +1051,14 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = mutableString case .prizeStars: attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor) + case let .starGift(amount, _, nameHidden, limitNumber, limitTotal, text, entities): + let _ = amount + let _ = nameHidden + let _ = limitNumber + let _ = limitTotal + let _ = text + let _ = entities + attributedString = nil case .unknown: attributedString = nil } From a0b2af37b0491c72f3b945e852cf2764d9661f90 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 16 Sep 2024 20:45:15 +0400 Subject: [PATCH 02/11] Update API --- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api23.swift | 14 +++++++------- .../TelegramEngine/Payments/StarGifts.swift | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 9a5127b5e7..55a7bcb245 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -890,7 +890,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) } dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } - dict[-1095743646] = { return Api.StarGift.parse_starGift($0) } + dict[-384008227] = { return Api.StarGift.parse_starGift($0) } dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) } dict[-1798404822] = { return Api.StarsGiveawayOption.parse_starsGiveawayOption($0) } dict[1411605001] = { return Api.StarsGiveawayWinnersOption.parse_starsGiveawayWinnersOption($0) } diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 33199a5d86..cf446813c3 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -574,17 +574,17 @@ public extension Api { } public extension Api { enum StarGift: TypeConstructorDescription { - case starGift(flags: Int32, id: Int64, document: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?) + case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGift(let flags, let id, let document, let stars, let availabilityRemains, let availabilityTotal): + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal): if boxed { - buffer.appendInt32(-1095743646) + buffer.appendInt32(-384008227) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) - document.serialize(buffer, true) + sticker.serialize(buffer, true) serializeInt64(stars, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityRemains!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityTotal!, buffer: buffer, boxed: false)} @@ -594,8 +594,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .starGift(let flags, let id, let document, let stars, let availabilityRemains, let availabilityTotal): - return ("starGift", [("flags", flags as Any), ("id", id as Any), ("document", document as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any)]) + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal): + 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)]) } } @@ -621,7 +621,7 @@ public extension Api { let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarGift.starGift(flags: _1!, id: _2!, document: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6) + return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6) } else { return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 679b1d222a..9704887a18 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -92,12 +92,12 @@ public struct StarGift: Equatable, Codable { extension StarGift { init?(apiStarGift: Api.StarGift) { switch apiStarGift { - case let .starGift(_, id, document, stars, availabilityRemains, availabilityTotal): + case let .starGift(_, id, sticker, stars, availabilityRemains, availabilityTotal): var availability: Availability? if let availabilityRemains, let availabilityTotal { availability = Availability(remains: availabilityRemains, total: availabilityTotal) } - guard let file = telegramMediaFileFromApiDocument(document) else { + guard let file = telegramMediaFileFromApiDocument(sticker) else { return nil } self.init(id: id, file: file, price: stars, availability: availability) @@ -123,7 +123,7 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> let updateSignal = _internal_cachedStarGifts(postbox: postbox) |> take(1) |> mapToSignal { list -> Signal in - return network.request(Api.functions.payments.getStarGifts(hash: list?.hashValue ?? 0)) + return network.request(Api.functions.payments.getStarGifts(hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) From 588cdeee7a26c495623a950773487a35d42fa300 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 17 Sep 2024 18:46:22 +0400 Subject: [PATCH 03/11] Update API --- submodules/TelegramApi/Sources/Api0.swift | 10 +- submodules/TelegramApi/Sources/Api10.swift | 18 +- submodules/TelegramApi/Sources/Api14.swift | 44 +-- submodules/TelegramApi/Sources/Api26.swift | 268 ++++---------- submodules/TelegramApi/Sources/Api27.swift | 338 +++++++++++------- submodules/TelegramApi/Sources/Api28.swift | 228 +++++++----- submodules/TelegramApi/Sources/Api29.swift | 152 +++++--- submodules/TelegramApi/Sources/Api30.swift | 172 +++------ submodules/TelegramApi/Sources/Api31.swift | 202 ++++++----- submodules/TelegramApi/Sources/Api32.swift | 170 ++++----- submodules/TelegramApi/Sources/Api33.swift | 218 ++++++----- submodules/TelegramApi/Sources/Api34.swift | 158 ++++++++ submodules/TelegramApi/Sources/Api36.swift | 56 ++- .../ApiUtils/TelegramMediaAction.swift | 7 +- .../SyncCore_TelegramMediaAction.swift | 21 +- .../Payments/BotPaymentForm.swift | 4 +- .../TelegramEngine/Payments/StarGifts.swift | 249 ++++++++++++- .../TelegramEngine/Payments/Stars.swift | 5 +- .../Payments/TelegramEnginePayments.swift | 10 + 19 files changed, 1378 insertions(+), 952 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 55a7bcb245..bb11da56d3 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -378,7 +378,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($0) } dict[-1734841331] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftCode($0) } dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) } - dict[-396206446] = { return Api.InputInvoice.parse_inputInvoiceStarGift($0) } + dict[634962392] = { return Api.InputInvoice.parse_inputInvoiceStarGift($0) } dict[1710230755] = { return Api.InputInvoice.parse_inputInvoiceStars($0) } dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) } dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) } @@ -574,7 +574,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } - dict[-1878231146] = { return Api.MessageAction.parse_messageActionStarGift($0) } + dict[-1682706620] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } @@ -1106,6 +1106,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-862357728] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } + dict[-291202450] = { return Api.UserStarGift.parse_userStarGift($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } dict[1703516023] = { return Api.UserStatus.parse_userStatusLastMonth($0) } dict[1410997530] = { return Api.UserStatus.parse_userStatusLastWeek($0) } @@ -1349,6 +1350,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-919881925] = { return Api.payments.StarsRevenueStats.parse_starsRevenueStats($0) } dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) } dict[-1141231252] = { return Api.payments.StarsStatus.parse_starsStatus($0) } + dict[1801827607] = { return Api.payments.UserStarGifts.parse_userStarGifts($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[541839704] = { return Api.phone.ExportedGroupCallInvite.parse_exportedGroupCallInvite($0) } dict[-1636664659] = { return Api.phone.GroupCall.parse_groupCall($0) } @@ -2101,6 +2103,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.UserProfilePhoto: _1.serialize(buffer, boxed) + case let _1 as Api.UserStarGift: + _1.serialize(buffer, boxed) case let _1 as Api.UserStatus: _1.serialize(buffer, boxed) case let _1 as Api.Username: @@ -2403,6 +2407,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.payments.StarsStatus: _1.serialize(buffer, boxed) + case let _1 as Api.payments.UserStarGifts: + _1.serialize(buffer, boxed) case let _1 as Api.payments.ValidatedRequestedInfo: _1.serialize(buffer, boxed) case let _1 as Api.phone.ExportedGroupCallInvite: diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index d9d45a32ea..531daed1cb 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -4,7 +4,7 @@ public extension Api { case inputInvoiceMessage(peer: Api.InputPeer, msgId: Int32) case inputInvoicePremiumGiftCode(purpose: Api.InputStorePaymentPurpose, option: Api.PremiumGiftCodeOption) case inputInvoiceSlug(slug: String) - case inputInvoiceStarGift(flags: Int32, peer: Api.InputPeer, giftId: Int64, message: Api.TextWithEntities?) + case inputInvoiceStarGift(flags: Int32, userId: Api.InputUser, giftId: Int64, message: Api.TextWithEntities?) case inputInvoiceStars(purpose: Api.InputStorePaymentPurpose) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -35,12 +35,12 @@ public extension Api { } serializeString(slug, buffer: buffer, boxed: false) break - case .inputInvoiceStarGift(let flags, let peer, let giftId, let message): + case .inputInvoiceStarGift(let flags, let userId, let giftId, let message): if boxed { - buffer.appendInt32(-396206446) + buffer.appendInt32(634962392) } serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) + userId.serialize(buffer, true) serializeInt64(giftId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} break @@ -63,8 +63,8 @@ public extension Api { return ("inputInvoicePremiumGiftCode", [("purpose", purpose as Any), ("option", option as Any)]) case .inputInvoiceSlug(let slug): return ("inputInvoiceSlug", [("slug", slug as Any)]) - case .inputInvoiceStarGift(let flags, let peer, let giftId, let message): - return ("inputInvoiceStarGift", [("flags", flags as Any), ("peer", peer as Any), ("giftId", giftId as Any), ("message", message as Any)]) + case .inputInvoiceStarGift(let flags, let userId, let giftId, let message): + return ("inputInvoiceStarGift", [("flags", flags as Any), ("userId", userId as Any), ("giftId", giftId as Any), ("message", message as Any)]) case .inputInvoiceStars(let purpose): return ("inputInvoiceStars", [("purpose", purpose as Any)]) } @@ -129,9 +129,9 @@ public extension Api { public static func parse_inputInvoiceStarGift(_ reader: BufferReader) -> InputInvoice? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.InputPeer? + var _2: Api.InputUser? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputPeer + _2 = Api.parse(reader, signature: signature) as? Api.InputUser } var _3: Int64? _3 = reader.readInt64() @@ -144,7 +144,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.InputInvoice.inputInvoiceStarGift(flags: _1!, peer: _2!, giftId: _3!, message: _4) + return Api.InputInvoice.inputInvoiceStarGift(flags: _1!, userId: _2!, giftId: _3!, message: _4) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index f2f22591bd..7f7ead710e 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -1009,7 +1009,7 @@ public extension Api { case messageActionSetChatTheme(emoticon: String) case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) - case messageActionStarGift(flags: Int32, starsAmount: Int64, giftId: Int64, limitedNumber: Int32?, limitedTotal: Int32?, message: Api.TextWithEntities?) + case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) @@ -1351,16 +1351,14 @@ public extension Api { serializeInt32(period, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} break - case .messageActionStarGift(let flags, let starsAmount, let giftId, let limitedNumber, let limitedTotal, let message): + case .messageActionStarGift(let flags, let gift, let message, let convertStars): if boxed { - buffer.appendInt32(-1878231146) + buffer.appendInt32(-1682706620) } serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(starsAmount, buffer: buffer, boxed: false) - serializeInt64(giftId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(limitedNumber!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(limitedTotal!, buffer: buffer, boxed: false)} + gift.serialize(buffer, true) if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} + serializeInt64(convertStars, buffer: buffer, boxed: false) break case .messageActionSuggestProfilePhoto(let photo): if boxed { @@ -1487,8 +1485,8 @@ public extension Api { return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)]) case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) - case .messageActionStarGift(let flags, let starsAmount, let giftId, let limitedNumber, let limitedTotal, let message): - return ("messageActionStarGift", [("flags", flags as Any), ("starsAmount", starsAmount as Any), ("giftId", giftId as Any), ("limitedNumber", limitedNumber as Any), ("limitedTotal", limitedTotal as Any), ("message", message as Any)]) + case .messageActionStarGift(let flags, let gift, let message, let convertStars): + return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any)]) case .messageActionSuggestProfilePhoto(let photo): return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): @@ -2123,26 +2121,22 @@ public extension Api { public static func parse_messageActionStarGift(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } - var _6: Api.TextWithEntities? + var _2: Api.StarGift? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _3: Api.TextWithEntities? if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } } + var _4: Int64? + _4 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageAction.messageActionStarGift(flags: _1!, starsAmount: _2!, giftId: _3!, limitedNumber: _4, limitedTotal: _5, message: _6) + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index 07aeb17b3b..345153edc1 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -850,6 +850,70 @@ public extension Api { } } +public extension Api { + enum UserStarGift: TypeConstructorDescription { + case userStarGift(flags: Int32, fromId: Int64?, date: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, msgId: Int32?, convertStars: Int64?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .userStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let convertStars): + if boxed { + buffer.appendInt32(-291202450) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt64(fromId!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) + gift.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 {message!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .userStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let convertStars): + return ("userStarGift", [("flags", flags as Any), ("fromId", fromId as Any), ("date", date as Any), ("gift", gift as Any), ("message", message as Any), ("msgId", msgId as Any), ("convertStars", convertStars as Any)]) + } + } + + public static func parse_userStarGift(_ reader: BufferReader) -> UserStarGift? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt64() } + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.StarGift? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _5: Api.TextWithEntities? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } + var _6: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_6 = reader.readInt32() } + var _7: Int64? + if Int(_1!) & Int(1 << 4) != 0 {_7 = reader.readInt64() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.UserStarGift.userStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, convertStars: _7) + } + else { + return nil + } + } + + } +} public extension Api { enum UserStatus: TypeConstructorDescription { case userStatusEmpty @@ -1458,207 +1522,3 @@ public extension Api { } } -public extension Api { - enum WebPage: TypeConstructorDescription { - case webPage(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?) - case webPageEmpty(flags: Int32, id: Int64, url: String?) - case webPageNotModified(flags: Int32, cachedPageViews: Int32?) - case webPagePending(flags: Int32, id: Int64, url: String?, date: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): - if boxed { - buffer.appendInt32(-392411726) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - serializeString(displayUrl, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(type!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(siteName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeString(description!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 4) != 0 {photo!.serialize(buffer, true)} - if Int(flags) & Int(1 << 5) != 0 {serializeString(embedUrl!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 5) != 0 {serializeString(embedType!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedWidth!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedHeight!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 7) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 8) != 0 {serializeString(author!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 9) != 0 {document!.serialize(buffer, true)} - if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)} - if Int(flags) & Int(1 << 12) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes!.count)) - for item in attributes! { - item.serialize(buffer, true) - }} - break - case .webPageEmpty(let flags, let id, let url): - if boxed { - buffer.appendInt32(555358088) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - break - case .webPageNotModified(let flags, let cachedPageViews): - if boxed { - buffer.appendInt32(1930545681) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(cachedPageViews!, buffer: buffer, boxed: false)} - break - case .webPagePending(let flags, let id, let url, let date): - if boxed { - buffer.appendInt32(-1328464313) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - serializeInt32(date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): - return ("webPage", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("displayUrl", displayUrl as Any), ("hash", hash as Any), ("type", type as Any), ("siteName", siteName as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("embedUrl", embedUrl as Any), ("embedType", embedType as Any), ("embedWidth", embedWidth as Any), ("embedHeight", embedHeight as Any), ("duration", duration as Any), ("author", author as Any), ("document", document as Any), ("cachedPage", cachedPage as Any), ("attributes", attributes as Any)]) - case .webPageEmpty(let flags, let id, let url): - return ("webPageEmpty", [("flags", flags as Any), ("id", id as Any), ("url", url as Any)]) - case .webPageNotModified(let flags, let cachedPageViews): - return ("webPageNotModified", [("flags", flags as Any), ("cachedPageViews", cachedPageViews as Any)]) - case .webPagePending(let flags, let id, let url, let date): - return ("webPagePending", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("date", date as Any)]) - } - } - - public static func parse_webPage(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } - var _8: String? - if Int(_1!) & Int(1 << 2) != 0 {_8 = parseString(reader) } - var _9: String? - if Int(_1!) & Int(1 << 3) != 0 {_9 = parseString(reader) } - var _10: Api.Photo? - if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.Photo - } } - var _11: String? - if Int(_1!) & Int(1 << 5) != 0 {_11 = parseString(reader) } - var _12: String? - if Int(_1!) & Int(1 << 5) != 0 {_12 = parseString(reader) } - var _13: Int32? - if Int(_1!) & Int(1 << 6) != 0 {_13 = reader.readInt32() } - var _14: Int32? - if Int(_1!) & Int(1 << 6) != 0 {_14 = reader.readInt32() } - var _15: Int32? - if Int(_1!) & Int(1 << 7) != 0 {_15 = reader.readInt32() } - var _16: String? - if Int(_1!) & Int(1 << 8) != 0 {_16 = parseString(reader) } - var _17: Api.Document? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.Document - } } - var _18: Api.Page? - if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.Page - } } - var _19: [Api.WebPageAttribute]? - if Int(_1!) & Int(1 << 12) != 0 {if let _ = reader.readInt32() { - _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebPageAttribute.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 5) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 5) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 12) == 0) || _19 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { - return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19) - } - else { - return nil - } - } - public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebPage.webPageEmpty(flags: _1!, id: _2!, url: _3) - } - else { - return nil - } - } - public static func parse_webPageNotModified(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.WebPage.webPageNotModified(flags: _1!, cachedPageViews: _2) - } - else { - return nil - } - } - public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebPage.webPagePending(flags: _1!, id: _2!, url: _3, date: _4!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 2518361353..8034ebaa39 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -1,3 +1,207 @@ +public extension Api { + enum WebPage: TypeConstructorDescription { + case webPage(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?) + case webPageEmpty(flags: Int32, id: Int64, url: String?) + case webPageNotModified(flags: Int32, cachedPageViews: Int32?) + case webPagePending(flags: Int32, id: Int64, url: String?, date: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): + if boxed { + buffer.appendInt32(-392411726) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + serializeString(displayUrl, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(type!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(siteName!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeString(description!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 5) != 0 {serializeString(embedUrl!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeString(embedType!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedWidth!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {serializeInt32(embedHeight!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {serializeString(author!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 9) != 0 {document!.serialize(buffer, true)} + if Int(flags) & Int(1 << 10) != 0 {cachedPage!.serialize(buffer, true)} + if Int(flags) & Int(1 << 12) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes!.count)) + for item in attributes! { + item.serialize(buffer, true) + }} + break + case .webPageEmpty(let flags, let id, let url): + if boxed { + buffer.appendInt32(555358088) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + break + case .webPageNotModified(let flags, let cachedPageViews): + if boxed { + buffer.appendInt32(1930545681) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(cachedPageViews!, buffer: buffer, boxed: false)} + break + case .webPagePending(let flags, let id, let url, let date): + if boxed { + buffer.appendInt32(-1328464313) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + serializeInt32(date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .webPage(let flags, let id, let url, let displayUrl, let hash, let type, let siteName, let title, let description, let photo, let embedUrl, let embedType, let embedWidth, let embedHeight, let duration, let author, let document, let cachedPage, let attributes): + return ("webPage", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("displayUrl", displayUrl as Any), ("hash", hash as Any), ("type", type as Any), ("siteName", siteName as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("embedUrl", embedUrl as Any), ("embedType", embedType as Any), ("embedWidth", embedWidth as Any), ("embedHeight", embedHeight as Any), ("duration", duration as Any), ("author", author as Any), ("document", document as Any), ("cachedPage", cachedPage as Any), ("attributes", attributes as Any)]) + case .webPageEmpty(let flags, let id, let url): + return ("webPageEmpty", [("flags", flags as Any), ("id", id as Any), ("url", url as Any)]) + case .webPageNotModified(let flags, let cachedPageViews): + return ("webPageNotModified", [("flags", flags as Any), ("cachedPageViews", cachedPageViews as Any)]) + case .webPagePending(let flags, let id, let url, let date): + return ("webPagePending", [("flags", flags as Any), ("id", id as Any), ("url", url as Any), ("date", date as Any)]) + } + } + + public static func parse_webPage(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + var _7: String? + if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) } + var _8: String? + if Int(_1!) & Int(1 << 2) != 0 {_8 = parseString(reader) } + var _9: String? + if Int(_1!) & Int(1 << 3) != 0 {_9 = parseString(reader) } + var _10: Api.Photo? + if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.Photo + } } + var _11: String? + if Int(_1!) & Int(1 << 5) != 0 {_11 = parseString(reader) } + var _12: String? + if Int(_1!) & Int(1 << 5) != 0 {_12 = parseString(reader) } + var _13: Int32? + if Int(_1!) & Int(1 << 6) != 0 {_13 = reader.readInt32() } + var _14: Int32? + if Int(_1!) & Int(1 << 6) != 0 {_14 = reader.readInt32() } + var _15: Int32? + if Int(_1!) & Int(1 << 7) != 0 {_15 = reader.readInt32() } + var _16: String? + if Int(_1!) & Int(1 << 8) != 0 {_16 = parseString(reader) } + var _17: Api.Document? + if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _18: Api.Page? + if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.Page + } } + var _19: [Api.WebPageAttribute]? + if Int(_1!) & Int(1 << 12) != 0 {if let _ = reader.readInt32() { + _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebPageAttribute.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 5) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 5) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil + let _c19 = (Int(_1!) & Int(1 << 12) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.WebPage.webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19) + } + else { + return nil + } + } + public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WebPage.webPageEmpty(flags: _1!, id: _2!, url: _3) + } + else { + return nil + } + } + public static func parse_webPageNotModified(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.WebPage.webPageNotModified(flags: _1!, cachedPageViews: _2) + } + else { + return nil + } + } + public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebPage.webPagePending(flags: _1!, id: _2!, url: _3, date: _4!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum WebPageAttribute: TypeConstructorDescription { case webPageAttributeStickerSet(flags: Int32, stickers: [Api.Document]) @@ -1230,137 +1434,3 @@ public extension Api.account { } } -public extension Api.account { - enum SentEmailCode: TypeConstructorDescription { - case sentEmailCode(emailPattern: String, length: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sentEmailCode(let emailPattern, let length): - if boxed { - buffer.appendInt32(-2128640689) - } - serializeString(emailPattern, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .sentEmailCode(let emailPattern, let length): - return ("sentEmailCode", [("emailPattern", emailPattern as Any), ("length", length as Any)]) - } - } - - public static func parse_sentEmailCode(_ reader: BufferReader) -> SentEmailCode? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.SentEmailCode.sentEmailCode(emailPattern: _1!, length: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.account { - enum Takeout: TypeConstructorDescription { - case takeout(id: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .takeout(let id): - if boxed { - buffer.appendInt32(1304052993) - } - serializeInt64(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .takeout(let id): - return ("takeout", [("id", id as Any)]) - } - } - - public static func parse_takeout(_ reader: BufferReader) -> Takeout? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.account.Takeout.takeout(id: _1!) - } - else { - return nil - } - } - - } -} -public extension Api.account { - enum Themes: TypeConstructorDescription { - case themes(hash: Int64, themes: [Api.Theme]) - case themesNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .themes(let hash, let themes): - if boxed { - buffer.appendInt32(-1707242387) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(themes.count)) - for item in themes { - item.serialize(buffer, true) - } - break - case .themesNotModified: - if boxed { - buffer.appendInt32(-199313886) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .themes(let hash, let themes): - return ("themes", [("hash", hash as Any), ("themes", themes as Any)]) - case .themesNotModified: - return ("themesNotModified", []) - } - } - - public static func parse_themes(_ reader: BufferReader) -> Themes? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Theme]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Theme.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.Themes.themes(hash: _1!, themes: _2!) - } - else { - return nil - } - } - public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { - return Api.account.Themes.themesNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 7f9b44e542..54e4821b55 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1,3 +1,137 @@ +public extension Api.account { + enum SentEmailCode: TypeConstructorDescription { + case sentEmailCode(emailPattern: String, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sentEmailCode(let emailPattern, let length): + if boxed { + buffer.appendInt32(-2128640689) + } + serializeString(emailPattern, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .sentEmailCode(let emailPattern, let length): + return ("sentEmailCode", [("emailPattern", emailPattern as Any), ("length", length as Any)]) + } + } + + public static func parse_sentEmailCode(_ reader: BufferReader) -> SentEmailCode? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.SentEmailCode.sentEmailCode(emailPattern: _1!, length: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.account { + enum Takeout: TypeConstructorDescription { + case takeout(id: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .takeout(let id): + if boxed { + buffer.appendInt32(1304052993) + } + serializeInt64(id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .takeout(let id): + return ("takeout", [("id", id as Any)]) + } + } + + public static func parse_takeout(_ reader: BufferReader) -> Takeout? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.account.Takeout.takeout(id: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.account { + enum Themes: TypeConstructorDescription { + case themes(hash: Int64, themes: [Api.Theme]) + case themesNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .themes(let hash, let themes): + if boxed { + buffer.appendInt32(-1707242387) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(themes.count)) + for item in themes { + item.serialize(buffer, true) + } + break + case .themesNotModified: + if boxed { + buffer.appendInt32(-199313886) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .themes(let hash, let themes): + return ("themes", [("hash", hash as Any), ("themes", themes as Any)]) + case .themesNotModified: + return ("themesNotModified", []) + } + } + + public static func parse_themes(_ reader: BufferReader) -> Themes? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Theme]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Theme.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.Themes.themes(hash: _1!, themes: _2!) + } + else { + return nil + } + } + public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { + return Api.account.Themes.themesNotModified + } + + } +} public extension Api.account { enum TmpPassword: TypeConstructorDescription { case tmpPassword(tmpPassword: Buffer, validUntil: Int32) @@ -876,97 +1010,3 @@ public extension Api.auth { } } -public extension Api.bots { - enum BotInfo: TypeConstructorDescription { - case botInfo(name: String, about: String, description: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botInfo(let name, let about, let description): - if boxed { - buffer.appendInt32(-391678544) - } - serializeString(name, buffer: buffer, boxed: false) - serializeString(about, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botInfo(let name, let about, let description): - return ("botInfo", [("name", name as Any), ("about", about as Any), ("description", description as Any)]) - } - } - - public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.bots.BotInfo.botInfo(name: _1!, about: _2!, description: _3!) - } - else { - return nil - } - } - - } -} -public extension Api.bots { - enum PopularAppBots: TypeConstructorDescription { - case popularAppBots(flags: Int32, nextOffset: String?, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .popularAppBots(let flags, let nextOffset, let users): - if boxed { - buffer.appendInt32(428978491) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .popularAppBots(let flags, let nextOffset, let users): - return ("popularAppBots", [("flags", flags as Any), ("nextOffset", nextOffset as Any), ("users", users as Any)]) - } - } - - public static func parse_popularAppBots(_ reader: BufferReader) -> PopularAppBots? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.bots.PopularAppBots.popularAppBots(flags: _1!, nextOffset: _2, users: _3!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 36dea196a1..ea00fe9e2c 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1,3 +1,97 @@ +public extension Api.bots { + enum BotInfo: TypeConstructorDescription { + case botInfo(name: String, about: String, description: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botInfo(let name, let about, let description): + if boxed { + buffer.appendInt32(-391678544) + } + serializeString(name, buffer: buffer, boxed: false) + serializeString(about, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botInfo(let name, let about, let description): + return ("botInfo", [("name", name as Any), ("about", about as Any), ("description", description as Any)]) + } + } + + public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.bots.BotInfo.botInfo(name: _1!, about: _2!, description: _3!) + } + else { + return nil + } + } + + } +} +public extension Api.bots { + enum PopularAppBots: TypeConstructorDescription { + case popularAppBots(flags: Int32, nextOffset: String?, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .popularAppBots(let flags, let nextOffset, let users): + if boxed { + buffer.appendInt32(428978491) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .popularAppBots(let flags, let nextOffset, let users): + return ("popularAppBots", [("flags", flags as Any), ("nextOffset", nextOffset as Any), ("users", users as Any)]) + } + } + + public static func parse_popularAppBots(_ reader: BufferReader) -> PopularAppBots? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.bots.PopularAppBots.popularAppBots(flags: _1!, nextOffset: _2, users: _3!) + } + else { + return nil + } + } + + } +} public extension Api.bots { enum PreviewInfo: TypeConstructorDescription { case previewInfo(media: [Api.BotPreviewMedia], langCodes: [String]) @@ -1398,61 +1492,3 @@ public extension Api.help { } } -public extension Api.help { - enum CountriesList: TypeConstructorDescription { - case countriesList(countries: [Api.help.Country], hash: Int32) - case countriesListNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .countriesList(let countries, let hash): - if boxed { - buffer.appendInt32(-2016381538) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(countries.count)) - for item in countries { - item.serialize(buffer, true) - } - serializeInt32(hash, buffer: buffer, boxed: false) - break - case .countriesListNotModified: - if boxed { - buffer.appendInt32(-1815339214) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .countriesList(let countries, let hash): - return ("countriesList", [("countries", countries as Any), ("hash", hash as Any)]) - case .countriesListNotModified: - return ("countriesListNotModified", []) - } - } - - public static func parse_countriesList(_ reader: BufferReader) -> CountriesList? { - var _1: [Api.help.Country]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.Country.self) - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.CountriesList.countriesList(countries: _1!, hash: _2!) - } - else { - return nil - } - } - public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? { - return Api.help.CountriesList.countriesListNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 8156e1ba08..4cb01769af 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -1,3 +1,61 @@ +public extension Api.help { + enum CountriesList: TypeConstructorDescription { + case countriesList(countries: [Api.help.Country], hash: Int32) + case countriesListNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .countriesList(let countries, let hash): + if boxed { + buffer.appendInt32(-2016381538) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(countries.count)) + for item in countries { + item.serialize(buffer, true) + } + serializeInt32(hash, buffer: buffer, boxed: false) + break + case .countriesListNotModified: + if boxed { + buffer.appendInt32(-1815339214) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .countriesList(let countries, let hash): + return ("countriesList", [("countries", countries as Any), ("hash", hash as Any)]) + case .countriesListNotModified: + return ("countriesListNotModified", []) + } + } + + public static func parse_countriesList(_ reader: BufferReader) -> CountriesList? { + var _1: [Api.help.Country]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.Country.self) + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.help.CountriesList.countriesList(countries: _1!, hash: _2!) + } + else { + return nil + } + } + public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? { + return Api.help.CountriesList.countriesListNotModified + } + + } +} public extension Api.help { enum Country: TypeConstructorDescription { case country(flags: Int32, iso2: String, defaultName: String, name: String?, countryCodes: [Api.help.CountryCode]) @@ -1236,117 +1294,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum ArchivedStickers: TypeConstructorDescription { - case archivedStickers(count: Int32, sets: [Api.StickerSetCovered]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .archivedStickers(let count, let sets): - if boxed { - buffer.appendInt32(1338747336) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sets.count)) - for item in sets { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .archivedStickers(let count, let sets): - return ("archivedStickers", [("count", count as Any), ("sets", sets as Any)]) - } - } - - public static func parse_archivedStickers(_ reader: BufferReader) -> ArchivedStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ArchivedStickers.archivedStickers(count: _1!, sets: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum AvailableEffects: TypeConstructorDescription { - case availableEffects(hash: Int32, effects: [Api.AvailableEffect], documents: [Api.Document]) - case availableEffectsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .availableEffects(let hash, let effects, let documents): - if boxed { - buffer.appendInt32(-1109696146) - } - serializeInt32(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(effects.count)) - for item in effects { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents.count)) - for item in documents { - item.serialize(buffer, true) - } - break - case .availableEffectsNotModified: - if boxed { - buffer.appendInt32(-772957605) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .availableEffects(let hash, let effects, let documents): - return ("availableEffects", [("hash", hash as Any), ("effects", effects as Any), ("documents", documents as Any)]) - case .availableEffectsNotModified: - return ("availableEffectsNotModified", []) - } - } - - public static func parse_availableEffects(_ reader: BufferReader) -> AvailableEffects? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.AvailableEffect]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableEffect.self) - } - var _3: [Api.Document]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.AvailableEffects.availableEffects(hash: _1!, effects: _2!, documents: _3!) - } - else { - return nil - } - } - public static func parse_availableEffectsNotModified(_ reader: BufferReader) -> AvailableEffects? { - return Api.messages.AvailableEffects.availableEffectsNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api31.swift b/submodules/TelegramApi/Sources/Api31.swift index 56ce04bdea..98d0728567 100644 --- a/submodules/TelegramApi/Sources/Api31.swift +++ b/submodules/TelegramApi/Sources/Api31.swift @@ -1,3 +1,117 @@ +public extension Api.messages { + enum ArchivedStickers: TypeConstructorDescription { + case archivedStickers(count: Int32, sets: [Api.StickerSetCovered]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .archivedStickers(let count, let sets): + if boxed { + buffer.appendInt32(1338747336) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sets.count)) + for item in sets { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .archivedStickers(let count, let sets): + return ("archivedStickers", [("count", count as Any), ("sets", sets as Any)]) + } + } + + public static func parse_archivedStickers(_ reader: BufferReader) -> ArchivedStickers? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.ArchivedStickers.archivedStickers(count: _1!, sets: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum AvailableEffects: TypeConstructorDescription { + case availableEffects(hash: Int32, effects: [Api.AvailableEffect], documents: [Api.Document]) + case availableEffectsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .availableEffects(let hash, let effects, let documents): + if boxed { + buffer.appendInt32(-1109696146) + } + serializeInt32(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(effects.count)) + for item in effects { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documents.count)) + for item in documents { + item.serialize(buffer, true) + } + break + case .availableEffectsNotModified: + if boxed { + buffer.appendInt32(-772957605) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .availableEffects(let hash, let effects, let documents): + return ("availableEffects", [("hash", hash as Any), ("effects", effects as Any), ("documents", documents as Any)]) + case .availableEffectsNotModified: + return ("availableEffectsNotModified", []) + } + } + + public static func parse_availableEffects(_ reader: BufferReader) -> AvailableEffects? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.AvailableEffect]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableEffect.self) + } + var _3: [Api.Document]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.AvailableEffects.availableEffects(hash: _1!, effects: _2!, documents: _3!) + } + else { + return nil + } + } + public static func parse_availableEffectsNotModified(_ reader: BufferReader) -> AvailableEffects? { + return Api.messages.AvailableEffects.availableEffectsNotModified + } + + } +} public extension Api.messages { enum AvailableReactions: TypeConstructorDescription { case availableReactions(hash: Int32, reactions: [Api.AvailableReaction]) @@ -1342,91 +1456,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum HighScores: TypeConstructorDescription { - case highScores(scores: [Api.HighScore], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .highScores(let scores, let users): - if boxed { - buffer.appendInt32(-1707344487) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(scores.count)) - for item in scores { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .highScores(let scores, let users): - return ("highScores", [("scores", scores as Any), ("users", users as Any)]) - } - } - - public static func parse_highScores(_ reader: BufferReader) -> HighScores? { - var _1: [Api.HighScore]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.HighScore.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.HighScores.highScores(scores: _1!, users: _2!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum HistoryImport: TypeConstructorDescription { - case historyImport(id: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .historyImport(let id): - if boxed { - buffer.appendInt32(375566091) - } - serializeInt64(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .historyImport(let id): - return ("historyImport", [("id", id as Any)]) - } - } - - public static func parse_historyImport(_ reader: BufferReader) -> HistoryImport? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.messages.HistoryImport.historyImport(id: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index d31e82a113..ae3204c4a7 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -1,3 +1,91 @@ +public extension Api.messages { + enum HighScores: TypeConstructorDescription { + case highScores(scores: [Api.HighScore], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .highScores(let scores, let users): + if boxed { + buffer.appendInt32(-1707344487) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(scores.count)) + for item in scores { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .highScores(let scores, let users): + return ("highScores", [("scores", scores as Any), ("users", users as Any)]) + } + } + + public static func parse_highScores(_ reader: BufferReader) -> HighScores? { + var _1: [Api.HighScore]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.HighScore.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.HighScores.highScores(scores: _1!, users: _2!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum HistoryImport: TypeConstructorDescription { + case historyImport(id: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .historyImport(let id): + if boxed { + buffer.appendInt32(375566091) + } + serializeInt64(id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .historyImport(let id): + return ("historyImport", [("id", id as Any)]) + } + } + + public static func parse_historyImport(_ reader: BufferReader) -> HistoryImport? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.messages.HistoryImport.historyImport(id: _1!) + } + else { + return nil + } + } + + } +} public extension Api.messages { enum HistoryImportParsed: TypeConstructorDescription { case historyImportParsed(flags: Int32, title: String?) @@ -1452,85 +1540,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum SponsoredMessages: TypeConstructorDescription { - case sponsoredMessages(flags: Int32, postsBetween: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User]) - case sponsoredMessagesEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): - if boxed { - buffer.appendInt32(-907141753) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(postsBetween!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .sponsoredMessagesEmpty: - if boxed { - buffer.appendInt32(406407439) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): - return ("sponsoredMessages", [("flags", flags as Any), ("postsBetween", postsBetween as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) - case .sponsoredMessagesEmpty: - return ("sponsoredMessagesEmpty", []) - } - } - - public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } - var _3: [Api.SponsoredMessage]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, messages: _3!, chats: _4!, users: _5!) - } - else { - return nil - } - } - public static func parse_sponsoredMessagesEmpty(_ reader: BufferReader) -> SponsoredMessages? { - return Api.messages.SponsoredMessages.sponsoredMessagesEmpty - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api33.swift b/submodules/TelegramApi/Sources/Api33.swift index 72d9f6b5cd..289b3d5fb5 100644 --- a/submodules/TelegramApi/Sources/Api33.swift +++ b/submodules/TelegramApi/Sources/Api33.swift @@ -1,3 +1,85 @@ +public extension Api.messages { + enum SponsoredMessages: TypeConstructorDescription { + case sponsoredMessages(flags: Int32, postsBetween: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User]) + case sponsoredMessagesEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): + if boxed { + buffer.appendInt32(-907141753) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(postsBetween!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .sponsoredMessagesEmpty: + if boxed { + buffer.appendInt32(406407439) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .sponsoredMessages(let flags, let postsBetween, let messages, let chats, let users): + return ("sponsoredMessages", [("flags", flags as Any), ("postsBetween", postsBetween as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .sponsoredMessagesEmpty: + return ("sponsoredMessagesEmpty", []) + } + } + + public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() } + var _3: [Api.SponsoredMessage]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.SponsoredMessages.sponsoredMessages(flags: _1!, postsBetween: _2, messages: _3!, chats: _4!, users: _5!) + } + else { + return nil + } + } + public static func parse_sponsoredMessagesEmpty(_ reader: BufferReader) -> SponsoredMessages? { + return Api.messages.SponsoredMessages.sponsoredMessagesEmpty + } + + } +} public extension Api.messages { enum StickerSet: TypeConstructorDescription { case stickerSet(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) @@ -1425,113 +1507,23 @@ public extension Api.payments { } } public extension Api.payments { - enum ValidatedRequestedInfo: TypeConstructorDescription { - case validatedRequestedInfo(flags: Int32, id: String?, shippingOptions: [Api.ShippingOption]?) + enum UserStarGifts: TypeConstructorDescription { + case userStarGifts(flags: Int32, count: Int32, gifts: [Api.UserStarGift], nextOffset: String?, users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .validatedRequestedInfo(let flags, let id, let shippingOptions): + case .userStarGifts(let flags, let count, let gifts, let nextOffset, let users): if boxed { - buffer.appendInt32(-784000893) + buffer.appendInt32(1801827607) } serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(id!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(shippingOptions!.count)) - for item in shippingOptions! { - item.serialize(buffer, true) - }} - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .validatedRequestedInfo(let flags, let id, let shippingOptions): - return ("validatedRequestedInfo", [("flags", flags as Any), ("id", id as Any), ("shippingOptions", shippingOptions as Any)]) - } - } - - public static func parse_validatedRequestedInfo(_ reader: BufferReader) -> ValidatedRequestedInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: [Api.ShippingOption]? - if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ShippingOption.self) - } } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3) - } - else { - return nil - } - } - - } -} -public extension Api.phone { - enum ExportedGroupCallInvite: TypeConstructorDescription { - case exportedGroupCallInvite(link: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .exportedGroupCallInvite(let link): - if boxed { - buffer.appendInt32(541839704) - } - serializeString(link, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .exportedGroupCallInvite(let link): - return ("exportedGroupCallInvite", [("link", link as Any)]) - } - } - - public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!) - } - else { - return nil - } - } - - } -} -public extension Api.phone { - enum GroupCall: TypeConstructorDescription { - case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): - if boxed { - buffer.appendInt32(-1636664659) - } - call.serialize(buffer, true) + serializeInt32(count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(participants.count)) - for item in participants { - item.serialize(buffer, true) - } - serializeString(participantsNextOffset, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { + buffer.appendInt32(Int32(gifts.count)) + for item in gifts { item.serialize(buffer, true) } + if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { @@ -1543,26 +1535,22 @@ public extension Api.phone { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): - return ("groupCall", [("call", call as Any), ("participants", participants as Any), ("participantsNextOffset", participantsNextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + case .userStarGifts(let flags, let count, let gifts, let nextOffset, let users): + return ("userStarGifts", [("flags", flags as Any), ("count", count as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("users", users as Any)]) } } - public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { - var _1: Api.GroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GroupCall - } - var _2: [Api.GroupCallParticipant]? + public static func parse_userStarGifts(_ reader: BufferReader) -> UserStarGifts? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.UserStarGift]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) - } - var _3: String? - _3 = parseString(reader) - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.UserStarGift.self) } + var _4: String? + if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) } var _5: [Api.User]? if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) @@ -1570,10 +1558,10 @@ public extension Api.phone { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!) + return Api.payments.UserStarGifts.userStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, users: _5!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api34.swift b/submodules/TelegramApi/Sources/Api34.swift index 778db2ffd1..222cf9735b 100644 --- a/submodules/TelegramApi/Sources/Api34.swift +++ b/submodules/TelegramApi/Sources/Api34.swift @@ -1,3 +1,161 @@ +public extension Api.payments { + enum ValidatedRequestedInfo: TypeConstructorDescription { + case validatedRequestedInfo(flags: Int32, id: String?, shippingOptions: [Api.ShippingOption]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .validatedRequestedInfo(let flags, let id, let shippingOptions): + if boxed { + buffer.appendInt32(-784000893) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(id!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(shippingOptions!.count)) + for item in shippingOptions! { + item.serialize(buffer, true) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .validatedRequestedInfo(let flags, let id, let shippingOptions): + return ("validatedRequestedInfo", [("flags", flags as Any), ("id", id as Any), ("shippingOptions", shippingOptions as Any)]) + } + } + + public static func parse_validatedRequestedInfo(_ reader: BufferReader) -> ValidatedRequestedInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: [Api.ShippingOption]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ShippingOption.self) + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3) + } + else { + return nil + } + } + + } +} +public extension Api.phone { + enum ExportedGroupCallInvite: TypeConstructorDescription { + case exportedGroupCallInvite(link: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedGroupCallInvite(let link): + if boxed { + buffer.appendInt32(541839704) + } + serializeString(link, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .exportedGroupCallInvite(let link): + return ("exportedGroupCallInvite", [("link", link as Any)]) + } + } + + public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(link: _1!) + } + else { + return nil + } + } + + } +} +public extension Api.phone { + enum GroupCall: TypeConstructorDescription { + case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): + if boxed { + buffer.appendInt32(-1636664659) + } + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(participants.count)) + for item in participants { + item.serialize(buffer, true) + } + serializeString(participantsNextOffset, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCall(let call, let participants, let participantsNextOffset, let chats, let users): + return ("groupCall", [("call", call as Any), ("participants", participants as Any), ("participantsNextOffset", participantsNextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { + var _1: Api.GroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + var _2: [Api.GroupCallParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + } + var _3: String? + _3 = parseString(reader) + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!) + } + else { + return nil + } + } + + } +} public extension Api.phone { enum GroupCallStreamChannels: TypeConstructorDescription { case groupCallStreamChannels(channels: [Api.GroupCallStreamChannel]) diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index 831da74e11..70376e3095 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -8838,6 +8838,22 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func convertStarGift(userId: Api.InputUser, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(69328935) + userId.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.convertStarGift", parameters: [("userId", String(describing: userId)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.payments { static func exportInvoice(invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -9144,6 +9160,23 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func getUserStarGifts(userId: Api.InputUser, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1584580577) + userId.serialize(buffer, true) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getUserStarGifts", parameters: [("userId", String(describing: userId)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UserStarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.UserStarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.UserStarGifts + } + return result + }) + } +} public extension Api.functions.payments { static func launchPrepaidGiveaway(peer: Api.InputPeer, giveawayId: Int64, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -9177,6 +9210,22 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func saveStarGift(userId: Api.InputUser, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1394197223) + userId.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.saveStarGift", parameters: [("userId", String(describing: userId)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.payments { static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -9199,13 +9248,12 @@ public extension Api.functions.payments { } } public extension Api.functions.payments { - static func sendStarsForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendStarsForm(formId: Int64, invoice: Api.InputInvoice) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(45839133) - serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(2040056084) serializeInt64(formId, buffer: buffer, boxed: false) invoice.serialize(buffer, true) - return (FunctionDescription(name: "payments.sendStarsForm", parameters: [("flags", String(describing: flags)), ("formId", String(describing: formId)), ("invoice", String(describing: invoice))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in + return (FunctionDescription(name: "payments.sendStarsForm", parameters: [("formId", String(describing: formId)), ("invoice", String(describing: invoice))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in let reader = BufferReader(buffer) var result: Api.payments.PaymentResult? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index a4712a1cf7..2557eb591c 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -150,7 +150,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId)) case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId): return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId))) - case let .messageActionStarGift(flags, starsAmount, giftId, limitedNumber, limitedTotal, message): + case let .messageActionStarGift(flags, apiGift, message, convertStars): let text: String? let entities: [MessageTextEntity]? switch message { @@ -161,7 +161,10 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe text = nil entities = nil } - return TelegramMediaAction(action: .starGift(amount: starsAmount, giftId: giftId, nameHidden: (flags & (1 << 2)) != 0, limitNumber: limitedNumber, limitTotal: limitedTotal, text: text, entities: entities)) + guard let gift = StarGift(apiStarGift: apiGift) else { + return nil + } + return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0)) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 33a20b4ea1..59239b2aba 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -130,7 +130,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String) case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?) - case starGift(amount: Int64, giftId: Int64, nameHidden: Bool, limitNumber: Int32?, limitTotal: Int32?, text: String?, entities: [MessageTextEntity]?) + case starGift(gift: StarGift, convertStars: Int64, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -252,7 +252,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId) case 44: - self = .starGift(amount: decoder.decodeInt64ForKey("amount", orElse: 0), giftId: decoder.decodeInt64ForKey("giftId", orElse: 0), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), limitNumber: decoder.decodeOptionalInt32ForKey("limitNumber"), limitTotal: decoder.decodeOptionalInt32ForKey("limitTotal"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities")) + self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeInt64ForKey("convertStars", orElse: 0), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false)) default: self = .unknown } @@ -528,18 +528,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "giveawayMsgId") } - case let .starGift(amount, giftId, nameHidden, limitNumber, limitTotal, text, entities): + case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted): encoder.encodeInt32(44, forKey: "_rawValue") - encoder.encodeInt64(amount, forKey: "amount") - encoder.encodeInt64(giftId, forKey: "giftId") - encoder.encodeBool(nameHidden, forKey: "nameHidden") - if let limitNumber, let limitTotal { - encoder.encodeInt32(limitNumber, forKey: "limitNumber") - encoder.encodeInt32(limitTotal, forKey: "limitTotal") - } else { - encoder.encodeNil(forKey: "limitNumber") - encoder.encodeNil(forKey: "limitTotal") - } + encoder.encodeObject(gift, forKey: "gift") + encoder.encodeInt64(convertStars, forKey: "convertStars") if let text, let entities { encoder.encodeString(text, forKey: "text") encoder.encodeObjectArray(entities, forKey: "entities") @@ -547,6 +539,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeNil(forKey: "text") encoder.encodeNil(forKey: "entities") } + encoder.encodeBool(nameHidden, forKey: "nameHidden") + encoder.encodeBool(savedToProfile, forKey: "savedToProfile") + encoder.encodeBool(converted, forKey: "converted") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 42cf15b8ea..85b69769eb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -347,7 +347,7 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv } return .inputInvoiceStars(purpose: .inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users)) case let .starGift(hideName, peerId, giftId, text, entities): - guard let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) else { + guard let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) else { return nil } var flags: Int32 = 0 @@ -359,7 +359,7 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv flags |= (1 << 1) message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? []) } - return .inputInvoiceStarGift(flags: flags, peer: apiPeer, giftId: giftId, message: message) + return .inputInvoiceStarGift(flags: flags, userId: inputUser, giftId: giftId, message: message) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 9704887a18..0d9cb8bb26 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -27,7 +27,7 @@ public final class StarGiftsList: Codable, Equatable { } } -public struct StarGift: Equatable, Codable { +public struct StarGift: Equatable, Codable, PostboxCoding { enum CodingKeys: String, CodingKey { case id case file @@ -35,7 +35,7 @@ public struct StarGift: Equatable, Codable { case availability } - public struct Availability: Equatable, Codable { + public struct Availability: Equatable, Codable, PostboxCoding { enum CodingKeys: String, CodingKey { case remains case total @@ -43,6 +43,21 @@ public struct StarGift: Equatable, Codable { public let remains: Int32 public let total: Int32 + + init(remains: Int32, total: Int32) { + self.remains = remains + self.total = total + } + + public init(decoder: PostboxDecoder) { + self.remains = decoder.decodeInt32ForKey(CodingKeys.remains.rawValue, orElse: 0) + self.total = decoder.decodeInt32ForKey(CodingKeys.total.rawValue, orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.remains, forKey: CodingKeys.remains.rawValue) + encoder.encodeInt32(self.total, forKey: CodingKeys.total.rawValue) + } } public enum DecodingError: Error { @@ -75,6 +90,13 @@ public struct StarGift: Equatable, Codable { self.availability = try container.decodeIfPresent(Availability.self, forKey: .availability) } + public init(decoder: PostboxDecoder) { + self.id = decoder.decodeInt64ForKey(CodingKeys.id.rawValue, orElse: 0) + self.file = decoder.decodeObjectForKey(CodingKeys.file.rawValue) as! TelegramMediaFile + self.price = decoder.decodeInt64ForKey(CodingKeys.price.rawValue, orElse: 0) + self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { StarGift.Availability(decoder: $0) }) as? StarGift.Availability + } + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.id, forKey: .id) @@ -87,6 +109,17 @@ public struct StarGift: Equatable, Codable { try container.encode(self.price, forKey: .price) try container.encodeIfPresent(self.availability, forKey: .availability) } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.id, forKey: CodingKeys.id.rawValue) + encoder.encodeObject(self.file, forKey: CodingKeys.file.rawValue) + encoder.encodeInt64(self.price, forKey: CodingKeys.price.rawValue) + if let availability = self.availability { + encoder.encodeObject(availability, forKey: CodingKeys.availability.rawValue) + } else { + encoder.encodeNil(forKey: CodingKeys.availability.rawValue) + } + } } extension StarGift { @@ -153,3 +186,215 @@ func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal then(.complete() |> suspendAwareDelay(2.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } + +func _internal_convertStarGift(account: Account, messageId: EngineMessage.Id) -> Signal { + return account.postbox.transaction { transaction -> Api.InputUser? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputUser) + } + |> mapToSignal { inputUser -> Signal in + guard let inputUser else { + return .complete() + } + return account.network.request(Api.functions.payments.convertStarGift(userId: inputUser, msgId: messageId.id)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> ignoreValues + } +} + +func _internal_saveStarGiftToProfile(account: Account, messageId: EngineMessage.Id) -> Signal { + return account.postbox.transaction { transaction -> Api.InputUser? in + return transaction.getPeer(messageId.peerId).flatMap(apiInputUser) + } + |> mapToSignal { inputUser -> Signal in + guard let inputUser else { + return .complete() + } + return account.network.request(Api.functions.payments.saveStarGift(userId: inputUser, msgId: messageId.id)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> ignoreValues + } +} + +private final class ProfileGiftsContextImpl { + private let queue: Queue + private let account: Account + private let peerId: PeerId + + private let disposable = MetaDisposable() + + private var gifts: [ProfileGiftsContext.State.StarGift] = [] + private var count: Int32? + private var dataState: ProfileGiftsContext.State.DataState = .ready(canLoadMore: true, nextOffset: nil) + + private let stateValue = Promise() + var state: Signal { + return self.stateValue.get() + } + + init(queue: Queue, account: Account, peerId: EnginePeer.Id) { + self.queue = queue + self.account = account + self.peerId = peerId + + self.loadMore() + } + + deinit { + self.disposable.dispose() + } + + func loadMore() { + if case let .ready(true, nextOffset) = self.dataState { + self.dataState = .loading + self.pushState() + + let peerId = self.peerId + let accountPeerId = self.account.peerId + let network = self.account.network + let postbox = self.account.postbox + let signal: Signal<([ProfileGiftsContext.State.StarGift], Int32, String?), NoError> = self.account.postbox.transaction { transaction -> Api.InputUser? in + return transaction.getPeer(peerId).flatMap(apiInputUser) + } + |> mapToSignal { inputUser -> Signal<([ProfileGiftsContext.State.StarGift], Int32, String?), NoError> in + guard let inputUser else { + return .single(([], 0, nil)) + } + return network.request(Api.functions.payments.getUserStarGifts(userId: inputUser, offset: nextOffset ?? "", limit: 32)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal<([ProfileGiftsContext.State.StarGift], Int32, String?), NoError> in + guard let result else { + return .single(([], 0, nil)) + } + return postbox.transaction { transaction -> ([ProfileGiftsContext.State.StarGift], Int32, String?) in + switch result { + case let .userStarGifts(_, count, apiGifts, nextOffset, users): + let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: users) + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + + let gifts = apiGifts.compactMap { ProfileGiftsContext.State.StarGift(apiUserStarGift: $0, transaction: transaction) } + return (gifts, count, nextOffset) + } + } + } + } + + self.disposable.set((signal + |> deliverOn(self.queue)).start(next: { [weak self] (gifts, count, nextOffset) in + guard let strongSelf = self else { + return + } + for gift in gifts { + strongSelf.gifts.append(gift) + } + + let updatedCount = max(Int32(strongSelf.gifts.count), count) + strongSelf.count = updatedCount + strongSelf.dataState = .ready(canLoadMore: count != 0 && updatedCount > strongSelf.gifts.count && nextOffset != nil, nextOffset: nextOffset) + strongSelf.pushState() + })) + } + } + + private func pushState() { + self.stateValue.set(.single(ProfileGiftsContext.State(gifts: self.gifts, count: self.count, dataState: self.dataState))) + } +} + +public final class ProfileGiftsContext { + public struct State: Equatable { + public struct StarGift: Equatable { + public let gift: TelegramCore.StarGift + public let fromPeer: EnginePeer? + public let date: Int32 + public let text: String? + public let entities: [MessageTextEntity]? + public let messageId: EngineMessage.Id? + public let nameHidden: Bool + public let convertStars: Int64? + } + + public enum DataState: Equatable { + case loading + case ready(canLoadMore: Bool, nextOffset: String?) + } + + public var gifts: [ProfileGiftsContext.State.StarGift] + public var count: Int32? + public var dataState: ProfileGiftsContext.State.DataState + } + + private let queue: Queue = .mainQueue() + private let impl: QueueLocalObject + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + disposable.set(impl.state.start(next: { value in + subscriber.putNext(value) + })) + } + + return disposable + } + } + + public init(account: Account, peerId: EnginePeer.Id) { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return ProfileGiftsContextImpl(queue: queue, account: account, peerId: peerId) + }) + } + + public func loadMore() { + self.impl.with { impl in + impl.loadMore() + } + } +} + +private extension ProfileGiftsContext.State.StarGift { + init?(apiUserStarGift: Api.UserStarGift, transaction: Transaction) { + switch apiUserStarGift { + case let .userStarGift(flags, fromId, date, apiGift, message, msgId, convertStars): + guard let gift = StarGift(apiStarGift: apiGift) else { + return nil + } + self.gift = gift + if let fromPeerId = fromId.flatMap({ EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value($0)) }) { + self.fromPeer = transaction.getPeer(fromPeerId).flatMap(EnginePeer.init) + } else { + self.fromPeer = nil + } + self.date = date + + if let message { + switch message { + case let .textWithEntities(text, entities): + self.text = text + self.entities = messageTextEntitiesFromApiEntities(entities) + } + } else { + self.text = nil + self.entities = nil + } + if let fromPeer = self.fromPeer, let msgId { + self.messageId = EngineMessage.Id(peerId: fromPeer.id, namespace: Namespaces.Message.Cloud, id: msgId) + } else { + self.messageId = nil + } + self.nameHidden = (flags & (1 << 0)) != 0 + self.convertStars = convertStars + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 4ca6d68b2b..f3b738d87a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1229,10 +1229,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot guard let invoice = invoice else { return .fail(.generic) } - - let flags: Int32 = 0 - - return account.network.request(Api.functions.payments.sendStarsForm(flags: flags, formId: formId, invoice: invoice)) + return account.network.request(Api.functions.payments.sendStarsForm(formId: formId, invoice: invoice)) |> map { result -> SendBotPaymentResult in switch result { case let .paymentResult(updates): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index d3211bf067..a52042b0a9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -112,5 +112,15 @@ public extension TelegramEngine { public func keepStarGiftsUpdated() -> Signal { return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network) } + + + public func convertStarGift(messageId: EngineMessage.Id) -> Signal { + return _internal_convertStarGift(account: self.account, messageId: messageId) + } + + public func saveStarGiftToProfile(messageId: EngineMessage.Id) -> Signal { + return _internal_saveStarGiftToProfile(account: self.account, messageId: messageId) + } + } } From f7e6755a392aa468433dab2761bf8c627479b126 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 19 Sep 2024 03:24:24 +0400 Subject: [PATCH 04/11] Update API --- .../Sources/AccountContext.swift | 1 + .../Sources/ChatController.swift | 8 +- .../AccountContext/Sources/Premium.swift | 4 + .../Sources/AttachmentPanel.swift | 2 +- .../Sources/BotCheckoutController.swift | 37 +- submodules/BrowserUI/BUILD | 1 + .../Sources/BrowserBookmarksScreen.swift | 2 +- .../BrowserUI/Sources/BrowserWebContent.swift | 151 +- submodules/PromptUI/BUILD | 1 + .../PromptUI/Sources/PromptController.swift | 539 ++++++- .../Source/Signal_Combine.swift | 6 + submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api26.swift | 18 +- submodules/TelegramApi/Sources/Api36.swift | 7 +- .../TelegramEngine/Payments/StarGifts.swift | 8 +- .../Payments/TelegramEnginePayments.swift | 6 +- .../Sources/DefaultDayPresentationTheme.swift | 3 +- .../Sources/ServiceMessageStrings.swift | 19 +- submodules/TelegramUI/BUILD | 1 + .../Sources/ChatMessageBubbleItemNode.swift | 2 + .../ChatMessageGiftBubbleContentNode.swift | 46 +- .../Sources/ChatMessageDateHeader.swift | 2 +- .../ChatMessageWebpageBubbleContentNode.swift | 2 +- .../ChatRecentActionsControllerNode.swift | 6 +- .../ChatSendAudioMessageContextPreview.swift | 2 +- .../Sources/ChatControllerInteraction.swift | 4 +- .../Sources/EmojiTextAttachmentView.swift | 68 + .../Components/Gifts/GiftItemComponent/BUILD | 36 + .../Sources/GiftItemComponent.swift | 524 +++++++ .../Components/Gifts/GiftOptionsScreen/BUILD | 49 + .../Sources/GiftOptionsScreen.swift | 909 +++++++++++ .../Components/Gifts/GiftSetupScreen/BUILD | 45 + .../Sources/ChatGiftPreviewItem.swift | 366 +++++ .../Sources/GiftSetupScreen.swift | 607 ++++++++ .../Components/Gifts/GiftViewScreen/BUILD | 44 + .../Sources/GiftViewScreen.swift | 1325 +++++++++++++++++ .../Sources/PeerInfoPaneNode.swift | 1 + .../PeerInfoScreen/Sources/PeerInfoData.swift | 46 +- .../Sources/PeerInfoPaneContainerNode.swift | 5 + .../Sources/PeerInfoScreen.swift | 2 +- .../PeerInfoVisualMediaPaneNode/BUILD | 3 + .../Sources/PeerInfoGiftsPaneNode.swift | 290 ++++ .../Sources/PeerInfoVisualMediaPaneNode.swift | 11 +- .../ItemShimmeringLoadingComponent/BUILD | 24 + .../ItemShimmeringLoadingComponent.swift} | 31 +- .../Stars/StarsPurchaseScreen/BUILD | 3 + .../Sources/StarsPurchaseScreen.swift | 3 +- .../Sources/StarsTransactionsScreen.swift | 1 - .../Sources/StoryItemContentComponent.swift | 6 +- .../Components/TabSelectorComponent/BUILD | 3 + .../Sources/TabSelectorComponent.swift | 63 +- .../Message/BotCopy.imageset/Contents.json | 12 + .../Chat/Message/BotCopy.imageset/copy_10.pdf | 65 + .../Message/GiftRibbon.imageset/Contents.json | 12 + .../GiftRibbon.imageset/giftline_chat.pdf | 83 ++ .../Premium/GiftRibbon.imageset/Contents.json | 12 + .../GiftRibbon.imageset/giftline_cell.pdf | 84 ++ .../TelegramUI/Sources/AppDelegate.swift | 8 +- .../Sources/ApplicationContext.swift | 8 +- .../Chat/ChatControllerOpenWebApp.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 25 +- .../Sources/ChatHistoryListNode.swift | 23 +- .../OverlayAudioPlayerControllerNode.swift | 2 +- .../Sources/SharedAccountContext.swift | 208 +-- .../Sources/SpotlightContacts.swift | 34 +- .../Sources/ChatTextInputAttributes.swift | 1 + 66 files changed, 5688 insertions(+), 236 deletions(-) create mode 100644 submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD create mode 100644 submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift create mode 100644 submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift create mode 100644 submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift create mode 100644 submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD rename submodules/TelegramUI/Components/Stars/{StarsPurchaseScreen/Sources/ItemLoadingComponent.swift => ItemShimmeringLoadingComponent/Sources/ItemShimmeringLoadingComponent.swift} (69%) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/copy_10.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/giftline_chat.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/giftline_cell.pdf diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 8a704ace67..fef2ed9501 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1017,6 +1017,7 @@ public protocol SharedAccountContext: AnyObject { func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController + func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 01a4f4a292..b2dd59e404 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -63,6 +63,7 @@ public final class ChatMessageItemAssociatedData: Equatable { public let isStandalone: Bool public let isInline: Bool public let showSensitiveContent: Bool + public let starGifts: [Int64: TelegramMediaFile] public init( automaticDownloadPeerType: MediaAutoDownloadPeerType, @@ -96,7 +97,8 @@ public final class ChatMessageItemAssociatedData: Equatable { deviceContactsNumbers: Set = Set(), isStandalone: Bool = false, isInline: Bool = false, - showSensitiveContent: Bool = false + showSensitiveContent: Bool = false, + starGifts: [Int64: TelegramMediaFile] = [:] ) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerId = automaticDownloadPeerId @@ -130,6 +132,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.isStandalone = isStandalone self.isInline = isInline self.showSensitiveContent = showSensitiveContent + self.starGifts = starGifts } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { @@ -223,6 +226,9 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.showSensitiveContent != rhs.showSensitiveContent { return false } + if lhs.starGifts != rhs.starGifts { + return false + } return true } } diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 5cd4fd732e..7f8d9fdaaa 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -276,3 +276,7 @@ public struct PremiumConfiguration { } } } + +public protocol GiftOptionsScreenProtocol { + +} diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index c472ceb465..5344b52b73 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -992,7 +992,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { sendWhenOnlineAvailable = true } } - if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { + if peer.id.isTelegramNotifications { sendWhenOnlineAvailable = false } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index dba9e62241..c224ed88b9 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -29,22 +29,27 @@ public final class BotCheckoutController: ViewController { public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal { let theme = context.sharedContext.currentPresentationData.with { $0 }.theme - let themeParams: [String: Any] = [ - "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb), - "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb), - "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb), - "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), - "link_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), - "button_color": Int32(bitPattern: theme.list.itemCheckColors.fillColor.rgb), - "button_text_color": Int32(bitPattern: theme.list.itemCheckColors.foregroundColor.rgb), - "header_bg_color": Int32(bitPattern: theme.rootController.navigationBar.opaqueBackgroundColor.rgb), - "accent_text_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), - "section_bg_color": Int32(bitPattern: theme.list.itemBlocksBackgroundColor.rgb), - "section_header_text_color": Int32(bitPattern: theme.list.freeTextColor.rgb), - "subtitle_text_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), - "destructive_text_color": Int32(bitPattern: theme.list.itemDestructiveColor.rgb), - "section_separator_color": Int32(bitPattern: theme.list.itemBlocksSeparatorColor.rgb) - ] + let themeParams: [String: Any]? + if case .starGift = source { + themeParams = nil + } else { + themeParams = [ + "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb), + "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb), + "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb), + "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "link_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "button_color": Int32(bitPattern: theme.list.itemCheckColors.fillColor.rgb), + "button_text_color": Int32(bitPattern: theme.list.itemCheckColors.foregroundColor.rgb), + "header_bg_color": Int32(bitPattern: theme.rootController.navigationBar.opaqueBackgroundColor.rgb), + "accent_text_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "section_bg_color": Int32(bitPattern: theme.list.itemBlocksBackgroundColor.rgb), + "section_header_text_color": Int32(bitPattern: theme.list.freeTextColor.rgb), + "subtitle_text_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "destructive_text_color": Int32(bitPattern: theme.list.itemDestructiveColor.rgb), + "section_separator_color": Int32(bitPattern: theme.list.itemBlocksSeparatorColor.rgb) + ] + } return context.engine.payments.fetchBotPaymentForm(source: source, themeParams: themeParams) |> mapError { _ -> FetchError in diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index 9f7432956e..e031d7675d 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -49,6 +49,7 @@ swift_library( "//submodules/TelegramUI/Components/SaveProgressScreen", "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/Utils/DeviceModel", + "//submodules/LegacyMediaPickerUI", ], visibility = [ "//visibility:public", diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index cef645b788..9fe3b22f61 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -147,7 +147,7 @@ public final class BrowserBookmarksScreen: ViewController { }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _, _ in + }, activateAdAction: { _, _, _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 8ea2acde0d..3229d42863 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -21,6 +21,7 @@ import UrlEscaping import UrlHandling import SaveProgressScreen import DeviceModel +import LegacyMediaPickerUI private final class TonSchemeHandler: NSObject, WKURLSchemeHandler { private final class PendingTask { @@ -170,7 +171,7 @@ private func computedUserAgent() -> String { return DeviceModel.current.isIpad ? "Version/\(osVersion) Safari/605.1.15" : "Version/\(osVersion) Mobile/\(firmwareVersion) Safari/604.1" } -final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate { +final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKUIDelegate, UIScrollViewDelegate, WKDownloadDelegate { private let context: AccountContext private var presentationData: PresentationData @@ -733,15 +734,15 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU @available(iOS 13.0, *) func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) { -// if #available(iOS 14.5, *), navigationAction.shouldPerformDownload { -// self.presentDownloadConfirmation(fileName: navigationAction.request.mainDocumentURL?.lastPathComponent ?? "file", proceed: { download in -// if download { -// decisionHandler(.download, preferences) -// } else { -//// decisionHandler(.cancel, preferences) -// } -// }) -// } else { + if #available(iOS 14.5, *), navigationAction.shouldPerformDownload { + self.presentDownloadConfirmation(fileName: navigationAction.request.mainDocumentURL?.lastPathComponent ?? "file", proceed: { download in + if download { + decisionHandler(.download, preferences) + } else { + decisionHandler(.cancel, preferences) + } + }) + } else { if let url = navigationAction.request.url?.absoluteString { if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url) || url.hasPrefix("tg://")) && !url.contains("/auth/push?") && !self._state.url.contains("/auth/push?") { decisionHandler(.cancel, preferences) @@ -758,24 +759,25 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } else { decisionHandler(.allow, preferences) } -// } + } } -// func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { -// if navigationResponse.canShowMIMEType { -// decisionHandler(.allow) -// } else if #available(iOS 14.5, *) { -// self.presentDownloadConfirmation(fileName: navigationResponse.response.suggestedFilename ?? "file", proceed: { download in -// if download { -// decisionHandler(.download) -// } else { -// decisionHandler(.cancel) -// } -// }) -// } else { -// decisionHandler(.cancel) -// } -// } + func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + if navigationResponse.canShowMIMEType { + decisionHandler(.allow) + } else if #available(iOS 14.5, *) { +// decisionHandler(.download) + self.presentDownloadConfirmation(fileName: navigationResponse.response.suggestedFilename ?? "file", proceed: { download in + if download { + decisionHandler(.download) + } else { + decisionHandler(.cancel) + } + }) + } else { + decisionHandler(.cancel) + } + } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let url = navigationAction.request.url?.absoluteString { @@ -790,6 +792,101 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU decisionHandler(.allow) } } + + private var downloadArguments: (String, String)? + private var downloadController: (AlertController, (Int64, Int64) -> Void)? + private var downloadProgressObserver: Any? + + @available(iOS 14.5, *) + func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) { + download.delegate = self + } + + @available(iOS 14.5, *) + func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) { + download.delegate = self + } + + @available(iOS 14.5, *) + func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) { + let path = NSTemporaryDirectory() + NSUUID().uuidString + self.downloadArguments = (path, suggestedFilename) + completionHandler(URL(fileURLWithPath: path)) + + let downloadController = progressAlertController(sharedContext: self.context.sharedContext, title: "", cancel: { [weak download] in + download?.cancel() + }) + self.downloadController = downloadController + self.present(downloadController.0, nil) + downloadController.1(download.progress.completedUnitCount, download.progress.totalUnitCount) + + self.downloadProgressObserver = download.progress.observe(\.fractionCompleted) { [weak self] progress, _ in + if let (_, update) = self?.downloadController { + update(progress.completedUnitCount, progress.totalUnitCount) + } + } + } + + @available(iOS 14.5, *) + func downloadDidFinish(_ download: WKDownload) { + if let (controller, _ ) = self.downloadController { + controller.dismissAnimated() + self.downloadController = nil + } + + if let (path, fileName) = self.downloadArguments { + let tempFile = TempBox.shared.file(path: path, fileName: fileName) + let url = URL(fileURLWithPath: tempFile.path) + + let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in + + }) + self.present(controller, nil) + + self.downloadArguments = nil + self.downloadProgressObserver = nil + } + } + + @available(iOS 14.5, *) + func download(_ download: WKDownload, didFailWithError error: Error, resumeData: Data?) { + self.downloadArguments = nil + self.downloadProgressObserver = nil + } + + func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + if let url = webView.url, !url.absoluteString.contains("beatsnvibes") { + completionHandler(.performDefaultHandling, nil) + return + } + var completed = false + + let host = webView.url?.host ?? "" + let authController = authController(sharedContext: self.context.sharedContext, updatedPresentationData: nil, title: "Sign in to \(host)", text: "Your login information will be sent securely.", apply: { result in + if !completed { + completed = true + if let (login, password) = result { + let credential = URLCredential( + user: login, + password: password, + persistence: .permanent + ) + completionHandler(.useCredential, credential) + } else { + completionHandler(.cancelAuthenticationChallenge, nil) + } + } + }) + authController.dismissed = { byOutsideTap in + if byOutsideTap { + if !completed { + completed = true + completionHandler(.cancelAuthenticationChallenge, nil) + } + } + } + self.present(authController, nil) + } private let isLoaded = ValuePromise(false) private var instantPageDisposable = MetaDisposable() @@ -880,7 +977,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - if [-1003, -1100, 102].contains((error as NSError).code) { + if [-1003, -1100].contains((error as NSError).code) { if let url = (error as NSError).userInfo["NSErrorFailingURLKey"] as? URL, url.absoluteString.hasPrefix("itms-appss:") { } else { self.currentError = error diff --git a/submodules/PromptUI/BUILD b/submodules/PromptUI/BUILD index aec3f5594f..ae10576e23 100644 --- a/submodules/PromptUI/BUILD +++ b/submodules/PromptUI/BUILD @@ -17,6 +17,7 @@ swift_library( "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramStringFormatting", ], visibility = [ "//visibility:public", diff --git a/submodules/PromptUI/Sources/PromptController.swift b/submodules/PromptUI/Sources/PromptController.swift index e6e9e19889..46e58a30ab 100644 --- a/submodules/PromptUI/Sources/PromptController.swift +++ b/submodules/PromptUI/Sources/PromptController.swift @@ -7,6 +7,7 @@ import Postbox import TelegramCore import TelegramPresentationData import AccountContext +import TelegramStringFormatting private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { private var theme: PresentationTheme @@ -39,7 +40,7 @@ private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDeleg } } - init(theme: PresentationTheme, placeholder: String, characterLimit: Int) { + init(theme: PresentationTheme, placeholder: String, characterLimit: Int, isPassword: Bool = false) { self.theme = theme self.characterLimit = characterLimit @@ -60,6 +61,7 @@ private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDeleg self.textInputNode.returnKeyType = .done self.textInputNode.autocorrectionType = .default self.textInputNode.tintColor = theme.actionSheet.controlAccentColor + self.textInputNode.isSecureTextEntry = isPassword self.placeholderNode = ASTextNode() self.placeholderNode.isUserInteractionEnabled = false @@ -448,3 +450,538 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent } return controller } + +private final class AuthAlertContentNode: AlertContentNode { + private let strings: PresentationStrings + + private let title: String + private let text: String + + private let titleNode: ASTextNode + private let textNode: ASTextNode + let inputFieldNode: PromptInputFieldNode + let passwordFieldNode: PromptInputFieldNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private let disposable = MetaDisposable() + + private var validLayout: CGSize? + + private let hapticFeedback = HapticFeedback() + + var complete: (() -> Void)? { + didSet { + self.inputFieldNode.complete = self.complete + } + } + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String) { + self.strings = strings + self.title = title + self.text = text + + self.titleNode = ASTextNode() + self.titleNode.maximumNumberOfLines = 2 + + self.textNode = ASTextNode() + self.textNode.maximumNumberOfLines = 2 + + self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: "User Name", characterLimit: 1024) + self.passwordFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: "Password", characterLimit: 1024, isPassword: true) + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + + self.addSubnode(self.inputFieldNode) + self.addSubnode(self.passwordFieldNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + self.actionNodes.last?.actionEnabled = true + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.inputFieldNode.updateHeight = { [weak self] in + if let strongSelf = self { + if let _ = strongSelf.validLayout { + strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring)) + } + } + } + + self.updateTheme(theme) + } + + deinit { + self.disposable.dispose() + } + + var login: String { + return self.inputFieldNode.text + } + + var password: String { + return self.passwordFieldNode.text + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) + + let hadValidLayout = self.validLayout != nil + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + let spacing: CGFloat = 5.0 + + let titleSize = self.titleNode.measure(measureSize) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 4.0 + + let textSize = self.textNode.measure(measureSize) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) + origin.y += textSize.height + 6.0 + spacing + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) + + var contentWidth = max(titleSize.width, minActionsWidth) + contentWidth = max(contentWidth, 234.0) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultWidth = contentWidth + insets.left + insets.right + + let inputFieldWidth = resultWidth + let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) + let inputHeight = inputFieldHeight + transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)) + transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) + + let fieldSpacing: CGFloat = -11.0 + origin.y += inputFieldHeight + fieldSpacing + + let passwordFieldHeight = self.passwordFieldNode.updateLayout(width: inputFieldWidth, transition: transition) + transition.updateFrame(node: self.passwordFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: passwordFieldHeight)) + + + let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + fieldSpacing + passwordFieldHeight + actionsHeight + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + if !hadValidLayout { + self.inputFieldNode.activateInput() + } + + return resultSize + } + + func animateError() { + self.inputFieldNode.layer.addShakeAnimation() + self.hapticFeedback.error() + } +} + +public func authController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, title: String, text: String, apply: @escaping ((String, String)?) -> Void) -> AlertController { + let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } + + var dismissImpl: ((Bool) -> Void)? + var applyImpl: (() -> Void)? + + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + apply(nil) + }), TextAlertAction(type: .defaultAction, title: "Sign In", action: { + dismissImpl?(true) + applyImpl?() + })] + + let contentNode = AuthAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text) + contentNode.complete = { + applyImpl?() + } + applyImpl = { [weak contentNode] in + guard let contentNode = contentNode else { + return + } + apply((contentNode.login, contentNode.password)) + } + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + let presentationDataDisposable = (updatedPresentationData?.signal ?? sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in + controller?.theme = AlertControllerTheme(presentationData: presentationData) + contentNode?.inputFieldNode.updateTheme(presentationData.theme) + }) + controller.dismissed = { _ in + presentationDataDisposable.dispose() + } + dismissImpl = { [weak controller] animated in + contentNode.inputFieldNode.deactivateInput() + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} + + +private final class ProgressAlertContentNode: AlertContentNode { + private let theme: AlertControllerTheme + private let strings: PresentationStrings + + private let title: String + var text: String { + didSet { + self.updateTheme(self.theme) + } + } + + private let titleNode: ASTextNode + private let textNode: ASTextNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private let disposable = MetaDisposable() + + private var validLayout: CGSize? + + private let hapticFeedback = HapticFeedback() + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String) { + self.theme = theme + self.strings = strings + self.title = title + self.text = text + + self.titleNode = ASTextNode() + self.titleNode.maximumNumberOfLines = 2 + + self.textNode = ASTextNode() + self.textNode.maximumNumberOfLines = 2 + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + self.actionNodes.last?.actionEnabled = true + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + + self.updateTheme(theme) + } + + deinit { + self.disposable.dispose() + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + let spacing: CGFloat = 5.0 + + let titleSize = self.titleNode.measure(measureSize) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 4.0 + + let textSize = self.textNode.measure(measureSize) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) + origin.y += textSize.height + 6.0 + spacing + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) + + var contentWidth = max(titleSize.width, minActionsWidth) + contentWidth = max(contentWidth, 234.0) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultWidth = contentWidth + insets.left + insets.right + + let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + actionsHeight + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + return resultSize + } +} + +public func progressAlertController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, title: String, cancel: @escaping () -> Void) -> (AlertController, (Int64, Int64) -> Void) { + let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } + + var dismissImpl: ((Bool) -> Void)? + var applyImpl: (() -> Void)? + + let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: "Cancel", action: { + dismissImpl?(true) + applyImpl?() + })] + + let contentNode = ProgressAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: "Downloading...", text: " ") + + let updateProgress: (Int64, Int64) -> Void = { [weak contentNode] current, total in + if let contentNode { + contentNode.text = "\(dataSizeString(Int(current), formatting: DataSizeStringFormatting(presentationData: presentationData))) of \(dataSizeString(Int(total), formatting: DataSizeStringFormatting(presentationData: presentationData)))" + } + } + applyImpl = { + dismissImpl?(true) + cancel() + } + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + let presentationDataDisposable = (updatedPresentationData?.signal ?? sharedContext.presentationData).start(next: { [weak controller] presentationData in + controller?.theme = AlertControllerTheme(presentationData: presentationData) + }) + controller.dismissed = { _ in + presentationDataDisposable.dispose() + } + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + + return (controller, updateProgress) +} diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index a32b6c972e..675ccef464 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -238,6 +238,12 @@ public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal, _ s13: Signal, _ s14: Signal, _ s15: Signal, _ s16: Signal, _ s17: Signal, _ s18: Signal, _ s19: Signal, _ s20: Signal, _ s21: Signal, _ s22: Signal, _ s23: Signal, _ s24: Signal, _ s25: Signal, _ s26: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19), signalOfAny(s20), signalOfAny(s21), signalOfAny(s22), signalOfAny(s23), signalOfAny(s24), signalOfAny(s25), signalOfAny(s26)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19, values[19] as! T20, values[20] as! T21, values[21] as! T22, values[22] as! T23, values[23] as! T24, values[24] as! T25, values[25] as! T26) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index bb11da56d3..b3c887f01a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1103,7 +1103,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[-2093920310] = { return Api.User.parse_user($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[-862357728] = { return Api.UserFull.parse_userFull($0) } + dict[525919081] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[-291202450] = { return Api.UserStarGift.parse_userStarGift($0) } diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index 345153edc1..820f50b8f8 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -606,13 +606,13 @@ public extension Api { } public extension Api { enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?) + case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage): + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount): if boxed { - buffer.appendInt32(-862357728) + buffer.appendInt32(525919081) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -647,14 +647,15 @@ public extension Api { if Int(flags2) & Int(1 << 5) != 0 {birthday!.serialize(buffer, true)} if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(personalChannelId!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 6) != 0 {serializeInt32(personalChannelMessage!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 8) != 0 {serializeInt32(stargiftsCount!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage): - return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any)]) + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount): + return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any)]) } } @@ -751,6 +752,8 @@ public extension Api { if Int(_2!) & Int(1 << 6) != 0 {_28 = reader.readInt64() } var _29: Int32? if Int(_2!) & Int(1 << 6) != 0 {_29 = reader.readInt32() } + var _30: Int32? + if Int(_2!) & Int(1 << 8) != 0 {_30 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -780,8 +783,9 @@ public extension Api { let _c27 = (Int(_2!) & Int(1 << 5) == 0) || _27 != nil let _c28 = (Int(_2!) & Int(1 << 6) == 0) || _28 != nil let _c29 = (Int(_2!) & Int(1 << 6) == 0) || _29 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 { - return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23, businessGreetingMessage: _24, businessAwayMessage: _25, businessIntro: _26, birthday: _27, personalChannelId: _28, personalChannelMessage: _29) + let _c30 = (Int(_2!) & Int(1 << 8) == 0) || _30 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 { + return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23, businessGreetingMessage: _24, businessAwayMessage: _25, businessIntro: _26, birthday: _27, personalChannelId: _28, personalChannelMessage: _29, stargiftsCount: _30) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index 70376e3095..740c313af2 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -9211,12 +9211,13 @@ public extension Api.functions.payments { } } public extension Api.functions.payments { - static func saveStarGift(userId: Api.InputUser, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func saveStarGift(flags: Int32, userId: Api.InputUser, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1394197223) + buffer.appendInt32(-2018709362) + serializeInt32(flags, buffer: buffer, boxed: false) userId.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.saveStarGift", parameters: [("userId", String(describing: userId)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + return (FunctionDescription(name: "payments.saveStarGift", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 0d9cb8bb26..e7f5a7bc62 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -204,7 +204,7 @@ func _internal_convertStarGift(account: Account, messageId: EngineMessage.Id) -> } } -func _internal_saveStarGiftToProfile(account: Account, messageId: EngineMessage.Id) -> Signal { +func _internal_updateStarGiftAddedToProfile(account: Account, messageId: EngineMessage.Id, added: Bool) -> Signal { return account.postbox.transaction { transaction -> Api.InputUser? in return transaction.getPeer(messageId.peerId).flatMap(apiInputUser) } @@ -212,7 +212,11 @@ func _internal_saveStarGiftToProfile(account: Account, messageId: EngineMessage. guard let inputUser else { return .complete() } - return account.network.request(Api.functions.payments.saveStarGift(userId: inputUser, msgId: messageId.id)) + var flags: Int32 = 0 + if !added { + flags |= (1 << 0) + } + return account.network.request(Api.functions.payments.saveStarGift(flags: flags, userId: inputUser, msgId: messageId.id)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index a52042b0a9..b383dd994a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -113,14 +113,12 @@ public extension TelegramEngine { return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network) } - public func convertStarGift(messageId: EngineMessage.Id) -> Signal { return _internal_convertStarGift(account: self.account, messageId: messageId) } - public func saveStarGiftToProfile(messageId: EngineMessage.Id) -> Signal { - return _internal_saveStarGiftToProfile(account: self.account, messageId: messageId) + public func updateStarGiftAddedToProfile(messageId: EngineMessage.Id, added: Bool) -> Signal { + return _internal_updateStarGiftAddedToProfile(account: self.account, messageId: messageId, added: added) } - } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 69b9a6813a..0a7d969a05 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -286,7 +286,6 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti reactionActiveMediaPlaceholder: UIColor(rgb: 0xffffff, alpha: 0.2) ) ), - linkHighlightColor: accentColor?.withAlphaComponent(0.3), accentTextColor: accentColor, accentControlColor: accentColor, accentControlDisabledColor: accentColor?.withAlphaComponent(0.7), @@ -331,7 +330,7 @@ public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, ti primaryTextColor: outgoingPrimaryTextColor, secondaryTextColor: outgoingSecondaryTextColor, linkTextColor: outgoingLinkTextColor, - linkHighlightColor: day ? nil : accentColor?.withAlphaComponent(0.3), + linkHighlightColor: day ? nil : outgoingLinkTextColor?.withAlphaComponent(0.3), scamColor: outgoingScamColor, accentTextColor: outgoingAccentTextColor, accentControlColor: outgoingControlColor, diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index d2d9eea5c7..729ef31388 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1051,14 +1051,27 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = mutableString case .prizeStars: attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor) - case let .starGift(amount, _, nameHidden, limitNumber, limitTotal, text, entities): - let _ = amount + case let .starGift(gift, _, nameHidden, limitNumber, limitTotal, text, entities): let _ = nameHidden let _ = limitNumber let _ = limitTotal let _ = text let _ = entities - attributedString = nil + + let starsPrice = "\(gift.price) Stars" + var authorName = compactAuthorName + var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)] + if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 { + authorName = strings.Notification_StarsGift_UnknownUser + peerIds = [] + } + if message.author?.id == accountPeerId { + attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentYou(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) + } else { + var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds) + attributes[1] = boldAttributes + attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes) + } case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 675a306fea..328418a38e 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -459,6 +459,7 @@ swift_library( "//submodules/TelegramUI/Components/MinimizedContainer", "//submodules/TelegramUI/Components/SpaceWarpView", "//submodules/TelegramUI/Components/MiniAppListScreen", + "//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index eca22e7b7f..8d74ca2fe0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -205,6 +205,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) } else if case .giftStars = action.action { result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + } else if case .starGift = action.action { + result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) skipText = true } else if case .suggestedProfilePhoto = action.action { result.append((message, ChatMessageProfilePhotoSuggestionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 69497821c0..d599e4f5a9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -243,6 +243,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var months: Int32 = 3 var animationName: String = "" + var animationFile: TelegramMediaFile? var title = item.presentationData.strings.Notification_PremiumGift_Title var text = "" var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View @@ -314,6 +315,35 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View hasServiceMessage = false } + case let .starGift(gift, convertStars, giftText, entities, nameHidden, savedToProfile, converted)://(amount, giftId, nameHidden, limitNumber, limitTotal, giftText, _): + let _ = nameHidden + let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" + title = nameHidden ? "Anonymous Gift" : "Gift from \(authorName)" + if let giftText, !giftText.isEmpty { + text = giftText + let _ = entities + } else { + if incoming { + if converted { + text = "You converted this gift to \(convertStars) Stars." + } else if savedToProfile { + text = "You are displaying this gift on your page. You can also convert it to \(convertStars) Stars." + } else { + text = "Display this gift on your page or convert it to \(convertStars) Stars." + } + } else { + var peerName = "" + if let peer = item.message.peers[item.message.id.peerId] { + peerName = EnginePeer(peer).compactDisplayTitle + } + if peerName.isEmpty { + text = "Display this gift on your page or convert it to \(convertStars) Stars." + } else { + text = "\(peerName) can keep this gift on their page or convert it to \(convertStars) Stars." + } + } + } + animationFile = gift.file default: break } @@ -396,7 +426,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if let strongSelf = self { if strongSelf.item == nil { strongSelf.animationNode.autoplay = true - strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 384, height: 384, playbackMode: .still(.end), mode: .direct(cachePathPrefix: nil)) + + if let file = animationFile { + strongSelf.animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 384, height: 384, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + } else if animationName.hasPrefix("Gift") { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 384, height: 384, playbackMode: .still(.end), mode: .direct(cachePathPrefix: nil)) + } } strongSelf.item = item @@ -412,8 +447,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate) strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) - let iconSize = CGSize(width: 160.0, height: 160.0) - strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0), size: iconSize) + var iconSize = CGSize(width: 160.0, height: 160.0) + var iconOffset: CGFloat = 0.0 + if let _ = animationFile { + iconSize = CGSize(width: 120.0, height: 120.0) + iconOffset = 32.0 + } + strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize) strongSelf.animationNode.updateLayout(size: iconSize) let _ = labelApply() diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 5ec7990494..644158a83a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -755,7 +755,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat self.controllerInteraction?.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, false, self, self.avatarNode.frame) } else if let peer = self.peer { if let adMessageId = self.adMessageId { - self.controllerInteraction?.activateAdAction(adMessageId, nil) + self.controllerInteraction?.activateAdAction(adMessageId, nil, false, false) } else { if let channel = peer as? TelegramChannel, case .broadcast = channel.info { self.controllerInteraction?.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, .default) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 49875649c4..ba57a1641a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -118,7 +118,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent self.contentNode.activateAction = { [weak self] in if let strongSelf = self, let item = strongSelf.item { if let _ = item.message.adAttribute { - item.controllerInteraction.activateAdAction(item.message.id, strongSelf.contentNode.makeProgress()) + item.controllerInteraction.activateAdAction(item.message.id, strongSelf.contentNode.makeProgress(), false, false) } else { var webPageContent: TelegramMediaWebpageLoadedContent? for media in item.message.media { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 6c39fe96db..80126b29ca 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -616,7 +616,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _, _ in + }, activateAdAction: { _, _, _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { @@ -1208,8 +1208,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let invoice { let inputData = Promise() inputData.set(BotCheckoutController.InputData.fetch(context: strongSelf.context, source: .slug(slug)) - |> map(Optional.init) - |> `catch` { _ -> Signal in + |> map(Optional.init) + |> `catch` { _ -> Signal in return .single(nil) }) strongSelf.controllerInteraction.presentController(BotCheckoutController(context: strongSelf.context, invoice: invoice, source: .slug(slug), inputData: inputData, completed: { currencyValue, receiptMessageId in diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index 714ce82b87..1c43afa28c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -473,7 +473,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _, _ in + }, activateAdAction: { _, _, _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 1d555712bf..d78c1a3c8d 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -252,7 +252,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void public let openJoinLink: (String) -> Void public let openWebView: (String, String, Bool, ChatOpenWebViewSource) -> Void - public let activateAdAction: (EngineMessage.Id, Promise?) -> Void + public let activateAdAction: (EngineMessage.Id, Promise?, Bool, Bool) -> Void public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void public let saveMediaToFiles: (EngineMessage.Id) -> Void public let openNoAdsDemo: () -> Void @@ -382,7 +382,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void, openJoinLink: @escaping (String) -> Void, openWebView: @escaping (String, String, Bool, ChatOpenWebViewSource) -> Void, - activateAdAction: @escaping (EngineMessage.Id, Promise?) -> Void, + activateAdAction: @escaping (EngineMessage.Id, Promise?, Bool, Bool) -> Void, openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void, saveMediaToFiles: @escaping (EngineMessage.Id) -> Void, openNoAdsDemo: @escaping () -> Void, diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index 4d91e53e12..84a6c22509 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -144,6 +144,34 @@ public func animationCacheFetchFile(postbox: Postbox, userLocation: MediaResourc } } +public func animationCacheLoadLocalFile(name: String, type: AnimationCacheAnimationType, keyframeOnly: Bool, customColor: UIColor?) -> (AnimationCacheFetchOptions) -> Disposable { + return { options in + let source = AnimatedStickerNodeLocalFileSource(name: name) + let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in + guard let result = result else { + return + } + + switch type { + case .video: + cacheVideoAnimation(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly, customColor: customColor) + case .lottie: + guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else { + options.writer.finish() + return + } + cacheLottieAnimation(data: data, width: Int(options.size.width), height: Int(options.size.height), keyframeOnly: keyframeOnly, writer: options.writer, firstFrameOnly: options.firstFrameOnly, customColor: customColor) + case .still: + cacheStillSticker(path: result, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, customColor: customColor) + } + }) + + return ActionDisposable { + dataDisposable.dispose() + } + } +} + private func generatePeerNameColorImage(nameColor: PeerNameColors.Colors, isDark: Bool, bounds: CGSize = CGSize(width: 40.0, height: 40.0), size: CGSize = CGSize(width: 40.0, height: 40.0)) -> UIImage? { return generateImage(bounds, rotatedContext: { contextSize, context in let bounds = CGRect(origin: CGPoint(), size: contextSize) @@ -310,6 +338,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { private var didProcessTintColor: Bool = false public private(set) var file: TelegramMediaFile? + private var localAnimationName: String? + private var infoDisposable: Disposable? private var disposable: Disposable? private var fetchDisposable: Disposable? @@ -440,6 +470,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } case .ton: self.updateTon() + case let .animation(name): + self.updateLocalAnimation(name: name, attemptSynchronousLoad: attemptSynchronousLoad) } } else if let file = file { self.updateFile(file: file, attemptSynchronousLoad: attemptSynchronousLoad) @@ -629,6 +661,42 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { self.contents = tonImage?.cgImage } + private func updateLocalAnimation(name: String, attemptSynchronousLoad: Bool) { + guard let arguments = self.arguments else { + return + } + + self.localAnimationName = name + + if attemptSynchronousLoad { + if !arguments.renderer.loadFirstFrameSynchronously(target: self, cache: arguments.cache, itemId: name, size: arguments.pixelSize) { + + } + + self.loadAnimation() + } else { + self.loadDisposable = arguments.renderer.loadFirstFrame(target: self, cache: arguments.cache, itemId: name, size: arguments.pixelSize, fetch: animationCacheLoadLocalFile(name: name, type: .lottie, keyframeOnly: true, customColor: nil), completion: { [weak self] result, isFinal in + guard let strongSelf = self else { + return + } + strongSelf.loadAnimation() + }) + } + } + + private func loadLocalAnimation() { + guard let arguments = self.arguments else { + return + } + + guard let name = self.localAnimationName else { + return + } + + let keyframeOnly = arguments.pixelSize.width >= 120.0 + self.disposable = arguments.renderer.add(target: self, cache: arguments.cache, itemId: name, unique: arguments.unique, size: arguments.pixelSize, fetch: animationCacheLoadLocalFile(name: name, type: .lottie, keyframeOnly: keyframeOnly, customColor: nil)) + } + private func updateFile(file: TelegramMediaFile, attemptSynchronousLoad: Bool) { guard let arguments = self.arguments else { return diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD new file mode 100644 index 0000000000..2a22ef269a --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD @@ -0,0 +1,36 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GiftItemComponent", + module_name = "GiftItemComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/MultilineTextWithEntitiesComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/TelegramStringFormatting", + "//submodules/PresentationDataUtils", + "//submodules/TextFormat", + "//submodules/AvatarNode", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView", + "//submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift new file mode 100644 index 0000000000..340460ce72 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -0,0 +1,524 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramCore +import TelegramPresentationData +import AppBundle +import AccountContext +import MultilineTextComponent +import MultilineTextWithEntitiesComponent +import EmojiTextAttachmentView +import TextFormat +import ItemShimmeringLoadingComponent +import AvatarNode + +public final class GiftItemComponent: Component { + public enum Subject: Equatable { + case premium(Int32) + case starGift(Int64, TelegramMediaFile) + } + + public struct Ribbon: Equatable { + public let text: String + public let color: UIColor + + public init(text: String, color: UIColor) { + self.text = text + self.color = color + } + } + + let context: AccountContext + let theme: PresentationTheme + let peer: EnginePeer? + let subject: Subject + let title: String? + let subtitle: String? + let price: String + let ribbon: Ribbon? + let isLoading: Bool + + public init( + context: AccountContext, + theme: PresentationTheme, + peer: EnginePeer?, + subject: Subject, + title: String? = nil, + subtitle: String? = nil, + price: String, + ribbon: Ribbon? = nil, + isLoading: Bool = false + ) { + self.context = context + self.theme = theme + self.peer = peer + self.subject = subject + self.title = title + self.subtitle = subtitle + self.price = price + self.ribbon = ribbon + self.isLoading = isLoading + } + + public static func ==(lhs: GiftItemComponent, rhs: GiftItemComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.subject != rhs.subject { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.subtitle != rhs.subtitle { + return false + } + if lhs.price != rhs.price { + return false + } + if lhs.ribbon != rhs.ribbon { + return false + } + if lhs.isLoading != rhs.isLoading { + return false + } + return true + } + + public final class View: UIView { + private var component: GiftItemComponent? + private weak var componentState: EmptyComponentState? + + private let backgroundLayer = SimpleLayer() + private var loadingBackground: ComponentView? + + private var avatarNode: AvatarNode? + private let title = ComponentView() + private let subtitle = ComponentView() + private let button = ComponentView() + private let ribbon = UIImageView() + private let ribbonText = ComponentView() + + private var animationLayer: InlineStickerItemLayer? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.layer.addSublayer(self.backgroundLayer) + + self.backgroundLayer.cornerRadius = 10.0 + if #available(iOS 13.0, *) { + self.backgroundLayer.cornerCurve = .circular + } + self.backgroundLayer.masksToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: GiftItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.componentState = state + + let size = CGSize(width: availableSize.width, height: component.title != nil ? 178.0 : 154.0) + + if component.isLoading { + let loadingBackground: ComponentView + if let current = self.loadingBackground { + loadingBackground = current + } else { + loadingBackground = ComponentView() + self.loadingBackground = loadingBackground + } + + let _ = loadingBackground.update( + transition: transition, + component: AnyComponent( + ItemShimmeringLoadingComponent(color: component.theme.list.itemAccentColor, cornerRadius: 10.0) + ), + environment: {}, + containerSize: size + ) + if let loadingBackgroundView = loadingBackground.view { + if loadingBackgroundView.layer.superlayer == nil { + self.layer.insertSublayer(loadingBackgroundView.layer, above: self.backgroundLayer) + } + loadingBackgroundView.frame = CGRect(origin: .zero, size: size) + } + } else if let loadingBackground = self.loadingBackground { + loadingBackground.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + loadingBackground.view?.layer.removeFromSuperlayer() + }) + self.loadingBackground = nil + } + + let emoji: ChatTextInputTextCustomEmojiAttribute? + var file: TelegramMediaFile? + var animationOffset: CGFloat = 0.0 + switch component.subject { + case let .premium(months): + emoji = ChatTextInputTextCustomEmojiAttribute( + interactivelySelectedFromPackId: nil, + fileId: 0, + file: nil, + custom: .animation(name: "Gift\(months)") + ) + case let .starGift(_, fileValue): + file = fileValue + emoji = ChatTextInputTextCustomEmojiAttribute( + interactivelySelectedFromPackId: nil, + fileId: fileValue.fileId.id, + file: fileValue + ) + animationOffset = 16.0 + } + + let iconSize = CGSize(width: 88.0, height: 88.0) + if self.animationLayer == nil, let emoji { + let animationLayer = InlineStickerItemLayer( + context: .account(component.context), + userLocation: .other, + attemptSynchronousLoad: false, + emoji: emoji, + file: file, + cache: component.context.animationCache, + renderer: component.context.animationRenderer, + unique: false, + placeholderColor: component.theme.list.mediaPlaceholderColor, + pointSize: CGSize(width: iconSize.width * 2.0, height: iconSize.height * 2.0), + loopCount: 1 + ) + animationLayer.isVisibleForAnimations = true + self.animationLayer = animationLayer + self.layer.addSublayer(animationLayer) + } + + if let animationLayer = self.animationLayer { + transition.setFrame(layer: animationLayer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: animationOffset), size: iconSize)) + } + + if let title = component.title { + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: title, font: Font.semibold(15.0), textColor: component.theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center + ) + ), + environment: {}, + containerSize: availableSize + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: 94.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + } + + if let subtitle = component.subtitle { + let subtitleSize = self.subtitle.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: component.theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center + ) + ), + environment: {}, + containerSize: availableSize + ) + let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) / 2.0), y: 112.0), size: subtitleSize) + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.addSubview(subtitleView) + } + transition.setFrame(view: subtitleView, frame: subtitleFrame) + } + } + + let buttonSize = self.button.update( + transition: transition, + component: AnyComponent( + ButtonContentComponent( + context: component.context, + text: component.price, + color: component.price.containsEmoji ? UIColor(rgb: 0xd3720a) : component.theme.list.itemAccentColor, + isStars: component.price.containsEmoji) + ), + environment: {}, + containerSize: availableSize + ) + let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0), y: size.height - buttonSize.height - 10.0), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: buttonFrame) + } + + if let ribbon = component.ribbon { + let ribbonTextSize = self.ribbonText.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: ribbon.text, font: Font.semibold(11.0), textColor: .white)), + horizontalAlignment: .center + ) + ), + environment: {}, + containerSize: availableSize + ) + if let ribbonTextView = self.ribbonText.view { + if ribbonTextView.superview == nil { + self.addSubview(self.ribbon) + self.addSubview(ribbonTextView) + } + ribbonTextView.bounds = CGRect(origin: .zero, size: ribbonTextSize) + + if self.ribbon.image == nil { + self.ribbon.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: [ribbon.color.withMultipliedBrightnessBy(1.1), ribbon.color.withMultipliedBrightnessBy(0.9)], direction: .diagonal) + } + if let ribbonImage = self.ribbon.image { + self.ribbon.frame = CGRect(origin: CGPoint(x: size.width - ribbonImage.size.width + 2.0, y: -2.0), size: ribbonImage.size) + } + ribbonTextView.transform = CGAffineTransform(rotationAngle: .pi / 4.0) + ribbonTextView.center = CGPoint(x: size.width - 20.0, y: 20.0) + } + } else { + if self.ribbonText.view?.superview != nil { + self.ribbon.removeFromSuperview() + self.ribbonText.view?.removeFromSuperview() + } + } + + if let peer = component.peer { + let avatarNode: AvatarNode + if let current = self.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0)) + self.addSubview(avatarNode.view) + self.avatarNode = avatarNode + } + + avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, displayDimensions: CGSize(width: 20.0, height: 20.0)) + avatarNode.frame = CGRect(origin: CGPoint(x: 2.0, y: 2.0), size: CGSize(width: 20.0, height: 20.0)) + } + + self.backgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor + transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size)) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ButtonContentComponent: Component { + let context: AccountContext + let text: String + let color: UIColor + let isStars: Bool + + public init( + context: AccountContext, + text: String, + color: UIColor, + isStars: Bool = false + ) { + self.context = context + self.text = text + self.color = color + self.isStars = isStars + } + + public static func ==(lhs: ButtonContentComponent, rhs: ButtonContentComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.color != rhs.color { + return false + } + if lhs.isStars != rhs.isStars { + return false + } + return true + } + + public final class View: UIView { + private var component: ButtonContentComponent? + private weak var componentState: EmptyComponentState? + + private let backgroundLayer = SimpleLayer() + private let title = ComponentView() + + private var starsLayer: StarsButtonEffectLayer? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.layer.addSublayer(self.backgroundLayer) + self.backgroundLayer.masksToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ButtonContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.componentState = state + + let attributedText = NSMutableAttributedString(string: component.text, font: Font.semibold(11.0), textColor: component.color) + let range = (attributedText.string as NSString).range(of: "⭐️") + if range.location != NSNotFound { + attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + attributedText.addAttribute(.font, value: Font.semibold(15.0), range: range) + attributedText.addAttribute(.baselineOffset, value: 2.0, range: NSRange(location: range.upperBound, length: attributedText.length - range.upperBound)) + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + placeholderColor: .white, + text: .plain(attributedText) + ) + ), + environment: {}, + containerSize: availableSize + ) + + let padding: CGFloat = 9.0 + let size = CGSize(width: titleSize.width + padding * 2.0, height: 30.0) + + if component.isStars { + let starsLayer: StarsButtonEffectLayer + if let current = self.starsLayer { + starsLayer = current + } else { + starsLayer = StarsButtonEffectLayer() + self.layer.addSublayer(starsLayer) + self.starsLayer = starsLayer + } + starsLayer.frame = CGRect(origin: .zero, size: size) + starsLayer.update(size: size) + } else { + self.starsLayer?.removeFromSuperlayer() + self.starsLayer = nil + } + + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + let backgroundColor: UIColor + if component.color.rgb == 0xd3720a { + backgroundColor = UIColor(rgb: 0xffc83d, alpha: 0.2) + } else { + backgroundColor = component.color.withAlphaComponent(0.1) + } + + self.backgroundLayer.backgroundColor = backgroundColor.cgColor + transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size)) + self.backgroundLayer.cornerRadius = size.height / 2.0 + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class StarsButtonEffectLayer: SimpleLayer { + let emitterLayer = CAEmitterLayer() + + override init() { + super.init() + + self.addSublayer(self.emitterLayer) + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + let color = UIColor(rgb: 0xffbe27) + + let emitter = CAEmitterCell() + emitter.name = "emitter" + emitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage + emitter.birthRate = 25.0 + emitter.lifetime = 2.0 + emitter.velocity = 12.0 + emitter.velocityRange = 3 + emitter.scale = 0.1 + emitter.scaleRange = 0.08 + emitter.alphaRange = 0.1 + emitter.emissionRange = .pi * 2.0 + emitter.setValue(3.0, forKey: "mass") + emitter.setValue(2.0, forKey: "massRange") + + let staticColors: [Any] = [ + color.withAlphaComponent(0.0).cgColor, + color.cgColor, + color.cgColor, + color.withAlphaComponent(0.0).cgColor + ] + let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife") + staticColorBehavior.setValue(staticColors, forKey: "colors") + emitter.setValue([staticColorBehavior], forKey: "emitterBehaviors") + + self.emitterLayer.emitterCells = [emitter] + } + + func update(size: CGSize) { + if self.emitterLayer.emitterCells == nil { + self.setup() + } + self.emitterLayer.emitterShape = .circle + self.emitterLayer.emitterSize = CGSize(width: size.width * 0.7, height: size.height * 0.7) + self.emitterLayer.emitterMode = .surface + self.emitterLayer.frame = CGRect(origin: .zero, size: size) + self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD new file mode 100644 index 0000000000..50f4c91908 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD @@ -0,0 +1,49 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GiftOptionsScreen", + module_name = "GiftOptionsScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/ItemListUI", + "//submodules/TelegramStringFormatting", + "//submodules/PresentationDataUtils", + "//submodules/Components/SheetComponent", + "//submodules/UndoUI", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ScrollComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/Components/BlurredBackgroundComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", + "//submodules/ConfettiEffect", + "//submodules/InAppPurchaseManager", + "//submodules/TelegramUI/Components/TabSelectorComponent", + "//submodules/TelegramUI/Components/Gifts/GiftSetupScreen", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift new file mode 100644 index 0000000000..f551c43f45 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -0,0 +1,909 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import PresentationDataUtils +import AccountContext +import ComponentFlow +import ViewControllerComponent +import MultilineTextComponent +import BalancedTextComponent +import BundleIconComponent +import Markdown +import TelegramStringFormatting +import PlainButtonComponent +import BlurredBackgroundComponent +import PremiumStarComponent +import ConfettiEffect +import TextFormat +import GiftItemComponent +import InAppPurchaseManager +import TabSelectorComponent +import GiftSetupScreen + +final class GiftOptionsScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let peerId: EnginePeer.Id + let premiumOptions: [CachedPremiumGiftOption] + + init( + context: AccountContext, + peerId: EnginePeer.Id, + premiumOptions: [CachedPremiumGiftOption] + ) { + self.context = context + self.peerId = peerId + self.premiumOptions = premiumOptions + } + + static func ==(lhs: GiftOptionsScreenComponent, rhs: GiftOptionsScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.premiumOptions != rhs.premiumOptions { + return false + } + return true + } + + private final class ScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + public enum StarsFilter: Int { + case all + case limited + case stars10 + case stars25 + case stars50 + case stars100 + } + + final class View: UIView, UIScrollViewDelegate { + private let topOverscrollLayer = SimpleLayer() + private let scrollView: ScrollView + + private let topPanel = ComponentView() + private let topSeparator = ComponentView() + private let cancelButton = ComponentView() + + private let header = ComponentView() + + private let premiumTitle = ComponentView() + private let premiumDescription = ComponentView() + private var premiumItems: [AnyHashable: ComponentView] = [:] + private var selectedPremiumGift: String? + + private let starsTitle = ComponentView() + private let starsDescription = ComponentView() + private var starsItems: [AnyHashable: ComponentView] = [:] + private let tabSelector = ComponentView() + private var starsFilter: StarsFilter = .all + + private var isUpdating: Bool = false + + private var component: GiftOptionsScreenComponent? + private(set) weak var state: State? + private var environment: EnvironmentType? + + private var starsItemsOrigin: CGFloat = 0.0 + + private var chevronImage: (UIImage, PresentationTheme)? + + override init(frame: CGRect) { + self.scrollView = ScrollView() + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.alwaysBounceVertical = true + + super.init(frame: frame) + + self.scrollView.delegate = self + self.addSubview(self.scrollView) + + self.scrollView.layer.addSublayer(self.topOverscrollLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func scrollToTop() { + self.scrollView.setContentOffset(CGPoint(), animated: true) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateScrolling(transition: .immediate) + } + + private func updateScrolling(transition: ComponentTransition) { + guard let environment = self.environment, let component = self.component else { + return + } + + let availableWidth = self.scrollView.bounds.width + let contentOffset = self.scrollView.contentOffset.y + + let topPanelAlpha = min(20.0, max(0.0, contentOffset - 95.0)) / 20.0 + if let topPanelView = self.topPanel.view, let topSeparator = self.topSeparator.view { + transition.setAlpha(view: topPanelView, alpha: topPanelAlpha) + transition.setAlpha(view: topSeparator, alpha: topPanelAlpha) + } + + let topInset: CGFloat = environment.navigationHeight - 56.0 + + let premiumTitleInitialPosition = (topInset + 160.0) + let premiumTitleOffsetDelta = premiumTitleInitialPosition - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) + let premiumTitleOffset = contentOffset + max(0.0, min(1.0, contentOffset / premiumTitleOffsetDelta)) * 10.0 + let premiumTitleFraction = max(0.0, min(1.0, premiumTitleOffset / premiumTitleOffsetDelta)) + let premiumTitleScale = 1.0 - premiumTitleFraction * 0.36 + var premiumTitleAdditionalOffset: CGFloat = 0.0 + + let starsTitleOffsetDelta = (topInset + 100.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) + + let starsTitleOffset: CGFloat + let starsTitleFraction: CGFloat + if contentOffset > 350 { + starsTitleOffset = contentOffset + max(0.0, min(1.0, (contentOffset - 350.0) / starsTitleOffsetDelta)) * 10.0 + starsTitleFraction = max(0.0, min(1.0, (starsTitleOffset - 350.0) / starsTitleOffsetDelta)) + if contentOffset > 380.0 { + premiumTitleAdditionalOffset = contentOffset - 380.0 + } + } else { + starsTitleOffset = contentOffset + starsTitleFraction = 0.0 + } + let starsTitleScale = 1.0 - starsTitleFraction * 0.36 + if let starsTitleView = self.starsTitle.view { + transition.setPosition(view: starsTitleView, position: CGPoint(x: availableWidth / 2.0, y: max(topInset + 455.0 - starsTitleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0))) + transition.setScale(view: starsTitleView, scale: starsTitleScale) + } + + if let premiumTitleView = self.premiumTitle.view { + transition.setPosition(view: premiumTitleView, position: CGPoint(x: availableWidth / 2.0, y: max(premiumTitleInitialPosition - premiumTitleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0) - premiumTitleAdditionalOffset)) + transition.setScale(view: premiumTitleView, scale: premiumTitleScale) + } + + + if let headerView = self.header.view { + transition.setPosition(view: headerView, position: CGPoint(x: availableWidth / 2.0, y: topInset + headerView.bounds.height / 2.0 - 30.0 - premiumTitleOffset * premiumTitleScale)) + transition.setScale(view: headerView, scale: premiumTitleScale) + } + + let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -10.0) + if let starGifts = self.state?.starGifts { + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + + let optionSpacing: CGFloat = 10.0 + let optionWidth = (availableWidth - sideInset * 2.0 - optionSpacing * 2.0) / 3.0 + let starsOptionSize = CGSize(width: optionWidth, height: 154.0) + + var validIds: [AnyHashable] = [] + var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: self.starsItemsOrigin), size: starsOptionSize) + + let controller = environment.controller + + for gift in starGifts { + var isVisible = false + if visibleBounds.intersects(itemFrame) { + isVisible = true + } + + if isVisible { + if self.starsFilter != .all { + switch self.starsFilter { + case .all: + break + case .limited: + if gift.availability == nil { + continue + } + case .stars10: + if gift.price != 10 { + continue + } + case .stars25: + if gift.price != 25 { + continue + } + case .stars50: + if gift.price != 50 { + continue + } + case .stars100: + if gift.price != 100 { + continue + } + } + } + + let itemId = AnyHashable(gift.id) + validIds.append(itemId) + + var itemTransition = transition + let visibleItem: ComponentView + if let current = self.starsItems[itemId] { + visibleItem = current + } else { + visibleItem = ComponentView() + if !transition.animation.isImmediate { + itemTransition = .immediate + } + self.starsItems[itemId] = visibleItem + } + + let _ = visibleItem.update( + transition: itemTransition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + GiftItemComponent( + context: component.context, + theme: environment.theme, + peer: nil, + subject: .starGift(gift.id, gift.file), + price: "⭐️ \(gift.price)", + ribbon: gift.availability != nil ? + GiftItemComponent.Ribbon( + text: "Limited", + color: UIColor(rgb: 0x58c1fe) + ) + : nil + ) + ), + effectAlignment: .center, + action: { [weak self] in + if let self, let component = self.component { + controller()?.push(GiftSetupScreen(context: component.context, peerId: component.peerId, gift: gift)) + } + }, + animateAlpha: false + ) + ), + environment: {}, + containerSize: starsOptionSize + ) + if let itemView = visibleItem.view { + if itemView.superview == nil { + self.scrollView.addSubview(itemView) + if !transition.animation.isImmediate { + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + transition.animateScale(view: itemView, from: 0.01, to: 1.0) + } + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + } + itemFrame.origin.x += itemFrame.width + optionSpacing + if itemFrame.maxX > availableWidth { + itemFrame.origin.x = sideInset + itemFrame.origin.y += starsOptionSize.height + optionSpacing + } + } + + var removeIds: [AnyHashable] = [] + for (id, item) in self.starsItems { + if !validIds.contains(id) { + removeIds.append(id) + if let itemView = item.view { + if !transition.animation.isImmediate { + itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + itemView.removeFromSuperview() + }) + } else { + itemView.removeFromSuperview() + } + } + } + } + for id in removeIds { + self.starsItems.removeValue(forKey: id) + } + } + } + + func update(component: GiftOptionsScreenComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let environment = environment[EnvironmentType.self].value + let controller = environment.controller + let themeUpdated = self.environment?.theme !== environment.theme + self.environment = environment + + if self.component == nil { + + } + + self.component = component + self.state = state + + if themeUpdated { + self.backgroundColor = environment.theme.list.blocksBackgroundColor + } + +// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let theme = environment.theme + let strings = environment.strings + + let textColor = theme.list.itemPrimaryTextColor + let accentColor = theme.list.itemAccentColor + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + + let bottomContentInset: CGFloat = 24.0 + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let sectionSpacing: CGFloat = 24.0 + + let _ = bottomContentInset + let _ = sectionSpacing + + var contentHeight: CGFloat = 0.0 + contentHeight += environment.navigationHeight - 56.0 + 188.0 + + let headerSize = self.header.update( + transition: .immediate, + component: AnyComponent( + GiftAvatarComponent( + context: component.context, + theme: theme, + peers: state.peer.flatMap { [$0] } ?? [], + isVisible: true, + hasIdleAnimations: true, + color: UIColor(rgb: 0xf9b004), + hasLargeParticles: true + ) + ), + environment: {}, + containerSize: CGSize(width: min(414.0, availableSize.width), height: 220.0) + ) + if let headerView = self.header.view { + if headerView.superview == nil { + self.addSubview(headerView) + } + transition.setBounds(view: headerView, bounds: CGRect(origin: .zero, size: headerSize)) + } + + let topPanelSize = self.topPanel.update( + transition: transition, + component: AnyComponent(BlurredBackgroundComponent( + color: theme.rootController.navigationBar.blurredBackgroundColor + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: environment.navigationHeight) + ) + + let topSeparatorSize = self.topSeparator.update( + transition: transition, + component: AnyComponent(Rectangle( + color: theme.rootController.navigationBar.separatorColor + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: UIScreenPixel) + ) + let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: topPanelSize.height)) + let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height)) + if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view { + if topPanelView.superview == nil { + self.addSubview(topPanelView) + self.addSubview(topSeparatorView) + } + transition.setFrame(view: topPanelView, frame: topPanelFrame) + transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame) + } + + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: strings.Common_Cancel, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)), + horizontalAlignment: .center + ) + ), + effectAlignment: .center, + action: { + controller()?.dismiss() + }, + animateScale: false + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0 - cancelButtonSize.height / 2.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + + let premiumTitleSize = self.premiumTitle.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "Gift Premium", font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + if let premiumTitleView = self.premiumTitle.view { + if premiumTitleView.superview == nil { + self.addSubview(premiumTitleView) + } + transition.setBounds(view: premiumTitleView, bounds: CGRect(origin: .zero, size: premiumTitleSize)) + } + + if self.chevronImage == nil || self.chevronImage?.1 !== theme { + self.chevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: accentColor)!, theme) + } + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + let peerName = state.peer?.compactDisplayTitle ?? "" + + let premiumDescriptionString = parseMarkdownIntoAttributedString("Give **\(peerName)** access to exclusive features with Telegram Premium. [See Features >]()", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + if let range = premiumDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 { + premiumDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: premiumDescriptionString.string)) + } + let premiumDescriptionSize = self.premiumDescription.update( + transition: transition, + component: AnyComponent(BalancedTextComponent( + text: .plain(premiumDescriptionString), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: accentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 1000.0) + ) + let premiumDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - premiumDescriptionSize.width) / 2.0), y: contentHeight), size: premiumDescriptionSize) + if let premiumDescriptionView = self.premiumDescription.view { + if premiumDescriptionView.superview == nil { + self.scrollView.addSubview(premiumDescriptionView) + } + transition.setFrame(view: premiumDescriptionView, frame: premiumDescriptionFrame) + } + contentHeight += premiumDescriptionSize.height + contentHeight += 11.0 + + let optionSpacing: CGFloat = 10.0 + let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0 + + if let premiumProducts = state.premiumProducts { + let premiumOptionSize = CGSize(width: optionWidth, height: 178.0) + + var validIds: [AnyHashable] = [] + var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumOptionSize) + for product in premiumProducts { + let itemId = AnyHashable(product.storeProduct.id) + validIds.append(itemId) + + var itemTransition = transition + let visibleItem: ComponentView + if let current = self.premiumItems[itemId] { + visibleItem = current + } else { + visibleItem = ComponentView() + if !transition.animation.isImmediate { + itemTransition = .immediate + } + self.premiumItems[itemId] = visibleItem + } + + let title: String + switch product.months { + case 6: + title = "6 months" + case 12: + title = "1 year" + default: + title = "3 months" + } + + let _ = visibleItem.update( + transition: itemTransition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + GiftItemComponent( + context: component.context, + theme: theme, + peer: nil, + subject: .premium(product.months), + title: title, + subtitle: "Premium", + price: product.price, + ribbon: product.discount.flatMap { + GiftItemComponent.Ribbon( + text: "-\($0)%", + color: UIColor(rgb: 0xfa4846) + ) + }, + isLoading: self.selectedPremiumGift == product.id + ) + ), + effectAlignment: .center, + action: { [weak self] in + self?.selectedPremiumGift = product.id + self?.state?.updated() + + Queue.mainQueue().after(4.0, { + self?.selectedPremiumGift = nil + self?.state?.updated() + }) + }, + animateAlpha: false + ) + ), + environment: {}, + containerSize: premiumOptionSize + ) + if let itemView = visibleItem.view { + if itemView.superview == nil { + self.scrollView.addSubview(itemView) + if !transition.animation.isImmediate { + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) + } + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + itemFrame.origin.x += itemFrame.width + optionSpacing + if itemFrame.maxX > availableSize.width { + itemFrame.origin.x = sideInset + itemFrame.origin.y += premiumOptionSize.height + optionSpacing + } + } + + var removeIds: [AnyHashable] = [] + for (id, item) in self.premiumItems { + if !validIds.contains(id) { + removeIds.append(id) + if let itemView = item.view { + if !transition.animation.isImmediate { + itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + itemView.removeFromSuperview() + }) + } else { + itemView.removeFromSuperview() + } + } + } + } + for id in removeIds { + self.premiumItems.removeValue(forKey: id) + } + + contentHeight += ceil(CGFloat(premiumProducts.count) / 3.0) * premiumOptionSize.height + contentHeight += 66.0 + } + + + let starsTitleSize = self.starsTitle.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "Send a Gift", font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + if let starsTitleView = self.starsTitle.view { + if starsTitleView.superview == nil { + self.addSubview(starsTitleView) + } + transition.setBounds(view: starsTitleView, bounds: CGRect(origin: .zero, size: starsTitleSize)) + } + + let starsDescriptionString = parseMarkdownIntoAttributedString("Give **\(peerName)** gifts that can be kept on the profile or converted to Stars. [What are Stars >]()", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + if let range = starsDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 { + starsDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: starsDescriptionString.string)) + } + let starsDescriptionSize = self.starsDescription.update( + transition: transition, + component: AnyComponent(BalancedTextComponent( + text: .plain(starsDescriptionString), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: accentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 1000.0) + ) + let starsDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - starsDescriptionSize.width) / 2.0), y: contentHeight), size: starsDescriptionSize) + if let starsDescriptionView = self.starsDescription.view { + if starsDescriptionView.superview == nil { + self.scrollView.addSubview(starsDescriptionView) + } + transition.setFrame(view: starsDescriptionView, frame: starsDescriptionFrame) + } + contentHeight += starsDescriptionSize.height + contentHeight += 16.0 + + let tabSelectorSize = self.tabSelector.update( + transition: transition, + component: AnyComponent(TabSelectorComponent( + context: component.context, + colors: TabSelectorComponent.Colors( + foreground: theme.list.itemSecondaryTextColor, + selection: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), + simple: true + ), + items: [ + TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.all.rawValue), + title: "All Gifts" + ), + TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.limited.rawValue), + title: "Limited" + ), + TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.stars10.rawValue), + title: "⭐️10" + ), + TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.stars25.rawValue), + title: "⭐️25" + ), + TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.stars50.rawValue), + title: "⭐️50" + ), + TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.stars100.rawValue), + title: "⭐️100" + ) + ], + selectedId: AnyHashable(self.starsFilter.rawValue), + setSelectedId: { [weak self] id in + guard let self, let idValue = id.base as? Int, let starsFilter = StarsFilter(rawValue: idValue) else { + return + } + if self.starsFilter != starsFilter { + self.starsFilter = starsFilter + self.state?.updated(transition: .easeInOut(duration: 0.25)) + } + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0) + ) + if let tabSelectorView = self.tabSelector.view { + if tabSelectorView.superview == nil { + self.scrollView.addSubview(tabSelectorView) + } + transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabSelectorSize.width) / 2.0), y: contentHeight), size: tabSelectorSize)) + } + contentHeight += tabSelectorSize.height + contentHeight += 19.0 + + if let starGifts = state.starGifts { + self.starsItemsOrigin = contentHeight + + let starsOptionSize = CGSize(width: optionWidth, height: 154.0) + contentHeight += ceil(CGFloat(starGifts.count) / 3.0) * starsOptionSize.height + contentHeight += 66.0 + } + + contentHeight += bottomContentInset + contentHeight += environment.safeInsets.bottom + + let previousBounds = self.scrollView.bounds + + let contentSize = CGSize(width: availableSize.width, height: contentHeight) + if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { + self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) + } + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0) + if self.scrollView.scrollIndicatorInsets != scrollInsets { + self.scrollView.scrollIndicatorInsets = scrollInsets + } + + if !previousBounds.isEmpty, !transition.animation.isImmediate { + let bounds = self.scrollView.bounds + if bounds.maxY != previousBounds.maxY { + let offsetY = previousBounds.maxY - bounds.maxY + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) + } + } + + self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0)) + + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + final class State: ComponentState { + private let context: AccountContext + private var disposable: Disposable? + private var updateDisposable: Disposable? + + fileprivate var peer: EnginePeer? + fileprivate var premiumProducts: [PremiumGiftProduct]? + fileprivate var starGifts: [StarGift]? + + init( + context: AccountContext, + peerId: EnginePeer.Id, + premiumOptions: [CachedPremiumGiftOption] + ) { + self.context = context + + super.init() + + let availableProducts: Signal<[InAppPurchaseManager.Product], NoError> + if let inAppPurchaseManager = context.inAppPurchaseManager { + availableProducts = inAppPurchaseManager.availableProducts + } else { + availableProducts = .single([]) + } + + self.disposable = combineLatest( + queue: Queue.mainQueue(), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer.init(id: peerId) + ), + availableProducts, + context.engine.payments.cachedStarGifts() + ).start(next: { [weak self] peer, availableProducts, starGifts in + guard let self, let peer else { + return + } + self.peer = peer + + let shortestOptionPrice: (Int64, NSDecimalNumber) + if let product = availableProducts.first(where: { $0.id.hasSuffix(".monthly") }) { + shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue) + } else { + shortestOptionPrice = (1, NSDecimalNumber(decimal: 1)) + } + + var premiumProducts: [PremiumGiftProduct] = [] + for option in premiumOptions { + if let product = availableProducts.first(where: { $0.id == option.storeProductId }), !product.isSubscription { + let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(option.months) / Float(shortestOptionPrice.0) + let discountValue = Int(round((1.0 - fraction) * 20.0) * 5.0) + premiumProducts.append(PremiumGiftProduct(giftOption: option, storeProduct: product, discount: discountValue > 0 ? discountValue : nil)) + } + } + self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months }) + + self.starGifts = starGifts + + self.updated() + }) + + self.updateDisposable = self.context.engine.payments.keepStarGiftsUpdated().start() + } + + deinit { + self.disposable?.dispose() + self.updateDisposable?.dispose() + } + } + + func makeState() -> State { + return State(context: self.context, peerId: self.peerId, premiumOptions: self.premiumOptions) + } + + func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScreenProtocol { + private let context: AccountContext + + public init(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption]) { + self.context = context + + super.init(context: context, component: GiftOptionsScreenComponent( + context: context, + peerId: peerId, + premiumOptions: premiumOptions + ), navigationBarAppearance: .none, theme: .default, updatedPresentationData: nil) + + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.context.sharedContext.currentPresentationData.with { $0 }.strings.Common_Back, style: .plain, target: nil, action: nil) + + + self.scrollToTop = { [weak self] in + guard let self, let componentView = self.node.hostView.componentView as? GiftOptionsScreenComponent.View else { + return + } + componentView.scrollToTop() + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } +} + +private struct PremiumGiftProduct: Equatable { + let giftOption: CachedPremiumGiftOption + let storeProduct: InAppPurchaseManager.Product + let discount: Int? + + var id: String { + return self.storeProduct.id + } + + var months: Int32 { + return self.giftOption.months + } + + var price: String { + return self.storeProduct.price + } + + var pricePerMonth: String { + return self.storeProduct.pricePerMonth(Int(self.months)) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD new file mode 100644 index 0000000000..1c0a9254dc --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD @@ -0,0 +1,45 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GiftSetupScreen", + module_name = "GiftSetupScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AccountContext", + "//submodules/PresentationDataUtils", + "//submodules/Markdown", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/AppBundle", + "//submodules/WallpaperBackgroundNode", + "//submodules/ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/TextFieldComponent", + "//submodules/TelegramUI/Components/ListItemComponentAdaptor", + "//submodules/BotPaymentsUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift new file mode 100644 index 0000000000..4273baca41 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -0,0 +1,366 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import Postbox +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import AccountContext +import WallpaperBackgroundNode +import ListItemComponentAdaptor + +final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { + let context: AccountContext + let theme: PresentationTheme + let componentTheme: PresentationTheme + let strings: PresentationStrings + let sectionId: ItemListSectionId + let fontSize: PresentationFontSize + let chatBubbleCorners: PresentationChatBubbleCorners + let wallpaper: TelegramWallpaper + let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder + + let accountPeer: EnginePeer? + let gift: StarGift + let text: String + + init( + context: AccountContext, + theme: PresentationTheme, + componentTheme: PresentationTheme, + strings: PresentationStrings, + sectionId: ItemListSectionId, + fontSize: PresentationFontSize, + chatBubbleCorners: PresentationChatBubbleCorners, + wallpaper: TelegramWallpaper, + dateTimeFormat: PresentationDateTimeFormat, + nameDisplayOrder: PresentationPersonNameOrder, + accountPeer: EnginePeer?, + gift: StarGift, + text: String + ) { + self.context = context + self.theme = theme + self.componentTheme = componentTheme + self.strings = strings + self.sectionId = sectionId + self.fontSize = fontSize + self.chatBubbleCorners = chatBubbleCorners + self.wallpaper = wallpaper + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.accountPeer = accountPeer + self.gift = gift + self.text = text + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatGiftPreviewItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ChatGiftPreviewItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } + + public func item() -> ListViewItem { + return self + } + + public static func ==(lhs: ChatGiftPreviewItem, rhs: ChatGiftPreviewItem) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.componentTheme !== rhs.componentTheme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.fontSize != rhs.fontSize { + return false + } + if lhs.chatBubbleCorners != rhs.chatBubbleCorners { + return false + } + if lhs.wallpaper != rhs.wallpaper { + return false + } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } + if lhs.nameDisplayOrder != rhs.nameDisplayOrder { + return false + } + if lhs.accountPeer != rhs.accountPeer { + return false + } + if lhs.text != rhs.text { + return false + } + return true + } +} + +final class ChatGiftPreviewItemNode: ListViewItemNode { + private var backgroundNode: WallpaperBackgroundNode? + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let maskNode: ASImageNode + + private let containerNode: ASDisplayNode + private var messageNodes: [ListViewItemNode]? + private var itemHeaderNodes: [ListViewItemNode.HeaderId: ListViewItemHeaderNode] = [:] + + private var item: ChatGiftPreviewItem? + + private let disposable = MetaDisposable() + + init() { + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.maskNode = ASImageNode() + + self.containerNode = ASDisplayNode() + self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) + + super.init(layerBacked: false, dynamicBounce: false) + + self.clipsToBounds = true + self.isUserInteractionEnabled = false + + self.addSubnode(self.containerNode) + } + + deinit { + self.disposable.dispose() + } + + func asyncLayout() -> (_ item: ChatGiftPreviewItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let currentNodes = self.messageNodes + + var currentBackgroundNode = self.backgroundNode + + return { item, params, neighbors in + if currentBackgroundNode == nil { + currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false) + currentBackgroundNode?.update(wallpaper: item.wallpaper, animated: false) + currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners) + } + + var insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + + let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1)) + + var items: [ListViewItem] = [] + for _ in 0 ..< 1 { + let authorPeerId = item.context.account.peerId + + var peers = SimpleDictionary() + let messages = SimpleDictionary() + + peers[authorPeerId] = item.accountPeer?._asPeer() + + let media: [Media] = [ + TelegramMediaAction(action: .starGift(gift: item.gift, convertStars: item.gift.price, text: item.text, entities: [], nameHidden: false, savedToProfile: false, converted: false)) + //TelegramMediaAction(action: .starGift(amount: item.gift.price, giftId: item.gift.id, nameHidden: false, limitNumber: item.gift.availability != nil ? 1 : nil, limitTotal: item.gift.availability?.total, text: item.text, entities: [])) + ] + let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false)) + } + + var nodes: [ListViewItemNode] = [] + if let messageNodes = currentNodes { + nodes = messageNodes + for i in 0 ..< items.count { + let itemNode = messageNodes[i] + items[i].updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + itemNode.isUserInteractionEnabled = false + + Queue.mainQueue().after(0.01) { + apply(ListViewItemApply(isOnScreen: true)) + } + }) + } + } else { + var messageNodes: [ListViewItemNode] = [] + for i in 0 ..< items.count { + var itemNode: ListViewItemNode? + items[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], completion: { node, apply in + itemNode = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode!.isUserInteractionEnabled = false + messageNodes.append(itemNode!) + } + nodes = messageNodes + } + + var contentSize = CGSize(width: params.width, height: 4.0 + 4.0) +// for node in nodes { +// contentSize.height += node.frame.size.height +// } + contentSize.height = 346.0 + insets = itemListNeighborsGroupedInsets(neighbors, params) + if params.width <= 320.0 { + insets.top = 0.0 + } + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: .zero) + let layoutSize = layout.size + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + + if let currentBackgroundNode { + currentBackgroundNode.update(wallpaper: item.wallpaper, animated: false) + currentBackgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) + } + + strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize) + + strongSelf.messageNodes = nodes + //var topOffset: CGFloat = 4.0 + for node in nodes { + if node.supernode == nil { + strongSelf.containerNode.addSubnode(node) + } + node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: floor((contentSize.height - node.frame.size.height) / 2.0)), size: node.frame.size), within: layoutSize) + //topOffset += node.frame.size.height + } + + if let currentBackgroundNode = currentBackgroundNode, strongSelf.backgroundNode !== currentBackgroundNode { + strongSelf.backgroundNode = currentBackgroundNode + strongSelf.insertSubnode(currentBackgroundNode, at: 0) + } + + strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor + + if strongSelf.topStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1) + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) + } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + if params.isStandalone { + strongSelf.topStripeNode.isHidden = true + strongSelf.bottomStripeNode.isHidden = true + strongSelf.maskNode.isHidden = true + } else { + let hasCorners = itemListHasRoundedBlockLayout(params) + + var hasTopCorners = false + var hasBottomCorners = false + + switch neighbors.top { + case .sameSection(false): + strongSelf.topStripeNode.isHidden = true + default: + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners + } + let bottomStripeInset: CGFloat + let bottomStripeOffset: CGFloat + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = 0.0 + bottomStripeOffset = -separatorHeight + strongSelf.bottomStripeNode.isHidden = false + default: + bottomStripeInset = 0.0 + bottomStripeOffset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners + } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil + + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + } + + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + + let displayMode: WallpaperDisplayMode + if abs(params.availableHeight - params.width) < 100.0, params.availableHeight > 700.0 { + displayMode = .halfAspectFill + } else { + if backgroundFrame.width > backgroundFrame.height * 4.0 { + if params.availableHeight < 700.0 { + displayMode = .halfAspectFill + } else { + displayMode = .aspectFill + } + } else { + displayMode = .aspectFill + } + } + + if let backgroundNode = strongSelf.backgroundNode { + backgroundNode.frame = backgroundFrame + backgroundNode.updateLayout(size: backgroundNode.bounds.size, displayMode: displayMode, transition: .immediate) + } + strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0) + } + }) + } + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift new file mode 100644 index 0000000000..f26cb3deb2 --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -0,0 +1,607 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import PresentationDataUtils +import AccountContext +import ComponentFlow +import ViewControllerComponent +import MultilineTextComponent +import BalancedTextComponent +import ListSectionComponent +import ListActionItemComponent +import ListMultilineTextFieldItemComponent +import ListItemComponentAdaptor +import BundleIconComponent +import LottieComponent +import TextFieldComponent +import ButtonComponent +import BotPaymentsUI + +final class GiftSetupScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let peerId: EnginePeer.Id + let gift: StarGift + + init( + context: AccountContext, + peerId: EnginePeer.Id, + gift: StarGift + ) { + self.context = context + self.peerId = peerId + self.gift = gift + } + + static func ==(lhs: GiftSetupScreenComponent, rhs: GiftSetupScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peerId != rhs.peerId { + return false + } + if lhs.gift != rhs.gift { + return false + } + return true + } + + private final class ScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + final class View: UIView, UIScrollViewDelegate { + private let topOverscrollLayer = SimpleLayer() + private let scrollView: ScrollView + + private let navigationTitle = ComponentView() + private let introContent = ComponentView() + private let introSection = ComponentView() + private let hideSection = ComponentView() + private let button = ComponentView() + + private var ignoreScrolling: Bool = false + private var isUpdating: Bool = false + + private var component: GiftSetupScreenComponent? + private(set) weak var state: EmptyComponentState? + private var environment: EnvironmentType? + + private let introPlaceholderTag = NSObject() + private let textInputState = ListMultilineTextFieldItemComponent.ExternalState() + private let textInputTag = NSObject() + private var resetText: String? + + private var hideName = false + + private var previousHadInputHeight: Bool = false + private var recenterOnTag: NSObject? + + private var peerMap: [EnginePeer.Id: EnginePeer] = [:] + + private var starImage: (UIImage, PresentationTheme)? + + override init(frame: CGRect) { + self.scrollView = ScrollView() + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.alwaysBounceVertical = true + + super.init(frame: frame) + + self.scrollView.delegate = self + self.addSubview(self.scrollView) + + self.scrollView.layer.addSublayer(self.topOverscrollLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func scrollToTop() { + self.scrollView.setContentOffset(CGPoint(), animated: true) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + private var scrolledUp = true + private func updateScrolling(transition: ComponentTransition) { + let navigationRevealOffsetY: CGFloat = 0.0 + + let navigationAlphaDistance: CGFloat = 16.0 + let navigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentOffset.y - navigationRevealOffsetY) / navigationAlphaDistance)) + if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { + transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha) + transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha) + } + + var scrolledUp = false + if navigationAlpha < 0.5 { + scrolledUp = true + } else if navigationAlpha > 0.5 { + scrolledUp = false + } + + if self.scrolledUp != scrolledUp { + self.scrolledUp = scrolledUp + if !self.isUpdating { + self.state?.updated() + } + } + + if let navigationTitleView = self.navigationTitle.view { + transition.setAlpha(view: navigationTitleView, alpha: 1.0) + } + } + + func proceed() { + guard let component = self.component else { + return + } + let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, peerId: component.peerId, giftId: component.gift.id, text: self.textInputState.text.string, entities: []) + let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + + let _ = (inputData + |> deliverOnMainQueue).startStandalone(next: { [weak self] inputData in + guard let inputData else { + return + } + let _ = (component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { + return + } + + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } + } + if !foundController { + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) + } + navigationController.setViewControllers(controllers, animated: true) + }) + }) + } + + func update(component: GiftSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + if self.component == nil { + let _ = (component.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId), + TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer in + guard let self else { + return + } + if let peer { + self.peerMap[peer.id] = peer + } + if let accountPeer { + self.peerMap[accountPeer.id] = accountPeer + } + + self.state?.updated() + }) + } + + let environment = environment[EnvironmentType.self].value + let themeUpdated = self.environment?.theme !== environment.theme + self.environment = environment + + self.component = component + self.state = state + + let alphaTransition: ComponentTransition + if !transition.animation.isImmediate { + alphaTransition = .easeInOut(duration: 0.25) + } else { + alphaTransition = .immediate + } + + if themeUpdated { + self.backgroundColor = environment.theme.list.blocksBackgroundColor + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + let _ = alphaTransition + let _ = presentationData + + let navigationTitleSize = self.navigationTitle.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "Send a Gift", font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize) + if let navigationTitleView = self.navigationTitle.view { + if navigationTitleView.superview == nil { + if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { + navigationBar.view.addSubview(navigationTitleView) + } + } + transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame) + } + + let bottomContentInset: CGFloat = 24.0 + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let sectionSpacing: CGFloat = 24.0 + + var contentHeight: CGFloat = 0.0 + + contentHeight += environment.navigationHeight + contentHeight += 26.0 + + self.recenterOnTag = nil + if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let targetView = hint.view { + if let textView = self.introSection.findTaggedView(tag: self.textInputTag) { + if targetView.isDescendant(of: textView) { + self.recenterOnTag = self.textInputTag + } + } + } + + var introSectionItems: [AnyComponentWithIdentity] = [] + introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag)))) + introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent( + externalState: self.textInputState, + context: component.context, + theme: environment.theme, + strings: environment.strings, + initialText: "", + resetText: self.resetText.flatMap { + return ListMultilineTextFieldItemComponent.ResetText(value: $0) + }, + placeholder: environment.strings.Business_Intro_IntroTextPlaceholder, + autocapitalizationType: .none, + autocorrectionType: .no, + returnKeyType: .done, + characterLimit: 70, + displayCharacterLimit: true, + emptyLineHandling: .notAllowed, + updated: { _ in + }, + returnKeyAction: { [weak self] in + guard let self else { + return + } + if let titleView = self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View { + titleView.endEditing(true) + } + }, + textUpdateTransition: .spring(duration: 0.4), + tag: self.textInputTag + )))) + self.resetText = nil + + let introSectionSize = self.introSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "CUSTOMIZE YOUR GIFT", + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: environment.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: introSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let introSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: introSectionSize) + if let introSectionView = self.introSection.view { + if introSectionView.superview == nil { + self.scrollView.addSubview(introSectionView) + self.introSection.parentState = state + } + transition.setFrame(view: introSectionView, frame: introSectionFrame) + } + contentHeight += introSectionSize.height + contentHeight += sectionSpacing + +// let titleText: String +// if self.titleInputState.text.string.isEmpty { +// titleText = environment.strings.Conversation_EmptyPlaceholder +// } else { +// let rawTitle = self.titleInputState.text.string +// titleText = rawTitle.count <= maxTitleLength ? rawTitle : String(rawTitle[rawTitle.startIndex ..< rawTitle.index(rawTitle.startIndex, offsetBy: maxTitleLength)]) +// } + +// let textText: String +// if self.textInputState.text.string.isEmpty { +// textText = environment.strings.Conversation_GreetingText +// } else { +// let rawText = self.textInputState.text.string +// textText = rawText.count <= maxTextLength ? rawText : String(rawText[rawText.startIndex ..< rawText.index(rawText.startIndex, offsetBy: maxTextLength)]) +// } + + let listItemParams = ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true) + let introContentSize = self.introContent.update( + transition: transition, + component: AnyComponent( + ListItemComponentAdaptor( + itemGenerator: ChatGiftPreviewItem( + context: component.context, + theme: environment.theme, + componentTheme: environment.theme, + strings: environment.strings, + sectionId: 0, + fontSize: presentationData.chatFontSize, + chatBubbleCorners: presentationData.chatBubbleCorners, + wallpaper: presentationData.chatWallpaper, + dateTimeFormat: environment.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + accountPeer: self.peerMap[component.context.account.peerId], + gift: component.gift, + text: self.textInputState.text.string + ), + params: listItemParams + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + if let introContentView = self.introContent.view { + if introContentView.superview == nil { + if let placeholderView = self.introSection.findTaggedView(tag: self.introPlaceholderTag) { + placeholderView.addSubview(introContentView) + } + } + transition.setFrame(view: introContentView, frame: CGRect(origin: CGPoint(), size: introContentSize)) + } + + if self.recenterOnTag == nil && self.previousHadInputHeight != (environment.inputHeight > 0.0) { + if self.textInputState.isEditing { + self.recenterOnTag = self.textInputTag + } + } + self.previousHadInputHeight = environment.inputHeight > 0.0 + + let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? "" + let hideSectionSize = self.hideSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Hide my name and message from visitors to \(peerName)'s profile. \(peerName) will still see your name and message.", + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: environment.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + items: [ + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Hide My Name", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.hideName, action: { [weak self] _ in + guard let self else { + return + } + self.hideName = !self.hideName + self.state?.updated(transition: .spring(duration: 0.4)) + })), + action: nil + ))) + ] + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let hideSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: hideSectionSize) + if let hideSectionView = self.hideSection.view { + if hideSectionView.superview == nil { + self.scrollView.addSubview(hideSectionView) + } + transition.setFrame(view: hideSectionView, frame: hideSectionFrame) + } + contentHeight += hideSectionSize.height + + contentHeight += bottomContentInset + + let inputHeight: CGFloat = environment.inputHeight + let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom) + contentHeight += combinedBottomInset + + + if self.starImage == nil || self.starImage?.1 !== environment.theme { + self.starImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: environment.theme.list.itemCheckColors.foregroundColor)!, environment.theme) + } + let amountString = presentationStringsFormattedNumber(Int32(component.gift.price), presentationData.dateTimeFormat.groupingSeparator) + let buttonAttributedString = NSMutableAttributedString(string: "Send a Gift for # \(amountString)", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.starImage?.0 { + buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.foregroundColor, value: environment.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string)) + } + + let buttonSize = self.button.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + color: environment.theme.list.itemCheckColors.fillColor, + foreground: environment.theme.list.itemCheckColors.foregroundColor, + pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 10.0 + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + self?.proceed() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50) + ) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + buttonView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - buttonSize.height), size: buttonSize) + } + + let previousBounds = self.scrollView.bounds + + self.ignoreScrolling = true + let contentSize = CGSize(width: availableSize.width, height: contentHeight) + if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { + self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) + } + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0) + if self.scrollView.scrollIndicatorInsets != scrollInsets { + self.scrollView.scrollIndicatorInsets = scrollInsets + } + + if !previousBounds.isEmpty, !transition.animation.isImmediate { + let bounds = self.scrollView.bounds + if bounds.maxY != previousBounds.maxY { + let offsetY = previousBounds.maxY - bounds.maxY + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) + } + } + + if let recenterOnTag = self.recenterOnTag { + self.recenterOnTag = nil + + if let targetView = self.introSection.findTaggedView(tag: recenterOnTag) { + let caretRect = targetView.convert(targetView.bounds, to: self.scrollView) + var scrollViewBounds = self.scrollView.bounds + let minButtonDistance: CGFloat = 16.0 + if -scrollViewBounds.minY + caretRect.maxY > availableSize.height - combinedBottomInset - minButtonDistance { + scrollViewBounds.origin.y = -(availableSize.height - combinedBottomInset - minButtonDistance - caretRect.maxY) + if scrollViewBounds.origin.y < 0.0 { + scrollViewBounds.origin.y = 0.0 + } + } + if self.scrollView.bounds != scrollViewBounds { + transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) + } + } + } + + self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0)) + self.ignoreScrolling = false + + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class GiftSetupScreen: ViewControllerComponentContainer { + private let context: AccountContext + + public init( + context: AccountContext, + peerId: EnginePeer.Id, + gift: StarGift + ) { + self.context = context + + super.init(context: context, component: GiftSetupScreenComponent( + context: context, + peerId: peerId, + gift: gift + ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) + + self.title = "" + //self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + + self.scrollToTop = { [weak self] in + guard let self, let componentView = self.node.hostView.componentView as? GiftSetupScreenComponent.View else { + return + } + componentView.scrollToTop() + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + @objc private func cancelPressed() { + self.dismiss() + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD new file mode 100644 index 0000000000..342a21824d --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -0,0 +1,44 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GiftViewScreen", + module_name = "GiftViewScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AccountContext", + "//submodules/PresentationDataUtils", + "//submodules/Markdown", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/AppBundle", + "//submodules/Components/SheetComponent", + "//submodules/Components/SolidRoundedButtonComponent", + "//submodules/TelegramUI/Components/Stars/StarsAvatarComponent", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView", + "//submodules/UndoUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift new file mode 100644 index 0000000000..28b3467edd --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -0,0 +1,1325 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import PresentationDataUtils +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import MultilineTextComponent +import BundleIconComponent +import SolidRoundedButtonComponent +import Markdown +import BalancedTextComponent +import AvatarNode +import TextFormat +import TelegramStringFormatting +import StarsAvatarComponent +import EmojiTextAttachmentView +import UndoUI + +private final class GiftViewSheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: GiftViewScreen.Subject + let cancel: (Bool) -> Void + let openPeer: (EnginePeer) -> Void + let updateSavedToProfile: (Bool) -> Void + let convertToStars: () -> Void + + init( + context: AccountContext, + subject: GiftViewScreen.Subject, + cancel: @escaping (Bool) -> Void, + openPeer: @escaping (EnginePeer) -> Void, + updateSavedToProfile: @escaping (Bool) -> Void, + convertToStars: @escaping () -> Void + ) { + self.context = context + self.subject = subject + self.cancel = cancel + self.openPeer = openPeer + self.updateSavedToProfile = updateSavedToProfile + self.convertToStars = convertToStars + } + + static func ==(lhs: GiftViewSheetContent, rhs: GiftViewSheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + private var disposable: Disposable? + var initialized = false + + var peerMap: [EnginePeer.Id: EnginePeer] = [:] + + var cachedCloseImage: (UIImage, PresentationTheme)? + var cachedChevronImage: (UIImage, PresentationTheme)? + + var inProgress = false + + init(context: AccountContext, subject: GiftViewScreen.Subject) { + self.context = context + + super.init() + + if let arguments = subject.arguments { + let peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId] + self.disposable = (context.engine.data.get( + EngineDataMap( + peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in + return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + } + ) + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] peers in + if let strongSelf = self { + var peersMap: [EnginePeer.Id: EnginePeer] = [:] + for peerId in peerIds { + if let maybePeer = peers[peerId], let peer = maybePeer { + peersMap[peerId] = peer + } + } + strongSelf.peerMap = peersMap + + strongSelf.initialized = true + + strongSelf.updated(transition: .immediate) + } + }) + } + } + + deinit { + self.disposable?.dispose() + } + } + + func makeState() -> State { + return State(context: self.context, subject: self.subject) + } + + static var body: Body { + let closeButton = Child(Button.self) + let animation = Child(GiftAnimationComponent.self) + let title = Child(MultilineTextComponent.self) + let amount = Child(BalancedTextComponent.self) + let amountStar = Child(BundleIconComponent.self) + let description = Child(MultilineTextComponent.self) + let table = Child(TableComponent.self) + let button = Child(SolidRoundedButtonComponent.self) + let secondaryButton = Child(SolidRoundedButtonComponent.self) + + let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: []) + + return { context in + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + + let component = context.component + let theme = environment.theme + let strings = environment.strings + let dateTimeFormat = environment.dateTimeFormat + + let state = context.state + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let textSideInset: CGFloat = 32.0 + environment.safeInsets.left + + let closeImage: UIImage + if let (image, theme) = state.cachedCloseImage, theme === environment.theme { + closeImage = image + } else { + closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! + state.cachedCloseImage = (closeImage, theme) + } + + let closeButton = closeButton.update( + component: Button( + content: AnyComponent(Image(image: closeImage)), + action: { [weak component] in + component?.cancel(true) + } + ), + availableSize: CGSize(width: 30.0, height: 30.0), + transition: .immediate + ) + + let animationFile: TelegramMediaFile? + let stars: Int64 + let convertStars: Int64 + let text: String? + let entities: [MessageTextEntity]? + let limitNumber: Int32? + let limitTotal: Int32? + var incoming = false + var savedToProfile = false + var converted = false + if let arguments = component.subject.arguments { + animationFile = arguments.gift.file + stars = arguments.gift.price + text = arguments.text + entities = arguments.entities + limitNumber = arguments.gift.availability?.remains + limitTotal = arguments.gift.availability?.total + convertStars = arguments.convertStars + incoming = arguments.incoming + savedToProfile = arguments.savedToProfile + converted = arguments.converted + } else { + animationFile = nil + stars = 0 + text = nil + entities = nil + limitNumber = nil + limitTotal = nil + convertStars = 0 + } + let _ = entities + let _ = limitNumber + + var descriptionText: String + if incoming { + if !converted { + descriptionText = "You can keep this gift in your Profile or convert it to \(convertStars) Stars. [More About Stars >]()" + } else { + descriptionText = "You converted this gift to \(convertStars) Stars. [More About Stars >]()" + } + } else if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] { + descriptionText = "\(peer.compactDisplayTitle) can keep this gift in their Profile or convert it to \(convertStars) Stars. [More About Stars >]()" + } else { + descriptionText = "" + } + if let spaceRegex { + let nsRange = NSRange(descriptionText.startIndex..., in: descriptionText) + let matches = spaceRegex.matches(in: descriptionText, options: [], range: nsRange) + var modifiedString = descriptionText + + for match in matches.reversed() { + let matchRange = Range(match.range, in: descriptionText)! + let matchedSubstring = String(descriptionText[matchRange]) + let replacedSubstring = matchedSubstring.replacingOccurrences(of: " ", with: "\u{00A0}") + modifiedString.replaceSubrange(matchRange, with: replacedSubstring) + } + descriptionText = modifiedString + } + + let formattedAmount = presentationStringsFormattedNumber(abs(Int32(stars)), dateTimeFormat.groupingSeparator) + let countFont: UIFont = Font.semibold(17.0) + let amountText = formattedAmount + let countColor = theme.list.itemDisclosureActions.constructive.fillColor + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: incoming ? "Received Gift" : "Gift", + font: Font.bold(25.0), + textColor: theme.actionSheet.primaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let amountAttributedText = NSMutableAttributedString(string: amountText, font: countFont, textColor: countColor) + let amount = amount.update( + component: BalancedTextComponent( + text: .plain(amountAttributedText), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + + let amountStar = amountStar.update( + component: BundleIconComponent( + name: "Premium/Stars/StarMedium", + tintColor: nil + ), + availableSize: context.availableSize, + transition: .immediate + ) + + let tableFont = Font.regular(15.0) + let tableTextColor = theme.list.itemPrimaryTextColor + var tableItems: [TableComponent.Item] = [] + + if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] { + tableItems.append(.init( + id: "to", + title: incoming ? strings.Stars_Transaction_From : strings.Stars_Transaction_To, + component: AnyComponent( + Button( + content: AnyComponent( + PeerCellComponent( + context: component.context, + theme: theme, + peer: peer + ) + ), + action: { + if "".isEmpty { + component.openPeer(peer) + Queue.mainQueue().after(1.0, { + component.cancel(false) + }) + } else { + if let controller = controller() as? GiftViewScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController { + chatController.playShakeAnimation() + } + component.cancel(true) + } + } + ) + ) + )) + } + + tableItems.append(.init( + id: "date", + title: strings.Stars_Transaction_Date, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: Int32(Date().timeIntervalSince1970), strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor))) + ) + )) + + if let limitTotal { + tableItems.append(.init( + id: "availability", + title: "Availability", + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: "1 of \(limitTotal)", font: tableFont, textColor: tableTextColor))) + ) + )) + } + + if let text { + tableItems.append(.init( + id: "text", + title: nil, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: text, font: tableFont, textColor: tableTextColor))) + ) + )) + } + + let table = table.update( + component: TableComponent( + theme: environment.theme, + items: tableItems + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), + transition: .immediate + ) + + let textFont = Font.regular(15.0) +// let boldTextFont = Font.semibold(15.0) +// let textColor = theme.actionSheet.secondaryTextColor + let linkColor = theme.actionSheet.controlAccentColor +// let destructiveColor = theme.actionSheet.destructiveActionTextColor +// let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in +// return (TelegramTextAttributes.URL, contents) +// }) +// let additional = additional.update( +// component: BalancedTextComponent( +// text: .markdown(text: additionalText, attributes: markdownAttributes), +// horizontalAlignment: .center, +// maximumNumberOfLines: 0, +// lineSpacing: 0.2, +// highlightColor: linkColor.withAlphaComponent(0.2), +// highlightAction: { attributes in +// if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { +// return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) +// } else { +// return nil +// } +// }, +// tapAction: { attributes, _ in +// if let controller = controller() as? GiftViewScreen, let navigationController = controller.navigationController as? NavigationController { +// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } +// component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Transaction_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) +// component.cancel(true) +// } +// } +// ), +// availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), +// transition: .immediate +// ) + + + + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: 177.0)) + ) + + var originY: CGFloat = 0.0 + if let animationFile { + let animation = animation.update( + component: GiftAnimationComponent( + context: component.context, + theme: environment.theme, + file: animationFile + ), + availableSize: CGSize(width: 128.0, height: 128.0), + transition: .immediate + ) + context.add(animation + .position(CGPoint(x: context.availableSize.width / 2.0, y: animation.size.height / 2.0 + 25.0)) + ) + originY += animation.size.height + } + originY += 69.0 + + var descriptionSize: CGSize = .zero + if !descriptionText.isEmpty { + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme { + state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) + } + + let textColor = theme.list.itemPrimaryTextColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + let attributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString + if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { + attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string)) + } + let description = description.update( + component: MultilineTextComponent( + text: .plain(attributedString), + horizontalAlignment: .center, + maximumNumberOfLines: 5, + lineSpacing: 0.2, + highlightColor: linkColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + descriptionSize = description.size + var descriptionOrigin = originY + if "".isEmpty { + descriptionOrigin += amount.size.height + 13.0 + } + context.add(description + .position(CGPoint(x: context.availableSize.width / 2.0, y: descriptionOrigin + description.size.height / 2.0)) + ) + originY += description.size.height + 10.0 + } + + let amountSpacing: CGFloat = 1.0 + let totalAmountWidth: CGFloat = amount.size.width + amountSpacing + amountStar.size.width + let amountOriginX: CGFloat = floor(context.availableSize.width - totalAmountWidth) / 2.0 + + var amountOrigin = originY + if "".isEmpty { + amountOrigin -= descriptionSize.height + 10.0 + originY += amount.size.height + 26.0 + } else { + originY += amount.size.height + 20.0 + } + + let amountLabelOriginX: CGFloat + let amountStarOriginX: CGFloat + if !"".isEmpty { + amountStarOriginX = amountOriginX + amountStar.size.width / 2.0 + amountLabelOriginX = amountOriginX + amountStar.size.width + amountSpacing + amount.size.width / 2.0 + } else { + amountLabelOriginX = amountOriginX + amount.size.width / 2.0 + amountStarOriginX = amountOriginX + amount.size.width + amountSpacing + amountStar.size.width / 2.0 + } + + context.add(amount + .position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0)) + ) + context.add(amountStar + .position(CGPoint(x: amountStarOriginX, y: amountOrigin + amountStar.size.height / 2.0 - UIScreenPixel)) + ) + + context.add(table + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0)) + ) + originY += table.size.height + 23.0 + +// context.add(additional +// .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + additional.size.height / 2.0)) +// ) +// originY += additional.size.height + 23.0 + +// if let statusText { +// originY += 7.0 +// let status = status.update( +// component: BalancedTextComponent( +// text: .plain(NSAttributedString(string: statusText, font: textFont, textColor: statusIsDestructive ? destructiveColor : textColor)), +// horizontalAlignment: .center, +// maximumNumberOfLines: 0, +// lineSpacing: 0.1 +// ), +// availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), +// transition: .immediate +// ) +// context.add(status +// .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + status.size.height / 2.0)) +// ) +// originY += status.size.height + (statusIsDestructive ? 23.0 : 13.0) +// } + + + if incoming && !converted { + let button = button.update( + component: SolidRoundedButtonComponent( + title: savedToProfile ? "Hide from My Page" : "Display on My Page", + theme: SolidRoundedButtonComponent.Theme(theme: theme), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: nil, + animationName: nil, + iconPosition: .left, + isLoading: state.inProgress, + action: { + component.updateSavedToProfile(!savedToProfile) + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: button.size) + context.add(button + .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) + ) + originY += button.size.height + originY += 7.0 + + let secondaryButton = secondaryButton.update( + component: SolidRoundedButtonComponent( + title: "Convert to \(convertStars) Stars", + theme: SolidRoundedButtonComponent.Theme(backgroundColor: .clear, foregroundColor: linkColor), + font: .regular, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: nil, + animationName: nil, + iconPosition: .left, + isLoading: false, + action: { + component.convertToStars() + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + let secondaryButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: secondaryButton.size) + context.add(secondaryButton + .position(CGPoint(x: secondaryButtonFrame.midX, y: secondaryButtonFrame.midY)) + ) + originY += secondaryButton.size.height + } + + context.add(closeButton + .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0)) + ) + + let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom) + + return contentSize + } + } +} + +private final class GiftViewSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: GiftViewScreen.Subject + let openPeer: (EnginePeer) -> Void + let updateSavedToProfile: (Bool) -> Void + let convertToStars: () -> Void + + init( + context: AccountContext, + subject: GiftViewScreen.Subject, + openPeer: @escaping (EnginePeer) -> Void, + updateSavedToProfile: @escaping (Bool) -> Void, + convertToStars: @escaping () -> Void + ) { + self.context = context + self.subject = subject + self.openPeer = openPeer + self.updateSavedToProfile = updateSavedToProfile + self.convertToStars = convertToStars + } + + static func ==(lhs: GiftViewSheetComponent, rhs: GiftViewSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + return true + } + + static var body: Body { + let sheet = Child(SheetComponent.self) + let animateOut = StoredActionSlot(Action.self) + + let sheetExternalState = SheetComponent.ExternalState() + + return { context in + let environment = context.environment[EnvironmentType.self] + let controller = environment.controller + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(GiftViewSheetContent( + context: context.component.context, + subject: context.component.subject, + cancel: { animate in + if animate { + if let controller = controller() as? GiftViewScreen { + controller.dismissAllTooltips() + animateOut.invoke(Action { [weak controller] _ in + controller?.dismiss(completion: nil) + }) + } + } else if let controller = controller() { + controller.dismiss(animated: false, completion: nil) + } + }, + openPeer: context.component.openPeer, + updateSavedToProfile: context.component.updateSavedToProfile, + convertToStars: context.component.convertToStars + )), + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + followContentSizeChanges: true, + clipsContent: true, + externalState: sheetExternalState, + animateOut: animateOut, + onPan: { + if let controller = controller() as? GiftViewScreen { + controller.dismissAllTooltips() + } + } + ), + environment: { + environment + SheetComponentEnvironment( + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + if animated { + if let controller = controller() as? GiftViewScreen { + controller.dismissAllTooltips() + animateOut.invoke(Action { _ in + controller.dismiss(completion: nil) + }) + } + } else { + if let controller = controller() as? GiftViewScreen { + controller.dismissAllTooltips() + controller.dismiss(completion: nil) + } + } + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + if let controller = controller(), !controller.automaticallyControlPresentationContextLayout { + let layout = ContainerViewLayout( + size: context.availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0), + safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right), + additionalInsets: .zero, + statusBarHeight: environment.statusBarHeight, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition) + } + + return context.availableSize + } + } +} + +public class GiftViewScreen: ViewControllerComponentContainer { + public enum Subject: Equatable { + case message(EngineMessage) + case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift) + + var arguments: (peerId: EnginePeer.Id, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, convertStars: Int64, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool)? { + switch self { + case let .message(message): + if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted) = action.action { + return (message.id.peerId, message.id, message.flags.contains(.Incoming), gift, convertStars, text, entities, nameHidden, savedToProfile, converted) + } + case let .profileGift(peerId, gift): + return (peerId, gift.messageId, false, gift.gift, gift.convertStars ?? 0, gift.text, gift.entities, gift.nameHidden, true, false) + } + return nil + } + } + + private let context: AccountContext + public var disposed: () -> Void = {} + + private let hapticFeedback = HapticFeedback() + + public init( + context: AccountContext, + subject: GiftViewScreen.Subject, + forceDark: Bool = false + ) { + self.context = context + + var openPeerImpl: ((EnginePeer) -> Void)? + var updateSavedToProfileImpl: ((Bool) -> Void)? + var convertToStarsImpl: (() -> Void)? + super.init( + context: context, + component: GiftViewSheetComponent( + context: context, + subject: subject, + openPeer: { peerId in + openPeerImpl?(peerId) + }, + updateSavedToProfile: { added in + updateSavedToProfileImpl?(added) + }, + convertToStars: { + convertToStarsImpl?() + } + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: forceDark ? .dark : .default + ) + + self.navigationPresentation = .flatModal + self.automaticallyControlPresentationContextLayout = false + + openPeerImpl = { [weak self] peer in + guard let self, let navigationController = self.navigationController as? NavigationController else { + return + } + self.dismissAllTooltips() + + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id) + ) + |> deliverOnMainQueue).start(next: { peer in + guard let peer else { + return + } + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true)) + }) + } + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + updateSavedToProfileImpl = { [weak self] added in + guard let self, let arguments = subject.arguments, let messageId = arguments.messageId else { + return + } + let _ = (context.engine.payments.updateStarGiftAddedToProfile(messageId: messageId, added: added) + |> deliverOnMainQueue).startStandalone() + + self.dismissAnimated() + + if let navigationController { + Queue.mainQueue().after(0.5) { + if let lastController = navigationController.viewControllers.last as? ViewController { + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .sticker(context: context, file: arguments.gift.file, loop: false, title: "Gift Saved to Profile", text: "The gift is now displayed in your profile.", undoText: nil, customAction: nil), + elevatedLayout: lastController is ChatController, + action: { _ in return true} + ) + lastController.present(resultController, in: .window(.root)) + } + } + } + } + + convertToStarsImpl = { [weak self] in + guard let self, case let .message(message) = subject, let arguments = subject.arguments, let messageId = arguments.messageId, let navigationController = self.navigationController as? NavigationController else { + return + } + let controller = textAlertController( + context: self.context, + title: "Convert Gift to Stars", + text: "Do you want to convert this gift from **\(message.author?.compactDisplayTitle ?? "")** to **\(arguments.convertStars) Stars**?\n\nThis action cannot be undone.", + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: "Convert", action: { [weak self, weak navigationController] in + let _ = (context.engine.payments.convertStarGift(messageId: messageId) + |> deliverOnMainQueue).startStandalone() + + self?.dismissAnimated() + + if let navigationController { + Queue.mainQueue().after(0.5) { + if let lastController = navigationController.viewControllers.last as? ViewController { + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .universal( + animation: "StarsBuy", + scale: 0.066, + colors: [:], + title: "Gift Converted", + text: "You received **\(arguments.convertStars) Stars** instead.", + customUndoText: nil, + timeout: nil + ), + elevatedLayout: lastController is ChatController, + action: { _ in return true} + ) + lastController.present(resultController, in: .window(.root)) + } + } + } + }) + ], + parseMarkdown: true + ) + self.present(controller, in: .window(.root)) + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposed() + } + + public override func viewDidLoad() { + super.viewDidLoad() + + self.view.disablesInteractiveModalDismiss = true + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.dismissAllTooltips() + } + + public func dismissAnimated() { + self.dismissAllTooltips() + + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() + } + } + + fileprivate func dismissAllTooltips() { +// self.window?.forEachController({ controller in +// if let controller = controller as? UndoOverlayController { +// controller.dismiss() +// } +// }) +// self.forEachController({ controller in +// if let controller = controller as? UndoOverlayController { +// controller.dismiss() +// } +// return true +// }) + } +} + +private final class TableComponent: CombinedComponent { + class Item: Equatable { + public let id: AnyHashable + public let title: String? + public let component: AnyComponent + public let insets: UIEdgeInsets? + + public init(id: IdType, title: String?, component: AnyComponent, insets: UIEdgeInsets? = nil) { + self.id = AnyHashable(id) + self.title = title + self.component = component + self.insets = insets + } + + public static func == (lhs: Item, rhs: Item) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.component != rhs.component { + return false + } + if lhs.insets != rhs.insets { + return false + } + return true + } + } + + private let theme: PresentationTheme + private let items: [Item] + + public init(theme: PresentationTheme, items: [Item]) { + self.theme = theme + self.items = items + } + + public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.items != rhs.items { + return false + } + return true + } + + final class State: ComponentState { + var cachedBorderImage: (UIImage, PresentationTheme)? + } + + func makeState() -> State { + return State() + } + + public static var body: Body { + let leftColumnBackground = Child(Rectangle.self) + let verticalBorder = Child(Rectangle.self) + let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) + let outerBorder = Child(Image.self) + + return { context in + let verticalPadding: CGFloat = 11.0 + let horizontalPadding: CGFloat = 12.0 + let borderWidth: CGFloat = 1.0 + + let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor + let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6) + + var leftColumnWidth: CGFloat = 0.0 + + var updatedTitleChildren: [Int: _UpdatedChildComponent] = [:] + var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = [] + var updatedBorderChildren: [_UpdatedChildComponent] = [] + + var i = 0 + for item in context.component.items { + guard let title = item.title else { + i += 1 + continue + } + let titleChild = titleChildren[item.id].update( + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: title, font: Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor)) + )), + availableSize: context.availableSize, + transition: context.transition + ) + updatedTitleChildren[i] = titleChild + + if titleChild.size.width > leftColumnWidth { + leftColumnWidth = titleChild.size.width + } + i += 1 + } + + leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0) + let rightColumnWidth = context.availableSize.width - leftColumnWidth + + i = 0 + var rowHeights: [Int: CGFloat] = [:] + var totalHeight: CGFloat = 0.0 + var innerTotalHeight: CGFloat = 0.0 + + for item in context.component.items { + let insets: UIEdgeInsets + if let customInsets = item.insets { + insets = customInsets + } else { + insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding) + } + let valueChild = valueChildren[item.id].update( + component: item.component, + availableSize: CGSize(width: rightColumnWidth - insets.left - insets.right, height: context.availableSize.height), + transition: context.transition + ) + updatedValueChildren.append((valueChild, insets)) + + var titleHeight: CGFloat = 0.0 + if let titleChild = updatedTitleChildren[i] { + titleHeight = titleChild.size.height + } + let rowHeight = max(40.0, max(titleHeight, valueChild.size.height) + verticalPadding * 2.0) + rowHeights[i] = rowHeight + totalHeight += rowHeight + if titleHeight > 0.0 { + innerTotalHeight += rowHeight + } + + if i < context.component.items.count - 1 { + let borderChild = borderChildren[item.id].update( + component: AnyComponent(Rectangle(color: borderColor)), + availableSize: CGSize(width: context.availableSize.width, height: borderWidth), + transition: context.transition + ) + updatedBorderChildren.append(borderChild) + } + + i += 1 + } + + let leftColumnBackground = leftColumnBackground.update( + component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor), + availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight), + transition: context.transition + ) + context.add( + leftColumnBackground + .position(CGPoint(x: leftColumnWidth / 2.0, y: innerTotalHeight / 2.0)) + ) + + let borderImage: UIImage + if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme { + borderImage = currentImage + } else { + let borderRadius: CGFloat = 5.0 + borderImage = generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in + let bounds = CGRect(origin: .zero, size: size) + context.setFillColor(backgroundColor.cgColor) + context.fill(bounds) + + let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) + context.setBlendMode(.clear) + context.addPath(path) + context.fillPath() + + context.setBlendMode(.normal) + context.setStrokeColor(borderColor.cgColor) + context.setLineWidth(borderWidth) + context.addPath(path) + context.strokePath() + })!.stretchableImage(withLeftCapWidth: 5, topCapHeight: 5) + context.state.cachedBorderImage = (borderImage, context.component.theme) + } + + let outerBorder = outerBorder.update( + component: Image(image: borderImage), + availableSize: CGSize(width: context.availableSize.width, height: totalHeight), + transition: context.transition + ) + context.add(outerBorder + .position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0)) + ) + + let verticalBorder = verticalBorder.update( + component: Rectangle(color: borderColor), + availableSize: CGSize(width: borderWidth, height: innerTotalHeight), + transition: context.transition + ) + context.add( + verticalBorder + .position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: innerTotalHeight / 2.0)) + ) + + i = 0 + var originY: CGFloat = 0.0 + for (valueChild, valueInsets) in updatedValueChildren { + let rowHeight = rowHeights[i] ?? 0.0 + + let valueFrame: CGRect + if let titleChild = updatedTitleChildren[i] { + let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size) + context.add(titleChild + .position(titleFrame.center) + ) + valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size) + } else { + valueFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: valueChild.size) + } + + context.add(valueChild + .position(valueFrame.center) + ) + + if i < updatedBorderChildren.count { + let borderChild = updatedBorderChildren[i] + context.add(borderChild + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0)) + ) + } + + originY += rowHeight + i += 1 + } + + return CGSize(width: context.availableSize.width, height: totalHeight) + } + } +} + +private final class PeerCellComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let peer: EnginePeer? + + init(context: AccountContext, theme: PresentationTheme, peer: EnginePeer?) { + self.context = context + self.theme = theme + self.peer = peer + } + + static func ==(lhs: PeerCellComponent, rhs: PeerCellComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.peer != rhs.peer { + return false + } + return true + } + + final class View: UIView { + private let avatar = ComponentView() + private let text = ComponentView() + + private var component: PeerCellComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: PeerCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let avatarSize = CGSize(width: 22.0, height: 22.0) + let spacing: CGFloat = 6.0 + + let peerName: String + let peer: StarsContext.State.Transaction.Peer + if let peerValue = component.peer { + peerName = peerValue.compactDisplayTitle + peer = .peer(peerValue) + } else { + peerName = "Hidden Name" + peer = .fragment + } + + let avatarNaturalSize = self.avatar.update( + transition: .immediate, + component: AnyComponent( + StarsAvatarComponent(context: component.context, theme: component.theme, peer: peer, photo: nil, media: [], backgroundColor: .clear) + ), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.theme.list.itemAccentColor, paragraphAlignment: .left)) + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height) + ) + + let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height) + + let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize) + + if let view = self.avatar.view { + if view.superview == nil { + self.addSubview(view) + } + let scale = avatarSize.width / avatarNaturalSize.width + view.transform = CGAffineTransform(scaleX: scale, y: scale) + view.frame = avatarFrame + } + + if let view = self.text.view { + if view.superview == nil { + self.addSubview(view) + } + let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + transition.setFrame(view: view, frame: textFrame) + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { + return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(backgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(foregroundColor.cgColor) + + context.move(to: CGPoint(x: 10.0, y: 10.0)) + context.addLine(to: CGPoint(x: 20.0, y: 20.0)) + context.strokePath() + + context.move(to: CGPoint(x: 20.0, y: 10.0)) + context.addLine(to: CGPoint(x: 10.0, y: 20.0)) + context.strokePath() + }) +} + +private final class GiftAnimationComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let file: TelegramMediaFile? + + public init( + context: AccountContext, + theme: PresentationTheme, + file: TelegramMediaFile? + ) { + self.context = context + self.theme = theme + self.file = file + } + + public static func ==(lhs: GiftAnimationComponent, rhs: GiftAnimationComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.file != rhs.file { + return false + } + return true + } + + public final class View: UIView { + private var component: GiftAnimationComponent? + private weak var componentState: EmptyComponentState? + + private var animationLayer: InlineStickerItemLayer? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: GiftAnimationComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.componentState = state + + let emoji = ChatTextInputTextCustomEmojiAttribute( + interactivelySelectedFromPackId: nil, + fileId: component.file?.fileId.id ?? 0, + file: component.file + ) + + let iconSize = availableSize + if self.animationLayer == nil { + let animationLayer = InlineStickerItemLayer( + context: .account(component.context), + userLocation: .other, + attemptSynchronousLoad: false, + emoji: emoji, + file: component.file, + cache: component.context.animationCache, + renderer: component.context.animationRenderer, + unique: true, + placeholderColor: component.theme.list.mediaPlaceholderColor, + pointSize: CGSize(width: iconSize.width * 1.2, height: iconSize.height * 1.2), + loopCount: 1 + ) + animationLayer.isVisibleForAnimations = true + self.animationLayer = animationLayer + self.layer.addSublayer(animationLayer) + } + if let animationLayer = self.animationLayer { + transition.setFrame(layer: animationLayer, frame: CGRect(origin: .zero, size: iconSize)) + } + + return iconSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift index 0306135d38..28ee1394a3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift @@ -20,6 +20,7 @@ public enum PeerInfoPaneKey: Int32 { case gifs case groupsInCommon case recommended + case gifts } public struct PeerInfoStatusData: Equatable { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 8e0477d4f1..a98f6b9ebb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -383,6 +383,7 @@ final class PeerInfoScreenData { let starsRevenueStatsState: StarsRevenueStats? let starsRevenueStatsContext: StarsRevenueStatsContext? let revenueStatsState: RevenueStats? + let profileGiftsContext: ProfileGiftsContext? let _isContact: Bool var forceIsContact: Bool = false @@ -428,7 +429,8 @@ final class PeerInfoScreenData { starsState: StarsContext.State?, starsRevenueStatsState: StarsRevenueStats?, starsRevenueStatsContext: StarsRevenueStatsContext?, - revenueStatsState: RevenueStats? + revenueStatsState: RevenueStats?, + profileGiftsContext: ProfileGiftsContext? ) { self.peer = peer self.chatPeer = chatPeer @@ -463,6 +465,7 @@ final class PeerInfoScreenData { self.starsRevenueStatsState = starsRevenueStatsState self.starsRevenueStatsContext = starsRevenueStatsContext self.revenueStatsState = revenueStatsState + self.profileGiftsContext = profileGiftsContext } } @@ -955,7 +958,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, starsState: starsState, starsRevenueStatsState: nil, starsRevenueStatsContext: nil, - revenueStatsState: nil + revenueStatsState: nil, + profileGiftsContext: nil ) } } @@ -1000,7 +1004,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsState: nil, starsRevenueStatsState: nil, starsRevenueStatsContext: nil, - revenueStatsState: nil + revenueStatsState: nil, + profileGiftsContext: nil )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -1012,6 +1017,13 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen groupsInCommon = nil } + let profileGiftsContext: ProfileGiftsContext? + if case .user = kind { + profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: userPeerId) + } else { + profileGiftsContext = nil + } + enum StatusInputData: Equatable { case none case presence(TelegramUserPresence) @@ -1247,6 +1259,16 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } } + let profileGiftsCount: Signal + if let profileGiftsContext { + profileGiftsCount = profileGiftsContext.state + |> map { state in + return state.count ?? 0 + } + } else { + profileGiftsCount = .single(0) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: isMyProfile, chatLocationContextHolder: chatLocationContextHolder), @@ -1263,9 +1285,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasBotPreviewItems, peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false), privacySettings, - starsRevenueContextAndState + starsRevenueContextAndState, + profileGiftsCount ) - |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, profileGiftsCount -> PeerInfoScreenData in var availablePanes = availablePanes if isMyProfile { availablePanes?.insert(.stories, at: 0) @@ -1277,6 +1300,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen availablePanes?.insert(.stories, at: 0) } + if profileGiftsCount > 0 { + availablePanes?.insert(.gifts, at: hasStories ? 1 : 0) + } + if availablePanes != nil, groupsInCommon != nil, let cachedData = peerView.cachedData as? CachedUserData { if cachedData.commonGroupCount != 0 { availablePanes?.append(.groupsInCommon) @@ -1370,7 +1397,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsState: nil, starsRevenueStatsState: starsRevenueContextAndState.1, starsRevenueStatsContext: starsRevenueContextAndState.0, - revenueStatsState: nil + revenueStatsState: nil, + profileGiftsContext: profileGiftsContext ) } case .channel: @@ -1579,7 +1607,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsState: nil, starsRevenueStatsState: starsRevenueContextAndState.1, starsRevenueStatsContext: starsRevenueContextAndState.0, - revenueStatsState: revenueContextAndState.1 + revenueStatsState: revenueContextAndState.1, + profileGiftsContext: nil ) } case let .group(groupId): @@ -1879,7 +1908,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen starsState: nil, starsRevenueStatsState: nil, starsRevenueStatsContext: nil, - revenueStatsState: nil + revenueStatsState: nil, + profileGiftsContext: nil )) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index dd48610be3..4d83efa582 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -407,6 +407,8 @@ private final class PeerInfoPendingPane { var captureProtected = data.peer?.isCopyProtectionEnabled ?? false let paneNode: PeerInfoPaneNode switch key { + case .gifts: + paneNode = PeerInfoGiftsPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, openPeerContextAction: openPeerContextAction, profileGifts: data.profileGiftsContext!) case .stories, .storyArchive, .botPreview: var canManage = false if let peer = data.peer { @@ -1149,6 +1151,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat title = presentationData.strings.DialogList_TabTitle case .savedMessages: title = presentationData.strings.PeerInfo_SavedMessagesTabTitle + case .gifts: + //TODO:localize + title = "Gifts" } return PeerInfoPaneSpecifier(key: key, title: title) }, selectedPane: self.currentPaneKey, disableSwitching: disableTabSwitching, transitionFraction: self.transitionFraction, transition: transition) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 37a38e0da3..82226426c4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -3472,7 +3472,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _, _ in + }, activateAdAction: { _, _, _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD index 1bef45d94e..6517691155 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD @@ -48,6 +48,9 @@ swift_library( "//submodules/Components/MultilineTextComponent", "//submodules/TelegramUI/Components/TabSelectorComponent", "//submodules/TelegramUI/Components/Settings/LanguageSelectionScreen", + "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", + "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", + "//submodules/TelegramUI/Components/ButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift new file mode 100644 index 0000000000..9a4c7f8122 --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -0,0 +1,290 @@ +import AsyncDisplayKit +import Display +import ComponentFlow +import TelegramCore +import SwiftSignalKit +import Postbox +import TelegramPresentationData +import AccountContext +import ContextUI +import PhotoResources +import TelegramUIPreferences +import ItemListPeerItem +import ItemListPeerActionItem +import MergeLists +import ItemListUI +import ChatControllerInteraction +import MultilineTextComponent +import Markdown +import PeerInfoPaneNode +import GiftItemComponent +import PlainButtonComponent +import GiftViewScreen +import ButtonComponent + +public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate { + private let context: AccountContext + private let peerId: PeerId + private let profileGifts: ProfileGiftsContext + + private var dataDisposable: Disposable? + + private let chatControllerInteraction: ChatControllerInteraction + private let openPeerContextAction: (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void + + public weak var parentController: ViewController? + + private let backgroundNode: ASDisplayNode + private let scrollNode: ASScrollNode + + private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData)? + + private var theme: PresentationTheme? + private let presentationDataPromise = Promise() + + private let ready = Promise() + private var didSetReady: Bool = false + public var isReady: Signal { + return self.ready.get() + } + + private let statusPromise = Promise(nil) + public var status: Signal { + self.statusPromise.get() + } + + public var tabBarOffsetUpdated: ((ContainedViewLayoutTransition) -> Void)? + public var tabBarOffset: CGFloat { + return 0.0 + } + + private var starsProducts: [ProfileGiftsContext.State.StarGift]? + + private var starsItems: [AnyHashable: ComponentView] = [:] + + public init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void, profileGifts: ProfileGiftsContext) { + self.context = context + self.peerId = peerId + self.chatControllerInteraction = chatControllerInteraction + self.openPeerContextAction = openPeerContextAction + self.profileGifts = profileGifts + + self.backgroundNode = ASDisplayNode() + self.scrollNode = ASScrollNode() + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.scrollNode) + + self.dataDisposable = (profileGifts.state + |> deliverOnMainQueue).startStrict(next: { [weak self] state in + guard let self else { + return + } + self.statusPromise.set(.single(PeerInfoStatusData(text: "\(state.count ?? 0) gifts", isActivity: true, key: .gifts))) + self.starsProducts = state.gifts + + if !self.didSetReady { + self.didSetReady = true + self.ready.set(.single(true)) + } + + self.updateScrolling() + }) + } + + deinit { + self.dataDisposable?.dispose() + } + + public override func didLoad() { + super.didLoad() + + self.scrollNode.view.delegate = self + } + + public func ensureMessageIsVisible(id: MessageId) { + } + + public func scrollToTop() -> Bool { + self.scrollNode.view.setContentOffset(.zero, animated: true) + return true + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateScrolling() + } + + func updateScrolling() { + if let starsProducts = self.starsProducts, let params = self.currentParams { + let optionSpacing: CGFloat = 10.0 + let sideInset = params.sideInset + 16.0 + + let itemsInRow = min(starsProducts.count, 3) + let optionWidth = (params.size.width - sideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) + + let starsOptionSize = CGSize(width: optionWidth, height: 154.0) + + let visibleBounds = self.scrollNode.bounds.insetBy(dx: 0.0, dy: -10.0) + + var validIds: [AnyHashable] = [] + var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: 60.0), size: starsOptionSize) + for product in starsProducts { + let itemId = AnyHashable(product.date) + validIds.append(itemId) + + let itemTransition = ComponentTransition.immediate + let visibleItem: ComponentView + if let current = self.starsItems[itemId] { + visibleItem = current + } else { + visibleItem = ComponentView() + self.starsItems[itemId] = visibleItem + } + + var isVisible = false + if visibleBounds.intersects(itemFrame) { + isVisible = true + } + + if isVisible { + let _ = visibleItem.update( + transition: itemTransition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + GiftItemComponent( + context: self.context, + theme: params.presentationData.theme, + peer: product.fromPeer, + subject: .starGift(product.gift.id, product.gift.file), + price: "⭐️ \(product.gift.price)", + ribbon: product.gift.availability != nil ? + GiftItemComponent.Ribbon( + text: "1 of 1K", + color: UIColor(rgb: 0x58c1fe) + ) + : nil + ) + ), + effectAlignment: .center, + action: { [weak self] in + if let self { + let controller = GiftViewScreen( + context: self.context, + subject: .profileGift(self.peerId, product) + ) + self.parentController?.push(controller) + } + }, + animateAlpha: false + ) + ), + environment: {}, + containerSize: starsOptionSize + ) + if let itemView = visibleItem.view { + if itemView.superview == nil { + self.scrollNode.view.addSubview(itemView) + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + } + itemFrame.origin.x += itemFrame.width + optionSpacing + if itemFrame.maxX > params.size.width { + itemFrame.origin.x = sideInset + itemFrame.origin.y += starsOptionSize.height + optionSpacing + } + } + + let contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * starsOptionSize.height + 60.0 + params.bottomInset + 16.0 + +// //TODO:localize +// let buttonSize = self.button.update( +// transition: .immediate, +// component: AnyComponent(ButtonComponent( +// background: ButtonComponent.Background( +// color: params.presentationData.theme.list.itemCheckColors.fillColor, +// foreground: params.presentationData.theme.list.itemCheckColors.foregroundColor, +// pressedColor: params.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), +// cornerRadius: 10.0 +// ), +// content: AnyComponentWithIdentity( +// id: AnyHashable(0), +// component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Send Gifts to Friends", font: Font.semibold(17.0), textColor: )params.presentationData.theme.list.itemCheckColors.foregroundColor))) +// ), +// isEnabled: true, +// displaysProgress: false, +// action: { +// +// } +// )), +// environment: {}, +// containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50) +// ) +// if let buttonView = self.button.view { +// if buttonView.superview == nil { +// self.addSubview(buttonView) +// } +// buttonView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - buttonSize.height), size: buttonSize) +// } + +// contentHeight += 100.0 + + + let contentSize = CGSize(width: params.size.width, height: contentHeight) + if self.scrollNode.view.contentSize != contentSize { + self.scrollNode.view.contentSize = contentSize + } + } + } + + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, sideInset, bottomInset, isScrollingLockedAtTop, presentationData) + self.presentationDataPromise.set(.single(presentationData)) + + self.backgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 48.0), size: size)) + transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) + + if isScrollingLockedAtTop { + self.scrollNode.view.contentOffset = .zero + } + self.scrollNode.view.isScrollEnabled = !isScrollingLockedAtTop + + self.updateScrolling() + } + + public func findLoadedMessage(id: MessageId) -> Message? { + return nil + } + + public func updateHiddenMedia() { + } + + public func transferVelocity(_ velocity: CGFloat) { + if velocity > 0.0 { +// self.scrollNode.transferVelocity(velocity) + } + } + + public func cancelPreviewGestures() { + } + + public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return nil + } + + public func addToTransitionSurface(view: UIView) { + } + + public func updateSelectedMessages(animated: Bool) { + } +} + +private struct StarsGiftProduct: Equatable { + let emoji: String + let price: Int64 + let isLimited: Bool +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift index f43cb26bca..229ca4404e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift @@ -870,7 +870,10 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme } let message = item.message - let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) && !self.revealedSpoilerMessageIds.contains(message.id) + var hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) && !self.revealedSpoilerMessageIds.contains(message.id) + if message.isSensitiveContent(platform: "ios") { + hasSpoiler = true + } layer.updateHasSpoiler(hasSpoiler: hasSpoiler) var selectedMedia: Media? @@ -1272,7 +1275,11 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, } strongSelf.chatControllerInteraction.toggleMessagesSelection([item.message.id], toggledValue) } else { - let _ = strongSelf.chatControllerInteraction.openMessage(item.message, OpenMessageParams(mode: .default)) + if item.message.isSensitiveContent(platform: "ios") { +// strongSelf.context.currentContentSettings.with { $0 }.ignoreContentRestrictionReasons + } else { + let _ = strongSelf.chatControllerInteraction.openMessage(item.message, OpenMessageParams(mode: .default)) + } } } diff --git a/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD b/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD new file mode 100644 index 0000000000..e75b7cea6e --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ItemShimmeringLoadingComponent", + module_name = "ItemShimmeringLoadingComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/ComponentFlow", + "//submodules/AppBundle", + "//submodules/Components/HierarchyTrackingLayer", + "//submodules/TelegramUI/Components/TextLoadingEffect", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/ItemLoadingComponent.swift b/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/Sources/ItemShimmeringLoadingComponent.swift similarity index 69% rename from submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/ItemLoadingComponent.swift rename to submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/Sources/ItemShimmeringLoadingComponent.swift index 96600232dd..83377ab8e1 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/ItemLoadingComponent.swift +++ b/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/Sources/ItemShimmeringLoadingComponent.swift @@ -6,17 +6,25 @@ import HierarchyTrackingLayer import ComponentFlow import TextLoadingEffect -final class ItemLoadingComponent: Component { +public final class ItemShimmeringLoadingComponent: Component { private let color: UIColor + private let cornerRadius: CGFloat - public init(color: UIColor) { + public init( + color: UIColor, + cornerRadius: CGFloat = 10.0 + ) { self.color = color + self.cornerRadius = cornerRadius } - public static func ==(lhs: ItemLoadingComponent, rhs: ItemLoadingComponent) -> Bool { + public static func ==(lhs: ItemShimmeringLoadingComponent, rhs: ItemShimmeringLoadingComponent) -> Bool { if !lhs.color.isEqual(rhs.color) { return false } + if lhs.cornerRadius != rhs.cornerRadius { + return false + } return true } @@ -28,16 +36,14 @@ final class ItemLoadingComponent: Component { private let borderMaskGradientView = UIImageView() private let borderMaskFillView = UIImageView() - private var component: ItemLoadingComponent? + private var component: ItemShimmeringLoadingComponent? override public init(frame: CGRect) { super.init(frame: frame) self.addSubview(self.loadingView) self.addSubview(self.borderView) - - self.borderView.image = generateFilledRoundedRectImage(size: CGSize(width: 24.0, height: 24.0), cornerRadius: 10.0, color: nil, strokeColor: .white, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10).withRenderingMode(.alwaysTemplate) - + self.borderMaskView.backgroundColor = .clear self.borderMaskFillView.backgroundColor = .white @@ -63,14 +69,23 @@ final class ItemLoadingComponent: Component { }) } - func update(component: ItemLoadingComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + func update(component: ItemShimmeringLoadingComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let isFirstTime = self.component == nil + let previousCornerRadius = self.component?.cornerRadius self.component = component + if previousCornerRadius != component.cornerRadius { + self.borderView.image = generateFilledRoundedRectImage(size: CGSize(width: 24.0, height: 24.0), cornerRadius: component.cornerRadius, color: nil, strokeColor: .white, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)?.stretchableImage(withLeftCapWidth: Int(component.cornerRadius), topCapHeight: Int(component.cornerRadius)).withRenderingMode(.alwaysTemplate) + } + self.borderView.tintColor = component.color self.loadingView.update(color: component.color, rect: CGRect(origin: .zero, size: availableSize)) + self.loadingView.frame = CGRect(origin: .zero, size: availableSize) + self.loadingView.layer.cornerRadius = component.cornerRadius + self.loadingView.clipsToBounds = true + transition.setFrame(view: self.borderView, frame: CGRect(origin: .zero, size: availableSize)) self.borderMaskView.frame = self.borderView.bounds diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD index dd5b8cbbb6..8eea622fc1 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD @@ -37,6 +37,9 @@ swift_library( "//submodules/Components/BlurredBackgroundComponent", "//submodules/Components/BundleIconComponent", "//submodules/ConfettiEffect", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index 5493510405..6dfa8bb2d5 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -25,6 +25,7 @@ import TextFormat import PremiumStarComponent import BundleIconComponent import ConfettiEffect +import ItemShimmeringLoadingComponent private struct StarsProduct: Equatable { enum Option: Equatable { @@ -328,7 +329,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { let backgroundComponent: AnyComponent? if product.storeProduct.id == context.component.selectedProductId { backgroundComponent = AnyComponent( - ItemLoadingComponent(color: environment.theme.list.itemAccentColor) + ItemShimmeringLoadingComponent(color: environment.theme.list.itemAccentColor) ) } else { backgroundComponent = nil diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 658dcf5d12..7248d88a8e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -239,7 +239,6 @@ final class StarsTransactionsScreenComponent: Component { if let starView = self.starView.view { let starPosition = CGPoint(x: self.scrollView.frame.width / 2.0, y: topInset + starView.bounds.height / 2.0 - 30.0 - titleOffset * titleScale) - headerTransition.setPosition(view: starView, position: starPosition) headerTransition.setScale(view: starView, scale: titleScale) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index fd2bef3098..5f299827df 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -918,7 +918,7 @@ final class StoryItemContentComponent: Component { } } - let shimmeringMediaAreas: [MediaArea] = component.item.mediaAreas.filter { mediaArea in + var shimmeringMediaAreas: [MediaArea] = component.item.mediaAreas.filter { mediaArea in if case .link = mediaArea { return true } else if case .venue = mediaArea { @@ -928,6 +928,10 @@ final class StoryItemContentComponent: Component { } } + if component.peer.id.isTelegramNotifications { + shimmeringMediaAreas = [] + } + if !shimmeringMediaAreas.isEmpty { let mediaAreasEffectView: StoryItemLoadingEffectView if let current = self.mediaAreasEffectView { diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/BUILD b/submodules/TelegramUI/Components/TabSelectorComponent/BUILD index 87087a3713..e5a61ad4ba 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/BUILD +++ b/submodules/TelegramUI/Components/TabSelectorComponent/BUILD @@ -13,6 +13,9 @@ swift_library( "//submodules/Display", "//submodules/ComponentFlow", "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/Components/MultilineTextWithEntitiesComponent", + "//submodules/TextFormat", + "//submodules/AccountContext" ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index dc2815c8c0..486ab7df91 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -3,18 +3,24 @@ import UIKit import Display import ComponentFlow import PlainButtonComponent +import MultilineTextWithEntitiesComponent +import TextFormat +import AccountContext public final class TabSelectorComponent: Component { public struct Colors: Equatable { public var foreground: UIColor public var selection: UIColor + public var simple: Bool public init( foreground: UIColor, - selection: UIColor + selection: UIColor, + simple: Bool = false ) { self.foreground = foreground self.selection = selection + self.simple = simple } } @@ -45,6 +51,7 @@ public final class TabSelectorComponent: Component { } } + public let context: AccountContext? public let colors: Colors public let customLayout: CustomLayout? public let items: [Item] @@ -53,6 +60,7 @@ public final class TabSelectorComponent: Component { public let transitionFraction: CGFloat? public init( + context: AccountContext? = nil, colors: Colors, customLayout: CustomLayout? = nil, items: [Item], @@ -60,6 +68,7 @@ public final class TabSelectorComponent: Component { setSelectedId: @escaping (AnyHashable) -> Void, transitionFraction: CGFloat? = nil ) { + self.context = context self.colors = colors self.customLayout = customLayout self.items = items @@ -69,6 +78,9 @@ public final class TabSelectorComponent: Component { } public static func ==(lhs: TabSelectorComponent, rhs: TabSelectorComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } if lhs.colors != rhs.colors { return false } @@ -211,6 +223,7 @@ public final class TabSelectorComponent: Component { transition: .immediate, component: AnyComponent(PlainButtonComponent( content: AnyComponent(ItemComponent( + context: component.context, text: item.title, font: itemFont, color: component.colors.foreground, @@ -254,7 +267,7 @@ public final class TabSelectorComponent: Component { } itemTransition.setPosition(view: itemTitleView, position: itemTitleFrame.origin) itemTransition.setBounds(view: itemTitleView, bounds: CGRect(origin: CGPoint(), size: itemTitleFrame.size)) - itemTransition.setAlpha(view: itemTitleView, alpha: item.id == component.selectedId || isLineSelection ? 1.0 : 0.4) + itemTransition.setAlpha(view: itemTitleView, alpha: item.id == component.selectedId || isLineSelection || component.colors.simple ? 1.0 : 0.4) } index += 1 } @@ -302,7 +315,7 @@ public final class TabSelectorComponent: Component { self.contentSize = CGSize(width: contentWidth, height: baseHeight + verticalInset * 2.0) self.disablesInteractiveTransitionGestureRecognizer = contentWidth > availableSize.width - if let selectedBackgroundRect { + if let selectedBackgroundRect, self.bounds.width > 0.0 { self.scrollRectToVisible(selectedBackgroundRect.insetBy(dx: -spacing, dy: 0.0), animated: false) } @@ -331,6 +344,7 @@ extension CGRect { } private final class ItemComponent: CombinedComponent { + let context: AccountContext? let text: String let font: UIFont let color: UIColor @@ -338,12 +352,14 @@ private final class ItemComponent: CombinedComponent { let selectionFraction: CGFloat init( + context: AccountContext?, text: String, font: UIFont, color: UIColor, selectedColor: UIColor, selectionFraction: CGFloat ) { + self.context = context self.text = text self.font = font self.color = color @@ -352,6 +368,9 @@ private final class ItemComponent: CombinedComponent { } static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } if lhs.text != rhs.text { return false } @@ -371,17 +390,25 @@ private final class ItemComponent: CombinedComponent { } static var body: Body { - let title = Child(Text.self) - let selectedTitle = Child(Text.self) + let title = Child(MultilineTextWithEntitiesComponent.self) + let selectedTitle = Child(MultilineTextWithEntitiesComponent.self) return { context in let component = context.component - + + let attributedTitle = NSMutableAttributedString(string: component.text, font: component.font, textColor: component.color) + var range = (attributedTitle.string as NSString).range(of: "⭐️") + if range.location != NSNotFound { + attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + } + let title = title.update( - component: Text( - text: component.text, - font: component.font, - color: component.color + component: MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context?.animationCache, + animationRenderer: component.context?.animationRenderer, + placeholderColor: .white, + text: .plain(attributedTitle) ), availableSize: context.availableSize, transition: .immediate @@ -391,11 +418,19 @@ private final class ItemComponent: CombinedComponent { .opacity(1.0 - component.selectionFraction) ) + let selectedAttributedTitle = NSMutableAttributedString(string: component.text, font: component.font, textColor: component.selectedColor) + range = (selectedAttributedTitle.string as NSString).range(of: "⭐️") + if range.location != NSNotFound { + selectedAttributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + } + let selectedTitle = selectedTitle.update( - component: Text( - text: component.text, - font: component.font, - color: component.selectedColor + component: MultilineTextWithEntitiesComponent( + context: nil, + animationCache: nil, + animationRenderer: nil, + placeholderColor: .white, + text: .plain(selectedAttributedTitle) ), availableSize: context.availableSize, transition: .immediate diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/Contents.json new file mode 100644 index 0000000000..ff2c7d7a8b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "copy_10.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/copy_10.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/copy_10.pdf new file mode 100644 index 0000000000..e8ae82e284 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/BotCopy.imageset/copy_10.pdf @@ -0,0 +1,65 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Filter /FlateDecode + /Length 3 0 R + >> +stream +x͎$5{Fi_3]4Z Luiw3?˗z|ן>~}瓻?z|^||;™Xgټ,,X.gbg-z#յZ@|qQTbbUZ0̱yKBo'rO[ӆZ7Y,eoľJe²ZdIH5@\-[*`5eWJ.Z \UdS f`"8 YvKE bƲ)<>racIn<Ȯ] ڮ4H*:FEWUȅ yZ t[jB jq,MI(pQD;(;Sz[IJbCf+|HWbۗZN9c[z|;~E-L K,ww{aI}GZjn_dGye- +l'vlC̲YeT|Xɤ`GrjD!x# y_Dz3[0Qf鮦_QiYAXȗt5Cm'iRQ6ҤC[X"O)/Q)0Att +/LB $G+-$ ]$A|az?XP4u1 +JEu߁ LtYQ=Z^&D;"3tλҼ6{jM[G8Z pH"q+$z'؂*1-˗qkӲQ Kq':bpZ&"4oj̙B)萷J2~ +@N<-6o|R%p)ʧ U ekNPCU5nJJ?_ +wC `Nx]tʀ( PÖO/FTk! +sU^RC"N)3/L~+ i{-&TA(OIK)_LRRgݭ>Bst4@f|Ϝl_$6ʶ! dC6ըK|sCfˉj`C/C.BM1ʃv4Uv8!t Pu|]'ԿT +endstream +endobj + +3 0 obj + 1308 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 10.000000 10.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001426 00000 n +0000001449 00000 n +0000001622 00000 n +0000001696 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1755 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/Contents.json new file mode 100644 index 0000000000..3f3ed2761d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "giftline_chat.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/giftline_chat.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/giftline_chat.pdf new file mode 100644 index 0000000000..3ef1493997 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/GiftRibbon.imageset/giftline_chat.pdf @@ -0,0 +1,83 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 2.784424 cm +0.000000 0.000000 0.000000 scn +62.376457 30.839119 m +33.623550 59.592026 l +31.548130 61.667446 30.510420 62.705158 29.299419 63.447258 c +28.225752 64.105202 27.055216 64.590057 25.830782 64.884018 c +24.449732 65.215576 22.982187 65.215576 20.047100 65.215576 c +7.725484 65.215576 l +5.302220 65.215576 4.090588 65.215576 3.529531 64.736389 c +3.042711 64.320602 2.784362 63.696896 2.834592 63.058659 c +2.892483 62.323093 3.749237 61.466339 5.462745 59.752831 c +62.537258 2.678318 l +64.250763 0.964813 65.107521 0.108055 65.843079 0.050171 c +66.481316 -0.000061 67.105026 0.258286 67.520813 0.745110 c +68.000000 1.306164 68.000000 2.517796 68.000000 4.941059 c +68.000000 17.262676 l +68.000000 20.197762 68.000000 21.665306 67.668442 23.046356 c +67.374481 24.270794 66.889626 25.441330 66.231682 26.514996 c +65.489578 27.725994 64.451874 28.763702 62.376457 30.839119 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 962 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 68.000000 68.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001052 00000 n +0000001074 00000 n +0000001247 00000 n +0000001321 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1380 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/Contents.json new file mode 100644 index 0000000000..bc85fccee1 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "giftline_cell.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/giftline_cell.pdf b/submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/giftline_cell.pdf new file mode 100644 index 0000000000..68d0f7b17d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/GiftRibbon.imageset/giftline_cell.pdf @@ -0,0 +1,84 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 2.784363 cm +0.000000 0.000000 0.000000 scn +52.376450 30.839190 m +33.623550 49.592087 l +31.548130 51.667507 30.510420 52.705219 29.299419 53.447319 c +28.225752 54.105263 27.055216 54.590115 25.830782 54.884075 c +24.449732 55.215637 22.982187 55.215637 20.047100 55.215637 c +7.725484 55.215637 l +5.302220 55.215637 4.090588 55.215637 3.529531 54.736450 c +3.042711 54.320667 2.784362 53.696957 2.834592 53.058720 c +2.892483 52.323154 3.749236 51.466400 5.462744 49.752892 c +52.537258 2.678379 l +54.250763 0.964874 55.107517 0.108120 55.843082 0.050228 c +56.481319 0.000000 57.105030 0.258350 57.520813 0.745167 c +58.000000 1.306225 58.000000 2.517857 58.000000 4.941120 c +58.000000 17.262737 l +58.000000 20.197823 58.000000 21.665367 57.668438 23.046417 c +57.374477 24.270853 56.889626 25.441389 56.231682 26.515057 c +55.489586 27.726049 54.451885 28.763756 52.376484 30.839149 c +52.376450 30.839190 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 983 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 58.000000 58.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001073 00000 n +0000001095 00000 n +0000001268 00000 n +0000001342 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1401 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index e73b473521..c3b1aedc8d 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -2365,7 +2365,7 @@ private func extractAccountManagerState(records: AccountRecordsView take(1) |> deliverOnMainQueue @@ -2478,7 +2478,7 @@ private func extractAccountManagerState(records: AccountRecordsView deliverOnMainQueue).start(next: { context in - context.openChatWithPeerId(peerId: peerId, threadId: threadId, messageId: messageId, activateInput: activateInput, storyId: storyId) + context.openChatWithPeerId(peerId: peerId, threadId: threadId, messageId: messageId, activateInput: activateInput, storyId: storyId, openAppIfAny: openAppIfAny) })) } diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 245323180c..89264f0f67 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -894,7 +894,7 @@ final class AuthorizedApplicationContext { })) } - func openChatWithPeerId(peerId: PeerId, threadId: Int64?, messageId: MessageId? = nil, activateInput: Bool = false, storyId: StoryId?) { + func openChatWithPeerId(peerId: PeerId, threadId: Int64?, messageId: MessageId? = nil, activateInput: Bool = false, storyId: StoryId?, openAppIfAny: Bool = false) { if let storyId { var controllers = self.rootController.viewControllers controllers = controllers.filter { c in @@ -945,7 +945,11 @@ final class AuthorizedApplicationContext { chatLocation = .peer(peer) } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil)) + if openAppIfAny, let parentController = self.rootController.viewControllers.last as? ViewController { + self.context.sharedContext.openWebApp(context: self.context, parentController: parentController, updatedPresentationData: nil, peer: peer, threadId: nil, buttonText: "", url: "", simple: true, source: .generic, skipTermsOfService: true) + } else { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: isOutgoingMessage ? messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false) } : nil, activateInput: activateInput ? .text : nil)) + } }) } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index b0374e4646..c899f6db8e 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -242,7 +242,7 @@ func openWebAppImpl(context: AccountContext, parentController: ViewController, u |> afterDisposed { updateProgress() }) - |> deliverOnMainQueue).startStrict(next: { [weak parentController] result in + |> deliverOnMainQueue).startStandalone(next: { [weak parentController] result in guard let parentController else { return } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 8ab7dc49e5..b4b2369518 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1113,12 +1113,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { strongSelf.present(BotReceiptController(context: strongSelf.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } - /*for attribute in message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - //strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId)) - break - } - }*/ return true case .setChatTheme: strongSelf.presentThemeSelection() @@ -1185,6 +1179,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration, giftCode: nil)) strongSelf.push(controller) return true + case .starGift: + let controller = strongSelf.context.sharedContext.makeGiftViewScreen(context: strongSelf.context, message: EngineMessage(message)) + strongSelf.push(controller) + return true case .giftStars: let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message)) strongSelf.push(controller) @@ -1280,6 +1278,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G standalone = true } + if let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { + if let file = message.media.first(where: { $0 is TelegramMediaFile}) as? TelegramMediaFile, file.isVideo && !file.isAnimated { + strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: true, fullscreen: false) + } else { + strongSelf.controllerInteraction?.activateAdAction(message.id, nil, true, false) + return true + } + } + return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: openChatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: strongSelf.chatLocationContextHolder, message: message, mediaIndex: params.mediaIndex, standalone: standalone, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.effectiveNavigationController, dismissInput: { self?.chatDisplayNode.dismissInput() }, present: { c, a, i in @@ -1391,7 +1398,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, openAd: { [weak self] messageId in if let strongSelf = self { - strongSelf.controllerInteraction?.activateAdAction(messageId, nil) + strongSelf.controllerInteraction?.activateAdAction(messageId, nil, true, true) } }, addContact: { [weak self] phoneNumber in if let strongSelf = self { @@ -3903,7 +3910,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.openWebApp(buttonText: buttonText, url: url, simple: simple, source: source) - }, activateAdAction: { [weak self] messageId, progress in + }, activateAdAction: { [weak self] messageId, progress, media, fullscreen in guard let self, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let adAttribute = message.adAttribute else { return } @@ -3917,7 +3924,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: false, fullscreen: false) + self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: media, fullscreen: fullscreen) self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: adAttribute.url, concealed: false, external: true, progress: progress)) }, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId, maxQuantity in guard let self else { diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index bd5bef6587..502b9a9c3a 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -351,7 +351,8 @@ private func extractAssociatedData( chatThemes: [TelegramTheme], deviceContactsNumbers: Set, isInline: Bool, - showSensitiveContent: Bool + showSensitiveContent: Bool, + starGifts: [Int64 : TelegramMediaFile] ) -> ChatMessageItemAssociatedData { var automaticDownloadPeerId: EnginePeer.Id? var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel @@ -406,7 +407,7 @@ private func extractAssociatedData( automaticDownloadPeerId = message.peerId } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent, starGifts: starGifts) } private extension ChatHistoryLocationInput { @@ -1610,6 +1611,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } |> distinctUntilChanged + let starGifts: Signal<[Int64 : TelegramMediaFile], NoError> = context.engine.payments.cachedStarGifts() + |> map { gifts in + var files: [Int64 : TelegramMediaFile] = [:] + if let gifts { + for gift in gifts { + files[gift.id] = gift.file + } + } + return files + } + let messageViewQueue = Queue.mainQueue() let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue, historyViewUpdate, @@ -1636,8 +1648,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto audioTranscriptionTrial, chatThemes, deviceContactsNumbers, - contentSettings - ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings in + contentSettings, + starGifts + ).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings, starGifts in let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises func applyHole() { @@ -1856,7 +1869,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto translateToLanguage = languageCode } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated, showSensitiveContent: contentSettings.ignoreContentRestrictionReasons.contains("sensitive")) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated, showSensitiveContent: contentSettings.ignoreContentRestrictionReasons.contains("sensitive"), starGifts: starGifts) var includeEmbeddedSavedChatInfo = false if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId, !rotated { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 8d9f42b048..287f786bf2 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -165,7 +165,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _, _ in + }, activateAdAction: { _, _, _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index d31dfde970..70e5e549d7 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -70,6 +70,8 @@ import StarsTransferScreen import StarsTransactionScreen import StarsWithdrawalScreen import MiniAppListScreen +import GiftOptionsScreen +import GiftViewScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -1776,7 +1778,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, openLargeEmojiInfo: { _, _, _ in }, openJoinLink: { _ in }, openWebView: { _, _, _, _ in - }, activateAdAction: { _, _ in + }, activateAdAction: { _, _, _, _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { @@ -2202,26 +2204,22 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let limit: Int32 = 10 - var reachedLimitImpl: ((Int32) -> Void)? +// let limit: Int32 = 10 +// var reachedLimitImpl: ((Int32) -> Void)? var presentBirthdayPickerImpl: (() -> Void)? - let mode: ContactMultiselectionControllerMode var starsMode: ContactSelectionControllerMode = .generic var currentBirthdays: [EnginePeer.Id: TelegramBirthday]? + if case let .chatList(birthdays) = source, let birthdays, !birthdays.isEmpty { - mode = .premiumGifting(birthdays: birthdays, selectToday: true, hasActions: true) + starsMode = .starsGifting(birthdays: birthdays, hasActions: true) currentBirthdays = birthdays } else if case let .settings(birthdays) = source, let birthdays, !birthdays.isEmpty { - mode = .premiumGifting(birthdays: birthdays, selectToday: false, hasActions: true) - currentBirthdays = birthdays - } else if case let .stars(birthdays) = source { - mode = .premiumGifting(birthdays: birthdays, selectToday: false, hasActions: false) - starsMode = .starsGifting(birthdays: birthdays, hasActions: false) + starsMode = .starsGifting(birthdays: birthdays, hasActions: true) currentBirthdays = birthdays } else { - mode = .premiumGifting(birthdays: nil, selectToday: false, hasActions: true) + starsMode = .starsGifting(birthdays: nil, hasActions: true) } - + let contactOptions: Signal<[ContactListAdditionalOption], NoError> if currentBirthdays != nil || "".isEmpty { contactOptions = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)) @@ -2247,104 +2245,122 @@ public final class SharedAccountContextImpl: SharedAccountContext { var openProfileImpl: ((EnginePeer) -> Void)? var sendMessageImpl: ((EnginePeer) -> Void)? + //TODO:localize let controller: ViewController - if case .stars = source { - let options = Promise<[StarsGiftOption]>() - options.set(context.engine.payments.starsGiftOptions(peerId: nil)) +// if case .stars = source { +// let options = Promise<[StarsGiftOption]>() +// options.set(context.engine.payments.starsGiftOptions(peerId: nil)) + let options = Promise<[PremiumGiftCodeOption]>() + options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) let contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams( context: context, mode: starsMode, autoDismiss: false, - title: { strings in return strings.Stars_Purchase_GiftStars }, - options: contactOptions - )) - let _ = (contactsController.result - |> deliverOnMainQueue).start(next: { result in - if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer { - completion?([peer.id]) + title: { strings in return "Gift Premium or Stars" }, + options: contactOptions, + openProfile: { peer in + openProfileImpl?(peer) + }, + sendMessage: { peer in + sendMessageImpl?(peer) } - }) - controller = contactsController - } else { - let options = Promise<[PremiumGiftCodeOption]>() - options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) - let contactsController = context.sharedContext.makeContactMultiselectionController( - ContactMultiselectionControllerParams( - context: context, - mode: mode, - options: contactOptions, - isPeerEnabled: { peer in - if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) { - return true - } else { - return false - } - }, - limit: limit, - reachedLimit: { limit in - reachedLimitImpl?(limit) - }, - openProfile: { peer in - openProfileImpl?(peer) - }, - sendMessage: { peer in - sendMessageImpl?(peer) - } - ) - ) + )) let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get()) .startStandalone(next: { [weak contactsController] result, options in - guard let controller = contactsController else { - return - } - var peerIds: [PeerId] = [] - if case let .result(peerIdsValue, _) = result { - peerIds = peerIdsValue.compactMap({ peerId in - if case let .peer(peerId) = peerId { - return peerId - } else { - return nil - } - }) - } - guard !peerIds.isEmpty else { - return - } - - let mappedOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } - var pushImpl: ((ViewController) -> Void)? - var filterImpl: (() -> Void)? - let giftController = PremiumGiftScreen(context: context, peerIds: peerIds, options: mappedOptions, source: source, pushController: { c in - pushImpl?(c) - }, completion: { - filterImpl?() + if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer { + let premiumOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } + let giftController = GiftOptionsScreen(context: context, peerId: peer.id, premiumOptions: premiumOptions) + giftController.navigationPresentation = .modal + contactsController?.push(giftController) + +// completion?([peer.id]) if case .chatList = source, let _ = currentBirthdays { let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone() } - }) - pushImpl = { [weak giftController] c in - giftController?.push(c) } - filterImpl = { [weak giftController] in - if let navigationController = giftController?.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is ContactMultiselectionController) && !($0 is PremiumGiftScreen) } - navigationController.setViewControllers(controllers, animated: true) - } - } - controller.push(giftController) }) controller = contactsController - } +// } else { +// let options = Promise<[PremiumGiftCodeOption]>() +// options.set(context.engine.payments.premiumGiftCodeOptions(peerId: nil)) +// let contactsController = context.sharedContext.makeContactMultiselectionController( +// ContactMultiselectionControllerParams( +// context: context, +// mode: mode, +// options: contactOptions, +// isPeerEnabled: { peer in +// if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) { +// return true +// } else { +// return false +// } +// }, +// limit: limit, +// reachedLimit: { limit in +// reachedLimitImpl?(limit) +// }, +// openProfile: { peer in +// openProfileImpl?(peer) +// }, +// sendMessage: { peer in +// sendMessageImpl?(peer) +// } +// ) +// ) +// let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get()) +// .startStandalone(next: { [weak contactsController] result, options in +// guard let controller = contactsController else { +// return +// } +// var peerIds: [PeerId] = [] +// if case let .result(peerIdsValue, _) = result { +// peerIds = peerIdsValue.compactMap({ peerId in +// if case let .peer(peerId) = peerId { +// return peerId +// } else { +// return nil +// } +// }) +// } +// guard !peerIds.isEmpty else { +// return +// } +// +// let mappedOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } +// var pushImpl: ((ViewController) -> Void)? +// var filterImpl: (() -> Void)? +// let giftController = PremiumGiftScreen(context: context, peerIds: peerIds, options: mappedOptions, source: source, pushController: { c in +// pushImpl?(c) +// }, completion: { +// filterImpl?() +// +// if case .chatList = source, let _ = currentBirthdays { +// let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .todayBirthdays).startStandalone() +// } +// }) +// pushImpl = { [weak giftController] c in +// giftController?.push(c) +// } +// filterImpl = { [weak giftController] in +// if let navigationController = giftController?.navigationController as? NavigationController { +// var controllers = navigationController.viewControllers +// controllers = controllers.filter { !($0 is ContactMultiselectionController) && !($0 is PremiumGiftScreen) } +// navigationController.setViewControllers(controllers, animated: true) +// } +// } +// controller.push(giftController) +// }) +// controller = contactsController +// } - reachedLimitImpl = { [weak controller] limit in - guard let controller else { - return - } - HapticFeedback().error() - controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Premium_Gift_ContactSelection_MaximumReached("\(limit)").string, timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } +// reachedLimitImpl = { [weak controller] limit in +// guard let controller else { +// return +// } +// HapticFeedback().error() +// controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Premium_Gift_ContactSelection_MaximumReached("\(limit)").string, timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current) +// } sendMessageImpl = { [weak self, weak controller] peer in guard let self, let controller, let navigationController = controller.navigationController as? NavigationController else { @@ -2795,6 +2811,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StarsTransactionScreen(context: context, subject: .boost(peerId, boost)) } + public func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController { + return GiftViewScreen(context: context, subject: .message(message)) + } + public func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal { return MiniAppListScreen.initialData(context: context) } diff --git a/submodules/TelegramUI/Sources/SpotlightContacts.swift b/submodules/TelegramUI/Sources/SpotlightContacts.swift index 3437c5e1a4..699ac90b87 100644 --- a/submodules/TelegramUI/Sources/SpotlightContacts.swift +++ b/submodules/TelegramUI/Sources/SpotlightContacts.swift @@ -186,12 +186,38 @@ private func manageableSpotlightContacts(appBasePath: String, accounts: Signal<[ return accounts |> mapToSignal { accounts -> Signal<[[EnginePeer.Id: SpotlightIndexStorageItem]], NoError> in return combineLatest(queue: queue, accounts.map { account -> Signal<[EnginePeer.Id: SpotlightIndexStorageItem], NoError> in - return TelegramEngine(account: account).data.subscribe( - TelegramEngine.EngineData.Item.Contacts.List(includePresences: false) + let engine = TelegramEngine(account: account) + let recentApps = engine.peers.recentApps() + |> mapToSignal { peerIds -> Signal<[EnginePeer], NoError> in + return engine.data.get( + EngineDataMap( + peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in + return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + } + ) + ) + |> map { result -> [EnginePeer] in + var peers: [EnginePeer] = [] + for (_, maybePeer) in result { + if let peer = maybePeer { + peers.append(peer) + } + } + return peers + } + } + return combineLatest( + engine.data.subscribe( + TelegramEngine.EngineData.Item.Contacts.List(includePresences: false) + ), + recentApps ) - |> map { view -> [EnginePeer.Id: SpotlightIndexStorageItem] in + |> map { view, recentApps -> [EnginePeer.Id: SpotlightIndexStorageItem] in var result: [EnginePeer.Id: SpotlightIndexStorageItem] = [:] - for peer in view.peers { + var peers: [EnginePeer] = [] + peers.append(contentsOf: view.peers) + peers.append(contentsOf: recentApps) + for peer in peers { if case let .user(user) = peer { let avatarSourcePath = smallestImageRepresentation(user.photo).flatMap { representation -> String? in let resourcePath = account.postbox.mediaBox.resourcePath(representation.resource) diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 45b3e874fa..86aa5d1b9a 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -409,6 +409,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { case nameColors([UInt32]) case stars(tinted: Bool) case ton + case animation(name: String) } public let interactivelySelectedFromPackId: ItemCollectionId? From ec117d5638380e0159a779b53e517872fe55c7fb Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 19 Sep 2024 03:58:55 +0400 Subject: [PATCH 05/11] Update API --- .../Sources/State/AccountViewTracker.swift | 2 +- .../SyncCore/SyncCore_CachedUserData.swift | 80 ++++++++++++------- .../TelegramEngine/Payments/StarGifts.swift | 2 +- .../Peers/UpdateCachedPeerData.swift | 5 +- .../Sources/Utils/PeerUtils.swift | 9 +++ 5 files changed, 63 insertions(+), 35 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 9262048466..c5daa87694 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1472,7 +1472,7 @@ public final class AccountViewTracker { if i < slice.count { let value = result[i] transaction.updatePeerCachedData(peerIds: Set([slice[i].0]), update: { _, cachedData in - var cachedData = cachedData as? CachedUserData ?? CachedUserData(about: nil, botInfo: nil, editableBotInfo: nil, peerStatusSettings: nil, pinnedMessageId: nil, isBlocked: false, commonGroupCount: 0, voiceCallsAvailable: true, videoCallsAvailable: true, callsPrivate: true, canPinMessages: true, hasScheduledMessages: true, autoremoveTimeout: .unknown, themeEmoticon: nil, photo: .unknown, personalPhoto: .unknown, fallbackPhoto: .unknown, premiumGiftOptions: [], voiceMessagesAvailable: true, wallpaper: nil, flags: [], businessHours: nil, businessLocation: nil, greetingMessage: nil, awayMessage: nil, connectedBot: nil, businessIntro: .unknown, birthday: nil, personalChannel: .unknown, botPreview: nil) + var cachedData = cachedData as? CachedUserData ?? CachedUserData(about: nil, botInfo: nil, editableBotInfo: nil, peerStatusSettings: nil, pinnedMessageId: nil, isBlocked: false, commonGroupCount: 0, voiceCallsAvailable: true, videoCallsAvailable: true, callsPrivate: true, canPinMessages: true, hasScheduledMessages: true, autoremoveTimeout: .unknown, themeEmoticon: nil, photo: .unknown, personalPhoto: .unknown, fallbackPhoto: .unknown, premiumGiftOptions: [], voiceMessagesAvailable: true, wallpaper: nil, flags: [], businessHours: nil, businessLocation: nil, greetingMessage: nil, awayMessage: nil, connectedBot: nil, businessIntro: .unknown, birthday: nil, personalChannel: .unknown, botPreview: nil, starGiftsCount: nil) var flags = cachedData.flags if case .boolTrue = value { flags.insert(.premiumRequired) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index f35daeb38a..514be59ea6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -744,6 +744,7 @@ public final class CachedUserData: CachedPeerData { public let birthday: TelegramBirthday? public let personalChannel: CachedTelegramPersonalChannel public let botPreview: BotPreview? + public let starGiftsCount: Int32? public let peerIds: Set public let messageIds: Set @@ -782,9 +783,10 @@ public final class CachedUserData: CachedPeerData { self.birthday = nil self.personalChannel = .unknown self.botPreview = nil + self.starGiftsCount = nil } - public init(about: String?, botInfo: BotInfo?, editableBotInfo: EditableBotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, fallbackPhoto: CachedPeerProfilePhoto, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool, wallpaper: TelegramWallpaper?, flags: CachedUserFlags, businessHours: TelegramBusinessHours?, businessLocation: TelegramBusinessLocation?, greetingMessage: TelegramBusinessGreetingMessage?, awayMessage: TelegramBusinessAwayMessage?, connectedBot: TelegramAccountConnectedBot?, businessIntro: CachedTelegramBusinessIntro, birthday: TelegramBirthday?, personalChannel: CachedTelegramPersonalChannel, botPreview: BotPreview?) { + public init(about: String?, botInfo: BotInfo?, editableBotInfo: EditableBotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, fallbackPhoto: CachedPeerProfilePhoto, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool, wallpaper: TelegramWallpaper?, flags: CachedUserFlags, businessHours: TelegramBusinessHours?, businessLocation: TelegramBusinessLocation?, greetingMessage: TelegramBusinessGreetingMessage?, awayMessage: TelegramBusinessAwayMessage?, connectedBot: TelegramAccountConnectedBot?, businessIntro: CachedTelegramBusinessIntro, birthday: TelegramBirthday?, personalChannel: CachedTelegramPersonalChannel, botPreview: BotPreview?, starGiftsCount: Int32?) { self.about = about self.botInfo = botInfo self.editableBotInfo = editableBotInfo @@ -815,6 +817,7 @@ public final class CachedUserData: CachedPeerData { self.birthday = birthday self.personalChannel = personalChannel self.botPreview = botPreview + self.starGiftsCount = starGiftsCount self.peerIds = Set() @@ -880,6 +883,8 @@ public final class CachedUserData: CachedPeerData { self.personalChannel = decoder.decodeCodable(CachedTelegramPersonalChannel.self, forKey: "pchan") ?? .unknown self.botPreview = decoder.decodeCodable(BotPreview.self, forKey: "botPreview") + + self.starGiftsCount = decoder.decodeOptionalInt32ForKey("starGiftsCount") } public func encode(_ encoder: PostboxEncoder) { @@ -985,6 +990,12 @@ public final class CachedUserData: CachedPeerData { } else { encoder.encodeNil(forKey: "botPreview") } + + if let starGiftsCount = self.starGiftsCount { + encoder.encodeInt32(starGiftsCount, forKey: "starGiftsCount") + } else { + encoder.encodeNil(forKey: "starGiftsCount") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -1025,128 +1036,135 @@ public final class CachedUserData: CachedPeerData { if other.botPreview != self.botPreview { return false } + if other.starGiftsCount != self.starGiftsCount { + return false + } return other.about == self.about && other.botInfo == self.botInfo && other.editableBotInfo == self.editableBotInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon && self.photo == other.photo && self.personalPhoto == other.personalPhoto && self.fallbackPhoto == other.fallbackPhoto && self.premiumGiftOptions == other.premiumGiftOptions && self.voiceMessagesAvailable == other.voiceMessagesAvailable && self.flags == other.flags && self.wallpaper == other.wallpaper } public func withUpdatedAbout(_ about: String?) -> CachedUserData { - return CachedUserData(about: about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedEditableBotInfo(_ editableBotInfo: EditableBotInfo?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedPhoto(_ photo: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedPersonalPhoto(_ personalPhoto: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedFallbackPhoto(_ fallbackPhoto: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedFlags(_ flags: CachedUserFlags) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedBusinessHours(_ businessHours: TelegramBusinessHours?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedBusinessLocation(_ businessLocation: TelegramBusinessLocation?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedGreetingMessage(_ greetingMessage: TelegramBusinessGreetingMessage?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedAwayMessage(_ awayMessage: TelegramBusinessAwayMessage?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedConnectedBot(_ connectedBot: TelegramAccountConnectedBot?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedBusinessIntro(_ businessIntro: TelegramBusinessIntro?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: .known(businessIntro), birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: .known(businessIntro), birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedBirthday(_ birthday: TelegramBirthday?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: birthday, personalChannel: self.personalChannel, botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedPersonalChannel(_ personalChannel: TelegramPersonalChannel?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: .known(personalChannel), botPreview: self.botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: .known(personalChannel), botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } public func withUpdatedBotPreview(_ botPreview: BotPreview?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: botPreview) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: botPreview, starGiftsCount: self.starGiftsCount) + } + + public func withUpdatedStarGiftsCount(_ starGiftsCount: Int32?) -> CachedUserData { + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index e7f5a7bc62..38ed0e981d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -130,7 +130,7 @@ extension StarGift { if let availabilityRemains, let availabilityTotal { availability = Availability(remains: availabilityRemains, total: availabilityTotal) } - guard let file = telegramMediaFileFromApiDocument(sticker) else { + guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else { return nil } self.init(id: id, file: file, price: stars, availability: availability) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 6f17d552ba..b2e538e6bb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -258,7 +258,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } switch fullUser { - case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFullNotifySettings)]) } @@ -270,7 +270,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee previous = CachedUserData() } switch fullUser { - case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions, userWallpaper, stories, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage): + case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions, userWallpaper, stories, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount): let _ = stories let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let isBlocked = (userFullFlags & (1 << 0)) != 0 @@ -414,6 +414,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedBirthday(mappedBirthday) .withUpdatedPersonalChannel(personalChannel) .withUpdatedBotPreview(botPreview) + .withUpdatedStarGiftsCount(starGiftsCount) } }) } diff --git a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift index 8b086c7903..ace609b0cc 100644 --- a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift @@ -448,6 +448,15 @@ public extension PeerId { return false } + var isVerificationCodes: Bool { + if self.namespace == Namespaces.Peer.CloudUser { + if self.id._internalGetInt64Value() == 489000 { + return true + } + } + return false + } + func isRepliesOrSavedMessages(accountPeerId: PeerId) -> Bool { if accountPeerId == self { return true From 3bdb213cc555a13c0d9962dca0d7b5cd92e76355 Mon Sep 17 00:00:00 2001 From: Mikhail Filimonov Date: Wed, 18 Sep 2024 21:51:36 -0300 Subject: [PATCH 06/11] - typo fix --- .../TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 514be59ea6..0cf834f0db 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -1164,7 +1164,7 @@ public final class CachedUserData: CachedPeerData { } public func withUpdatedStarGiftsCount(_ starGiftsCount: Int32?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: self.starGiftsCount) + return CachedUserData(about: self.about, botInfo: self.botInfo, editableBotInfo: self.editableBotInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags, businessHours: self.businessHours, businessLocation: self.businessLocation, greetingMessage: self.greetingMessage, awayMessage: self.awayMessage, connectedBot: self.connectedBot, businessIntro: self.businessIntro, birthday: self.birthday, personalChannel: self.personalChannel, botPreview: self.botPreview, starGiftsCount: starGiftsCount) } } From 164824d1e5b02198deaf9a46f8308b4498a58d83 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 20 Sep 2024 15:15:50 +0400 Subject: [PATCH 07/11] Update API --- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api23.swift | 18 +-- .../TelegramEngine/Payments/StarGifts.swift | 15 ++- .../ChatMessageGiftBubbleContentNode.swift | 9 +- .../Sources/GiftOptionsScreen.swift | 117 +++++++++--------- .../Sources/ChatGiftPreviewItem.swift | 3 +- .../PeerInfoScreen/Sources/PeerInfoData.swift | 28 ++--- 7 files changed, 104 insertions(+), 88 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index b3c887f01a..9faafe7bc0 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -890,7 +890,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) } dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } - dict[-384008227] = { return Api.StarGift.parse_starGift($0) } + dict[-1365150482] = { return Api.StarGift.parse_starGift($0) } dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) } dict[-1798404822] = { return Api.StarsGiveawayOption.parse_starsGiveawayOption($0) } dict[1411605001] = { return Api.StarsGiveawayWinnersOption.parse_starsGiveawayWinnersOption($0) } diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index cf446813c3..5868481552 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -574,13 +574,13 @@ public extension Api { } public extension Api { enum StarGift: TypeConstructorDescription { - case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?) + case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, convertStars: Int64) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal): + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let convertStars): if boxed { - buffer.appendInt32(-384008227) + buffer.appendInt32(-1365150482) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -588,14 +588,15 @@ public extension Api { serializeInt64(stars, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityRemains!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityTotal!, buffer: buffer, boxed: false)} + serializeInt64(convertStars, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal): - 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)]) + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let convertStars): + 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), ("convertStars", convertStars as Any)]) } } @@ -614,14 +615,17 @@ public extension Api { if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } var _6: Int32? if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() } + var _7: Int64? + _7 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6) + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, convertStars: _7!) } else { return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 38ed0e981d..2ebfdbddb5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -32,6 +32,7 @@ public struct StarGift: Equatable, Codable, PostboxCoding { case id case file case price + case convertStars case availability } @@ -67,12 +68,14 @@ public struct StarGift: Equatable, Codable, PostboxCoding { public let id: Int64 public let file: TelegramMediaFile public let price: Int64 + public let convertStars: Int64 public let availability: Availability? - public init(id: Int64, file: TelegramMediaFile, price: Int64, availability: Availability?) { + public init(id: Int64, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?) { self.id = id self.file = file self.price = price + self.convertStars = convertStars self.availability = availability } @@ -87,6 +90,7 @@ public struct StarGift: Equatable, Codable, PostboxCoding { } self.price = try container.decode(Int64.self, forKey: .price) + self.convertStars = try container.decodeIfPresent(Int64.self, forKey: .convertStars) ?? 0 self.availability = try container.decodeIfPresent(Availability.self, forKey: .availability) } @@ -94,6 +98,7 @@ public struct StarGift: Equatable, Codable, PostboxCoding { self.id = decoder.decodeInt64ForKey(CodingKeys.id.rawValue, orElse: 0) self.file = decoder.decodeObjectForKey(CodingKeys.file.rawValue) as! TelegramMediaFile self.price = decoder.decodeInt64ForKey(CodingKeys.price.rawValue, orElse: 0) + self.convertStars = decoder.decodeInt64ForKey(CodingKeys.convertStars.rawValue, orElse: 0) self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { StarGift.Availability(decoder: $0) }) as? StarGift.Availability } @@ -107,6 +112,7 @@ public struct StarGift: Equatable, Codable, PostboxCoding { try container.encode(fileData, forKey: .file) try container.encode(self.price, forKey: .price) + try container.encode(self.convertStars, forKey: .convertStars) try container.encodeIfPresent(self.availability, forKey: .availability) } @@ -114,6 +120,7 @@ public struct StarGift: Equatable, Codable, PostboxCoding { encoder.encodeInt64(self.id, forKey: CodingKeys.id.rawValue) encoder.encodeObject(self.file, forKey: CodingKeys.file.rawValue) encoder.encodeInt64(self.price, forKey: CodingKeys.price.rawValue) + encoder.encodeInt64(self.convertStars, forKey: CodingKeys.convertStars.rawValue) if let availability = self.availability { encoder.encodeObject(availability, forKey: CodingKeys.availability.rawValue) } else { @@ -125,7 +132,7 @@ public struct StarGift: Equatable, Codable, PostboxCoding { extension StarGift { init?(apiStarGift: Api.StarGift) { switch apiStarGift { - case let .starGift(_, id, sticker, stars, availabilityRemains, availabilityTotal): + case let .starGift(_, id, sticker, stars, availabilityRemains, availabilityTotal, convertStars): var availability: Availability? if let availabilityRemains, let availabilityTotal { availability = Availability(remains: availabilityRemains, total: availabilityTotal) @@ -133,7 +140,7 @@ extension StarGift { guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else { return nil } - self.init(id: id, file: file, price: stars, availability: availability) + self.init(id: id, file: file, price: stars, convertStars: convertStars, availability: availability) } } } @@ -323,6 +330,7 @@ public final class ProfileGiftsContext { public let entities: [MessageTextEntity]? public let messageId: EngineMessage.Id? public let nameHidden: Bool + public let savedToProfile: Bool public let convertStars: Int64? } @@ -398,6 +406,7 @@ private extension ProfileGiftsContext.State.StarGift { self.messageId = nil } self.nameHidden = (flags & (1 << 0)) != 0 + self.savedToProfile = (flags & (1 << 5)) == 0 self.convertStars = convertStars } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index d599e4f5a9..576330bd64 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -69,7 +69,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } - private var animationDisposable: Disposable? + private var fetchDisposable: Disposable? private var setupTimestamp: Double? required public init() { @@ -144,7 +144,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } deinit { - self.animationDisposable?.dispose() + self.fetchDisposable?.dispose() self.currentProgressDisposable?.dispose() } @@ -318,7 +318,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { case let .starGift(gift, convertStars, giftText, entities, nameHidden, savedToProfile, converted)://(amount, giftId, nameHidden, limitNumber, limitTotal, giftText, _): let _ = nameHidden let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" - title = nameHidden ? "Anonymous Gift" : "Gift from \(authorName)" + title = "Gift from \(authorName)" if let giftText, !giftText.isEmpty { text = giftText let _ = entities @@ -429,6 +429,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if let file = animationFile { strongSelf.animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 384, height: 384, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + if strongSelf.fetchDisposable == nil { + strongSelf.fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: item.context.account.postbox, userLocation: .other, fileReference: .message(message: MessageReference(item.message), media: file), resource: file.resource).start() + } } else if animationName.hasPrefix("Gift") { strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 384, height: 384, playbackMode: .still(.end), mode: .direct(cachePathPrefix: nil)) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index f551c43f45..78a2fdb53a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -62,13 +62,32 @@ final class GiftOptionsScreenComponent: Component { } } - public enum StarsFilter: Int { + public enum StarsFilter: Equatable { case all case limited - case stars10 - case stars25 - case stars50 - case stars100 + case stars(Int64) + + init(rawValue: Int64) { + switch rawValue { + case 0: + self = .all + case -1: + self = .limited + default: + self = .stars(rawValue) + } + } + + public var rawValue: Int64 { + switch self { + case .all: + return 0 + case .limited: + return -1 + case let .stars(stars): + return stars + } + } } final class View: UIView, UIScrollViewDelegate { @@ -212,30 +231,16 @@ final class GiftOptionsScreenComponent: Component { } if isVisible { - if self.starsFilter != .all { - switch self.starsFilter { - case .all: - break - case .limited: - if gift.availability == nil { - continue - } - case .stars10: - if gift.price != 10 { - continue - } - case .stars25: - if gift.price != 25 { - continue - } - case .stars50: - if gift.price != 50 { - continue - } - case .stars100: - if gift.price != 100 { - continue - } + switch self.starsFilter { + case .all: + break + case .limited: + if gift.availability == nil { + continue + } + case let .stars(stars): + if gift.price != stars { + continue } } @@ -670,6 +675,30 @@ final class GiftOptionsScreenComponent: Component { contentHeight += starsDescriptionSize.height contentHeight += 16.0 + var tabSelectorItems: [TabSelectorComponent.Item] = [] + tabSelectorItems.append(TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.all.rawValue), + title: "All Gifts" + )) + tabSelectorItems.append(TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.limited.rawValue), + title: "Limited" + )) + + var starsAmountsSet = Set() + if let starGifts = self.state?.starGifts { + for product in starGifts { + starsAmountsSet.insert(product.price) + } + } + let starsAmounts = Array(starsAmountsSet).sorted() + for amount in starsAmounts { + tabSelectorItems.append(TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.stars(amount).rawValue), + title: "⭐️\(amount)" + )) + } + let tabSelectorSize = self.tabSelector.update( transition: transition, component: AnyComponent(TabSelectorComponent( @@ -679,37 +708,13 @@ final class GiftOptionsScreenComponent: Component { selection: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), simple: true ), - items: [ - TabSelectorComponent.Item( - id: AnyHashable(StarsFilter.all.rawValue), - title: "All Gifts" - ), - TabSelectorComponent.Item( - id: AnyHashable(StarsFilter.limited.rawValue), - title: "Limited" - ), - TabSelectorComponent.Item( - id: AnyHashable(StarsFilter.stars10.rawValue), - title: "⭐️10" - ), - TabSelectorComponent.Item( - id: AnyHashable(StarsFilter.stars25.rawValue), - title: "⭐️25" - ), - TabSelectorComponent.Item( - id: AnyHashable(StarsFilter.stars50.rawValue), - title: "⭐️50" - ), - TabSelectorComponent.Item( - id: AnyHashable(StarsFilter.stars100.rawValue), - title: "⭐️100" - ) - ], + items: tabSelectorItems, selectedId: AnyHashable(self.starsFilter.rawValue), setSelectedId: { [weak self] id in - guard let self, let idValue = id.base as? Int, let starsFilter = StarsFilter(rawValue: idValue) else { + guard let self, let idValue = id.base as? Int64 else { return } + let starsFilter = StarsFilter(rawValue: idValue) if self.starsFilter != starsFilter { self.starsFilter = starsFilter self.state?.updated(transition: .easeInOut(duration: 0.25)) diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index 4273baca41..8a37f061b5 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -199,8 +199,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { peers[authorPeerId] = item.accountPeer?._asPeer() let media: [Media] = [ - TelegramMediaAction(action: .starGift(gift: item.gift, convertStars: item.gift.price, text: item.text, entities: [], nameHidden: false, savedToProfile: false, converted: false)) - //TelegramMediaAction(action: .starGift(amount: item.gift.price, giftId: item.gift.id, nameHidden: false, limitNumber: item.gift.availability != nil ? 1 : nil, limitTotal: item.gift.availability?.total, text: item.text, entities: [])) + TelegramMediaAction(action: .starGift(gift: item.gift, convertStars: item.gift.convertStars, text: item.text, entities: [], nameHidden: false, savedToProfile: false, converted: false)) ] let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index a98f6b9ebb..a1b50340e2 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -1258,17 +1258,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen return (starsRevenueStatsContext, state.stats) } } - - let profileGiftsCount: Signal - if let profileGiftsContext { - profileGiftsCount = profileGiftsContext.state - |> map { state in - return state.count ?? 0 - } - } else { - profileGiftsCount = .single(0) - } - + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: isMyProfile, chatLocationContextHolder: chatLocationContextHolder), @@ -1285,23 +1275,29 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasBotPreviewItems, peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false), privacySettings, - starsRevenueContextAndState, - profileGiftsCount + starsRevenueContextAndState ) - |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, profileGiftsCount -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState -> PeerInfoScreenData in var availablePanes = availablePanes if isMyProfile { availablePanes?.insert(.stories, at: 0) if let hasStoryArchive, hasStoryArchive { availablePanes?.insert(.storyArchive, at: 1) } + if availablePanes != nil, profileGiftsContext != nil, let cachedData = peerView.cachedData as? CachedUserData { + if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 { + availablePanes?.insert(.gifts, at: hasStoryArchive == true ? 2 : 1) + } + } } else if let hasStories { if hasStories, peerView.peers[peerView.peerId] is TelegramUser, peerView.peerId != context.account.peerId { availablePanes?.insert(.stories, at: 0) } - if profileGiftsCount > 0 { - availablePanes?.insert(.gifts, at: hasStories ? 1 : 0) + if availablePanes != nil, profileGiftsContext != nil, let cachedData = peerView.cachedData as? CachedUserData { + if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 { + availablePanes?.insert(.gifts, at: hasStories ? 1 : 0) + } } if availablePanes != nil, groupsInCommon != nil, let cachedData = peerView.cachedData as? CachedUserData { From 24ca7ba16e5a4b4be19c56967fe99d180d0eb8ea Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 20 Sep 2024 18:33:40 +0400 Subject: [PATCH 08/11] Fix gestures --- submodules/Display/Source/WindowContent.swift | 12 +++++++++++- submodules/Display/Source/WindowPanRecognizer.swift | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/submodules/Display/Source/WindowContent.swift b/submodules/Display/Source/WindowContent.swift index e1bbea3b17..b716d17fb4 100644 --- a/submodules/Display/Source/WindowContent.swift +++ b/submodules/Display/Source/WindowContent.swift @@ -231,6 +231,16 @@ private func layoutMetricsForScreenSize(size: CGSize, orientation: UIInterfaceOr } public final class WindowKeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + if let view = gestureRecognizer.view { + let location = touch.location(in: gestureRecognizer.view) + if location.y > view.bounds.height - 44.0 { + return false + } + } + return true + } + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } @@ -1299,7 +1309,7 @@ public class Window1 { } } - @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { + @objc func panGesture(_ recognizer: WindowPanRecognizer) { switch recognizer.state { case .began: self.panGestureBegan(location: recognizer.location(in: recognizer.view)) diff --git a/submodules/Display/Source/WindowPanRecognizer.swift b/submodules/Display/Source/WindowPanRecognizer.swift index 53ed394912..7ef93a5ca6 100644 --- a/submodules/Display/Source/WindowPanRecognizer.swift +++ b/submodules/Display/Source/WindowPanRecognizer.swift @@ -7,6 +7,7 @@ public final class WindowPanRecognizer: UIGestureRecognizer { public var ended: ((CGPoint, CGPoint?) -> Void)? private var previousPoints: [(CGPoint, Double)] = [] + private var previousVelocity: CGFloat = 0.0 override public func reset() { super.reset() @@ -45,6 +46,11 @@ public final class WindowPanRecognizer: UIGestureRecognizer { } } + func velocity(in view: UIView?) -> CGPoint { + let point = CGPoint(x: 0.0, y: self.previousVelocity) + return self.view?.convert(point, to: view) ?? .zero + } + override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) @@ -68,9 +74,12 @@ public final class WindowPanRecognizer: UIGestureRecognizer { override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) + self.state = .ended + if let touch = touches.first { let location = touch.location(in: self.view) self.addPoint(location) + self.previousVelocity = self.estimateVerticalVelocity() self.ended?(location, CGPoint(x: 0.0, y: self.estimateVerticalVelocity())) } } @@ -78,6 +87,8 @@ public final class WindowPanRecognizer: UIGestureRecognizer { override public func touchesCancelled(_ touches: Set, with event: UIEvent) { super.touchesCancelled(touches, with: event) + self.state = .cancelled + if let touch = touches.first { self.ended?(touch.location(in: self.view), nil) } From 39b27018bd6acd9ad324b9370c5deb784198dcca Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 20 Sep 2024 18:34:06 +0400 Subject: [PATCH 09/11] Codes and ads improvements --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 2 ++ submodules/ChatListUI/Sources/Node/ChatListItem.swift | 6 +++--- .../Sources/DeviceLocationManager.swift | 2 +- .../Sources/ChatMessageAttachedContentNode.swift | 8 +++++--- submodules/TelegramUI/Sources/ChatController.swift | 4 ++-- .../TelegramUI/Sources/ChatHistoryEntriesForView.swift | 2 ++ .../Sources/MediaAutoDownloadSettings.swift | 5 ++++- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 2eb8b2e591..27c0caee78 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12939,3 +12939,5 @@ Sorry for the inconvenience."; "Notification.StarsGiveaway.Subtitle" = "You won a prize in a giveaway organized by **%1$@**.\n\nYour prize is **%2$@**."; "Notification.StarsGiveaway.Subtitle.Stars_1" = "%@ Star"; "Notification.StarsGiveaway.Subtitle.Stars_any" = "%@ Stars"; + +"VerificationCodes.DescriptionText" = "This chat is used to receive verification codes from third-party services."; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index b86e567d5c..8628c5f602 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -576,7 +576,7 @@ private enum RevealOptionKey: Int32 { } private func canArchivePeer(id: EnginePeer.Id, accountPeerId: EnginePeer.Id) -> Bool { - if id.namespace == Namespaces.Peer.CloudUser && id.id._internalGetInt64Value() == 777000 { + if id.isTelegramNotifications { return false } if id == accountPeerId { @@ -913,7 +913,7 @@ private final class ChatListMediaPreviewNode: ASDisplayNode { } } -private let loginCodeRegex = try? NSRegularExpression(pattern: "[\\d\\-]{5,7}", options: []) +private let loginCodeRegex = try? NSRegularExpression(pattern: "\\b\\d{5,8}\\b", options: []) public class ChatListItemNode: ItemListRevealOptionsItemNode { final class TopicItemNode: ASDisplayNode { @@ -2371,7 +2371,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } } - if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 { + if message.id.peerId.isTelegramNotifications || message.id.peerId.isVerificationCodes { if let cached = currentCustomTextEntities, cached.matches(text: message.text) { customTextEntities = cached } else if let matches = loginCodeRegex?.matches(in: message.text, options: [], range: NSMakeRange(0, (message.text as NSString).length)) { diff --git a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift index 7f6756ad14..a3fcbc3fad 100644 --- a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift +++ b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift @@ -54,7 +54,7 @@ public final class DeviceLocationManager: NSObject { self.manager.delegate = self self.manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters -// self.manager.distanceFilter = 5.0 + self.manager.distanceFilter = kCLDistanceFilterNone self.manager.activityType = .other self.manager.pausesLocationUpdatesAutomatically = false self.manager.headingFilter = 2.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 6ef71e157f..bcac257337 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -221,6 +221,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } } + let isAd = message.adAttribute != nil + var isReplyThread = false if case .replyThread = chatLocation { isReplyThread = true @@ -352,7 +354,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { contentFileValue = file } - if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) { + if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file, isAd: isAd) { contentMediaAutomaticDownload = .full } else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) { contentMediaAutomaticDownload = .prefetch @@ -404,7 +406,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } else { let contentMode: InteractiveMediaNodeContentMode = contentMediaAspectFilled ? .aspectFill : .aspectFit - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: contentMediaValue) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: contentMediaValue, isAd: isAd) let (_, initialImageWidth, refineLayout) = makeContentMedia( context, @@ -435,7 +437,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { let contentFileContinueLayout: ChatMessageInteractiveFileNode.ContinueLayout? if let contentFileValue { - let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: contentFileValue) + let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: contentFileValue, isAd: isAd) let (_, refineLayout) = makeContentFile(ChatMessageInteractiveFileNode.Arguments( context: context, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index df29584775..0619a8a7b5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -7784,10 +7784,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } }) - } else if peerId.namespace == Namespaces.Peer.CloudUser && peerId.id._internalGetInt64Value() == 777000 { + } else if peerId.isTelegramNotifications { self.screenCaptureManager = ScreenCaptureDetectionManager(check: { [weak self] in if let strongSelf = self, strongSelf.traceVisibility() { - let loginCodeRegex = try? NSRegularExpression(pattern: "[\\d\\-]{5,7}", options: []) + let loginCodeRegex = try? NSRegularExpression(pattern: "\\b\\d{5,7}\\b", options: []) var loginCodesToInvalidate: [String] = [] strongSelf.chatDisplayNode.historyNode.forEachVisibleMessageItemNode({ itemNode in if let text = itemNode.item?.message.text, let matches = loginCodeRegex?.matches(in: text, options: [], range: NSMakeRange(0, (text as NSString).length)), let match = matches.first { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 0c8f47b758..7b756fe096 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -446,6 +446,8 @@ func chatHistoryEntriesForView( } if case let .peer(peerId) = location, peerId.isReplies { entries.insert(.ChatInfoEntry("", presentationData.strings.RepliesChat_DescriptionText, nil, nil, presentationData), at: 0) + } else if case let .peer(peerId) = location, peerId.isVerificationCodes { + entries.insert(.ChatInfoEntry("", presentationData.strings.VerificationCodes_DescriptionText, nil, nil, presentationData), at: 0) } else if let cachedPeerData = cachedPeerData as? CachedUserData, let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty { entries.insert(.ChatInfoEntry(presentationData.strings.Bot_DescriptionTitle, botInfo.description, botInfo.photo, botInfo.video, presentationData), at: 0) } else { diff --git a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift index 5e2beb637c..fc8fceb504 100644 --- a/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/MediaAutoDownloadSettings.swift @@ -564,7 +564,10 @@ public func isAutodownloadEnabledForAnyPeerType(category: MediaAutoDownloadCateg return category.contacts || category.otherPrivate || category.groups || category.channels } -public func shouldDownloadMediaAutomatically(settings: MediaAutoDownloadSettings, peerType: MediaAutoDownloadPeerType, networkType: MediaAutoDownloadNetworkType, authorPeerId: PeerId? = nil, contactsPeerIds: Set = Set(), media: Media?, isStory: Bool = false) -> Bool { +public func shouldDownloadMediaAutomatically(settings: MediaAutoDownloadSettings, peerType: MediaAutoDownloadPeerType, networkType: MediaAutoDownloadNetworkType, authorPeerId: PeerId? = nil, contactsPeerIds: Set = Set(), media: Media?, isStory: Bool = false, isAd: Bool = false) -> Bool { + if isAd { + return true + } if (networkType == .cellular && !settings.cellular.enabled) || (networkType == .wifi && !settings.wifi.enabled) { return false } From 04d7a791c987440286305257999610a5806a5d40 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 21 Sep 2024 02:23:42 +0400 Subject: [PATCH 10/11] Codes improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 4 ++-- .../Sources/ChatListSearchContainerNode.swift | 2 +- .../Sources/ChatListSearchListPaneNode.swift | 2 +- .../Sources/Node/ChatListItem.swift | 22 ++++++++++++------- .../Sources/ChatMessageDateHeader.swift | 6 ++++- .../ChatMessageTextBubbleContentNode.swift | 4 ++-- .../Sources/PeerInfoScreen.swift | 4 ++-- .../Chat/ChatControllerLoadDisplayNode.swift | 4 ++-- .../TelegramUI/Sources/ChatController.swift | 7 +++++- .../Sources/ChatControllerNode.swift | 2 +- 10 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 27c0caee78..cdb5e338f9 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -5822,8 +5822,6 @@ Sorry for the inconvenience."; "Conversation.EditingPhotoPanelTitle" = "Edit Photo"; -"Conversation.TextCopied" = "Text copied to clipboard"; - "Media.LimitedAccessTitle" = "Limited Access to Media"; "Media.LimitedAccessText" = "You've given Telegram access only to select number of photos."; "Media.LimitedAccessManage" = "Manage"; @@ -12941,3 +12939,5 @@ Sorry for the inconvenience."; "Notification.StarsGiveaway.Subtitle.Stars_any" = "%@ Stars"; "VerificationCodes.DescriptionText" = "This chat is used to receive verification codes from third-party services."; + +"Conversation.CodeCopied" = "Code copied to clipboard"; diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 5538521545..b5168b166d 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -806,7 +806,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo var type: PeerType = .group for message in messages { if let user = message.author?._asPeer() as? TelegramUser { - if user.botInfo != nil { + if user.botInfo != nil && !user.id.isVerificationCodes { type = .bot } else { type = .user diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 55526da10d..3620d86c21 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -764,7 +764,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } var status: ContactsPeerItemStatus = .none - if case let .user(user) = primaryPeer, let _ = user.botInfo { + if case let .user(user) = primaryPeer, let _ = user.botInfo, !primaryPeer.id.isVerificationCodes { if let subscriberCount = user.subscriberCount { status = .custom(string: presentationData.strings.Conversation_StatusBotSubscribers(subscriberCount), multiline: false, isActive: false, icon: nil) } else { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 8628c5f602..852175365b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2270,14 +2270,20 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if let messagePeer = itemPeer.chatMainPeer { peerText = messagePeer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } - } else if let message = messages.last, let author = message.author?._asPeer(), let peer = itemPeer.chatMainPeer, !isUser { - if case let .channel(peer) = peer, case .broadcast = peer.info { - } else if !displayAsMessage { - if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature { - peerText = authorSignature - } else { - peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - authorIsCurrentChat = author.id == peer.id + } else if let message = messages.last, let author = message.author?._asPeer(), let peer = itemPeer.chatMainPeer { + if peer.id.isVerificationCodes { + if let message = messages.last, let forwardInfo = message.forwardInfo, let author = forwardInfo.author { + peerText = EnginePeer(author).compactDisplayTitle + } + } else if !isUser { + if case let .channel(peer) = peer, case .broadcast = peer.info { + } else if !displayAsMessage { + if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature { + peerText = authorSignature + } else { + peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + authorIsCurrentChat = author.id == peer.id + } } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 6df74b6802..d104205259 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -551,7 +551,11 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat } public func setPeer(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, peer: Peer, authorOfMessage: MessageReference?, emptyColor: UIColor) { - self.containerNode.isGestureEnabled = true + if let messageReference = self.messageReference, let id = messageReference.id { + self.containerNode.isGestureEnabled = !id.peerId.isVerificationCodes + } else { + self.containerNode.isGestureEnabled = true + } var overrideImage: AvatarNodeImageOverride? if peer.isDeleted { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index d41a6440f9..2c88943faf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -1376,7 +1376,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } - let enableCopy = !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected() + let enableCopy = (!item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected()) || item.message.id.peerId.isVerificationCodes textSelectionNode.enableCopy = enableCopy var enableQuote = !item.message.text.isEmpty @@ -1390,7 +1390,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if !item.controllerInteraction.canSendMessages() && !enableCopy { enableQuote = false } - if item.message.id.peerId.namespace == Namespaces.Peer.SecretChat { + if item.message.id.peerId.namespace == Namespaces.Peer.SecretChat || item.message.id.peerId.isVerificationCodes { enableQuote = false } if item.message.containsSecretMedia { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 72710d6c80..57410dcdee 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -11663,7 +11663,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var isBot = false for message in messages { if let author = message.author, case let .user(user) = author { - if user.botInfo != nil { + if user.botInfo != nil && !user.id.isVerificationCodes { isBot = true } break @@ -11673,7 +11673,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if isBot { type = .bot } else if let user = peer as? TelegramUser { - if user.botInfo != nil { + if user.botInfo != nil && !user.id.isVerificationCodes { type = .bot } else { type = .user diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 5a303be715..d3bd5649e0 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -4062,7 +4062,7 @@ extension ChatControllerImpl { } var isBot = false for message in messages { - if let author = message.author, case let .user(user) = author, user.botInfo != nil { + if let author = message.author, case let .user(user) = author, user.botInfo != nil && !user.id.isVerificationCodes { isBot = true break } @@ -4071,7 +4071,7 @@ extension ChatControllerImpl { if isBot { type = .bot } else if let user = peer as? TelegramUser { - if user.botInfo != nil { + if user.botInfo != nil && !user.id.isVerificationCodes { type = .bot } else { type = .user diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 0619a8a7b5..ef377d9c6a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3798,8 +3798,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { storeMessageTextInPasteboard(text, entities: nil) + var infoText = presentationData.strings.Conversation_TextCopied + if let peerId = strongSelf.chatLocation.peerId, peerId.isVerificationCodes && text.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil { + infoText = presentationData.strings.Conversation_CodeCopied + } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: infoText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }), in: .current) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 33d49ba476..d461f2a2d1 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1110,7 +1110,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - let isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat + let isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true if self.historyNodeContainer.isSecret != isSecret { self.historyNodeContainer.isSecret = isSecret setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, isSecret) From 2c56786809e04d77d56ad18208abc7478c08b2e7 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 23 Sep 2024 22:44:29 +0400 Subject: [PATCH 11/11] Gifts improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 5 + .../Sources/AccountContext.swift | 1 + .../AccountContext/Sources/Premium.swift | 1 + .../TelegramEngine/Payments/StarGifts.swift | 49 ++ submodules/TelegramUI/BUILD | 1 + .../ChatMessageGiftBubbleContentNode.swift | 169 ++++-- .../Sources/GiftItemComponent.swift | 108 +++- .../Sources/GiftOptionsScreen.swift | 201 +++++- .../Sources/ChatGiftPreviewItem.swift | 15 +- .../Sources/GiftSetupScreen.swift | 100 ++- .../Components/Gifts/GiftViewScreen/BUILD | 1 + .../Sources/GiftViewScreen.swift | 206 ++++--- .../PeerInfoRecommendedChannelsPane.swift | 1 - .../PeerInfoVisualMediaPaneNode/BUILD | 1 + .../Sources/PeerInfoGiftsPaneNode.swift | 195 ++++-- .../Components/Stars/StarsIntroScreen/BUILD | 44 ++ .../Sources/StarsIntroScreen.swift | 573 ++++++++++++++++++ .../Sources/StarsPurchaseScreen.swift | 10 +- .../HiddenIcon.imageset/Contents.json | 12 + .../HiddenIcon.imageset/hidden_30.pdf | Bin 0 -> 1543 bytes .../Premium/StarsPerk/Contents.json | 9 + .../StarsPerk/Gift.imageset/Contents.json | 12 + .../StarsPerk/Gift.imageset/gift_30 (4).pdf | Bin 0 -> 4312 bytes .../StarsPerk/Media.imageset/Contents.json | 12 + .../StarsPerk/Media.imageset/unlock_30.pdf | Bin 0 -> 2124 bytes .../StarsPerk/Miniapp.imageset/Contents.json | 12 + .../StarsPerk/Miniapp.imageset/bot_30.pdf | Bin 0 -> 2702 bytes .../StarsPerk/Reaction.imageset/Contents.json | 12 + .../StarsPerk/Reaction.imageset/cash_30.pdf | 62 ++ .../Sources/SharedAccountContext.swift | 11 +- 30 files changed, 1564 insertions(+), 259 deletions(-) create mode 100644 submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Stars/StarsIntroScreen/Sources/StarsIntroScreen.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Peer Info/HiddenIcon.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Peer Info/HiddenIcon.imageset/hidden_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Gift.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Gift.imageset/gift_30 (4).pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Media.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Media.imageset/unlock_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Miniapp.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Miniapp.imageset/bot_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/cash_30.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index cdb5e338f9..60ec2f47b2 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12941,3 +12941,8 @@ Sorry for the inconvenience."; "VerificationCodes.DescriptionText" = "This chat is used to receive verification codes from third-party services."; "Conversation.CodeCopied" = "Code copied to clipboard"; + +"Stars.Purchase.StarGiftInfo" = "Buy Stars to send **%@** gifts that can be kept on the profile or converted to Stars."; + +"SharedMedia.GiftCount_1" = "%@ gift"; +"SharedMedia.GiftCount_any" = "%@ gifts"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index fef2ed9501..b0d5799d9a 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1017,6 +1017,7 @@ public protocol SharedAccountContext: AnyObject { func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController + func makeStarsIntroScreen(context: AccountContext) -> ViewController func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 7f8d9fdaaa..0edf8c349c 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -130,6 +130,7 @@ public enum StarsPurchasePurpose: Equatable { case subscription(peerId: EnginePeer.Id, requiredStars: Int64, renew: Bool) case gift(peerId: EnginePeer.Id) case unlockMedia(requiredStars: Int64) + case starGift(peerId: EnginePeer.Id, requiredStars: Int64) } public struct PremiumConfiguration { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 2ebfdbddb5..b297f7ba13 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -238,6 +238,7 @@ private final class ProfileGiftsContextImpl { private let peerId: PeerId private let disposable = MetaDisposable() + private let actionDisposable = MetaDisposable() private var gifts: [ProfileGiftsContext.State.StarGift] = [] private var count: Int32? @@ -258,6 +259,7 @@ private final class ProfileGiftsContextImpl { deinit { self.disposable.dispose() + self.actionDisposable.dispose() } func loadMore() { @@ -315,6 +317,27 @@ private final class ProfileGiftsContextImpl { } } + func updateStarGiftAddedToProfile(messageId: EngineMessage.Id, added: Bool) { + self.actionDisposable.set( + _internal_updateStarGiftAddedToProfile(account: self.account, messageId: messageId, added: added).startStrict() + ) + if let index = self.gifts.firstIndex(where: { $0.messageId == messageId }) { + self.gifts[index] = self.gifts[index].withSavedToProfile(added) + } + self.pushState() + } + + func convertStarGift(messageId: EngineMessage.Id) { + self.actionDisposable.set( + _internal_convertStarGift(account: self.account, messageId: messageId).startStrict() + ) + if let count = self.count { + self.count = max(0, count - 1) + } + self.gifts.removeAll(where: { $0.messageId == messageId }) + self.pushState() + } + private func pushState() { self.stateValue.set(.single(ProfileGiftsContext.State(gifts: self.gifts, count: self.count, dataState: self.dataState))) } @@ -332,6 +355,20 @@ public final class ProfileGiftsContext { public let nameHidden: Bool public let savedToProfile: Bool public let convertStars: Int64? + + public func withSavedToProfile(_ savedToProfile: Bool) -> StarGift { + return StarGift( + gift: self.gift, + fromPeer: self.fromPeer, + date: self.date, + text: self.text, + entities: self.entities, + messageId: self.messageId, + nameHidden: self.nameHidden, + savedToProfile: savedToProfile, + convertStars: self.convertStars + ) + } } public enum DataState: Equatable { @@ -373,6 +410,18 @@ public final class ProfileGiftsContext { impl.loadMore() } } + + public func updateStarGiftAddedToProfile(messageId: EngineMessage.Id, added: Bool) { + self.impl.with { impl in + impl.updateStarGiftAddedToProfile(messageId: messageId, added: added) + } + } + + public func convertStarGift(messageId: EngineMessage.Id) { + self.impl.with { impl in + impl.convertStarGift(messageId: messageId) + } + } } private extension ProfileGiftsContext.State.StarGift { diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 328418a38e..f4d9204e2a 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -459,6 +459,7 @@ swift_library( "//submodules/TelegramUI/Components/MinimizedContainer", "//submodules/TelegramUI/Components/SpaceWarpView", "//submodules/TelegramUI/Components/MiniAppListScreen", + "//submodules/TelegramUI/Components/Stars/StarsIntroScreen", "//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 576330bd64..6f86b3e5d8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -31,13 +31,16 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let backgroundMaskNode: ASImageNode private var linkHighlightingNode: LinkHighlightingNode? + private let mediaBackgroundMaskNode: ASImageNode private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? - private let mediaBackgroundNode: NavigationBackgroundNode private let titleNode: TextNode private let subtitleNode: TextNode private let placeholderNode: StickerShimmerEffectNode private let animationNode: AnimatedStickerNode + private let ribbonBackgroundNode: ASImageNode + private let ribbonTextNode: TextNode + private var shimmerEffectNode: ShimmerEffectForegroundNode? private let buttonNode: HighlightTrackingButtonNode private let buttonStarsNode: PremiumStarsNode @@ -79,9 +82,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.backgroundMaskNode = ASImageNode() - self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear) - self.mediaBackgroundNode.clipsToBounds = true - self.mediaBackgroundNode.cornerRadius = 24.0 + self.mediaBackgroundMaskNode = ASImageNode() self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false @@ -107,19 +108,30 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.buttonTitleNode.isUserInteractionEnabled = false self.buttonTitleNode.displaysAsynchronously = false + self.ribbonBackgroundNode = ASImageNode() + self.ribbonBackgroundNode.displaysAsynchronously = false + + self.ribbonTextNode = TextNode() + self.ribbonTextNode.isUserInteractionEnabled = false + self.ribbonTextNode.displaysAsynchronously = false + super.init() self.addSubnode(self.labelNode) - self.addSubnode(self.mediaBackgroundNode) self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) + self.addSubnode(self.subtitleNode) + self.addSubnode(self.placeholderNode) self.addSubnode(self.animationNode) self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.buttonStarsNode) self.addSubnode(self.buttonTitleNode) + self.addSubnode(self.ribbonBackgroundNode) + self.addSubnode(self.ribbonTextNode) + self.buttonNode.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { @@ -226,7 +238,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) - + let makeRibbonTextLayout = TextNode.asyncLayout(self.ribbonTextNode) + let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage return { item, layoutConstants, _, _, _, _ in @@ -247,6 +260,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var title = item.presentationData.strings.Notification_PremiumGift_Title var text = "" var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View + var ribbonTitle = "" var hasServiceMessage = true var textSpacing: CGFloat = 0.0 for media in item.message.media { @@ -315,8 +329,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View hasServiceMessage = false } - case let .starGift(gift, convertStars, giftText, entities, nameHidden, savedToProfile, converted)://(amount, giftId, nameHidden, limitNumber, limitTotal, giftText, _): + case let .starGift(gift, convertStars, giftText, entities, nameHidden, savedToProfile, converted): let _ = nameHidden + //TODO:localize let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" title = "Gift from \(authorName)" if let giftText, !giftText.isEmpty { @@ -344,6 +359,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } animationFile = gift.file + if let availability = gift.availability { + ribbonTitle = "1 of \(availability.total)" + } default: break } @@ -377,6 +395,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) giftSize.height = titleLayout.size.height + textSpacing + subtitleLayout.size.height + 212.0 @@ -424,31 +444,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { return (backgroundSize.width, { boundingWidth in return (backgroundSize, { [weak self] animation, synchronousLoads, _ in if let strongSelf = self { - if strongSelf.item == nil { - strongSelf.animationNode.autoplay = true - - if let file = animationFile { - strongSelf.animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 384, height: 384, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) - if strongSelf.fetchDisposable == nil { - strongSelf.fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: item.context.account.postbox, userLocation: .other, fileReference: .message(message: MessageReference(item.message), media: file), resource: file.resource).start() - } - } else if animationName.hasPrefix("Gift") { - strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 384, height: 384, playbackMode: .still(.end), mode: .direct(cachePathPrefix: nil)) - } - } - strongSelf.item = item - - strongSelf.updateVisibility() + let overlayColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) - strongSelf.labelNode.isHidden = !hasServiceMessage - let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 16.0 : 0.0), size: giftSize) let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0) - strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame - - strongSelf.mediaBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) - strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate) - strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) var iconSize = CGSize(width: 160.0, height: 160.0) var iconOffset: CGFloat = 0.0 @@ -456,13 +455,56 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { iconSize = CGSize(width: 120.0, height: 120.0) iconOffset = 32.0 } - strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize) + let animationFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY - 16.0 + iconOffset), size: iconSize) + strongSelf.animationNode.frame = animationFrame + + if strongSelf.item == nil { + strongSelf.animationNode.started = { [weak self] in + if let strongSelf = self { + let current = CACurrentMediaTime() + if let setupTimestamp = strongSelf.setupTimestamp, current - setupTimestamp > 0.3 { + if !strongSelf.placeholderNode.alpha.isZero { + strongSelf.animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.removePlaceholder(animated: true) + } + } else { + strongSelf.removePlaceholder(animated: false) + } + } + } + + strongSelf.animationNode.autoplay = true + + if let file = animationFile { + strongSelf.animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 384, height: 384, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + if strongSelf.fetchDisposable == nil { + strongSelf.fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: item.context.account.postbox, userLocation: .other, fileReference: .message(message: MessageReference(item.message), media: file), resource: file.resource).start() + } + + if let immediateThumbnailData = file.immediateThumbnailData { + let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper) + strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: overlayColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency) + } + } else if animationName.hasPrefix("Gift") { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 384, height: 384, playbackMode: .still(.end), mode: .direct(cachePathPrefix: nil)) + } + } + strongSelf.item = item + + strongSelf.updateVisibility() + + strongSelf.labelNode.isHidden = !hasServiceMessage + + strongSelf.buttonNode.backgroundColor = overlayColor + strongSelf.animationNode.updateLayout(size: iconSize) + strongSelf.placeholderNode.frame = animationFrame let _ = labelApply() let _ = titleApply() let _ = subtitleApply() let _ = buttonTitleApply() + let _ = ribbonTextApply() let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: 2.0), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame @@ -479,23 +521,58 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: subtitleFrame.maxY + 10.0), size: buttonSize) strongSelf.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize) - - if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { - if strongSelf.mediaBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { - strongSelf.mediaBackgroundNode.isHidden = true - backgroundContent.clipsToBounds = true - backgroundContent.allowsGroupOpacity = true - backgroundContent.cornerRadius = 24.0 - - strongSelf.mediaBackgroundContent = backgroundContent - strongSelf.insertSubnode(backgroundContent, at: 0) + + if ribbonTextLayout.size.width > 0.0 { + if strongSelf.ribbonBackgroundNode.image == nil { + let ribbonImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/GiftRibbon"), color: overlayColor) + strongSelf.ribbonBackgroundNode.image = ribbonImage } + if let ribbonImage = strongSelf.ribbonBackgroundNode.image { + let ribbonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.maxX - ribbonImage.size.width + 2.0, y: mediaBackgroundFrame.minY - 2.0), size: ribbonImage.size) + strongSelf.ribbonBackgroundNode.frame = ribbonFrame + + strongSelf.ribbonTextNode.transform = CATransform3DMakeRotation(.pi / 4.0, 0.0, 0.0, 1.0) + strongSelf.ribbonTextNode.bounds = CGRect(origin: .zero, size: ribbonTextLayout.size) + strongSelf.ribbonTextNode.position = ribbonFrame.center.offsetBy(dx: 7.0, dy: -6.0) + } + } + + if strongSelf.mediaBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + backgroundContent.cornerRadius = 24.0 - strongSelf.mediaBackgroundContent?.frame = mediaBackgroundFrame - } else { - strongSelf.mediaBackgroundNode.isHidden = false - strongSelf.mediaBackgroundContent?.removeFromSupernode() - strongSelf.mediaBackgroundContent = nil + strongSelf.mediaBackgroundContent = backgroundContent + strongSelf.insertSubnode(backgroundContent, at: 0) + } + + if let backgroundContent = strongSelf.mediaBackgroundContent { + if ribbonTextLayout.size.width > 0.0 { + let backgroundMaskFrame = mediaBackgroundFrame.insetBy(dx: -2.0, dy: -2.0) + backgroundContent.frame = backgroundMaskFrame + backgroundContent.cornerRadius = 0.0 + + if strongSelf.mediaBackgroundMaskNode.image?.size != mediaBackgroundFrame.size { + strongSelf.mediaBackgroundMaskNode.image = generateImage(backgroundMaskFrame.size, contextGenerator: { size, context in + let bounds = CGRect(origin: .zero, size: size) + context.clear(bounds) + + context.setFillColor(UIColor.black.cgColor) + context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: 2.0, dy: 2.0), cornerRadius: 24.0).cgPath) + context.fillPath() + + if let ribbonImage = UIImage(bundleImageName: "Chat/Message/GiftRibbon"), let cgImage = ribbonImage.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(x: bounds.width - ribbonImage.size.width, y: bounds.height - ribbonImage.size.height), size: ribbonImage.size), byTiling: false) + } + }) + } + backgroundContent.view.mask = strongSelf.mediaBackgroundMaskNode.view + strongSelf.mediaBackgroundMaskNode.frame = CGRect(origin: .zero, size: backgroundMaskFrame.size) + } else { + backgroundContent.frame = mediaBackgroundFrame + backgroundContent.clipsToBounds = true + backgroundContent.cornerRadius = 24.0 + backgroundContent.view.mask = nil + } } let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) @@ -645,7 +722,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { return ChatMessageBubbleContentTapAction(content: .ignore) } else if let backgroundNode = self.backgroundNode, backgroundNode.frame.contains(point) { return ChatMessageBubbleContentTapAction(content: .openMessage) - } else if self.mediaBackgroundNode.frame.contains(point) { + } else if self.mediaBackgroundContent?.frame.contains(point) == true { return ChatMessageBubbleContentTapAction(content: .openMessage) } else { return ChatMessageBubbleContentTapAction(content: .none) diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 340460ce72..2f1b3612be 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -20,35 +20,62 @@ public final class GiftItemComponent: Component { } public struct Ribbon: Equatable { + public enum Color { + case red + case blue + + var colors: [UIColor] { + switch self { + case .red: + return [ + UIColor(rgb: 0xed1c26), + UIColor(rgb: 0xff5c55) + + ] + case .blue: + return [ + UIColor(rgb: 0x34a4fc), + UIColor(rgb: 0x6fd3ff) + ] + } + } + } public let text: String - public let color: UIColor + public let color: Color - public init(text: String, color: UIColor) { + public init(text: String, color: Color) { self.text = text self.color = color } } + public enum Peer: Equatable { + case peer(EnginePeer) + case anonymous + } + let context: AccountContext let theme: PresentationTheme - let peer: EnginePeer? - let subject: Subject + let peer: GiftItemComponent.Peer? + let subject: GiftItemComponent.Subject let title: String? let subtitle: String? let price: String let ribbon: Ribbon? let isLoading: Bool + let isHidden: Bool public init( context: AccountContext, theme: PresentationTheme, - peer: EnginePeer?, - subject: Subject, + peer: GiftItemComponent.Peer?, + subject: GiftItemComponent.Subject, title: String? = nil, subtitle: String? = nil, price: String, ribbon: Ribbon? = nil, - isLoading: Bool = false + isLoading: Bool = false, + isHidden: Bool = false ) { self.context = context self.theme = theme @@ -59,6 +86,7 @@ public final class GiftItemComponent: Component { self.price = price self.ribbon = ribbon self.isLoading = isLoading + self.isHidden = isHidden } public static func ==(lhs: GiftItemComponent, rhs: GiftItemComponent) -> Bool { @@ -89,6 +117,9 @@ public final class GiftItemComponent: Component { if lhs.isLoading != rhs.isLoading { return false } + if lhs.isHidden != rhs.isHidden { + return false + } return true } @@ -108,6 +139,9 @@ public final class GiftItemComponent: Component { private var animationLayer: InlineStickerItemLayer? + private var hiddenIconBackground: UIVisualEffectView? + private var hiddenIcon: UIImageView? + override init(frame: CGRect) { super.init(frame: frame) @@ -125,6 +159,8 @@ public final class GiftItemComponent: Component { } func update(component: GiftItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let isFirstTime = self.component == nil + self.component = component self.componentState = state @@ -201,8 +237,9 @@ public final class GiftItemComponent: Component { self.layer.addSublayer(animationLayer) } + let animationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: animationOffset), size: iconSize) if let animationLayer = self.animationLayer { - transition.setFrame(layer: animationLayer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: animationOffset), size: iconSize)) + transition.setFrame(layer: animationLayer, frame: animationFrame) } if let title = component.title { @@ -287,7 +324,7 @@ public final class GiftItemComponent: Component { ribbonTextView.bounds = CGRect(origin: .zero, size: ribbonTextSize) if self.ribbon.image == nil { - self.ribbon.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: [ribbon.color.withMultipliedBrightnessBy(1.1), ribbon.color.withMultipliedBrightnessBy(0.9)], direction: .diagonal) + self.ribbon.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: ribbon.color.colors, direction: .diagonal) } if let ribbonImage = self.ribbon.image { self.ribbon.frame = CGRect(origin: CGPoint(x: size.width - ribbonImage.size.width + 2.0, y: -2.0), size: ribbonImage.size) @@ -312,13 +349,64 @@ public final class GiftItemComponent: Component { self.avatarNode = avatarNode } - avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, displayDimensions: CGSize(width: 20.0, height: 20.0)) + switch peer { + case let .peer(peer): + avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, displayDimensions: CGSize(width: 20.0, height: 20.0)) + case .anonymous: + avatarNode.setPeer(context: component.context, theme: component.theme, peer: nil, overrideImage: .anonymousSavedMessagesIcon(isColored: true)) + } + avatarNode.frame = CGRect(origin: CGPoint(x: 2.0, y: 2.0), size: CGSize(width: 20.0, height: 20.0)) } self.backgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size)) + if component.isHidden { + let hiddenIconBackground: UIVisualEffectView + let hiddenIcon: UIImageView + if let currentBackground = self.hiddenIconBackground, let currentIcon = self.hiddenIcon { + hiddenIconBackground = currentBackground + hiddenIcon = currentIcon + } else { + let blurEffect: UIBlurEffect + if #available(iOS 13.0, *) { + blurEffect = UIBlurEffect(style: .systemThinMaterialDark) + } else { + blurEffect = UIBlurEffect(style: .dark) + } + hiddenIconBackground = UIVisualEffectView(effect: blurEffect) + hiddenIconBackground.clipsToBounds = true + hiddenIconBackground.layer.cornerRadius = 15.0 + self.hiddenIconBackground = hiddenIconBackground + + hiddenIcon = UIImageView(image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/HiddenIcon"), color: .white)) + self.hiddenIcon = hiddenIcon + + self.addSubview(hiddenIconBackground) + hiddenIconBackground.contentView.addSubview(hiddenIcon) + + if !isFirstTime { + hiddenIconBackground.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2) + hiddenIconBackground.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + + let iconSize = CGSize(width: 30.0, height: 30.0) + hiddenIconBackground.frame = iconSize.centered(around: animationFrame.center) + hiddenIcon.frame = CGRect(origin: .zero, size: iconSize) + } else { + if let hiddenIconBackground = self.hiddenIconBackground { + self.hiddenIconBackground = nil + self.hiddenIcon = nil + + hiddenIconBackground.layer.animateAlpha(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { _ in + hiddenIconBackground.removeFromSuperview() + }) + hiddenIconBackground.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + } + } + return size } } diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 78a2fdb53a..eb54cd350d 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -30,15 +30,18 @@ final class GiftOptionsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let starsContext: StarsContext let peerId: EnginePeer.Id let premiumOptions: [CachedPremiumGiftOption] init( context: AccountContext, + starsContext: StarsContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption] ) { self.context = context + self.starsContext = starsContext self.peerId = peerId self.premiumOptions = premiumOptions } @@ -100,10 +103,15 @@ final class GiftOptionsScreenComponent: Component { private let header = ComponentView() + private let balanceTitle = ComponentView() + private let balanceValue = ComponentView() + private let balanceIcon = ComponentView() + private let premiumTitle = ComponentView() private let premiumDescription = ComponentView() private var premiumItems: [AnyHashable: ComponentView] = [:] - private var selectedPremiumGift: String? + private var inProgressPremiumGift: String? + private let purchaseDisposable = MetaDisposable() private let starsTitle = ComponentView() private let starsDescription = ComponentView() @@ -113,6 +121,9 @@ final class GiftOptionsScreenComponent: Component { private var isUpdating: Bool = false + private var starsStateDisposable: Disposable? + private var starsState: StarsContext.State? + private var component: GiftOptionsScreenComponent? private(set) weak var state: State? private var environment: EnvironmentType? @@ -147,6 +158,8 @@ final class GiftOptionsScreenComponent: Component { } deinit { + self.starsStateDisposable?.dispose() + self.purchaseDisposable.dispose() } func scrollToTop() { @@ -205,7 +218,6 @@ final class GiftOptionsScreenComponent: Component { transition.setScale(view: premiumTitleView, scale: premiumTitleScale) } - if let headerView = self.header.view { transition.setPosition(view: headerView, position: CGPoint(x: availableWidth / 2.0, y: topInset + headerView.bounds.height / 2.0 - 30.0 - premiumTitleOffset * premiumTitleScale)) transition.setScale(view: headerView, scale: premiumTitleScale) @@ -273,7 +285,7 @@ final class GiftOptionsScreenComponent: Component { ribbon: gift.availability != nil ? GiftItemComponent.Ribbon( text: "Limited", - color: UIColor(rgb: 0x58c1fe) + color: .blue ) : nil ) @@ -330,6 +342,88 @@ final class GiftOptionsScreenComponent: Component { } } + private func buyPremium(_ product: PremiumGiftProduct) { + guard let component = self.component, let inAppPurchaseManager = self.component?.context.inAppPurchaseManager, self.inProgressPremiumGift == nil else { + return + } + + self.inProgressPremiumGift = product.id + self.state?.updated() + + let (currency, amount) = product.storeProduct.priceCurrencyAndAmount + + addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_accept") + + let purpose: AppStoreTransactionPurpose = .giftCode(peerIds: [component.peerId], boostPeer: nil, currency: currency, amount: amount) + let quantity: Int32 = 1 + + let _ = (component.context.engine.payments.canPurchasePremium(purpose: purpose) + |> deliverOnMainQueue).start(next: { [weak self] available in + if let strongSelf = self { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + if available { + strongSelf.purchaseDisposable.set((inAppPurchaseManager.buyProduct(product.storeProduct, quantity: quantity, purpose: purpose) + |> deliverOnMainQueue).start(next: { [weak self] status in + guard let self, case .purchased = status, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { + return + } + + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftOptionsScreen) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } + } + if !foundController { + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) + } + navigationController.setViewControllers(controllers, animated: true) + }, error: { [weak self] error in + guard let self, let controller = self.environment?.controller() else { + return + } + self.inProgressPremiumGift = nil + self.state?.updated(transition: .immediate) + + var errorText: String? + switch error { + case .generic: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .network: + errorText = presentationData.strings.Premium_Purchase_ErrorNetwork + case .notAllowed: + errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed + case .cantMakePayments: + errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments + case .assignFailed: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .tryLater: + errorText = presentationData.strings.Premium_Purchase_ErrorUnknown + case .cancelled: + break + } + + if let errorText { + addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_fail") + + let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + controller.present(alertController, in: .window(.root)) + } + })) + } else { + self?.inProgressPremiumGift = nil + self?.state?.updated(transition: .immediate) + } + } + }) + } + func update(component: GiftOptionsScreenComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -340,13 +434,21 @@ final class GiftOptionsScreenComponent: Component { let controller = environment.controller let themeUpdated = self.environment?.theme !== environment.theme self.environment = environment + self.state = state if self.component == nil { - + self.starsStateDisposable = (component.starsContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + self.starsState = state + if !self.isUpdating { + self.state?.updated() + } + }) } - self.component = component - self.state = state if themeUpdated { self.backgroundColor = environment.theme.list.blocksBackgroundColor @@ -451,6 +553,55 @@ final class GiftOptionsScreenComponent: Component { transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) } + let balanceTitleSize = self.balanceTitle.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Stars_Purchase_Balance, + font: Font.regular(14.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 1 + )), + environment: {}, + containerSize: availableSize + ) + let balanceValueSize = self.balanceValue.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: presentationStringsFormattedNumber(Int32(self.starsState?.balance ?? 0), environment.dateTimeFormat.groupingSeparator), + font: Font.semibold(14.0), + textColor: environment.theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 1 + )), + environment: {}, + containerSize: availableSize + ) + let balanceIconSize = self.balanceIcon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil)), + environment: {}, + containerSize: availableSize + ) + + if let balanceTitleView = self.balanceTitle.view, let balanceValueView = self.balanceValue.view, let balanceIconView = self.balanceIcon.view { + if balanceTitleView.superview == nil { + self.addSubview(balanceTitleView) + self.addSubview(balanceValueView) + self.addSubview(balanceIconView) + } + let navigationHeight = environment.navigationHeight - environment.statusBarHeight + let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitleSize.height - balanceValueSize.height) / 2.0 + balanceTitleView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0) + balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize) + balanceValueView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0) + balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize) + balanceIconView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel) + balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize) + } + let premiumTitleSize = self.premiumTitle.update( transition: transition, component: AnyComponent(MultilineTextComponent( @@ -494,8 +645,13 @@ final class GiftOptionsScreenComponent: Component { return nil } }, - tapAction: { _, _ in - + tapAction: { [weak self] _, _ in + guard let self, let component = self.component, let environment = self.environment else { + return + } + let introController = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .settings, forceDark: false, dismissed: nil) + introController.navigationPresentation = .modal + environment.controller()?.push(introController) } )), environment: {}, @@ -561,21 +717,15 @@ final class GiftOptionsScreenComponent: Component { ribbon: product.discount.flatMap { GiftItemComponent.Ribbon( text: "-\($0)%", - color: UIColor(rgb: 0xfa4846) + color: .red ) }, - isLoading: self.selectedPremiumGift == product.id + isLoading: self.inProgressPremiumGift == product.id ) ), effectAlignment: .center, action: { [weak self] in - self?.selectedPremiumGift = product.id - self?.state?.updated() - - Queue.mainQueue().after(4.0, { - self?.selectedPremiumGift = nil - self?.state?.updated() - }) + self?.buyPremium(product) }, animateAlpha: false ) @@ -658,8 +808,13 @@ final class GiftOptionsScreenComponent: Component { return nil } }, - tapAction: { _, _ in - + tapAction: { [weak self] _, _ in + guard let self, let component = self.component, let environment = self.environment else { + return + } + let introController = component.context.sharedContext.makeStarsIntroScreen(context: component.context) + introController.navigationPresentation = .modal + environment.controller()?.push(introController) } )), environment: {}, @@ -859,11 +1014,17 @@ final class GiftOptionsScreenComponent: Component { public final class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScreenProtocol { private let context: AccountContext - public init(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption]) { + public init( + context: AccountContext, + starsContext: StarsContext, + peerId: EnginePeer.Id, + premiumOptions: [CachedPremiumGiftOption] + ) { self.context = context super.init(context: context, component: GiftOptionsScreenComponent( context: context, + starsContext: starsContext, peerId: peerId, premiumOptions: premiumOptions ), navigationBarAppearance: .none, theme: .default, updatedPresentationData: nil) diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index 8a37f061b5..35f48321e7 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -148,6 +148,8 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { private let disposable = MetaDisposable() + private var initialBubbleHeight: CGFloat? + init() { self.topStripeNode = ASDisplayNode() self.topStripeNode.isLayerBacked = true @@ -235,14 +237,13 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { }) itemNode!.isUserInteractionEnabled = false messageNodes.append(itemNode!) + + self.initialBubbleHeight = itemNode?.frame.height } nodes = messageNodes } var contentSize = CGSize(width: params.width, height: 4.0 + 4.0) -// for node in nodes { -// contentSize.height += node.frame.size.height -// } contentSize.height = 346.0 insets = itemListNeighborsGroupedInsets(neighbors, params) if params.width <= 320.0 { @@ -269,7 +270,13 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { if node.supernode == nil { strongSelf.containerNode.addSubnode(node) } - node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: floor((contentSize.height - node.frame.size.height) / 2.0)), size: node.frame.size), within: layoutSize) + let bubbleHeight: CGFloat + if let initialBubbleHeight = strongSelf.initialBubbleHeight { + bubbleHeight = max(node.frame.height, initialBubbleHeight) + } else { + bubbleHeight = node.frame.height + } + node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: floor((contentSize.height - bubbleHeight) / 2.0)), size: node.frame.size), within: layoutSize) //topOffset += node.frame.size.height } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index f26cb3deb2..805b8aaf6c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -90,6 +90,14 @@ final class GiftSetupScreenComponent: Component { private var starImage: (UIImage, PresentationTheme)? + private var optionsDisposable: Disposable? + private(set) var options: [StarsTopUpOption] = [] { + didSet { + self.optionsPromise.set(self.options) + } + } + private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil) + override init(frame: CGRect) { self.scrollView = ScrollView() self.scrollView.showsVerticalScrollIndicator = true @@ -159,45 +167,77 @@ final class GiftSetupScreenComponent: Component { } func proceed() { - guard let component = self.component else { + guard let component = self.component, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else { return } - let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, peerId: component.peerId, giftId: component.gift.id, text: self.textInputState.text.string, entities: []) - let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - let _ = (inputData - |> deliverOnMainQueue).startStandalone(next: { [weak self] inputData in - guard let inputData else { + let proceed = { [weak self] in + guard let self else { return } - let _ = (component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source) - |> deliverOnMainQueue).start(next: { [weak self] result in - guard let self, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { + let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, peerId: component.peerId, giftId: component.gift.id, text: self.textInputState.text.string, entities: []) + let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + + let _ = (inputData + |> deliverOnMainQueue).startStandalone(next: { [weak self] inputData in + guard let inputData else { return } - - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { - chatController.hintPlayNextOutgoingGift() - foundController = true - break + let _ = (component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { + return } - } - if !foundController { - let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) - } - navigationController.setViewControllers(controllers, animated: true) + + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } + } + if !foundController { + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) + } + navigationController.setViewControllers(controllers, animated: true) + }) }) - }) + } + + if starsState.balance < component.gift.price { + let _ = (self.optionsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] options in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + let purchaseController = component.context.sharedContext.makeStarsPurchaseScreen( + context: component.context, + starsContext: starsContext, + options: options ?? [], + purpose: .starGift(peerId: component.peerId, requiredStars: component.gift.price), + completion: { [weak starsContext] stars in + starsContext?.add(balance: stars) + Queue.mainQueue().after(0.1) { + proceed() + } + } + ) + controller.push(purchaseController) + }) + } else { + proceed() + } } func update(component: GiftSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index 342a21824d..6bb84c21b4 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -25,6 +25,7 @@ swift_library( "//submodules/Components/ViewControllerComponent", "//submodules/Components/BundleIconComponent", "//submodules/Components/MultilineTextComponent", + "//submodules/Components/MultilineTextWithEntitiesComponent", "//submodules/Components/BalancedTextComponent", "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 28b3467edd..b569c9bb4e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -12,6 +12,7 @@ import ComponentFlow import ViewControllerComponent import SheetComponent import MultilineTextComponent +import MultilineTextWithEntitiesComponent import BundleIconComponent import SolidRoundedButtonComponent import Markdown @@ -32,6 +33,7 @@ private final class GiftViewSheetContent: CombinedComponent { let openPeer: (EnginePeer) -> Void let updateSavedToProfile: (Bool) -> Void let convertToStars: () -> Void + let openStarsIntro: () -> Void init( context: AccountContext, @@ -39,7 +41,8 @@ private final class GiftViewSheetContent: CombinedComponent { cancel: @escaping (Bool) -> Void, openPeer: @escaping (EnginePeer) -> Void, updateSavedToProfile: @escaping (Bool) -> Void, - convertToStars: @escaping () -> Void + convertToStars: @escaping () -> Void, + openStarsIntro: @escaping () -> Void ) { self.context = context self.subject = subject @@ -47,6 +50,7 @@ private final class GiftViewSheetContent: CombinedComponent { self.openPeer = openPeer self.updateSavedToProfile = updateSavedToProfile self.convertToStars = convertToStars + self.openStarsIntro = openStarsIntro } static func ==(lhs: GiftViewSheetContent, rhs: GiftViewSheetContent) -> Bool { @@ -176,7 +180,7 @@ private final class GiftViewSheetContent: CombinedComponent { limitNumber = arguments.gift.availability?.remains limitTotal = arguments.gift.availability?.total convertStars = arguments.convertStars - incoming = arguments.incoming + incoming = arguments.incoming || arguments.peerId == component.context.account.peerId savedToProfile = arguments.savedToProfile converted = arguments.converted } else { @@ -259,7 +263,13 @@ private final class GiftViewSheetContent: CombinedComponent { ) let tableFont = Font.regular(15.0) + let tableBoldFont = Font.semibold(15.0) + let tableItalicFont = Font.italic(15.0) + let tableBoldItalicFont = Font.semiboldItalic(15.0) + let tableMonospaceFont = Font.monospace(15.0) + let tableTextColor = theme.list.itemPrimaryTextColor + let tableLinkColor = theme.list.itemAccentColor var tableItems: [TableComponent.Item] = [] if let peerId = component.subject.arguments?.peerId, let peer = state.peerMap[peerId] { @@ -291,6 +301,18 @@ private final class GiftViewSheetContent: CombinedComponent { ) ) )) + } else { + tableItems.append(.init( + id: "from", + title: strings.Stars_Transaction_From, + component: AnyComponent( + PeerCellComponent( + context: component.context, + theme: theme, + peer: nil + ) + ) + )) } tableItems.append(.init( @@ -312,11 +334,19 @@ private final class GiftViewSheetContent: CombinedComponent { } if let text { + let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: tableTextColor, linkColor: tableLinkColor, baseFont: tableFont, linkFont: tableFont, boldFont: tableBoldFont, italicFont: tableItalicFont, boldItalicFont: tableBoldItalicFont, fixedFont: tableMonospaceFont, blockQuoteFont: tableFont, message: nil) + tableItems.append(.init( id: "text", title: nil, component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: text, font: tableFont, textColor: tableTextColor))) + MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + placeholderColor: theme.list.mediaPlaceholderColor, + text: .plain(attributedText) + ) ) )) } @@ -331,40 +361,7 @@ private final class GiftViewSheetContent: CombinedComponent { ) let textFont = Font.regular(15.0) -// let boldTextFont = Font.semibold(15.0) -// let textColor = theme.actionSheet.secondaryTextColor let linkColor = theme.actionSheet.controlAccentColor -// let destructiveColor = theme.actionSheet.destructiveActionTextColor -// let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in -// return (TelegramTextAttributes.URL, contents) -// }) -// let additional = additional.update( -// component: BalancedTextComponent( -// text: .markdown(text: additionalText, attributes: markdownAttributes), -// horizontalAlignment: .center, -// maximumNumberOfLines: 0, -// lineSpacing: 0.2, -// highlightColor: linkColor.withAlphaComponent(0.2), -// highlightAction: { attributes in -// if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { -// return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) -// } else { -// return nil -// } -// }, -// tapAction: { attributes, _ in -// if let controller = controller() as? GiftViewScreen, let navigationController = controller.navigationController as? NavigationController { -// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } -// component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Transaction_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) -// component.cancel(true) -// } -// } -// ), -// availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), -// transition: .immediate -// ) - - context.add(title .position(CGPoint(x: context.availableSize.width / 2.0, y: 177.0)) @@ -417,7 +414,7 @@ private final class GiftViewSheetContent: CombinedComponent { } }, tapAction: { _, _ in - + component.openStarsIntro() } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), @@ -467,31 +464,7 @@ private final class GiftViewSheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0)) ) originY += table.size.height + 23.0 - -// context.add(additional -// .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + additional.size.height / 2.0)) -// ) -// originY += additional.size.height + 23.0 - -// if let statusText { -// originY += 7.0 -// let status = status.update( -// component: BalancedTextComponent( -// text: .plain(NSAttributedString(string: statusText, font: textFont, textColor: statusIsDestructive ? destructiveColor : textColor)), -// horizontalAlignment: .center, -// maximumNumberOfLines: 0, -// lineSpacing: 0.1 -// ), -// availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), -// transition: .immediate -// ) -// context.add(status -// .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + status.size.height / 2.0)) -// ) -// originY += status.size.height + (statusIsDestructive ? 23.0 : 13.0) -// } - - + if incoming && !converted { let button = button.update( component: SolidRoundedButtonComponent( @@ -545,6 +518,33 @@ private final class GiftViewSheetContent: CombinedComponent { .position(CGPoint(x: secondaryButtonFrame.midX, y: secondaryButtonFrame.midY)) ) originY += secondaryButton.size.height + } else { + let button = button.update( + component: SolidRoundedButtonComponent( + title: strings.Common_OK, + theme: SolidRoundedButtonComponent.Theme(theme: theme), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: nil, + animationName: nil, + iconPosition: .left, + isLoading: state.inProgress, + action: { + component.cancel(true) + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: button.size) + context.add(button + .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) + ) + originY += button.size.height + originY += 7.0 } context.add(closeButton @@ -566,19 +566,22 @@ private final class GiftViewSheetComponent: CombinedComponent { let openPeer: (EnginePeer) -> Void let updateSavedToProfile: (Bool) -> Void let convertToStars: () -> Void + let openStarsIntro: () -> Void init( context: AccountContext, subject: GiftViewScreen.Subject, openPeer: @escaping (EnginePeer) -> Void, updateSavedToProfile: @escaping (Bool) -> Void, - convertToStars: @escaping () -> Void + convertToStars: @escaping () -> Void, + openStarsIntro: @escaping () -> Void ) { self.context = context self.subject = subject self.openPeer = openPeer self.updateSavedToProfile = updateSavedToProfile self.convertToStars = convertToStars + self.openStarsIntro = openStarsIntro } static func ==(lhs: GiftViewSheetComponent, rhs: GiftViewSheetComponent) -> Bool { @@ -620,7 +623,8 @@ private final class GiftViewSheetComponent: CombinedComponent { }, openPeer: context.component.openPeer, updateSavedToProfile: context.component.updateSavedToProfile, - convertToStars: context.component.convertToStars + convertToStars: context.component.convertToStars, + openStarsIntro: context.component.openStarsIntro )), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, @@ -698,7 +702,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { return (message.id.peerId, message.id, message.flags.contains(.Incoming), gift, convertStars, text, entities, nameHidden, savedToProfile, converted) } case let .profileGift(peerId, gift): - return (peerId, gift.messageId, false, gift.gift, gift.convertStars ?? 0, gift.text, gift.entities, gift.nameHidden, true, false) + return (peerId, gift.messageId, false, gift.gift, gift.convertStars ?? 0, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false) } return nil } @@ -712,13 +716,17 @@ public class GiftViewScreen: ViewControllerComponentContainer { public init( context: AccountContext, subject: GiftViewScreen.Subject, - forceDark: Bool = false + forceDark: Bool = false, + updateSavedToProfile: ((Bool) -> Void)? = nil, + convertToStars: (() -> Void)? = nil ) { self.context = context var openPeerImpl: ((EnginePeer) -> Void)? var updateSavedToProfileImpl: ((Bool) -> Void)? var convertToStarsImpl: (() -> Void)? + var openStarsIntroImpl: (() -> Void)? + super.init( context: context, component: GiftViewSheetComponent( @@ -732,6 +740,9 @@ public class GiftViewScreen: ViewControllerComponentContainer { }, convertToStars: { convertToStarsImpl?() + }, + openStarsIntro: { + openStarsIntroImpl?() } ), navigationBarAppearance: .none, @@ -764,8 +775,12 @@ public class GiftViewScreen: ViewControllerComponentContainer { guard let self, let arguments = subject.arguments, let messageId = arguments.messageId else { return } - let _ = (context.engine.payments.updateStarGiftAddedToProfile(messageId: messageId, added: added) - |> deliverOnMainQueue).startStandalone() + if let updateSavedToProfile { + updateSavedToProfile(added) + } else { + let _ = (context.engine.payments.updateStarGiftAddedToProfile(messageId: messageId, added: added) + |> deliverOnMainQueue).startStandalone() + } self.dismissAnimated() @@ -774,7 +789,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { if let lastController = navigationController.viewControllers.last as? ViewController { let resultController = UndoOverlayController( presentationData: presentationData, - content: .sticker(context: context, file: arguments.gift.file, loop: false, title: "Gift Saved to Profile", text: "The gift is now displayed in your profile.", undoText: nil, customAction: nil), + content: .sticker(context: context, file: arguments.gift.file, loop: false, title: added ? "Gift Saved to Profile" : "Gift Removed from Profile", text: added ? "The gift is now displayed in your profile." : "The gift is no longer displayed in your profile.", undoText: nil, customAction: nil), elevatedLayout: lastController is ChatController, action: { _ in return true} ) @@ -795,9 +810,12 @@ public class GiftViewScreen: ViewControllerComponentContainer { actions: [ TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: "Convert", action: { [weak self, weak navigationController] in - let _ = (context.engine.payments.convertStarGift(messageId: messageId) - |> deliverOnMainQueue).startStandalone() - + if let convertToStars { + convertToStars() + } else { + let _ = (context.engine.payments.convertStarGift(messageId: messageId) + |> deliverOnMainQueue).startStandalone() + } self?.dismissAnimated() if let navigationController { @@ -827,6 +845,14 @@ public class GiftViewScreen: ViewControllerComponentContainer { ) self.present(controller, in: .window(.root)) } + openStarsIntroImpl = { [weak self] in + guard let self else { + return + } + let introController = context.sharedContext.makeStarsIntroScreen(context: context) + introController.navigationPresentation = .modal + self.push(introController) + } } required public init(coder aDecoder: NSCoder) { @@ -1130,14 +1156,18 @@ private final class PeerCellComponent: Component { } final class View: UIView { - private let avatar = ComponentView() + private let avatarNode: AvatarNode private let text = ComponentView() private var component: PeerCellComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0)) + super.init(frame: frame) + + self.addSubnode(self.avatarNode) } required init?(coder: NSCoder) { @@ -1152,29 +1182,25 @@ private final class PeerCellComponent: Component { let spacing: CGFloat = 6.0 let peerName: String - let peer: StarsContext.State.Transaction.Peer + let avatarOverride: AvatarNodeImageOverride? if let peerValue = component.peer { peerName = peerValue.compactDisplayTitle - peer = .peer(peerValue) + avatarOverride = nil } else { + //TODO:localize peerName = "Hidden Name" - peer = .fragment + avatarOverride = .anonymousSavedMessagesIcon(isColored: true) } - let avatarNaturalSize = self.avatar.update( - transition: .immediate, - component: AnyComponent( - StarsAvatarComponent(context: component.context, theme: component.theme, peer: peer, photo: nil, media: [], backgroundColor: .clear) - ), - environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) - ) + let avatarNaturalSize = CGSize(width: 40.0, height: 40.0) + self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, overrideImage: avatarOverride) + self.avatarNode.bounds = CGRect(origin: .zero, size: avatarNaturalSize) let textSize = self.text.update( transition: .immediate, component: AnyComponent( MultilineTextComponent( - text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.theme.list.itemAccentColor, paragraphAlignment: .left)) + text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.peer != nil ? component.theme.list.itemAccentColor : component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left)) ) ), environment: {}, @@ -1184,15 +1210,7 @@ private final class PeerCellComponent: Component { let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height) let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize) - - if let view = self.avatar.view { - if view.superview == nil { - self.addSubview(view) - } - let scale = avatarSize.width / avatarNaturalSize.width - view.transform = CGAffineTransform(scaleX: scale, y: scale) - view.frame = avatarFrame - } + self.avatarNode.frame = avatarFrame if let view = self.text.view { if view.superview == nil { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift index 991453a817..2dd9c7b988 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift @@ -100,7 +100,6 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode private let listNode: ListView private var currentEntries: [RecommendedChannelsListEntry] = [] private var currentState: (RecommendedChannels?, Bool)? - private var canLoadMore: Bool = false private var enqueuedTransactions: [RecommendedChannelsListTransaction] = [] private var unlockBackground: UIImageView? diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD index 6517691155..27132baef3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/BUILD @@ -51,6 +51,7 @@ swift_library( "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/Components/BalancedTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index 9a4c7f8122..f99b353467 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -15,12 +15,13 @@ import MergeLists import ItemListUI import ChatControllerInteraction import MultilineTextComponent +import BalancedTextComponent import Markdown import PeerInfoPaneNode import GiftItemComponent import PlainButtonComponent import GiftViewScreen -import ButtonComponent +import SolidRoundedButtonNode public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate { private let context: AccountContext @@ -37,6 +38,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private let backgroundNode: ASDisplayNode private let scrollNode: ASScrollNode + private var unlockBackground: UIImageView? + private var unlockText: ComponentView? + private var unlockButton: SolidRoundedButtonNode? + private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData)? private var theme: PresentationTheme? @@ -82,7 +87,8 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr guard let self else { return } - self.statusPromise.set(.single(PeerInfoStatusData(text: "\(state.count ?? 0) gifts", isActivity: true, key: .gifts))) + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.statusPromise.set(.single(PeerInfoStatusData(text: presentationData.strings.SharedMedia_GiftCount(state.count ?? 0), isActivity: true, key: .gifts))) self.starsProducts = state.gifts if !self.didSetReady { @@ -149,6 +155,13 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } if isVisible { + let ribbonText: String? + if let availability = product.gift.availability { + //TODO:localize + ribbonText = "1 of \(compactNumericCountString(Int(availability.total)))" + } else { + ribbonText = nil + } let _ = visibleItem.update( transition: itemTransition, component: AnyComponent( @@ -157,26 +170,36 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr GiftItemComponent( context: self.context, theme: params.presentationData.theme, - peer: product.fromPeer, + peer: product.fromPeer.flatMap { .peer($0) } ?? .anonymous, subject: .starGift(product.gift.id, product.gift.file), price: "⭐️ \(product.gift.price)", - ribbon: product.gift.availability != nil ? - GiftItemComponent.Ribbon( - text: "1 of 1K", - color: UIColor(rgb: 0x58c1fe) - ) - : nil + ribbon: ribbonText.flatMap { GiftItemComponent.Ribbon(text: $0, color: .blue) }, + isHidden: !product.savedToProfile ) ), effectAlignment: .center, action: { [weak self] in - if let self { - let controller = GiftViewScreen( - context: self.context, - subject: .profileGift(self.peerId, product) - ) - self.parentController?.push(controller) + guard let self else { + return } + let controller = GiftViewScreen( + context: self.context, + subject: .profileGift(self.peerId, product), + updateSavedToProfile: { [weak self] added in + guard let self, let messageId = product.messageId else { + return + } + self.profileGifts.updateStarGiftAddedToProfile(messageId: messageId, added: added) + }, + convertToStars: { [weak self] in + guard let self, let messageId = product.messageId else { + return + } + self.profileGifts.convertStarGift(messageId: messageId) + } + ) + self.parentController?.push(controller) + }, animateAlpha: false ) @@ -198,46 +221,124 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } - let contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * starsOptionSize.height + 60.0 + params.bottomInset + 16.0 + var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * starsOptionSize.height + 60.0 + 16.0 -// //TODO:localize -// let buttonSize = self.button.update( -// transition: .immediate, -// component: AnyComponent(ButtonComponent( -// background: ButtonComponent.Background( -// color: params.presentationData.theme.list.itemCheckColors.fillColor, -// foreground: params.presentationData.theme.list.itemCheckColors.foregroundColor, -// pressedColor: params.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), -// cornerRadius: 10.0 -// ), -// content: AnyComponentWithIdentity( -// id: AnyHashable(0), -// component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Send Gifts to Friends", font: Font.semibold(17.0), textColor: )params.presentationData.theme.list.itemCheckColors.foregroundColor))) -// ), -// isEnabled: true, -// displaysProgress: false, -// action: { -// -// } -// )), -// environment: {}, -// containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50) -// ) -// if let buttonView = self.button.view { -// if buttonView.superview == nil { -// self.addSubview(buttonView) -// } -// buttonView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - buttonSize.height), size: buttonSize) -// } - -// contentHeight += 100.0 + if self.peerId == self.context.account.peerId { + let transition = ComponentTransition.immediate + + let size = params.size + let sideInset = params.sideInset + let bottomInset = params.bottomInset + let presentationData = params.presentationData + + let themeUpdated = self.theme !== presentationData.theme + self.theme = presentationData.theme + + let unlockText: ComponentView + let unlockBackground: UIImageView + let unlockButton: SolidRoundedButtonNode + if let current = self.unlockText { + unlockText = current + } else { + unlockText = ComponentView() + self.unlockText = unlockText + } + + if let current = self.unlockBackground { + unlockBackground = current + } else { + unlockBackground = UIImageView() + unlockBackground.contentMode = .scaleToFill + self.view.addSubview(unlockBackground) + self.unlockBackground = unlockBackground + } + + if let current = self.unlockButton { + unlockButton = current + } else { + unlockButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 10.0) + self.view.addSubview(unlockButton.view) + self.unlockButton = unlockButton + + //TODO:localize + unlockButton.title = "Send Gifts to Friends" + + unlockButton.pressed = { [weak self] in + self?.buttonPressed() + } + } + if themeUpdated { + let topColor = presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.0) + let bottomColor = presentationData.theme.list.plainBackgroundColor + unlockBackground.image = generateGradientImage(size: CGSize(width: 1.0, height: 170.0), colors: [topColor, bottomColor, bottomColor], locations: [0.0, 0.3, 1.0]) + unlockButton.updateTheme(SolidRoundedButtonTheme(theme: presentationData.theme)) + } + + let textFont = Font.regular(13.0) + let boldTextFont = Font.semibold(13.0) + let textColor = presentationData.theme.list.itemSecondaryTextColor + let linkColor = presentationData.theme.list.itemAccentColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: boldTextFont, textColor: linkColor), linkAttribute: { _ in + return nil + }) + + let scrollOffset: CGFloat = min(0.0, self.scrollNode.view.contentOffset.y + bottomInset + 80.0) + + transition.setFrame(view: unlockBackground, frame: CGRect(x: 0.0, y: size.height - bottomInset - 170.0 + scrollOffset, width: size.width, height: bottomInset + 170.0)) + + let buttonSideInset = sideInset + 16.0 + let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) + transition.setFrame(view: unlockButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - 26.0), size: buttonSize)) + let _ = unlockButton.updateLayout(width: buttonSize.width, transition: .immediate) + + let unlockSize = unlockText.update( + transition: .immediate, + component: AnyComponent( + BalancedTextComponent( + text: .markdown(text: "These gifts were sent to you by other users. Tap on a gift to exchange it for Stars or change its privacy settings.", attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ) + ), + environment: {}, + containerSize: CGSize(width: size.width - 32.0, height: 200.0) + ) + if let view = unlockText.view { + if view.superview == nil { + view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonPressed))) + self.scrollNode.view.addSubview(view) + } + transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floor((size.width - unlockSize.width) / 2.0), y: contentHeight), size: unlockSize)) + } + contentHeight += unlockSize.height + } + contentHeight += params.bottomInset let contentSize = CGSize(width: params.size.width, height: contentHeight) if self.scrollNode.view.contentSize != contentSize { self.scrollNode.view.contentSize = contentSize } } + + let bottomOffset = max(0.0, self.scrollNode.view.contentSize.height - self.scrollNode.view.contentOffset.y - self.scrollNode.view.frame.height) + if bottomOffset < 100.0 { + self.profileGifts.loadMore() + } + } + + @objc private func buttonPressed() { + let _ = (self.context.account.stateManager.contactBirthdays + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] birthdays in + guard let self else { + return + } + let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .settings(birthdays), completion: nil) + controller.navigationPresentation = .modal + self.chatControllerInteraction.navigationController()?.pushViewController(controller) + }) } public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { diff --git a/submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD new file mode 100644 index 0000000000..598da6fc38 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD @@ -0,0 +1,44 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StarsIntroScreen", + module_name = "StarsIntroScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/ItemListUI", + "//submodules/TelegramStringFormatting", + "//submodules/PresentationDataUtils", + "//submodules/Components/SheetComponent", + "//submodules/UndoUI", + "//submodules/TextFormat", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ScrollComponent", + "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/SolidRoundedButtonComponent", + "//submodules/Components/BlurredBackgroundComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stars/StarsIntroScreen/Sources/StarsIntroScreen.swift b/submodules/TelegramUI/Components/Stars/StarsIntroScreen/Sources/StarsIntroScreen.swift new file mode 100644 index 0000000000..db13e8975a --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsIntroScreen/Sources/StarsIntroScreen.swift @@ -0,0 +1,573 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import Markdown +import TextFormat +import TelegramPresentationData +import ViewControllerComponent +import ScrollComponent +import BundleIconComponent +import BalancedTextComponent +import MultilineTextComponent +import SolidRoundedButtonComponent +import AccountContext +import ScrollComponent +import BlurredBackgroundComponent +import PremiumStarComponent + +private final class ScrollContent: CombinedComponent { + typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment) + + let context: AccountContext + let openExamples: () -> Void + let dismiss: () -> Void + + init( + context: AccountContext, + openExamples: @escaping () -> Void, + dismiss: @escaping () -> Void + ) { + self.context = context + self.openExamples = openExamples + self.dismiss = dismiss + } + + static func ==(lhs: ScrollContent, rhs: ScrollContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + static var body: Body { + let star = Child(PremiumStarComponent.self) + + let title = Child(BalancedTextComponent.self) + let text = Child(BalancedTextComponent.self) + let list = Child(List.self) + + return { context in + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let component = context.component + + let theme = environment.theme + //let strings = environment.strings + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let textSideInset: CGFloat = 30.0 + environment.safeInsets.left + + let titleFont = Font.semibold(20.0) + let textFont = Font.regular(15.0) + + let textColor = theme.actionSheet.primaryTextColor + let secondaryTextColor = theme.actionSheet.secondaryTextColor + let linkColor = theme.actionSheet.controlAccentColor + + let spacing: CGFloat = 16.0 + var contentSize = CGSize(width: context.availableSize.width, height: 152.0) + + let star = star.update( + component: PremiumStarComponent( + theme: environment.theme, + isIntro: true, + isVisible: true, + hasIdleAnimations: true, + colors: [ + UIColor(rgb: 0xe57d02), + UIColor(rgb: 0xf09903), + UIColor(rgb: 0xf9b004), + UIColor(rgb: 0xfdd219) + ], + particleColor: UIColor(rgb: 0xf9b004), + backgroundColor: environment.theme.list.plainBackgroundColor + ), + availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), + transition: context.transition + ) + context.add(star + .position(CGPoint(x: context.availableSize.width / 2.0, y: environment.navigationHeight + 24.0)) + ) + + let title = title.update( + component: BalancedTextComponent( + text: .plain(NSAttributedString(string: "What are Stars?", font: titleFont, textColor: textColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + ) + contentSize.height += title.size.height + contentSize.height += spacing - 8.0 + + let text = text.update( + component: BalancedTextComponent( + text: .plain(NSAttributedString(string: "Buy packages of Stars on Telegram that let you do following:", font: textFont, textColor: secondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(text + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) + ) + contentSize.height += text.size.height + contentSize.height += spacing + + var items: [AnyComponentWithIdentity] = [] + items.append( + AnyComponentWithIdentity( + id: "gift", + component: AnyComponent(ParagraphComponent( + title: "Send Gifts to Friends", + titleColor: textColor, + text: "Give your friends gifts that can be kept on their profiles or converted to Stars.", + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/StarsPerk/Gift", + iconColor: linkColor + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "miniapp", + component: AnyComponent(ParagraphComponent( + title: "Use Stars in Miniapps", + titleColor: textColor, + text: "Buy additional content and services in Telegram miniapps. [See Examples >]()", + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/StarsPerk/Miniapp", + iconColor: linkColor, + action: { + component.openExamples() + } + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "media", + component: AnyComponent(ParagraphComponent( + title: "Unlock Content in Channels", + titleColor: textColor, + text: "Get access to paid content and services in Telegram channels.", + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/StarsPerk/Media", + iconColor: linkColor + )) + ) + ) + items.append( + AnyComponentWithIdentity( + id: "reaction", + component: AnyComponent(ParagraphComponent( + title: "Send Star Reactions", + titleColor: textColor, + text: "Support your favorite channels by sending Star reactions to their posts.", + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/StarsPerk/Reaction", + iconColor: linkColor + )) + ) + ) + + let list = list.update( + component: List(items), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 10000.0), + transition: context.transition + ) + context.add(list + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0)) + ) + contentSize.height += list.size.height + contentSize.height += spacing - 9.0 + + contentSize.height += 12.0 + 50.0 + if environment.safeInsets.bottom > 0 { + contentSize.height += environment.safeInsets.bottom + 5.0 + } else { + contentSize.height += 12.0 + } + + return contentSize + } + } +} + +private final class ContainerComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let openExamples: () -> Void + + init( + context: AccountContext, + openExamples: @escaping () -> Void + ) { + self.context = context + self.openExamples = openExamples + } + + static func ==(lhs: ContainerComponent, rhs: ContainerComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + final class State: ComponentState { + var topContentOffset: CGFloat? + var bottomContentOffset: CGFloat? + } + + func makeState() -> State { + return State() + } + + static var body: Body { + let background = Child(Rectangle.self) + let scroll = Child(ScrollComponent.self) + let bottomPanel = Child(BlurredBackgroundComponent.self) + let bottomSeparator = Child(Rectangle.self) + let actionButton = Child(SolidRoundedButtonComponent.self) + let scrollExternalState = ScrollComponent.ExternalState() + + return { context in + let environment = context.environment[EnvironmentType.self] + let theme = environment.theme + //let strings = environment.strings + let state = context.state + + let controller = environment.controller + + let background = background.update( + component: Rectangle(color: environment.theme.list.plainBackgroundColor), + environment: {}, + availableSize: context.availableSize, + transition: context.transition + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + let scroll = scroll.update( + component: ScrollComponent( + content: AnyComponent(ScrollContent( + context: context.component.context, + openExamples: context.component.openExamples, + dismiss: { + controller()?.dismiss() + } + )), + externalState: scrollExternalState, + contentInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0), + contentOffsetUpdated: { [weak state] topContentOffset, bottomContentOffset in + state?.topContentOffset = topContentOffset + state?.bottomContentOffset = bottomContentOffset + Queue.mainQueue().justDispatch { + state?.updated(transition: .immediate) + } + }, + contentOffsetWillCommit: { targetContentOffset in + } + ), + environment: { environment }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(scroll + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + let buttonHeight: CGFloat = 50.0 + let bottomPanelPadding: CGFloat = 12.0 + let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding + let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset + + let bottomPanelAlpha: CGFloat + if scrollExternalState.contentHeight > context.availableSize.height { + if let bottomContentOffset = state.bottomContentOffset { + bottomPanelAlpha = min(16.0, bottomContentOffset) / 16.0 + } else { + bottomPanelAlpha = 1.0 + } + } else { + bottomPanelAlpha = 0.0 + } + + let bottomPanel = bottomPanel.update( + component: BlurredBackgroundComponent( + color: theme.rootController.tabBar.backgroundColor + ), + availableSize: CGSize(width: context.availableSize.width, height: bottomPanelHeight), + transition: context.transition + ) + let bottomSeparator = bottomSeparator.update( + component: Rectangle( + color: theme.rootController.tabBar.separatorColor + ), + availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), + transition: context.transition + ) + + context.add(bottomPanel + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height / 2.0)) + .opacity(bottomPanelAlpha) + ) + context.add(bottomSeparator + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height)) + .opacity(bottomPanelAlpha) + ) + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let actionButton = actionButton.update( + component: SolidRoundedButtonComponent( + title: "Got It", + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: theme.list.itemCheckColors.fillColor, + backgroundColors: [], + foregroundColor: theme.list.itemCheckColors.foregroundColor + ), + font: .bold, + fontSize: 17.0, + height: buttonHeight, + cornerRadius: 10.0, + gloss: false, + iconName: nil, + animationName: nil, + iconPosition: .left, + action: { + controller()?.dismiss() + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + context.add(actionButton + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanelHeight + bottomPanelPadding + actionButton.size.height / 2.0)) + ) + + return context.availableSize + } + } +} + +public final class StarsIntroScreen: ViewControllerComponentContainer { + private let context: AccountContext + + public init( + context: AccountContext, + forceDark: Bool = false + ) { + self.context = context + + var openExamplesImpl: (() -> Void)? + super.init( + context: context, + component: ContainerComponent( + context: context, + openExamples: { + openExamplesImpl?() + } + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: forceDark ? .dark : .default + ) + + self.navigationPresentation = .modal + + openExamplesImpl = { [weak self] in + guard let self else { + return + } + let _ = (context.sharedContext.makeMiniAppListScreenInitialData(context: context) + |> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in + guard let self, let navigationController = self.navigationController as? NavigationController else { + return + } + navigationController.pushViewController(context.sharedContext.makeMiniAppListScreen(context: context, initialData: initialData)) + }) + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class ParagraphComponent: CombinedComponent { + let title: String + let titleColor: UIColor + let text: String + let textColor: UIColor + let accentColor: UIColor + let iconName: String + let iconColor: UIColor + let action: () -> Void + + public init( + title: String, + titleColor: UIColor, + text: String, + textColor: UIColor, + accentColor: UIColor, + iconName: String, + iconColor: UIColor, + action: @escaping () -> Void = {} + ) { + self.title = title + self.titleColor = titleColor + self.text = text + self.textColor = textColor + self.accentColor = accentColor + self.iconName = iconName + self.iconColor = iconColor + self.action = action + } + + static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.titleColor != rhs.titleColor { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + if lhs.accentColor != rhs.accentColor { + return false + } + if lhs.iconName != rhs.iconName { + return false + } + if lhs.iconColor != rhs.iconColor { + return false + } + return true + } + + final class State: ComponentState { + var cachedChevronImage: (UIImage, UIColor)? + } + + func makeState() -> State { + return State() + } + + static var body: Body { + let title = Child(MultilineTextComponent.self) + let text = Child(MultilineTextComponent.self) + let icon = Child(BundleIconComponent.self) + + return { context in + let component = context.component + let state = context.state + + let leftInset: CGFloat = 32.0 + let rightInset: CGFloat = 24.0 + let textSideInset: CGFloat = leftInset + 8.0 + let spacing: CGFloat = 5.0 + + let textTopInset: CGFloat = 9.0 + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.title, + font: Font.semibold(15.0), + textColor: component.titleColor, + paragraphAlignment: .natural + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + let textColor = component.textColor + let accentColor = component.accentColor + let markdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: accentColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 != accentColor { + state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: accentColor)!, accentColor) + } + let textAttributedString = parseMarkdownIntoAttributedString(component.text, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString + if let range = textAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { + textAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: textAttributedString.string)) + } + + let text = text.update( + component: MultilineTextComponent( + text: .plain(textAttributedString), + horizontalAlignment: .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { _, _ in + component.action() + } + ), + availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height), + transition: .immediate + ) + + let icon = icon.update( + component: BundleIconComponent( + name: component.iconName, + tintColor: component.iconColor + ), + availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), + transition: .immediate + ) + + context.add(title + .position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0)) + ) + + context.add(text + .position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0)) + ) + + context.add(icon + .position(CGPoint(x: 15.0, y: textTopInset + 18.0)) + ) + + return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0) + } + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index 6dfa8bb2d5..c49448d6af 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -237,6 +237,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { textString = renew ? strings.Stars_Purchase_SubscriptionRenewInfo(component.peers.first?.value.compactDisplayTitle ?? "").string : strings.Stars_Purchase_SubscriptionInfo(component.peers.first?.value.compactDisplayTitle ?? "").string case .unlockMedia: textString = strings.Stars_Purchase_StarsNeededUnlockInfo + case .starGift: + textString = strings.Stars_Purchase_StarGiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string } let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { contents in @@ -815,11 +817,9 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { switch context.component.purpose { case .generic: titleText = strings.Stars_Purchase_GetStars - case let .topUp(requiredStars, _): - titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) case .gift: titleText = strings.Stars_Purchase_GiftStars - case let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars): + case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars): titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) } @@ -1239,6 +1239,8 @@ private extension StarsPurchasePurpose { return [peerId] case let .subscription(peerId, _, _): return [peerId] + case let .starGift(peerId, _): + return [peerId] default: return [] } @@ -1256,6 +1258,8 @@ private extension StarsPurchasePurpose { return requiredStars case let .unlockMedia(requiredStars): return requiredStars + case let .starGift(_, requiredStars): + return requiredStars default: return nil } diff --git a/submodules/TelegramUI/Images.xcassets/Peer Info/HiddenIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Peer Info/HiddenIcon.imageset/Contents.json new file mode 100644 index 0000000000..126065aee3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Peer Info/HiddenIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "hidden_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Peer Info/HiddenIcon.imageset/hidden_30.pdf b/submodules/TelegramUI/Images.xcassets/Peer Info/HiddenIcon.imageset/hidden_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7ff4831d79107a982fae465761b9a552df3a1842 GIT binary patch literal 1543 zcmY!laBvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~f-!gM*>$(wcv`<#ajoSJnW<>` z=jJxnU3tGb+`7bea0alv`22kFI%RH;JsbaIo_ILt@Iw1P-#-4YmzV$l=kN5t*Y|JZ zUmxGEf4ly+&9R?1k8f|Ezkd1ax$^J!wSAm0OK+FGUGDWw@8$Q|*iDW&awv6vU61O% zoc}B`%C>$Q^Ag=AtPe@tE^Q=yQP|d5K1co0<%Vvhn#%Rnii_8*T#&!;^#qB{-gm!5 zWLXx?FFMnanq8l*p(j%96xb%P(-64^iPEDL!;RpXt- zlT#0G3AN5Rr?fA_s$3@!Tif$x)d{=N&K76xu&GCpBcVs`l}FE^F3wa-R3y zQsv#@XRaPDCvrq^<0^|LshtTr@tY;{FLQDjp110B>rvaqw$+UDq>8v{SG9t{uUkc5 zYONo76*#Y5a!a`PtIM_7W{aX`_o|#-^qDhV%)Kt9_syx3?HigOM;xB=ZR^E3>xz_4 z)s*`^JK`MRtH}QI#NT&jyZ6rawNt6f*AR(ovrd;@SYJ}T=fY;&^#PwOf4V7&wdXA zL|lh!0pqiCVn$hNf*AsqY1^At&AxXr`S3wgx55+Li#!W_)+lwlxl7b;&6qgNEb70~ z7xwAja(}n?n6-Uijy7h>-f_Bc%DG94oVKQx_>|v@2;9?_dg_he?4`~;NyS=M%g<&# z)&F$k)q_6OUomS6{>4mXfAu|N$HZx!rn2Q5FFslm_tZIcVg9pIrIRG*h#U&PBr!+$ z^%|Dlx6V9S6=-w8L|yq{KgU&bP7wR3h^+)aLJjX7+1e%Kj>rhC6@0qh@@|Fl9$ti%9RGeIl(% z*>8$6LN|8kPKcYyXnS+Q(F&X6j-R%%-=1MOBi2XKYtedzLBATb@5w|!GnG83KhD-@y?4D<}Z0KqVZ3n5rAlY&x<^Gl18Q;QWq#S5rh z02LL^`FSO&c|aRMWeZF&Ah8H2reF#cLMnkkLhzDF-#aq}=u`!e4}uh6_BiL40`(eV zxW5=u62St)48Ql_d%qBnW?Fogjb^va|$735t-Rfg#X~s6vJo7U)7oX29Trswyc;%*;tG;sV8< zrwcGRG>Y?cQ#BPdGE+1mfubLjpI@Q?@-R4n^@B63Qh}}q7t4u7C1CFu8XFmMsj9mA Gy8!@9=2D^n literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Gift.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Gift.imageset/Contents.json new file mode 100644 index 0000000000..923ba188ab --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Gift.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "gift_30 (4).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Gift.imageset/gift_30 (4).pdf b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Gift.imageset/gift_30 (4).pdf new file mode 100644 index 0000000000000000000000000000000000000000..0d6d36c6032106b352f30cc3e5ed1452079ff59a GIT binary patch literal 4312 zcmZXYc{G%L8^?QEB$5y%OJm>HVPsGCP$-hI4aPcSVrIx5vV}opYpmIK29vQRS!3+G zp=4jPC(=tj^*rzUUVq%@y1(bTe%ErJbD#71ap|h435f|yQBa5hL;(m(2MPc{P7a`; zKmm2KK6#}O|Fv6#FbE2&0<}U|Lyt3#O+*{&WQ(!`0FT4(v3^!^M&x-==&Oe(XdxeBU28Kic2OnfFa?@HyOjU7LNh2PY^TZXBU}@wIdA(nFhG zUk~>``>fz^_WVFO=LhcY>oI#IJ`BE9!tlbXIw52gpYN7t&$*U$1QG8a8$7#5KINt`Q}2= zg?*2Ru!_W?*_+>p$~EpiBer0XrO~@JesM7=I9E#3!jk%nbm-j`T-}m>&Y7YTtHkkX zm1Rd`x3C~-H^GoW_|&BX>v&gYc=y7~i`gC{z1w3s;xCUl4X2YAp^+f3^1O;t@l__t z@ZOz;9L@x&gEX3_M2cz_GxS zPd!C2t7yefs4H9zU-*VtpQXxRmK{E6rnLrIo>Hu z7O^Y`w)HGpO#m$=n~m3N0}Ws$W4tN&uV@7guDH>V67Z!WVvmgJ5@Azs#Df`!=7pAM zy(KgRMgd5af zZxzW)80Wz_EbZZ?BX#v-y%Yk0^l?xfMhnuj1EbV_kh4E6?bTULg{fEQ4#%kvDx?HN zJAb$zXG?g>d%D(Atk_W{dsQ*I%g5*y&wEOn)t%C+YxxJ-(cUPu_5K;st~XtPk$q7j zPq!n<1LAD9D!|Av@R_QV2|>z2pCFVSY4_^Mfgvvlyv=q39Lqfl z<6mg#C{51$y#cVtBjXG%-7^u@%JAc(W?E2oJ6M4<#Z~Ueu^GP5b+r@kL+Xw%b8E0g zH+uvIPKUVI;q zxx9mZ=d~iGk`LH+mZ}->!l~~mzNG;u(KdN9pX_EgHk_1_ zM)OveX3BekWpT*Z+dj8M{Z15(E8`S115;uEiRWb_W>WuR@T!D$sO(jxmg1r3Ldk+_ zWe!$a3q-grBK}ak{3F5SyV;`PXt}BJ@bzT-m-*ia`!(s+4ZePE#y%gxWIs+1ca1fi zS)D(q{1ED_Rs@KPQFVHqz3>6xP(?)^CEg;8M!?wnM)(XI77|y}a0z0Cp|5otwRP&2 zRC`U^E_~Btt;y8nS}4p=?#~WfbCFdMa?QLhTp{EUk$>)5W?QBg^`o$+v;rcKV6i2{ zo0rLU?H!SpCn=3rmY*dAMc3q)cT4J!RpS)mUN#&8EYMUnhX{pXM8CiStuxx!4lJ^S zSYk5!@6aEVuab;6J~+c3sl>AzkXK{KfxL->yRz^6QAUf43Pl}}a-)=IV>Z;v6V7jO z@?M2!OG9kVcE^>!E~HFO&e?A{x-wIvik6T!t9?uLIzEDV<$*K-JQ-nSEg6zgGHE1W z3%+|n+#t!WKHl$*UE2281eVMUJfN4f*&UhM7Z*SH%AcdeKQz5yn6(1`1$HynnU-40 zk%B!*Nj}V2@#w(MIPzd+DLtV_cZ3X_^r);zI4pxH&OU=~?%~m{9St<`S@f9Z$i)Eo zrv6+gkUp}Jf6)!Tm2+KVJ~uoy<5?c7j%xFqvg<~XS_KW^UeZc4|HfH<7-&}s>K`{+ zX2bG&+;NzV=dEb;wNOyrqR|^@IC1_cQ;r$&xDL6$yqK=>lzEFtjOD0c?Ktz$)#J4# zB6%m+dk4u<;;1iT8_Xv?TK$$(L9OSL+olm0WfbNjrS{=!yGqArf``a546@yL1#Vv% zIY~_=#dLYDTzH?AvMOa`3OI~ zb;G*$QWtX0D_2F!QnREx+#$1Xr5#_1u-V3SAOC$RFw(xkm~CFK`rC})5Nj^>@JpN* ziY0GqG$|#-vdM0djKJU0KZaP??8wcR(#Uid<6?Vy2RQs|#(Z*}ym`fTdOo?Gu~X7_ zEr=oqS~z{yc~GK!x+mj7n_*D)ba(>&w_>)=Mw4Z7IMtBw2QvG69+#qaX7R!-)lN+9 z>>{@@(N88^rMYlQWfwAW8hvk~(-kP?=XW8usD%&0dM*+ z4l_%YYw&M!&&%naniYvmjv>vM0rB!036&*Xd6tS;_GXpICFrhYV%5eppo|vk=KApW&e3B~di``#EujHln6Y_`gk7YyYozM-@KysopMDB60)<@BgOxpGb zEF*8?4Os15$XL6RmAiZ(@^19e+;9F0 z?^8yha@5Y5IRaB;k-6e&S7{;K()Ww#3`r{LZg<`&Cg-xYw^{70I19l{Q0^Y)J*1 zPBsIcFP(XdbGV%16!_Qlik`^;F6#?Dr}Ie1wL9gtRA#gMx#Q1zi21G@wAW_Wna4hv z@~N&d$!R&LdxzF+-YzT1nbbnvrE&Lnpo6#z`$f;Eyg;nCeETxV6tx}_k`ua67L7&i zU0>n9Noz_wG#FdDBIAFVnWQDyV&lCqIwOkts&PyC?B}P9NvFgBy61X9V%?B4f(!$Z z8p%0pPO+0;jOzBcsZvEB4%X4vrb?JweCny4q8dq^A!BJAUi(s0>^vj7sOC70zK_qc zXO6-uQ7taS1jgRqPRg+f=M-gN^7IZ!GW66*by1!jYK|L~wS3Lkw<@luzEzQLka}Yj zD@E^scFtJE+LCbb-u&d{&MAJm5c;!8duWKx`>9qbWD-iGTTle{gH zvdBVK!KjCBD%GY_%J6BsLJCcjVvlbu?w4YV_d(3(TjGnq7( zleVgZp76$<;U+ypUw~Ale_B}Si&E)}97&Z^eKs539*%!r>Z7ruqm~{Frj#Y12ND~I zciH4SrCDrv7b)el1|Fr-SQXgu!3rZtWNO`}3Mp@i{T4Y*DM%~4ZEyeWbmh|{_U%Mr ztrU=NLa#W(YIUtA?-1A6OXsd!29*zoVI;=`=H5M?64Q>_&I1Wj7^9dqqDM(vqm(MhoWj-cq#w&GV zD7SHc#`x}RC-&|q+d7o;>A`bxK22C@=atmtg^Sz`n(u#LJDL)T>s1JutCm8Bl~(k= znOuEyIoP&jBeD0B6Sd!jxa;D2Clc;Ixshl%x&mLFF|!$0na6dl`w7MlZ7M`!MJlW; z&NH>feW}=prIlETAm_j-ic9F^FzQF*qxpgZ0;0iJmhv$*^-DHeoCh1;7= z-G{YK<<+nZ6TU7%uHw3!$1iO2ljmtcbTt9=88$IN>@#c9Zk|5jYZWpR~g}if;t^n zJb~rkqAtYcSOZA@E&U6rPozJnEuv*_ecUSGWCDG_?>fo|^l`4(|Midjh1S2f0s60# zP6kB&DR|P@ufdfeCPCrUyT_)j9r1!50_x=@^aJx!J4b?_n)a3~*u*WQ}% r*HT3E5eO9EWZ<6*7P)Wl0X^>i=RE_&1@&_r2~lZL3N9|ydusmy%o+Bo literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Media.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Media.imageset/Contents.json new file mode 100644 index 0000000000..6a409d155b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Media.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "unlock_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Media.imageset/unlock_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Media.imageset/unlock_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..67593f36987371d8d76c0f5e07d27005ec365ec5 GIT binary patch literal 2124 zcmZXVc|4T)AHa(hhLFvf<8hDHF@td|Bj;e;V~8A?$qZA>$c%fW=|}E$Y&miyjACN# zQksy>#Fi1|itVsv97QOxWLG;wwC(Tn$MgC=@7MGBem|ej^Ll;X8Xk5CU8tT32m}Qn z010~%1OOHmfTbk}M-1V;f(-u6^$~<{3N8xJM}%W2I6K@4QV5Ql!3}*E95Iv<1{iWb zM{yB%2QnoJhlv2i?Tsj8H$FEOwV&Ap<%*yWXrOWl{*{wAFGuF73KZ+d-1abedz4ls|3Eth^hTNH$CUdu}cA)1Mz>{z`OApIpq|csVooI^n2IW`~TK z7r3sqaNtSNqA-eZBL#C_^wJ z-$)`Y9U+#VXP+%##YP&bd+YwPdeEj38BqDGt&jtTO@->@Sj)dWa5)8k$(N?ofR{^W z!QJwv$9&~&y}oVG3Ct8JX}&RhX~(W z*r{@_jofU7Ba2Hy-8MDoSc=_zw-s0hhdW<(VPGkJ(OBTVN~tuu6OGhMQ2wX~?I@(9 zB#yVj#BZSU`&fFSK03V&#u*`kH1uksT{EYusbGK7y7wI1M{Bqy>p_Jm zlD*J{{^{CdczWlYV9m9Wu+2bxUd$@vU~v525GG|Gi)A2o2pKQ@%5Hv7hhbAf?vg6(C=EQc(r;A^_?1T7_rbu*!PMNtB?C|wagEoeJEKL1GL9$YHe zS$|AksYyd;sYv{%%q z3X2}L_WA}d9znc%sR2SiXgMejdD2H`1`9+$YwFiLOGSM}SCTpvP&pT`%);{P31;rESK;gSvB>rtaU$9kkPrYp2p=Vrmya-AMyR63zGE^3ZmxUKx(0U_vt=xZCVlm~vVlMKqtDKC>c)>hpv?rv#u&quCPZz4c<@bL0^)6ywRyGeD+ zhYQHz*%F-}TSc<`?v+=@zViGA+nbc^8mD;Kqg4X0nQdopH}+nn$;ld<2A{9T zm?of7Aa7n4?4Q2+M|E+kl6-g?zkNx7!=ayUVJMAIOxeQ^!Px>tzZx~M$My{ zBXI_O!2LNF$#1i?5EW^6$6vVyjirqgt6)W9W){3TO9b=!fbn=1MzK5J93SE0Vheh@8EQzdr?5ESg=_1U}uLso|keq98i?1^yAR6#C)s@6)^Hz2lOXt&LMHn^8 zvoDpdmpv7Gk!@S_P~}9jTfpLb#;@ybe&n&N;TEXl^d_|LP}i>uhHl#$WBk zYmmHqAg{3P#%(baOgJg@>(sa?93BJ#OhDW1^PYe)3}y%excGBGA;8uOAnqbVZk@c? zMNpUt_n-U+nQ;sI8krcI{TG=UeMzK5VF=;4C=l=OIoWZ?0Vb0oaM}Qv5Td<36nzwl jL;-jWZw*-Ai;#fhR=?$TOcZ6S9jF<^0HmQ|?~eEf03~5- literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Miniapp.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Miniapp.imageset/Contents.json new file mode 100644 index 0000000000..e582efcfd3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Miniapp.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "bot_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Miniapp.imageset/bot_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Miniapp.imageset/bot_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e682166cc8a986ec536d2dd519b2bb3415f3b00d GIT binary patch literal 2702 zcmZXWdpwhiAIB~CMeg_8<2Foevk5aZ-rJ~4Gr%c5VP9n+Kx;VG*AJ6Oid|u!C^T+en#@b>G5QZjTFam%B)F3h#04yv3 zOG_}3Lg2oFjsC8~Fr)}Nkp{pp5qLV$mKaPW5IGqfgSikXq4Y2S$q9GoNSq2WbQ%#K z3629rUL46%HkLm3VOzQj!+80sv+Yd3VqCHpHXD+I)w*#`Nh|q~oAKNnt@64DB6F(l;yZ7n3JbFV&v?WiCQ66BKoMioTu`2UM zKw;;T=LarK@1w4Vn?~ZPijCT&g;ir;?zu zKXfGdqCYasR5?~Mx|u!@q`?!l=vnD{cATkvWc*Zq>cH_<$@2Sb z&3J=^W4z%$kinY@$gRVQlvlp@!xUtLS{cON{vnUeycCxx%3OQgx}ntPvvI{&Sr2np z0&n`BmJw#aXHS+Vq5i1JBuqdf*p12`dZzlZz|v4cilXHQzKZqe#oFYKB%fXTi4IxRM3YPigvG_zL19SI}WCG2r z65)aphx*~eU2s}sYav#{AxdO1UU?r+YeQd?dOm2@33vWA(O~x;o4v2bNio=RI=-4E zPd=pTI=l7zAz;gC%?g!Mg6?-+vG(?D1oCT$V2#3zY>=#dr3Ve#1ivp8&p7k0qza55 zlc+R}i}cS{a-Ta~EnkE-VNUTFTenN}nTJ~$X%$H-*4Py$XlR1#MJnVfXK8yZr6ztz ztkSjB)hj_a_V4ZT^?E78Z7k7B+2u~A_2*=|(2Kecy!^OA|AeDF zGEYToV(DUlHu3>9YMO1-q2va_2){PSx}{WnFg3CM8vlS#mL88&;?kmMk)UP{#3Ris zs+9d^2Hxs?T#VeM=Qd0%MP>_B3d80drS7%dF7vPLdvJ;sF@KidNcKSIj_J^f1I9+v zfPR@!HbP)nJ)U$6IevvUCUpPSb+Zg%yW77QiKVL--mN*PXV!)Bbw4Ayy;Oh!*R%|y zaG_})#%#n?qkEs~MABjV4TNL2OUihzYn4k%uUvE=Il~%?ZqLZ-#W{C{=R9cf$bjyk z4p(M9kg8rCSN)%W$RU{u21$dC|3y39ZL5CIGgo$^L_wS&Mi)ZEW=LelB`S2K$j^6@ z?BnbZn619Khe-~zMnM_%P&qg@e_6L&#I-8{U0=v>A6mSsVXcpdX7HWBG@>sgg?1i1 za1w4FJb82j^m4BDWTPD`iJ&mRt>K#@3$S|IV&M^S6II`KwZ=}CA_Emq&1l-ER0LCFu7GG zs%zTx8LN;liA=h2tppU#CN6 zIGAA+Z-jKn#5$~|23SgXdKb4vjajAJ=903q_rBkhg8jN-*mymGS}QF!Q~A2|O|UY8R87W3x3 zQuj0YdlwUnaV8b0RRyLT!!caZ_lJx9*Y`b%9L_1YR@UIJ+F>;egZZ?J1vqrupm>)+ zM?X?XN-Bew#MK{Co&BCnRvBvPi~E^oe5{=D1uYw#@UV4+q|4maI+tz$f)HCN2zHNJ z)5S=fimY8$ML4T4&gfuc*32s(`bxXHS}Q(c#;_|2sr*so*PI7{QUqUA92&${gl^p(zw9JqGae|3(zJ_$VR( z!{S4~BZe!HK*C#7;{ZPZZV3Nke>uoq=`ZhlC3j*BHI^1ki~+cm!^IOWvDi>4bRvb* zk&8IrMJ%4i(E#Ib(%(RcEA0Xj%$Y>sj0$i!a0kBkv7yFtauNTTKjteCeZR(me^lZw z7_-}uJJ{F7ZSZt_1U2-Zsc|%72pA5SfWNNKeF6uJjgiIxNB(9AIPk>-D7zApGbh(} zB_o6}=M?{vO#TyvM49|M3T5j)<(xw|h8juK19V9Qy{|`sxl^fh ifV=RQ1H(K>2}I8HU+54|qkmb)2xSTfYirxNVg3Zve`;+2 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/Contents.json new file mode 100644 index 0000000000..a7e458b55c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cash_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/cash_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/cash_30.pdf new file mode 100644 index 0000000000..ccfbed1a33 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/StarsPerk/Reaction.imageset/cash_30.pdf @@ -0,0 +1,62 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Filter /FlateDecode + /Length 3 0 R + >> +stream +xeKT1 EYEKxjRwK%%Ĺvl'ӟ_ӷ/z36/kEBqv"6*\&[&Q!c;~sn]flI؛N ϱ6ț=`r~ni#fx-te0K]&S4 .b-%٦ ]:э&6JqZ ]_6ѝ &A:TnZU<o֫D/)[}iR%9A9t:qN8UGp2&\ĺY]ȅrDu +$>`!*HjM@%IZ/R*^l5b)d[,ےa1f #0xMb_Ԥ[,`.h+͘ր|o +hp!AV; ڷNHv9 -ĩ`xefxpc:;#$}!m*r[ +{bX= byFWAP֌+vp^vo,o̴xpHYZړg^=ٔCs

> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000915 00000 n +0000000937 00000 n +0000001110 00000 n +0000001184 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1243 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 1fcc5ef531..aefebec13e 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -72,6 +72,7 @@ import StarsWithdrawalScreen import MiniAppListScreen import GiftOptionsScreen import GiftViewScreen +import StarsIntroScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -2205,8 +2206,6 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Void)?) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } -// let limit: Int32 = 10 -// var reachedLimitImpl: ((Int32) -> Void)? var presentBirthdayPickerImpl: (() -> Void)? var starsMode: ContactSelectionControllerMode = .generic var currentBirthdays: [EnginePeer.Id: TelegramBirthday]? @@ -2268,9 +2267,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { )) let _ = combineLatest(queue: Queue.mainQueue(), contactsController.result, options.get()) .startStandalone(next: { [weak contactsController] result, options in - if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer { + if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext { let premiumOptions = options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) } - let giftController = GiftOptionsScreen(context: context, peerId: peer.id, premiumOptions: premiumOptions) + let giftController = GiftOptionsScreen(context: context, starsContext: starsContext, peerId: peer.id, premiumOptions: premiumOptions) giftController.navigationPresentation = .modal contactsController?.push(giftController) @@ -2812,6 +2811,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StarsTransactionScreen(context: context, subject: .boost(peerId, boost)) } + public func makeStarsIntroScreen(context: AccountContext) -> ViewController { + return StarsIntroScreen(context: context) + } + public func makeGiftViewScreen(context: AccountContext, message: EngineMessage) -> ViewController { return GiftViewScreen(context: context, subject: .message(message)) }