From 76e8d79f9f4076a5415567e1c841d3467b23affa Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 6 Sep 2025 13:35:19 +0500 Subject: [PATCH 01/13] Update API --- .../Sources/AccountContext.swift | 2 +- .../Sources/ChatListController.swift | 2 +- submodules/TelegramApi/Sources/Api0.swift | 5 +-- submodules/TelegramApi/Sources/Api10.swift | 22 +++++++++++++ submodules/TelegramApi/Sources/Api15.swift | 18 ++++++----- submodules/TelegramApi/Sources/Api23.swift | 18 ++++++----- .../ApiUtils/TelegramMediaAction.swift | 4 +-- .../SyncCore_TelegramMediaAction.swift | 13 +++++--- .../Payments/BotPaymentForm.swift | 5 ++- .../TelegramEngine/Payments/StarGifts.swift | 31 +++++++++++++------ .../TelegramEngine/Payments/Stars.swift | 7 +++-- .../Sources/PresenceStrings.swift | 11 +++++++ .../Sources/ServiceMessageStrings.swift | 2 +- .../ChatMessageGiftBubbleContentNode.swift | 4 +-- .../ChatMessagePaymentAlertController.swift | 1 + .../Sources/ChatSendStarsScreen.swift | 11 +++++-- .../Sources/GiftOptionsScreen.swift | 1 + .../Sources/GiftSetupScreen.swift | 3 +- .../Components/Gifts/GiftViewScreen/BUILD | 1 + .../Sources/GiftViewScreen.swift | 1 + .../Sources/MediaEditorScreen.swift | 2 +- .../Sources/StarsPurchaseScreen.swift | 1 + .../Sources/StarsStatisticsScreen.swift | 2 +- .../Sources/StarsTransactionsScreen.swift | 3 +- .../Sources/StarsTransferScreen.swift | 1 + .../Sources/StarsWithdrawalScreen.swift | 2 +- .../Chat/ChatControllerLoadDisplayNode.swift | 2 +- ...ChatControllerOpenMessageContextMenu.swift | 2 +- .../Chat/ChatControllerPaidMessage.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 4 +-- .../ChatInterfaceStateContextMenus.swift | 2 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 2 +- .../Sources/SharedAccountContext.swift | 5 +-- 33 files changed, 136 insertions(+), 56 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 6a36eb689d..5aecfa6c41 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1310,7 +1310,7 @@ public protocol SharedAccountContext: AnyObject { func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController - func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, completion: @escaping (Int64) -> Void) -> ViewController + func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, targetPeerId: EnginePeer.Id?, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, extendedMedia: [TelegramExtendedMedia], inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController func makeStarsSubscriptionTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, link: String, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, navigateToPeer: @escaping (EnginePeer) -> Void) -> ViewController func makeStarsTransactionScreen(context: AccountContext, transaction: StarsContext.State.Transaction, peer: EnginePeer) -> ViewController diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 8d8254d63d..d973c9b490 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -6248,7 +6248,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let starsContext = self.context.starsContext else { return } - let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: [], purpose: amount.flatMap({ .topUp(requiredStars: $0, purpose: "subs") }) ?? .generic, completion: { _ in }) + let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: [], purpose: amount.flatMap({ .topUp(requiredStars: $0, purpose: "subs") }) ?? .generic, targetPeerId: nil, completion: { _ in }) self.push(controller) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 07641c811d..ee347a3e50 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -394,6 +394,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-625298705] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftStars($0) } dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) } dict[-396206446] = { return Api.InputInvoice.parse_inputInvoiceStarGift($0) } + dict[153344209] = { return Api.InputInvoice.parse_inputInvoiceStarGiftDropOriginalDetails($0) } dict[-1710536520] = { return Api.InputInvoice.parse_inputInvoiceStarGiftPrepaidUpgrade($0) } dict[-1012968668] = { return Api.InputInvoice.parse_inputInvoiceStarGiftResale($0) } dict[1247763417] = { return Api.InputInvoice.parse_inputInvoiceStarGiftTransfer($0) } @@ -610,7 +611,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[-229775366] = { return Api.MessageAction.parse_messageActionStarGift($0) } - dict[888627955] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } + dict[-1787656893] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[-293988970] = { return Api.MessageAction.parse_messageActionSuggestedPostApproval($0) } dict[1777932024] = { return Api.MessageAction.parse_messageActionSuggestedPostRefund($0) } @@ -895,7 +896,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1681948327] = { return Api.SavedDialog.parse_monoForumDialog($0) } dict[-1115174036] = { return Api.SavedDialog.parse_savedDialog($0) } dict[-881854424] = { return Api.SavedReactionTag.parse_savedReactionTag($0) } - dict[430552434] = { return Api.SavedStarGift.parse_savedStarGift($0) } + dict[-1987861422] = { return Api.SavedStarGift.parse_savedStarGift($0) } dict[1040931690] = { return Api.SearchPostsFlood.parse_searchPostsFlood($0) } dict[-911191137] = { return Api.SearchResultsCalendarPeriod.parse_searchResultsCalendarPeriod($0) } dict[2137295719] = { return Api.SearchResultsPosition.parse_searchResultPosition($0) } diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index da1c5b0399..dc5d19a80f 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -256,6 +256,7 @@ public extension Api { case inputInvoicePremiumGiftStars(flags: Int32, userId: Api.InputUser, months: Int32, message: Api.TextWithEntities?) case inputInvoiceSlug(slug: String) case inputInvoiceStarGift(flags: Int32, peer: Api.InputPeer, giftId: Int64, message: Api.TextWithEntities?) + case inputInvoiceStarGiftDropOriginalDetails(stargift: Api.InputSavedStarGift) case inputInvoiceStarGiftPrepaidUpgrade(peer: Api.InputPeer, hash: String) case inputInvoiceStarGiftResale(flags: Int32, slug: String, toId: Api.InputPeer) case inputInvoiceStarGiftTransfer(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) @@ -321,6 +322,12 @@ public extension Api { serializeInt64(giftId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} break + case .inputInvoiceStarGiftDropOriginalDetails(let stargift): + if boxed { + buffer.appendInt32(153344209) + } + stargift.serialize(buffer, true) + break case .inputInvoiceStarGiftPrepaidUpgrade(let peer, let hash): if boxed { buffer.appendInt32(-1710536520) @@ -377,6 +384,8 @@ public extension Api { 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 .inputInvoiceStarGiftDropOriginalDetails(let stargift): + return ("inputInvoiceStarGiftDropOriginalDetails", [("stargift", stargift as Any)]) case .inputInvoiceStarGiftPrepaidUpgrade(let peer, let hash): return ("inputInvoiceStarGiftPrepaidUpgrade", [("peer", peer as Any), ("hash", hash as Any)]) case .inputInvoiceStarGiftResale(let flags, let slug, let toId): @@ -523,6 +532,19 @@ public extension Api { return nil } } + public static func parse_inputInvoiceStarGiftDropOriginalDetails(_ reader: BufferReader) -> InputInvoice? { + var _1: Api.InputSavedStarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputSavedStarGift + } + let _c1 = _1 != nil + if _c1 { + return Api.InputInvoice.inputInvoiceStarGiftDropOriginalDetails(stargift: _1!) + } + else { + return nil + } + } public static func parse_inputInvoiceStarGiftPrepaidUpgrade(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputPeer? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index ca9922c304..28616ad79f 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -394,7 +394,7 @@ public extension Api { case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, prepaidUpgradeHash: String?, giftMsgId: Int32?) - case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?) + case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?, dropOriginalDetailsStars: Int64?) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionSuggestedPostApproval(flags: Int32, rejectComment: String?, scheduleDate: Int32?, price: Api.StarsAmount?) case messageActionSuggestedPostRefund(flags: Int32) @@ -799,9 +799,9 @@ public extension Api { if Int(flags) & Int(1 << 14) != 0 {serializeString(prepaidUpgradeHash!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(giftMsgId!, buffer: buffer, boxed: false)} break - case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt): + case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt, let dropOriginalDetailsStars): if boxed { - buffer.appendInt32(888627955) + buffer.appendInt32(-1787656893) } serializeInt32(flags, buffer: buffer, boxed: false) gift.serialize(buffer, true) @@ -813,6 +813,7 @@ public extension Api { if Int(flags) & Int(1 << 8) != 0 {resaleAmount!.serialize(buffer, true)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(canTransferAt!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(canResellAt!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 12) != 0 {serializeInt64(dropOriginalDetailsStars!, buffer: buffer, boxed: false)} break case .messageActionSuggestProfilePhoto(let photo): if boxed { @@ -995,8 +996,8 @@ public extension Api { return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeMsgId, let upgradeStars, let fromId, let peer, let savedId, let prepaidUpgradeHash, let giftMsgId): return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeMsgId", upgradeMsgId as Any), ("upgradeStars", upgradeStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any), ("giftMsgId", giftMsgId as Any)]) - case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt): - return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleAmount", resaleAmount as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any)]) + case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt, let dropOriginalDetailsStars): + return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleAmount", resaleAmount as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("dropOriginalDetailsStars", dropOriginalDetailsStars as Any)]) case .messageActionSuggestProfilePhoto(let photo): return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let price): @@ -1810,6 +1811,8 @@ public extension Api { if Int(_1!) & Int(1 << 9) != 0 {_9 = reader.readInt32() } var _10: Int32? if Int(_1!) & Int(1 << 10) != 0 {_10 = reader.readInt32() } + var _11: Int64? + if Int(_1!) & Int(1 << 12) != 0 {_11 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil @@ -1820,8 +1823,9 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 9) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 10) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10) + let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10, dropOriginalDetailsStars: _11) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 7a79ed07e0..1c602ab476 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -626,13 +626,13 @@ public extension Api { } public extension Api { enum SavedStarGift: TypeConstructorDescription { - case savedStarGift(flags: Int32, fromId: Api.Peer?, date: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, msgId: Int32?, savedId: Int64?, convertStars: Int64?, upgradeStars: Int64?, canExportAt: Int32?, transferStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?, collectionId: [Int32]?, prepaidUpgradeHash: String?) + case savedStarGift(flags: Int32, fromId: Api.Peer?, date: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, msgId: Int32?, savedId: Int64?, convertStars: Int64?, upgradeStars: Int64?, canExportAt: Int32?, transferStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?, collectionId: [Int32]?, prepaidUpgradeHash: String?, dropOriginalDetailsStars: Int64?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash): + case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash, let dropOriginalDetailsStars): if boxed { - buffer.appendInt32(430552434) + buffer.appendInt32(-1987861422) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {fromId!.serialize(buffer, true)} @@ -653,14 +653,15 @@ public extension Api { serializeInt32(item, buffer: buffer, boxed: false) }} if Int(flags) & Int(1 << 16) != 0 {serializeString(prepaidUpgradeHash!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 18) != 0 {serializeInt64(dropOriginalDetailsStars!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash): - return ("savedStarGift", [("flags", flags as Any), ("fromId", fromId as Any), ("date", date as Any), ("gift", gift as Any), ("message", message as Any), ("msgId", msgId as Any), ("savedId", savedId as Any), ("convertStars", convertStars as Any), ("upgradeStars", upgradeStars as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("collectionId", collectionId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any)]) + case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash, let dropOriginalDetailsStars): + return ("savedStarGift", [("flags", flags as Any), ("fromId", fromId as Any), ("date", date as Any), ("gift", gift as Any), ("message", message as Any), ("msgId", msgId as Any), ("savedId", savedId as Any), ("convertStars", convertStars as Any), ("upgradeStars", upgradeStars as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("collectionId", collectionId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any), ("dropOriginalDetailsStars", dropOriginalDetailsStars as Any)]) } } @@ -703,6 +704,8 @@ public extension Api { } } var _15: String? if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) } + var _16: Int64? + if Int(_1!) & Int(1 << 18) != 0 {_16 = reader.readInt64() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = _3 != nil @@ -718,8 +721,9 @@ public extension Api { let _c13 = (Int(_1!) & Int(1 << 14) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.SavedStarGift.savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15) + let _c16 = (Int(_1!) & Int(1 << 18) == 0) || _16 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 { + return Api.SavedStarGift.savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15, dropOriginalDetailsStars: _16) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index d90a71b734..37e7e5e2b8 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -196,11 +196,11 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return nil } return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash, giftMessageId: giftMessageId, upgradeSeparate: (flags & (1 << 16)) != 0)) - case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate): + case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars): guard let gift = StarGift(apiStarGift: apiGift) else { return nil } - return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate)) + return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars)) case let .messageActionPaidMessagesRefunded(count, stars): return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars)) case let .messageActionPaidMessagesPrice(flags, stars): diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 028ef95a92..e7052f7854 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -244,7 +244,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?) case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool) - case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?) + case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?) case paidMessagesRefunded(count: Int32, stars: Int64) case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool) case conferenceCall(ConferenceCall) @@ -389,7 +389,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else if let stars = decoder.decodeOptionalInt64ForKey("resaleStars") { resaleAmount = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars) } - self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate")) + self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars")) case 46: self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0)) case 47: @@ -771,7 +771,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeNil(forKey: "giftMessageId") } encoder.encodeBool(upgradeSeparate, forKey: "upgradeSeparate") - case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate): + case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars): encoder.encodeInt32(45, forKey: "_rawValue") encoder.encodeObject(gift, forKey: "gift") encoder.encodeBool(isUpgrade, forKey: "isUpgrade") @@ -819,6 +819,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "canResaleDate") } + if let dropOriginalDetailsStars { + encoder.encodeInt64(dropOriginalDetailsStars, forKey: "dropOriginalDetailsStars") + } else { + encoder.encodeNil(forKey: "dropOriginalDetailsStars") + } case let .paidMessagesRefunded(count, stars): encoder.encodeInt32(46, forKey: "_rawValue") encoder.encodeInt32(count, forKey: "count") @@ -908,7 +913,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { peerIds.append(releasedBy) } return peerIds - case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _): + case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _): var peerIds: [PeerId] = [] if let peerId { peerIds.append(peerId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 11194a5e51..755a87caec 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -19,6 +19,7 @@ public enum BotPaymentInvoiceSource { case premiumGift(peerId: EnginePeer.Id, option: CachedPremiumGiftOption, text: String?, entities: [MessageTextEntity]?) case starGiftResale(slug: String, toPeerId: EnginePeer.Id, ton: Bool) case starGiftPrepaidUpgrade(peerId: EnginePeer.Id, hash: String) + case starGiftDropOriginalInfo(reference: StarGiftReference) } public struct BotPaymentInvoiceFields: OptionSet { @@ -420,6 +421,8 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv return nil } return .inputInvoiceStarGiftPrepaidUpgrade(peer: inputPeer, hash: hash) + case let .starGiftDropOriginalInfo(reference): + return reference.apiStarGiftReference(transaction: transaction).flatMap { .inputInvoiceStarGiftDropOriginalDetails(stargift: $0) } } } @@ -761,7 +764,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa receiptMessageId = id } } - case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade: + case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalInfo: receiptMessageId = nil } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index f127989c2e..437ad1ffea 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -1225,7 +1225,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star case let .updateNewMessage(message, _, _): if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) { for media in message.media { - if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate) = action.action, case let .Id(messageId) = message.id { + if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars) = action.action, case let .Id(messageId) = message.id { let reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) @@ -1251,7 +1251,8 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star canResaleDate: canResaleDate, collectionIds: nil, prepaidUpgradeHash: nil, - upgradeSeparate: false + upgradeSeparate: false, + dropOriginalDetailsStars: dropOriginalDetailsStars )) } } @@ -2164,6 +2165,7 @@ public final class ProfileGiftsContext { case collectionIds case prepaidUpgradeHash case upgradeSeparate + case dropOriginalDetailsStars } public let gift: TelegramCore.StarGift @@ -2185,6 +2187,7 @@ public final class ProfileGiftsContext { public let collectionIds: [Int32]? public let prepaidUpgradeHash: String? public let upgradeSeparate: Bool + public let dropOriginalDetailsStars: Int64? fileprivate let _fromPeerId: EnginePeer.Id? @@ -2211,7 +2214,8 @@ public final class ProfileGiftsContext { canResaleDate: Int32?, collectionIds: [Int32]?, prepaidUpgradeHash: String?, - upgradeSeparate: Bool + upgradeSeparate: Bool, + dropOriginalDetailsStars: Int64? ) { self.gift = gift self.reference = reference @@ -2233,6 +2237,7 @@ public final class ProfileGiftsContext { self.collectionIds = collectionIds self.prepaidUpgradeHash = prepaidUpgradeHash self.upgradeSeparate = upgradeSeparate + self.dropOriginalDetailsStars = dropOriginalDetailsStars } public init(from decoder: Decoder) throws { @@ -2264,6 +2269,7 @@ public final class ProfileGiftsContext { self.collectionIds = try container.decodeIfPresent([Int32].self, forKey: .collectionIds) self.prepaidUpgradeHash = try container.decodeIfPresent(String.self, forKey: .prepaidUpgradeHash) self.upgradeSeparate = try container.decodeIfPresent(Bool.self, forKey: .upgradeSeparate) ?? false + self.dropOriginalDetailsStars = try container.decodeIfPresent(Int64.self, forKey: .dropOriginalDetailsStars) } public func encode(to encoder: Encoder) throws { @@ -2288,6 +2294,7 @@ public final class ProfileGiftsContext { try container.encodeIfPresent(self.collectionIds, forKey: .collectionIds) try container.encodeIfPresent(self.prepaidUpgradeHash, forKey: .prepaidUpgradeHash) try container.encode(self.upgradeSeparate, forKey: .upgradeSeparate) + try container.encodeIfPresent(self.dropOriginalDetailsStars, forKey: .dropOriginalDetailsStars) } public func withGift(_ gift: TelegramCore.StarGift) -> StarGift { @@ -2310,7 +2317,8 @@ public final class ProfileGiftsContext { canResaleDate: self.canResaleDate, collectionIds: self.collectionIds, prepaidUpgradeHash: self.prepaidUpgradeHash, - upgradeSeparate: self.upgradeSeparate + upgradeSeparate: self.upgradeSeparate, + dropOriginalDetailsStars: self.dropOriginalDetailsStars ) } @@ -2334,7 +2342,8 @@ public final class ProfileGiftsContext { canResaleDate: self.canResaleDate, collectionIds: self.collectionIds, prepaidUpgradeHash: self.prepaidUpgradeHash, - upgradeSeparate: self.upgradeSeparate + upgradeSeparate: self.upgradeSeparate, + dropOriginalDetailsStars: self.dropOriginalDetailsStars ) } @@ -2358,7 +2367,8 @@ public final class ProfileGiftsContext { canResaleDate: self.canResaleDate, collectionIds: self.collectionIds, prepaidUpgradeHash: self.prepaidUpgradeHash, - upgradeSeparate: self.upgradeSeparate + upgradeSeparate: self.upgradeSeparate, + dropOriginalDetailsStars: self.dropOriginalDetailsStars ) } fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift { @@ -2381,7 +2391,8 @@ public final class ProfileGiftsContext { canResaleDate: self.canResaleDate, collectionIds: self.collectionIds, prepaidUpgradeHash: self.prepaidUpgradeHash, - upgradeSeparate: self.upgradeSeparate + upgradeSeparate: self.upgradeSeparate, + dropOriginalDetailsStars: self.dropOriginalDetailsStars ) } @@ -2405,7 +2416,8 @@ public final class ProfileGiftsContext { canResaleDate: self.canResaleDate, collectionIds: collectionIds, prepaidUpgradeHash: self.prepaidUpgradeHash, - upgradeSeparate: self.upgradeSeparate + upgradeSeparate: self.upgradeSeparate, + dropOriginalDetailsStars: self.dropOriginalDetailsStars ) } } @@ -2609,7 +2621,7 @@ public final class ProfileGiftsContext { extension ProfileGiftsContext.State.StarGift { init?(apiSavedStarGift: Api.SavedStarGift, peerId: EnginePeer.Id, transaction: Transaction) { switch apiSavedStarGift { - case let .savedStarGift(flags, fromId, date, apiGift, message, msgId, savedId, convertStars, upgradeStars, canExportDate, transferStars, canTransferAt, canResaleAt, collectionIds, prepaidUpgradeHash): + case let .savedStarGift(flags, fromId, date, apiGift, message, msgId, savedId, convertStars, upgradeStars, canExportDate, transferStars, canTransferAt, canResaleAt, collectionIds, prepaidUpgradeHash, dropOriginalDetailsStars): guard let gift = StarGift(apiStarGift: apiGift) else { return nil } @@ -2658,6 +2670,7 @@ extension ProfileGiftsContext.State.StarGift { self.collectionIds = collectionIds self.prepaidUpgradeHash = prepaidUpgradeHash self.upgradeSeparate = (flags & (1 << 17)) != 0 + self.dropOriginalDetailsStars = dropOriginalDetailsStars } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 1b1e7ba771..4c2bcc9481 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1626,10 +1626,10 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot receiptMessageId = id } } - case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade: + case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalInfo: receiptMessageId = nil } - } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate) = action.action, case let .Id(messageId) = message.id { + } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars) = action.action, case let .Id(messageId) = message.id { let reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) @@ -1655,7 +1655,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot canResaleDate: canResaleDate, collectionIds: nil, prepaidUpgradeHash: nil, - upgradeSeparate: false + upgradeSeparate: false, + dropOriginalDetailsStars: dropOriginalDetailsStars ) } } diff --git a/submodules/TelegramStringFormatting/Sources/PresenceStrings.swift b/submodules/TelegramStringFormatting/Sources/PresenceStrings.swift index 8a8176e703..877eaa0b8b 100644 --- a/submodules/TelegramStringFormatting/Sources/PresenceStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/PresenceStrings.swift @@ -671,3 +671,14 @@ public func stringForIntervalSinceUpdateAction(strings: PresentationStrings, val return strings.Chat_NonContactUser_UpdatedDays(Int32(round(Float(value) / (24 * 60 * 60)))) } } + +public func stringForGiftUpgradeTimestamp(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, timestamp: Int32) -> String { + var t: time_t = time_t(timestamp) + var timeinfo: tm = tm() + localtime_r(&t, &timeinfo) + + let time = stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, dateTimeFormat: dateTimeFormat) + let date = strings.Date_ChatDateHeader(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)").string + + return "\(time), \(date)" +} diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 0f9bc6fdf7..d44cba23de 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1257,7 +1257,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes) } } - case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _): + case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _): if case let .unique(gift) = gift { if !forAdditionalServiceMessage && !"".isEmpty { attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 838412e9d1..6bc942ba62 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -278,7 +278,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { switch action.action { case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): releasedBy = gift.releasedBy - case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _): releasedBy = gift.releasedBy default: break @@ -650,7 +650,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { buttonTitle = item.presentationData.strings.Notification_StarGift_View } } - case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _): + case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _): if case let .unique(uniqueGift) = gift { isStarGift = true diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift index 81fe4dc6bc..93134eede7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift @@ -439,6 +439,7 @@ public class ChatMessagePaymentAlertController: AlertController { starsContext: starsContext, options: options, purpose: .generic, + targetPeerId: nil, completion: { _ in } ) navigationController.pushViewController(controller) diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 46503735dc..1411ca146a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -1420,11 +1420,17 @@ private final class ChatSendStarsScreenComponent: Component { theme: environment.theme, currency: .stars, action: { [weak self] in - guard let self, let starsContext = context.starsContext, let navigationController = self.environment?.controller()?.navigationController as? NavigationController else { + guard let self, let component = self.component, let starsContext = context.starsContext, let navigationController = self.environment?.controller()?.navigationController as? NavigationController else { return } self.environment?.controller()?.dismiss() + let targetPeerId: EnginePeer.Id + switch component.initialData.subjectInitialData { + case let .react(reactData): + targetPeerId = reactData.peer.id + } + let _ = (context.engine.payments.starsTopUpOptions() |> take(1) |> deliverOnMainQueue).startStandalone(next: { options in @@ -1433,6 +1439,7 @@ private final class ChatSendStarsScreenComponent: Component { starsContext: starsContext, options: options, purpose: .generic, + targetPeerId: targetPeerId, completion: { _ in } ) navigationController.pushViewController(controller) @@ -2295,7 +2302,7 @@ private final class ChatSendStarsScreenComponent: Component { purchasePurpose = .reactions(peerId: reactData.peer.id, requiredStars: Int64(self.amount.realValue)) } - let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: purchasePurpose, completion: { result in + let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: purchasePurpose, targetPeerId: nil, completion: { result in let _ = result //TODO:release }) diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 637ae69fd4..cbbbcc7441 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -837,6 +837,7 @@ final class GiftOptionsScreenComponent: Component { starsContext: starsContext, options: options ?? [], purpose: .transferStarGift(requiredStars: transferStars), + targetPeerId: nil, completion: { stars in starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) proceed(true) diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index 8002c57a46..16d5bb870c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -519,6 +519,7 @@ final class GiftSetupScreenComponent: Component { starsContext: starsContext, options: options ?? [], purpose: .starGift(peerId: component.peerId, requiredStars: finalPrice), + targetPeerId: nil, completion: { [weak self, weak starsContext] stars in guard let self, let starsContext else { return @@ -1130,7 +1131,7 @@ final class GiftSetupScreenComponent: Component { |> filter { $0 != nil } |> take(1) |> deliverOnMainQueue).startStandalone(next: { options in - let purchaseController = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options ?? [], purpose: .generic, completion: { stars in + let purchaseController = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options ?? [], purpose: .generic, targetPeerId: nil, completion: { stars in starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) }) controller.push(purchaseController) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index 47ded4318a..956417154e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -55,6 +55,7 @@ swift_library( "//submodules/TelegramUI/Components/Stars/BalanceNeededScreen", "//submodules/TelegramUI/Components/ChatThemeScreen", "//submodules/ImageBlur", + "//submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index e868f15c2f..f9f410b955 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1943,6 +1943,7 @@ private final class GiftViewSheetContent: CombinedComponent { starsContext: starsContext, options: options ?? [], purpose: .upgradeStarGift(requiredStars: price), + targetPeerId: nil, completion: { [weak self, weak starsContext] stars in guard let self, let starsContext else { return diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index de9497d768..49f2834ccf 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3730,7 +3730,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } } else if case let .gift(gift) = subject { isGift = true - let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil))] + let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil, dropOriginalDetailsStars: nil))] let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messages = .single([message]) } else { diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index 6a5acba10c..5847fe1151 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -1010,6 +1010,7 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { starsContext: StarsContext, options: [Any] = [], purpose: StarsPurchasePurpose, + targetPeerId: EnginePeer.Id?, completion: @escaping (Int64) -> Void = { _ in } ) { self.context = context diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index 5156bf757c..9b7279aec3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -910,7 +910,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { guard let self, let starsContext = context.starsContext else { return } - let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, completion: { [weak self] stars in + let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, targetPeerId: nil, completion: { [weak self] stars in guard let self else { return } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 52d60129f8..487cd63aa5 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -1334,7 +1334,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { guard let self else { return } - let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, completion: { [weak self] stars in + let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, targetPeerId: nil, completion: { [weak self] stars in guard let self else { return } @@ -1458,6 +1458,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { starsContext: starsContext, options: options, purpose: .gift(peerId: peerId), + targetPeerId: nil, completion: { [weak self] stars in guard let self else { return diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 195dffb752..48f3109be5 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -588,6 +588,7 @@ private final class SheetContent: CombinedComponent { starsContext: starsContext, options: state?.options ?? [], purpose: purpose, + targetPeerId: nil, completion: { [weak starsContext] stars in guard let starsContext else { return diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 5bf3c37110..34945bf30b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -894,7 +894,7 @@ private final class SheetContent: CombinedComponent { guard let controller, let state else { return } - let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in + let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, targetPeerId: nil, completion: { _ in }) controller.push(purchaseController) }) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index a0ea346e17..cba1adbcbf 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -4288,7 +4288,7 @@ extension ChatControllerImpl { guard let self else { return } - let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in + let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .generic, targetPeerId: nil, completion: { _ in }) self.push(controller) }) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index e0d05c58b9..41d40bcaea 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -462,7 +462,7 @@ extension ChatControllerImpl { return } - let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: message.id.peerId, requiredStars: 1), completion: { result in + let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: message.id.peerId, requiredStars: 1), targetPeerId: nil, completion: { result in let _ = result }) strongSelf.push(purchaseScreen) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift index 62620a0dff..33eafb721a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift @@ -76,7 +76,7 @@ extension ChatControllerImpl { guard let self else { return } - let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), completion: { stars in + let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), targetPeerId: nil, completion: { stars in starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) let _ = (starsContext.onUpdate |> deliverOnMainQueue).start(next: { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 70c2e024cd..45fed1b64a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1811,7 +1811,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: peerId, requiredStars: 1), completion: { result in + let purchaseScreen = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .reactions(peerId: peerId, requiredStars: 1), targetPeerId: nil, completion: { result in let _ = result }) strongSelf.push(purchaseScreen) @@ -2441,7 +2441,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf else { return } - let purchaseController = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in + let purchaseController = strongSelf.context.sharedContext.makeStarsPurchaseScreen(context: strongSelf.context, starsContext: starsContext, options: options, purpose: .generic, targetPeerId: nil, completion: { _ in }) strongSelf.push(purchaseController) }) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index c762d6e293..12fe97c3fe 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1131,7 +1131,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState let sendGiftTitle: String var isIncoming = message.effectivelyIncoming(context.account.peerId) for media in message.media { - if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _) = action.action { + if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action { if isUpgrade && message.author?.id == context.account.peerId { isIncoming = true } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index ae30ec10c5..783790aa69 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -844,7 +844,7 @@ func openResolvedUrlImpl( dismissInput() if let starsContext = context.starsContext { let proceed = { - let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .topUp(requiredStars: amount, purpose: purpose), completion: { _ in }) + let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .topUp(requiredStars: amount, purpose: purpose), targetPeerId: nil, completion: { _ in }) if let navigationController = navigationController { navigationController.pushViewController(controller, animated: true) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 59b425118f..99ce9bc265 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3287,6 +3287,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { starsContext: starsContext, options: options ?? [], purpose: .transferStarGift(requiredStars: transferStars), + targetPeerId: nil, completion: { stars in starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) proceed(true) @@ -3693,8 +3694,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return StarsTransactionsScreen(context: context, starsContext: starsContext) } - public func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, completion: @escaping (Int64) -> Void) -> ViewController { - return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: purpose, completion: completion) + public func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, targetPeerId: EnginePeer.Id?, completion: @escaping (Int64) -> Void) -> ViewController { + return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: purpose, targetPeerId: targetPeerId, completion: completion) } public func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, extendedMedia: [TelegramExtendedMedia], inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController { From 33fe6eb47635dff5fe4353016c4aa82f8b99bb32 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 6 Sep 2025 14:59:50 +0500 Subject: [PATCH 02/13] iPad wallpaper improvements --- .../Display/Source/ContainerViewLayout.swift | 65 ++++++++++++------- .../Sources/ChatControllerNode.swift | 13 ++-- .../Sources/WallpaperBackgroundNode.swift | 16 +++-- 3 files changed, 63 insertions(+), 31 deletions(-) diff --git a/submodules/Display/Source/ContainerViewLayout.swift b/submodules/Display/Source/ContainerViewLayout.swift index f67059afc6..bfd209491b 100644 --- a/submodules/Display/Source/ContainerViewLayout.swift +++ b/submodules/Display/Source/ContainerViewLayout.swift @@ -140,36 +140,55 @@ public extension ContainerViewLayout { return false } + var deviceOrientationSize: CGSize { + let screenSize = self.deviceMetrics.screenSize + return self.actualOrientation == .landscape ? CGSize(width: screenSize.height, height: screenSize.width) : screenSize + } + var inSplitView: Bool { - var maybeSplitView = false - if case .tablet = self.deviceMetrics.type { - if case .compact = self.metrics.widthClass { - maybeSplitView = true - } - if case .compact = self.metrics.heightClass { - maybeSplitView = true - } + guard case .tablet = self.deviceMetrics.type else { + return false } - if maybeSplitView && abs(max(self.size.width, self.size.height) - self.deviceMetrics.screenSize.height) < 1.0 { + guard self.metrics.widthClass == .compact || self.metrics.heightClass == .compact else { + return false + } + + let orient = self.deviceOrientationSize + guard abs(self.size.height - orient.height) < 1.0 else { + return false + } + + let ratio = self.size.width / max(orient.width, 1.0) + let tol: CGFloat = 0.04 + let isSplitFraction = abs(ratio - 0.5) < tol || abs(ratio - (1.0/3.0)) < tol || abs(ratio - (2.0/3.0)) < tol + + return isSplitFraction + } + + var inSlideOver: Bool { + guard case .tablet = self.deviceMetrics.type else { + return false + } + guard self.metrics.widthClass == .compact || self.metrics.heightClass == .compact else { + return false + } + let currentLong = max(self.size.width, self.size.height) + let screenLong = max(self.deviceMetrics.screenSize.width, self.deviceMetrics.screenSize.height) + + if abs(currentLong - screenLong) > 10.0 { return true } return false } - var inSlideOver: Bool { - var maybeSlideOver = false - if case .tablet = self.deviceMetrics.type { - if case .compact = self.metrics.widthClass { - maybeSlideOver = true - } - if case .compact = self.metrics.heightClass { - maybeSlideOver = true - } - } - if maybeSlideOver && abs(max(self.size.width, self.size.height) - self.deviceMetrics.screenSize.height) > 10.0 { - return true - } - return false + var actualOrientation: LayoutOrientation { + let screenPortraitHeight = max(self.deviceMetrics.screenSize.width, self.deviceMetrics.screenSize.height) + let screenPortraitWidth = min(self.deviceMetrics.screenSize.width, self.deviceMetrics.screenSize.height) + + let deltaPortrait = abs(self.size.height - screenPortraitHeight) + let deltaLandscape = abs(self.size.height - screenPortraitWidth) + + return deltaLandscape < deltaPortrait ? .landscape : .portrait } var orientation: LayoutOrientation { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 4666344780..7cd2e4e94a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1929,10 +1929,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if case .regular = layout.metrics.widthClass, layout.size.height == layout.deviceMetrics.screenSize.width { displayMode = .aspectFit } else if case .compact = layout.metrics.widthClass { - if layout.size.width < layout.size.height && layout.size.height < layout.deviceMetrics.screenSize.height { - wallpaperBounds.size = layout.deviceMetrics.screenSize - } else if layout.size.width > layout.size.height && layout.size.height < layout.deviceMetrics.screenSize.width { - wallpaperBounds.size = layout.deviceMetrics.screenSize + if layout.inSplitView { + displayMode = .aspectFit + } else if layout.inSlideOver { + switch layout.actualOrientation { + case .portrait: + wallpaperBounds.size = CGSize(width: layout.size.width, height: layout.deviceMetrics.screenSize.height) + case .landscape: + wallpaperBounds.size = CGSize(width: layout.size.width, height: layout.deviceMetrics.screenSize.width) + } } } self.backgroundNode.updateLayout(size: wallpaperBounds.size, displayMode: displayMode, transition: transition) diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 4bfe3ee995..e605ee443a 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -1401,16 +1401,24 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou let targetSize: CGSize = self.bounds.size let containerSize: CGSize = rect.containerSize - let useAspectFit: Bool = false - let renderScale: CGFloat = useAspectFit + let isAspectFit: Bool = (displayMode == .aspectFit || displayMode == .halfAspectFill) + + let renderScale: CGFloat = isAspectFit ? min(targetSize.width / containerSize.width, targetSize.height / containerSize.height) : max(targetSize.width / containerSize.width, targetSize.height / containerSize.height) let drawingSize = CGSize(width: containerSize.width * renderScale, height: containerSize.height * renderScale) - let offsetX = (targetSize.width - drawingSize.width) * 0.5 - let offsetY = (targetSize.height - drawingSize.height) * 0.5 + let offsetX: CGFloat + let offsetY: CGFloat + if isAspectFit { + offsetX = 0.0 + offsetY = (targetSize.height - drawingSize.height) * 0.5 + } else { + offsetX = (targetSize.width - drawingSize.width) * 0.5 + offsetY = (targetSize.height - drawingSize.height) * 0.5 + } let onScreenCenter = CGPoint(x: offsetX + rect.center.x * renderScale, y: offsetY + rect.center.y * renderScale) From c28ca9ec166f2333b1e4bfc124c2567468c00742 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 6 Sep 2025 15:16:47 +0500 Subject: [PATCH 03/13] Fix build --- .../Gifts/GiftViewScreen/Sources/GiftViewScreen.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index f9f410b955..242814eb97 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1558,6 +1558,7 @@ private final class GiftViewSheetContent: CombinedComponent { starsContext: starsContext, options: options ?? [], purpose: .buyStarGift(requiredStars: resellAmount.amount.value), + targetPeerId: nil, completion: { [weak self, weak starsContext] stars in guard let self, let starsContext else { return @@ -1851,6 +1852,7 @@ private final class GiftViewSheetContent: CombinedComponent { starsContext: starsContext, options: options ?? [], purpose: .upgradeStarGift(requiredStars: price), + targetPeerId: nil, completion: { [weak self, weak starsContext] stars in guard let self, let starsContext else { return @@ -3528,7 +3530,7 @@ private final class GiftViewSheetContent: CombinedComponent { var itemAlignment: HStackAlignment = .left var itemSpacing: CGFloat = 4.0 - if id == "originalInfo" { + if !"".isEmpty && id == "originalInfo" { items.append(AnyComponentWithIdentity( id: AnyHashable(1), component: AnyComponent(Button( @@ -3539,7 +3541,7 @@ private final class GiftViewSheetContent: CombinedComponent { )) )) itemAlignment = .alternatingLeftRight - itemSpacing = 16.0 + itemSpacing = 8.0 } var itemComponent = AnyComponent( @@ -3655,7 +3657,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let upgradeStars, upgradeStars > 0 { finalStars += upgradeStars } - let valueString = "\(presentationStringsFormattedNumber(abs(Int32(finalStars)), dateTimeFormat.groupingSeparator))⭐️" + let valueString = "\(presentationStringsFormattedNumber(abs(Int32(clamping: finalStars)), dateTimeFormat.groupingSeparator))⭐️" let valueAttributedString = NSMutableAttributedString(string: valueString, font: tableFont, textColor: tableTextColor) let range = (valueAttributedString.string as NSString).range(of: "⭐️") if range.location != NSNotFound { @@ -3781,7 +3783,7 @@ private final class GiftViewSheetContent: CombinedComponent { let table = table.update( component: TableComponent( - theme: environment.theme, + theme: theme, items: tableItems ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), From 5c0796b536dda0bfc4362b59437a500ccba68f5a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 6 Sep 2025 15:21:16 +0500 Subject: [PATCH 04/13] Fix build --- .../Gifts/GiftViewScreen/Sources/GiftViewScreen.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 242814eb97..d826c3ff36 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -4510,7 +4510,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { reference = .message(messageId: message.id) } return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate) - case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate): + case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, _): var reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) @@ -4842,6 +4842,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { starsContext: starsContext, options: options, purpose: .generic, + targetPeerId: nil, completion: { _ in } ) navigationController.pushViewController(controller) From 141e7c1f49685ac1e1109c9efadd8f30c47c3bc2 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 7 Sep 2025 06:49:28 +0500 Subject: [PATCH 05/13] Gift original details removal --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../AccountContext/Sources/Premium.swift | 1 + .../MultilineTextWithEntitiesComponent.swift | 15 ++- .../Payments/BotPaymentForm.swift | 6 +- .../TelegramEngine/Payments/StarGifts.swift | 69 ++++++++++++ .../TelegramEngine/Payments/Stars.swift | 2 +- .../Payments/TelegramEnginePayments.swift | 4 + .../GiftRemoveInfoAlertController.swift | 6 +- .../Sources/GiftViewScreen.swift | 106 +++++++++++++++--- .../Sources/TableComponent.swift | 18 ++- .../Sources/PeerInfoScreen.swift | 6 + .../Sources/GiftsListView.swift | 6 + .../Sources/StarsPurchaseScreen.swift | 4 +- submodules/WebUI/Sources/WebAppWebView.swift | 2 +- 14 files changed, 221 insertions(+), 26 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index bc5270db2d..9adf4412eb 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15013,3 +15013,5 @@ Sorry for the inconvenience."; "Gift.Options.Collectibles.Text" = "Collectible gifts are unique digital items you can exchange or sell."; "Gift.Upgrade.UpgradeFor" = "Upgrade for %@"; + +"Stars.Purchase.RemoveOriginalDetailsStarGiftInfo" = "Buy Stars to remove original details of your gift."; diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 997b692306..47e50d3b10 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -143,6 +143,7 @@ public enum StarsPurchasePurpose: Equatable { case transferStarGift(requiredStars: Int64) case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64) case buyStarGift(requiredStars: Int64) + case removeOriginalDetailsStarGift(requiredStars: Int64) } public struct PremiumConfiguration { diff --git a/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift b/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift index aeb2ea838f..2476daa993 100644 --- a/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift +++ b/submodules/Components/MultilineTextWithEntitiesComponent/Sources/MultilineTextWithEntitiesComponent.swift @@ -35,6 +35,7 @@ public final class MultilineTextWithEntitiesComponent: Component { public let manualVisibilityControl: Bool public let resetAnimationsOnVisibilityChange: Bool public let displaysAsynchronously: Bool + public let maxWidth: CGFloat? public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? @@ -60,6 +61,7 @@ public final class MultilineTextWithEntitiesComponent: Component { manualVisibilityControl: Bool = false, resetAnimationsOnVisibilityChange: Bool = false, displaysAsynchronously: Bool = true, + maxWidth: CGFloat? = nil, highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil, tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil, longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil @@ -85,6 +87,7 @@ public final class MultilineTextWithEntitiesComponent: Component { self.manualVisibilityControl = manualVisibilityControl self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange self.displaysAsynchronously = displaysAsynchronously + self.maxWidth = maxWidth self.tapAction = tapAction self.longTapAction = longTapAction } @@ -126,6 +129,9 @@ public final class MultilineTextWithEntitiesComponent: Component { if lhs.displaysAsynchronously != rhs.displaysAsynchronously { return false } + if lhs.maxWidth != rhs.maxWidth { + return false + } if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor { if !lhsTextShadowColor.isEqual(rhsTextShadowColor) { return false @@ -237,7 +243,12 @@ public final class MultilineTextWithEntitiesComponent: Component { ) } - let size = self.textNode.updateLayout(availableSize) + var constrainedSize = availableSize + if let maxWidth = component.maxWidth { + constrainedSize.width = maxWidth + } + + let size = self.textNode.updateLayout(constrainedSize) self.textNode.frame = CGRect(origin: .zero, size: size) if component.handleSpoilers { @@ -266,7 +277,7 @@ public final class MultilineTextWithEntitiesComponent: Component { spoilerTextNode.textStroke = component.textStroke spoilerTextNode.isUserInteractionEnabled = false - let size = spoilerTextNode.updateLayout(availableSize) + let size = spoilerTextNode.updateLayout(constrainedSize) spoilerTextNode.frame = CGRect(origin: .zero, size: size) if spoilerTextNode.view.superview == nil { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 755a87caec..29a0d4dffe 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -19,7 +19,7 @@ public enum BotPaymentInvoiceSource { case premiumGift(peerId: EnginePeer.Id, option: CachedPremiumGiftOption, text: String?, entities: [MessageTextEntity]?) case starGiftResale(slug: String, toPeerId: EnginePeer.Id, ton: Bool) case starGiftPrepaidUpgrade(peerId: EnginePeer.Id, hash: String) - case starGiftDropOriginalInfo(reference: StarGiftReference) + case starGiftDropOriginalDetails(reference: StarGiftReference) } public struct BotPaymentInvoiceFields: OptionSet { @@ -421,7 +421,7 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv return nil } return .inputInvoiceStarGiftPrepaidUpgrade(peer: inputPeer, hash: hash) - case let .starGiftDropOriginalInfo(reference): + case let .starGiftDropOriginalDetails(reference): return reference.apiStarGiftReference(transaction: transaction).flatMap { .inputInvoiceStarGiftDropOriginalDetails(stargift: $0) } } } @@ -764,7 +764,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa receiptMessageId = id } } - case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalInfo: + case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails: receiptMessageId = nil } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 437ad1ffea..fdf02ea5f8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -830,6 +830,27 @@ public enum StarGift: Equatable, Codable, PostboxCoding { themePeerId: themePeerId ) } + + public func withAttributes(_ attributes: [Attribute]) -> UniqueGift { + return UniqueGift( + id: self.id, + giftId: self.giftId, + title: self.title, + number: self.number, + slug: self.slug, + owner: self.owner, + attributes: attributes, + availability: self.availability, + giftAddress: self.giftAddress, + resellAmounts: self.resellAmounts, + resellForTonOnly: self.resellForTonOnly, + releasedBy: self.releasedBy, + valueAmount: self.valueAmount, + valueCurrency: self.valueCurrency, + flags: self.flags, + themePeerId: self.themePeerId + ) + } } public enum DecodingError: Error { @@ -1135,6 +1156,25 @@ func _internal_buyStarGift(account: Account, slug: String, peerId: EnginePeer.Id } } +public enum DropStarGiftOriginalDetailsError { + case generic +} + +func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGiftReference) -> Signal { + let source: BotPaymentInvoiceSource = .starGiftDropOriginalDetails(reference: reference) + return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil) + |> `catch` { error -> Signal in + return .fail(.generic) + } + |> mapToSignal { paymentForm in + return _internal_sendStarsPaymentForm(account: account, formId: paymentForm.id, source: source) + |> mapError { _ -> DropStarGiftOriginalDetailsError in + return .generic + } + |> ignoreValues + } +} + func _internal_transferStarGift(account: Account, prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal { return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputSavedStarGift)? in guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer), let starGift = reference.apiStarGiftReference(transaction: transaction) else { @@ -1791,6 +1831,21 @@ private final class ProfileGiftsContextImpl { }) ) } + + public func dropOriginalDetails(reference: StarGiftReference) -> Signal { + if let index = self.gifts.firstIndex(where: { $0.reference == reference }), case let .unique(uniqueGift) = self.gifts[index].gift { + let updatedUniqueGift = uniqueGift.withAttributes(uniqueGift.attributes.filter { $0.attributeType != .originalInfo }) + self.gifts[index] = self.gifts[index].withGift(.unique(updatedUniqueGift)) + } + if let index = self.filteredGifts.firstIndex(where: { $0.reference == reference }), case let .unique(uniqueGift) = self.filteredGifts[index].gift { + let updatedUniqueGift = uniqueGift.withAttributes(uniqueGift.attributes.filter { $0.attributeType != .originalInfo }) + self.filteredGifts[index] = self.filteredGifts[index].withGift(.unique(updatedUniqueGift)) + } + + self.pushState() + + return _internal_dropStarGiftOriginalDetails(account: self.account, reference: reference) + } func convertStarGift(reference: StarGiftReference) { self.actionDisposable.set( @@ -2503,6 +2558,20 @@ public final class ProfileGiftsContext { } } + public func dropOriginalDetails(reference: StarGiftReference) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.dropOriginalDetails(reference: reference).start(error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + })) + } + return disposable + } + } + public func convertStarGift(reference: StarGiftReference) { self.impl.with { impl in impl.convertStarGift(reference: reference) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 4c2bcc9481..7b8bb42737 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1626,7 +1626,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot receiptMessageId = id } } - case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalInfo: + case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails: receiptMessageId = nil } } else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars) = action.action, case let .Id(messageId) = message.id { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index d1e4183ffb..4454f56559 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -125,6 +125,10 @@ public extension TelegramEngine { return _internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: added) } + public func dropStarGiftOriginalDetails(reference: StarGiftReference) -> Signal { + return _internal_dropStarGiftOriginalDetails(account: self.account, reference: reference) + } + public func transferStarGift(prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal { return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift index 203f9ec1a4..4e4a1940a2 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift @@ -241,7 +241,7 @@ private final class GiftRemoveInfoAlertContentNode: AlertContentNode { ) ), environment: {}, - containerSize: CGSize(width: contentWidth - 32.0, height: size.height) + containerSize: CGSize(width: contentWidth - 64.0, height: size.height) ) let infoFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - infoSize.width) / 2.0), y: titleSize.height + textSize.height + 54.0), size: infoSize) if let view = self.infoView.view { @@ -326,8 +326,8 @@ public func giftRemoveInfoAlertController( var contentNode: GiftRemoveInfoAlertContentNode? var dismissImpl: ((Bool) -> Void)? - let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in - contentNode?.inProgress = true + let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { + dismissImpl?(true) commit() }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { dismissImpl?(true) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index d826c3ff36..2436a8385f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -664,22 +664,98 @@ private final class GiftViewSheetContent: CombinedComponent { controller.push(introController) } - func openRemoveInfo() { - guard let controller = self.getController(), let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift else { + func openDropOriginalDetails() { + guard let controller = self.getController(), let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, let price = self.subject.arguments?.dropOriginalDetailsStars else { return } - //TODO:release let removeInfoController = giftRemoveInfoAlertController( context: self.context, gift: uniqueGift, peers: self.peerMap, - removeInfoStars: 1000, + removeInfoStars: price, navigationController: controller.navigationController as? NavigationController, - commit: {} + commit: { [weak self] in + guard let self else { + return + } + self.commitDropOriginalDetails() + } ) controller.present(removeInfoController, in: .window(.root)) } + func commitDropOriginalDetails() { + guard let arguments = self.subject.arguments, let controller = self.getController() as? GiftViewScreen, let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, let starsContext = self.context.starsContext, let starsState = starsContext.currentState, let reference = arguments.reference, let price = self.subject.arguments?.dropOriginalDetailsStars else { + return + } + + let context = self.context + let proceed = { + let dropOriginalDetailsImpl = controller.dropOriginalDetails + + let signal: Signal + if let dropOriginalDetailsImpl { + signal = dropOriginalDetailsImpl(reference) + } else { + signal = (context.engine.payments.dropStarGiftOriginalDetails(reference: reference) + |> deliverOnMainQueue) + } + + self.upgradeDisposable = (signal + |> deliverOnMainQueue).start(error: { _ in + }, completed: { [weak self, weak starsContext] in + guard let self else { + return + } + Queue.mainQueue().after(0.5) { + starsContext?.load(force: true) + } + switch self.subject { + case let .profileGift(peerId, gift): + let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo } + self.subject = .profileGift(peerId, gift.withGift(.unique(uniqueGift.withAttributes(updatedAttributes)))) + default: + break + } + self.updated(transition: .spring(duration: 0.3)) + }) + } + + if starsState.balance < StarsAmount(value: price, nanos: 0) { + let _ = (self.optionsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] options in + guard let self, let controller = self.getController() else { + return + } + let purchaseController = self.context.sharedContext.makeStarsPurchaseScreen( + context: self.context, + starsContext: starsContext, + options: options ?? [], + purpose: .removeOriginalDetailsStarGift(requiredStars: price), + targetPeerId: nil, + completion: { [weak self, weak starsContext] stars in + guard let self, let starsContext else { + return + } + self.inProgress = true + self.updated() + + starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) + let _ = (starsContext.onUpdate + |> deliverOnMainQueue).start(next: { + proceed() + }) + } + ) + controller.push(purchaseController) + }) + } else { + proceed() + } + } + private var isOpeningValue = false func openValue() { guard let controller = self.getController(), let gift = self.subject.arguments?.gift, case let .unique(uniqueGift) = gift, !self.isOpeningValue else { @@ -3493,6 +3569,7 @@ private final class GiftViewSheetContent: CombinedComponent { insets: id == "originalInfo" ? UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0) : .zero, highlightColor: tableLinkColor.withAlphaComponent(0.1), handleSpoilers: true, + maxWidth: id == "originalInfo" ? context.availableSize.width - sideInset * 2.0 - 68.0 : nil, highlightAction: { attributes in if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] { return NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention) @@ -3530,13 +3607,13 @@ private final class GiftViewSheetContent: CombinedComponent { var itemAlignment: HStackAlignment = .left var itemSpacing: CGFloat = 4.0 - if !"".isEmpty && id == "originalInfo" { + if id == "originalInfo", let _ = subject.arguments?.dropOriginalDetailsStars { items.append(AnyComponentWithIdentity( id: AnyHashable(1), component: AnyComponent(Button( content: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Delete", tintColor: tableLinkColor)), action: { [weak state] in - state?.openRemoveInfo() + state?.openDropOriginalDetails() } )) )) @@ -4495,7 +4572,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { case upgradePreview([StarGift.UniqueGift.Attribute], String) case wearPreview(StarGift.UniqueGift) - var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?, prepaidUpgradeHash: String?, upgradeSeparate: Bool)? { + var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?, prepaidUpgradeHash: String?, upgradeSeparate: Bool, dropOriginalDetailsStars: Int64?)? { switch self { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { @@ -4509,8 +4586,8 @@ public class GiftViewScreen: ViewControllerComponentContainer { } else { reference = .message(messageId: message.id) } - return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate) - case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, _): + return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate, nil) + case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars): var reference: StarGiftReference if let peerId, let savedId { reference = .peer(peerId: peerId, id: savedId) @@ -4534,13 +4611,13 @@ public class GiftViewScreen: ViewControllerComponentContainer { if case let .unique(uniqueGift) = gift { resellAmounts = uniqueGift.resellAmounts } - return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate, nil, false) + return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate, nil, false, dropOriginalDetailsStars) default: return nil } } case let .uniqueGift(gift, _), let .wearPreview(gift): - return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil, nil, false) + return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil, nil, false, nil) case let .profileGift(peerId, gift): var messageId: EngineMessage.Id? if case let .message(messageIdValue) = gift.reference { @@ -4550,7 +4627,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { if case let .unique(uniqueGift) = gift.gift { resellAmounts = uniqueGift.resellAmounts } - return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash, gift.upgradeSeparate) + return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash, gift.upgradeSeparate, gift.dropOriginalDetailsStars) case .soldOutGift: return nil case .upgradePreview: @@ -4592,6 +4669,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { fileprivate let updateSavedToProfile: ((StarGiftReference, Bool) -> Void)? fileprivate let convertToStars: ((StarGiftReference) -> Void)? + fileprivate let dropOriginalDetails: ((StarGiftReference) -> Signal)? fileprivate let transferGift: ((Bool, StarGiftReference, EnginePeer.Id) -> Signal)? fileprivate let upgradeGift: ((Int64?, StarGiftReference, Bool) -> Signal)? fileprivate let buyGift: ((String, EnginePeer.Id, CurrencyAmount?) -> Signal)? @@ -4610,6 +4688,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { forceDark: Bool = false, updateSavedToProfile: ((StarGiftReference, Bool) -> Void)? = nil, convertToStars: ((StarGiftReference) -> Void)? = nil, + dropOriginalDetails: ((StarGiftReference) -> Signal)? = nil, transferGift: ((Bool, StarGiftReference, EnginePeer.Id) -> Signal)? = nil, upgradeGift: ((Int64?, StarGiftReference, Bool) -> Signal)? = nil, buyGift: ((String, EnginePeer.Id, CurrencyAmount?) -> Signal)? = nil, @@ -4623,6 +4702,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { self.updateSavedToProfile = updateSavedToProfile self.convertToStars = convertToStars + self.dropOriginalDetails = dropOriginalDetails self.transferGift = transferGift self.upgradeGift = upgradeGift self.buyGift = buyGift diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift index 751dd50684..7c9bf666be 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift @@ -7,15 +7,22 @@ import MultilineTextComponent final class TableComponent: CombinedComponent { class Item: Equatable { + enum TitleFont { + case regular + case bold + } + public let id: AnyHashable public let title: String? + public let titleFont: TitleFont public let hasBackground: Bool public let component: AnyComponent public let insets: UIEdgeInsets? - public init(id: IdType, title: String?, hasBackground: Bool = false, component: AnyComponent, insets: UIEdgeInsets? = nil) { + public init(id: IdType, title: String?, titleFont: TitleFont = .regular, hasBackground: Bool = false, component: AnyComponent, insets: UIEdgeInsets? = nil) { self.id = AnyHashable(id) self.title = title + self.titleFont = titleFont self.hasBackground = hasBackground self.component = component self.insets = insets @@ -28,6 +35,9 @@ final class TableComponent: CombinedComponent { if lhs.title != rhs.title { return false } + if lhs.titleFont != rhs.titleFont { + return false + } if lhs.hasBackground != rhs.hasBackground { return false } @@ -99,7 +109,7 @@ final class TableComponent: CombinedComponent { } 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)) + text: .plain(NSAttributedString(string: title, font: item.titleFont == .bold ? Font.semibold(15.0) : Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor)) )), availableSize: context.availableSize, transition: context.transition @@ -259,12 +269,16 @@ final class TableComponent: CombinedComponent { context.add(valueChild .position(valueFrame.center) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) ) 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)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) ) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index ba34471182..5233c7af60 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -5020,6 +5020,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } profileGifts.convertStarGift(reference: reference) }, + dropOriginalDetails: { [weak profileGifts] reference in + guard let profileGifts else { + return .complete() + } + return profileGifts.dropOriginalDetails(reference: reference) + }, transferGift: { [weak profileGifts] prepaid, reference, peerId in guard let profileGifts else { return .complete() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift index ac6ade2870..08c6b85a34 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift @@ -617,6 +617,12 @@ final class GiftsListView: UIView { } self.profileGifts.convertStarGift(reference: reference) }, + dropOriginalDetails: { [weak self] reference in + guard let self else { + return .complete() + } + return self.profileGifts.dropOriginalDetails(reference: reference) + }, transferGift: { [weak self] prepaid, reference, peerId in guard let self else { return .complete() diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index 5847fe1151..adfe977e20 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -251,6 +251,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { } case .buyStarGift: textString = strings.Stars_Purchase_BuyStarGiftInfo + case .removeOriginalDetailsStarGift: + textString = strings.Stars_Purchase_RemoveOriginalDetailsStarGiftInfo } let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { contents in @@ -834,7 +836,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { titleText = strings.Stars_Purchase_GetStars case .gift: titleText = strings.Stars_Purchase_GiftStars - case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars), let .buyStarGift(requiredStars): + case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars), let .buyStarGift(requiredStars), let .removeOriginalDetailsStarGift(requiredStars): titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) } diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index 3ad33bca74..2c82e6c57a 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -227,7 +227,7 @@ final class WebAppWebView: WKWebView { } func sendEvent(name: String, data: String?) { - let script = "window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))" + let script = "window.TelegramGameProxy && window.TelegramGameProxy.receiveEvent && window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))" self.evaluateJavaScript(script, completionHandler: { _, _ in }) } From 4fc3ba70fc501ec44e3e3197a745f8d226d4bc3d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 7 Sep 2025 14:57:43 +0500 Subject: [PATCH 06/13] Various fixes --- .../Sources/Components/MediaStreamComponent.swift | 15 +++++++-------- .../Components/MediaStreamVideoComponent.swift | 4 +++- .../Sources/MinimizedContainer.swift | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index b292d0058b..8691273307 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -257,7 +257,7 @@ public final class MediaStreamComponent: CombinedComponent { let local = Local() return { context in - _body(context, local) // { context in + _body(context, local) } } @@ -1052,10 +1052,10 @@ public final class MediaStreamComponentController: ViewControllerComponentContai view.expandFromPictureInPicture() } - self.view.clipsToBounds = true - - self.view.layer.animatePosition(from: CGPoint(x: self.view.frame.center.x, y: self.view.bounds.maxY + self.view.bounds.height / 2), to: self.view.center, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in - }) + self.view.clipsToBounds = true + + self.view.layer.animatePosition(from: CGPoint(x: self.view.frame.center.x, y: self.view.bounds.maxY + self.view.bounds.height / 2), to: self.view.center, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in + }) self.view.layer.allowsGroupOpacity = true @@ -1081,8 +1081,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai override public func viewDidLoad() { super.viewDidLoad() - // TODO: replace with actual color - backgroundDimView.backgroundColor = .black.withAlphaComponent(0.3) + self.backgroundDimView.backgroundColor = .black.withAlphaComponent(0.3) self.view.clipsToBounds = false } @@ -1094,7 +1093,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai override public func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let dimViewSide: CGFloat = max(view.bounds.width, view.bounds.height) - backgroundDimView.frame = .init(x: view.bounds.midX - dimViewSide / 2, y: -view.bounds.height * 3, width: dimViewSide, height: view.bounds.height * 4) + self.backgroundDimView.frame = .init(x: view.bounds.midX - dimViewSide / 2, y: -view.bounds.height * 3, width: dimViewSide, height: view.bounds.height * 4) } public func dismiss(closing: Bool, manual: Bool) { diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift index f7d28ce101..f029ed407e 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift @@ -580,7 +580,9 @@ final class MediaStreamVideoComponent: Component { videoView.isHidden = true } } else { - videoSize = CGSize(width: 16 / 9 * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height)) + let availableVideoWidth = min(availableSize.width, availableSize.height) - videoInset * 2 + let availableVideoHeight = availableVideoWidth * 9.0 / 16 + videoSize = CGSize(width: 16 / 9 * 100.0, height: 100.0).aspectFitted(.init(width: availableVideoWidth, height: availableVideoHeight)) } let loadingBlurViewFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize) diff --git a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift index 27629ffebe..723e207e34 100644 --- a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift +++ b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift @@ -431,7 +431,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll if let _ = self.item(at: location.y) { if self.isExpanded { - return abs(velocity.x) > abs(velocity.y) + return abs(velocity.x) > abs(velocity.y) && !self.isApplyingTransition } else { return abs(velocity.y) > abs(velocity.x) } From 3e483aad40216a4f36e739a0f4b007faabec20ba Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 9 Sep 2025 04:28:27 +0400 Subject: [PATCH 07/13] Gift upgrade improvements --- MODULE.bazel.lock | 2 +- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../Components/Gifts/GiftViewScreen/BUILD | 2 +- .../Sources/GiftUpgradeCostScreen.swift | 683 ++++++++++++++++++ .../Sources/GiftViewScreen.swift | 242 ++++++- .../PeerInfo/ProfileLevelInfoScreen/BUILD | 1 + .../Sources/ProfileLevelInfoScreen.swift | 17 +- .../ProfileLevelRatingBarComponent/BUILD | 32 + .../Sources/ProfileLevelRatingBarBadge.swift | 27 +- .../ProfileLevelRatingBarComponent.swift | 75 +- 10 files changed, 1013 insertions(+), 71 deletions(-) create mode 100644 submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift create mode 100644 submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD rename submodules/TelegramUI/Components/PeerInfo/{ProfileLevelInfoScreen => ProfileLevelRatingBarComponent}/Sources/ProfileLevelRatingBarBadge.swift (94%) rename submodules/TelegramUI/Components/PeerInfo/{ProfileLevelInfoScreen => ProfileLevelRatingBarComponent}/Sources/ProfileLevelRatingBarComponent.swift (92%) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index ed86535e8b..c8f2a8c8b2 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -159,7 +159,7 @@ "moduleExtensions": { "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { "general": { - "bzlTransitiveDigest": "RjubjYIojbv0PxTpnoknalV9QzT9asbV7elDuN7m2A4=", + "bzlTransitiveDigest": "xcBTf2+GaloFpg7YEh/Bv+1yAczRkiCt3DGws4K7kSk=", "usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 9adf4412eb..a457c76e14 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15013,5 +15013,8 @@ Sorry for the inconvenience."; "Gift.Options.Collectibles.Text" = "Collectible gifts are unique digital items you can exchange or sell."; "Gift.Upgrade.UpgradeFor" = "Upgrade for %@"; +"Gift.Upgrade.PriceWillDecrease" = "Price will decrease in {m}:{s}"; "Stars.Purchase.RemoveOriginalDetailsStarGiftInfo" = "Buy Stars to remove original details of your gift."; + + diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index 956417154e..f444c05ad3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -55,7 +55,7 @@ swift_library( "//submodules/TelegramUI/Components/Stars/BalanceNeededScreen", "//submodules/TelegramUI/Components/ChatThemeScreen", "//submodules/ImageBlur", - "//submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen", + "//submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift new file mode 100644 index 0000000000..6819bfd15c --- /dev/null +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift @@ -0,0 +1,683 @@ +import Foundation +import UIKit +import SwiftSignalKit +import Display +import TelegramCore +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AccountContext +import ViewControllerComponent +import MultilineTextComponent +import MultilineTextWithEntitiesComponent +import BalancedTextComponent +import ButtonComponent +import PresentationDataUtils +import LottieComponent +import ProfileLevelRatingBarComponent +import TextFormat +import TelegramStringFormatting + +private final class GiftUpgradeCostScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let upgradePreview: StarGiftUpgradePreview + + init( + context: AccountContext, + upgradePreview: StarGiftUpgradePreview + ) { + self.context = context + self.upgradePreview = upgradePreview + } + + static func ==(lhs: GiftUpgradeCostScreenComponent, rhs: GiftUpgradeCostScreenComponent) -> Bool { + return true + } + + private final class ScrollView: UIScrollView { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + } + + private struct ItemLayout: Equatable { + var containerSize: CGSize + var containerInset: CGFloat + var bottomInset: CGFloat + var topInset: CGFloat + + init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) { + self.containerSize = containerSize + self.containerInset = containerInset + self.bottomInset = bottomInset + self.topInset = topInset + } + } + + final class View: UIView, UIScrollViewDelegate { + private let dimView: UIView + private let backgroundLayer: SimpleLayer + private let navigationBarContainer: SparseContainerView + private let navigationBackgroundView: BlurredBackgroundView + private let navigationBarSeparator: SimpleLayer + private let scrollView: ScrollView + private let scrollContentClippingView: SparseContainerView + private let scrollContentView: UIView + + private let closeButton = ComponentView() + + private let title = ComponentView() + private let descriptionText = ComponentView() + private let bar = ComponentView() + private let table = ComponentView() + private let additionalDescription = ComponentView() + + private let bottomPanelContainer: UIView + private let bottomPanelSeparator: SimpleLayer + private let actionButton = ComponentView() + + private var isFirstTimeApplyingModalFactor: Bool = true + private var ignoreScrolling: Bool = false + + private var component: GiftUpgradeCostScreenComponent? + private weak var state: EmptyComponentState? + private var environment: ViewControllerComponentContainer.Environment? + private var isUpdating: Bool = false + + private var itemLayout: ItemLayout? + private var topOffsetDistance: CGFloat? + + private var cachedCloseImage: UIImage? + + private var upgradePreviewTimer: SwiftSignalKit.Timer? + private var effectiveUpgradePrice: StarGiftUpgradePreview.Price? + + override init(frame: CGRect) { + self.dimView = UIView() + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.backgroundLayer.cornerRadius = 10.0 + + self.navigationBarContainer = SparseContainerView() + + self.navigationBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + self.navigationBarSeparator = SimpleLayer() + + self.scrollView = ScrollView() + + self.scrollContentClippingView = SparseContainerView() + self.scrollContentClippingView.clipsToBounds = true + + self.scrollContentView = UIView() + + self.bottomPanelContainer = UIView() + self.bottomPanelSeparator = SimpleLayer() + + super.init(frame: frame) + + self.addSubview(self.dimView) + self.layer.addSublayer(self.backgroundLayer) + + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.clipsToBounds = false + self.scrollView.contentInsetAdjustmentBehavior = .never + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = true + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + + self.addSubview(self.scrollContentClippingView) + self.scrollContentClippingView.addSubview(self.scrollView) + + self.scrollView.addSubview(self.scrollContentView) + + self.addSubview(self.navigationBarContainer) + self.addSubview(self.bottomPanelContainer) + + self.navigationBarContainer.addSubview(self.navigationBackgroundView) + self.navigationBarContainer.layer.addSublayer(self.navigationBarSeparator) + + self.layer.addSublayer(self.bottomPanelSeparator) + + self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.bounds.contains(point) { + return nil + } + if !self.backgroundLayer.frame.contains(point) { + return self.dimView + } + + if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { + return result + } + + let result = super.hitTest(point, with: event) + return result + } + + @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + guard let environment = self.environment, let controller = environment.controller() else { + return + } + controller.dismiss() + } + } + + func upgradePreviewTimerTick() { + guard let upgradePreview = self.component?.upgradePreview else { + return + } + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if let currentPrice = self.effectiveUpgradePrice { + if let price = upgradePreview.nextPrices.reversed().first(where: { currentTime >= $0.date }) { + if price.stars != currentPrice.stars { + self.effectiveUpgradePrice = price + if !self.isUpdating { + self.state?.updated(transition: .immediate.withUserData(ProfileLevelRatingBarComponent.TransitionHint(animate: true))) + } + } + } else { + self.upgradePreviewTimer?.invalidate() + self.upgradePreviewTimer = nil + } + } else if let price = upgradePreview.nextPrices.reversed().first(where: { currentTime >= $0.date}) { + self.effectiveUpgradePrice = price + if !self.isUpdating { + self.state?.updated() + } + } + } + + private func updateScrolling(transition: ComponentTransition) { + guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else { + return + } + var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset + + let titleTransformFraction: CGFloat = max(0.0, min(1.0, -topOffset / 20.0)) + + let navigationAlpha: CGFloat = titleTransformFraction + transition.setAlpha(view: self.navigationBackgroundView, alpha: navigationAlpha) + transition.setAlpha(layer: self.navigationBarSeparator, alpha: navigationAlpha) + + let bottomPanelAlphaDistance: CGFloat = 20.0 + let bottomPanelDistance: CGFloat = self.scrollView.contentSize.height - self.scrollView.bounds.maxY + let bottomPanelAlphaFraction: CGFloat = max(0.0, min(1.0, bottomPanelDistance / bottomPanelAlphaDistance)) + + let bottomPanelAlpha: CGFloat = bottomPanelAlphaFraction + if self.bottomPanelSeparator.opacity != Float(bottomPanelAlpha) { + let alphaTransition = transition + alphaTransition.setAlpha(layer: self.bottomPanelSeparator, alpha: bottomPanelAlpha) + } + + topOffset = max(0.0, topOffset) + transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) + + transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) + + let topOffsetDistance: CGFloat = 80.0 + self.topOffsetDistance = topOffsetDistance + var topOffsetFraction = topOffset / topOffsetDistance + topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) + + let transitionFactor: CGFloat = 1.0 - topOffsetFraction + var modalOverlayTransition = transition + if self.isFirstTimeApplyingModalFactor { + self.isFirstTimeApplyingModalFactor = false + modalOverlayTransition = .spring(duration: 0.5) + } + if self.isUpdating { + DispatchQueue.main.async { [weak controller] in + guard let controller else { + return + } + controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalOverlayTransition.containedViewLayoutTransition) + } + } else { + controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalOverlayTransition.containedViewLayoutTransition) + } + } + + func animateIn() { + self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.bottomPanelContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.bottomPanelSeparator.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + + func animateOut(completion: @escaping () -> Void) { + let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY + + self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.bottomPanelContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.bottomPanelSeparator.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + + if let environment = self.environment, let controller = environment.controller() { + controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } + + func update(component: GiftUpgradeCostScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let themeUpdated = self.environment?.theme !== environment.theme + + let resetScrolling = self.scrollView.bounds.width != availableSize.width + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + + let isFirstTime = self.component == nil + self.component = component + self.state = state + self.environment = environment + + if isFirstTime { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if let _ = component.upgradePreview.nextPrices.first(where: { currentTime < $0.date }) { + self.upgradePreviewTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + self?.upgradePreviewTimerTick() + }, queue: Queue.mainQueue()) + self.upgradePreviewTimer?.start() + self.upgradePreviewTimerTick() + } + } + + if themeUpdated { + self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor + + self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) + self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor + self.bottomPanelSeparator.backgroundColor = environment.theme.rootController.tabBar.separatorColor.cgColor + } + + transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) + + var contentHeight: CGFloat = 0.0 + + let closeImage: UIImage + if let image = self.cachedCloseImage, !themeUpdated { + closeImage = image + } else { + closeImage = generateCloseButtonImage(backgroundColor: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))! + self.cachedCloseImage = closeImage + } + + let closeButtonSize = self.closeButton.update( + transition: transition, + component: AnyComponent(Button( + content: AnyComponent(Image(image: closeImage, size: closeImage.size)), + action: { [weak self] in + guard let self, let controller = self.environment?.controller() else { + return + } + controller.dismiss() + } + ).minSize(CGSize(width: 62.0, height: 56.0))), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - closeButtonSize.width, y: 0.0), size: closeButtonSize) + if let closeButtonView = self.closeButton.view { + if closeButtonView.superview == nil { + self.navigationBarContainer.addSubview(closeButtonView) + } + transition.setFrame(view: closeButtonView, frame: closeButtonFrame) + } + + let containerInset: CGFloat = environment.statusBarHeight + 10.0 + + let clippingY: CGFloat + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Upgrade Cost", font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((56.0 - titleSize.height) * 0.5)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.navigationBarContainer.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + contentHeight += 56.0 + + let navigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: 54.0)) + transition.setFrame(view: self.navigationBackgroundView, frame: navigationBackgroundFrame) + self.navigationBackgroundView.update(size: navigationBackgroundFrame.size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition) + transition.setFrame(layer: self.navigationBarSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: availableSize.width, height: UIScreenPixel))) + + var value: CGFloat = 0.0 + if let startStars = component.upgradePreview.prices.first?.stars, let endStars = component.upgradePreview.prices.last?.stars { + let effectiveValue = self.effectiveUpgradePrice?.stars ?? endStars + value = CGFloat(effectiveValue - endStars) / CGFloat(startStars - endStars) + } + + //TODO:localize + let barSize = self.bar.update( + transition: transition, + component: AnyComponent(ProfileLevelRatingBarComponent( + theme: environment.theme, + value: 1.0 - value, + leftLabel: "\(component.upgradePreview.prices.first?.stars ?? 0) Stars", + rightLabel: "\(component.upgradePreview.prices.last?.stars ?? 0) Stars", + badgeValue: "\(self.effectiveUpgradePrice?.stars ?? 0)", + badgeTotal: "", + level: 0, + icon: .stars, + inversed: true + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 110.0) + ) + let barFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - barSize.width) * 0.5), y: contentHeight), size: barSize) + if let barView = self.bar.view { + if barView.superview == nil { + self.scrollContentView.addSubview(barView) + } + transition.setFrame(view: barView, frame: barFrame) + } + contentHeight += barSize.height + 25.0 + + let descriptionSize = self.descriptionText.update( + transition: transition, + component: AnyComponent(BalancedTextComponent( + text: .plain(NSAttributedString( + string: "Users who upgrade their gifts first get collectibles with shorter numbers.", + font: Font.regular(15.0), + textColor: environment.theme.list.itemPrimaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 3, + lineSpacing: 0.2 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 50.0, height: .greatestFiniteMagnitude) + ) + let descriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionSize.width) * 0.5), y: contentHeight), size: descriptionSize) + if let descriptionView = self.descriptionText.view { + if descriptionView.superview == nil { + self.scrollContentView.addSubview(descriptionView) + } + transition.setFrame(view: descriptionView, frame: descriptionFrame) + } + contentHeight += descriptionSize.height + 23.0 + + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + + var tableItems: [TableComponent.Item] = [] + for price in component.upgradePreview.prices { + if price.date < currentTime { + continue + } + let valueString = "⭐️\(presentationStringsFormattedNumber(abs(Int32(clamping: price.stars)), environment.dateTimeFormat.groupingSeparator))" + let valueAttributedString = NSMutableAttributedString(string: valueString, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor) + let range = (valueAttributedString.string as NSString).range(of: "⭐️") + if range.location != NSNotFound { + valueAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + valueAttributedString.addAttribute(.baselineOffset, value: 1.0, range: range) + } + tableItems.append(TableComponent.Item( + id: price.stars, + title: stringForGiftUpgradeTimestamp(strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, timestamp: price.date), + titleFont: .bold, + component: AnyComponent(MultilineTextWithEntitiesComponent(context: component.context, animationCache: component.context.animationCache, animationRenderer: component.context.animationRenderer, placeholderColor: .white, text: .plain(valueAttributedString))) + )) + } + let tableSize = self.table.update( + transition: transition, + component: AnyComponent(TableComponent( + theme: environment.theme, + items: tableItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude) + ) + let tableFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - tableSize.width) * 0.5), y: contentHeight), size: tableSize) + if let tableView = self.table.view { + if tableView.superview == nil { + self.scrollContentView.addSubview(tableView) + } + transition.setFrame(view: tableView, frame: tableFrame) + } + contentHeight += tableSize.height + 15.0 + + let additionalDescriptionSize = self.additionalDescription.update( + transition: transition, + component: AnyComponent(BalancedTextComponent( + text: .plain(NSAttributedString( + string: "Upgrade cost drops every minute.", + font: Font.regular(13.0), + textColor: environment.theme.list.itemSecondaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 5, + lineSpacing: 0.2 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 50.0, height: .greatestFiniteMagnitude) + ) + let additionalDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - additionalDescriptionSize.width) * 0.5), y: contentHeight), size: additionalDescriptionSize) + if let additionalDescriptionView = self.additionalDescription.view { + if additionalDescriptionView.superview == nil { + self.scrollContentView.addSubview(additionalDescriptionView) + } + transition.setFrame(view: additionalDescriptionView, frame: additionalDescriptionFrame) + } + contentHeight += additionalDescriptionSize.height + 15.0 + + + let actionButtonTitle: String = "Understood" + + var buttonTitle: [AnyComponentWithIdentity] = [] + let playButtonAnimation = ActionSlot() + buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_ok"), + color: environment.theme.list.itemCheckColors.foregroundColor, + startingPosition: .begin, + size: CGSize(width: 28.0, height: 28.0), + playOnce: playButtonAnimation + )))) + buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( + text: actionButtonTitle, + badge: 0, + textColor: environment.theme.list.itemCheckColors.foregroundColor, + badgeBackground: environment.theme.list.itemCheckColors.foregroundColor, + badgeForeground: environment.theme.list.itemCheckColors.fillColor + )))) + + let actionButtonSize = self.actionButton.update( + transition: transition, + 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) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack(buttonTitle, spacing: 2.0)) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + guard let self else { + return + } + self.environment?.controller()?.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + ) + + let bottomPanelHeight = 10.0 + environment.safeInsets.bottom + actionButtonSize.height + + let bottomPanelSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0), size: CGSize(width: availableSize.width, height: UIScreenPixel)) + transition.setFrame(layer: self.bottomPanelSeparator, frame: bottomPanelSeparatorFrame) + + let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight), size: CGSize(width: availableSize.width, height: bottomPanelHeight)) + transition.setFrame(view: self.bottomPanelContainer, frame: bottomPanelFrame) + + let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: actionButtonSize) + if let actionButtonView = self.actionButton.view { + if actionButtonView.superview == nil { + self.bottomPanelContainer.addSubview(actionButtonView) + playButtonAnimation.invoke(Void()) + } + transition.setFrame(view: actionButtonView, frame: actionButtonFrame) + } + + contentHeight += bottomPanelHeight + + clippingY = bottomPanelFrame.minY - 8.0 + + let topInset: CGFloat = max(0.0, availableSize.height - containerInset - contentHeight) + + let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) + + self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset) + + transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) + + transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) + transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize)) + + let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: clippingY - containerInset)) + transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) + transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) + + self.ignoreScrolling = true + let previousBounds = self.scrollView.bounds + transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) + let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) + if contentSize != self.scrollView.contentSize { + self.scrollView.contentSize = contentSize + } + if resetScrolling { + self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) + } else { + 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.ignoreScrolling = false + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class GiftUpgradeCostScreen: ViewControllerComponentContainer { + private let context: AccountContext + private var isDismissed: Bool = false + + public init( + context: AccountContext, + upgradePreview: StarGiftUpgradePreview + ) { + self.context = context + + super.init(context: context, component: GiftUpgradeCostScreenComponent( + context: context, + upgradePreview: upgradePreview + ), navigationBarAppearance: .none, theme: .default) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + + if let componentView = self.node.hostView.componentView as? GiftUpgradeCostScreenComponent.View { + componentView.animateIn() + } + } + + override public func dismiss(completion: (() -> Void)? = nil) { + if !self.isDismissed { + self.isDismissed = true + + if let componentView = self.node.hostView.componentView as? GiftUpgradeCostScreenComponent.View { + componentView.animateOut(completion: { [weak self] in + completion?() + self?.dismiss(animated: false) + }) + } else { + self.dismiss(animated: false) + } + } + } +} + diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 2436a8385f..27dc1c1eb9 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -37,6 +37,8 @@ import BalanceNeededScreen import GiftItemComponent import GiftAnimationComponent import ChatThemeScreen +import ProfileLevelRatingBarComponent +import AnimatedTextComponent private final class GiftViewSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -108,13 +110,14 @@ private final class GiftViewSheetContent: CombinedComponent { var canSkip = false var testUpgradeAnimation = !"".isEmpty - - var nextGiftToUpgrade: ProfileGiftsContext.State.StarGift? - var inUpgradePreview = false + var upgradeForm: BotPaymentForm? var upgradeFormDisposable: Disposable? var upgradeDisposable: Disposable? + var scheduledUpgradeCommit = false + let levelsDisposable = MetaDisposable() + var nextGiftToUpgrade: ProfileGiftsContext.State.StarGift? var buyForm: BotPaymentForm? var buyFormDisposable: Disposable? @@ -125,8 +128,11 @@ private final class GiftViewSheetContent: CombinedComponent { var pendingWear = false var pendingTakeOff = false + var inUpgradePreview = false + var scheduledUpgradePreview = false var upgradePreview: StarGiftUpgradePreview? let upgradePreviewDisposable = DisposableSet() + var upgradePreviewTimer: SwiftSignalKit.Timer? var keepOriginalInfo = false @@ -264,17 +270,27 @@ private final class GiftViewSheetContent: CombinedComponent { } self.updated() + + if arguments.upgradeStars == nil { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if let _ = upgradePreview.nextPrices.first(where: { currentTime < $0.date }) { + self.upgradePreviewTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + self?.upgradePreviewTimerTick() + }, queue: Queue.mainQueue()) + self.upgradePreviewTimer?.start() + self.upgradePreviewTimerTick() + } + } + + if self.scheduledUpgradePreview { + self.inProgress = false + self.scheduledUpgradePreview = false + self.requestUpgradePreview() + } })) - if arguments.upgradeStars == nil, let reference = arguments.reference { - self.upgradeFormDisposable = (context.engine.payments.fetchBotPaymentForm(source: .starGiftUpgrade(keepOriginalInfo: false, reference: reference), themeParams: nil) - |> deliverOnMainQueue).start(next: { [weak self] paymentForm in - guard let self else { - return - } - self.upgradeForm = paymentForm - self.updated() - }) + if arguments.upgradeStars == nil { + self.fetchUpgradeForm() } } } @@ -1404,14 +1420,72 @@ private final class GiftViewSheetContent: CombinedComponent { } } + private func fetchUpgradeForm() { + guard let reference = self.subject.arguments?.reference else { + return + } + self.upgradeForm = nil + self.upgradeFormDisposable = (self.context.engine.payments.fetchBotPaymentForm(source: .starGiftUpgrade(keepOriginalInfo: false, reference: reference), themeParams: nil) + |> deliverOnMainQueue).start(next: { [weak self] paymentForm in + guard let self else { + return + } + self.upgradeForm = paymentForm + self.updated() + + if self.scheduledUpgradeCommit { + self.scheduledUpgradeCommit = false + self.commitUpgrade() + } + }) + } + + private(set) var effectiveUpgradePrice: StarGiftUpgradePreview.Price? + private(set) var nextUpgradePrice: StarGiftUpgradePreview.Price? + + func upgradePreviewTimerTick() { + guard let upgradePreview = self.upgradePreview else { + return + } + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if let currentPrice = self.effectiveUpgradePrice { + if let price = upgradePreview.nextPrices.reversed().first(where: { currentTime >= $0.date }) { + if price.stars != currentPrice.stars { + self.effectiveUpgradePrice = price + if let nextPrice = upgradePreview.nextPrices.first(where: { $0.stars < price.stars }) { + self.nextUpgradePrice = nextPrice + } + self.fetchUpgradeForm() + } + } else { + self.upgradePreviewTimer?.invalidate() + self.upgradePreviewTimer = nil + } + } else if let price = upgradePreview.nextPrices.reversed().first(where: { currentTime >= $0.date}) { + self.effectiveUpgradePrice = price + if let nextPrice = upgradePreview.nextPrices.first(where: { $0.stars < price.stars }) { + self.nextUpgradePrice = nextPrice + } + } + + self.updated() + } + func requestUpgradePreview() { - self.context.starsContext?.load(force: false) - - self.inUpgradePreview = true - self.updated(transition: .spring(duration: 0.4)) - - if let controller = self.getController() as? GiftViewScreen, self.upgradeForm != nil { - controller.showBalance = true + if let _ = self.upgradePreview { + self.context.starsContext?.load(force: false) + + self.inUpgradePreview = true + self.updated(transition: .spring(duration: 0.4)) + + if let controller = self.getController() as? GiftViewScreen, self.upgradeForm != nil { + controller.showBalance = true + } + } else { + self.scheduledUpgradePreview = true + + self.inProgress = true + self.updated() } } @@ -1948,13 +2022,19 @@ private final class GiftViewSheetContent: CombinedComponent { } else { proceed(upgradeForm.id) } + } else { + self.scheduledUpgradeCommit = true } - -// if let controller = self.getController() as? GiftViewScreen { -// controller.showBalance = true -// } } + func openUpgradePricePreview() { + guard let controller = self.getController(), let upgradePreview = self.upgradePreview else { + return + } + let costController = GiftUpgradeCostScreen(context: self.context, upgradePreview: upgradePreview) + controller.push(costController) + } + func commitPrepaidUpgrade() { guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let prepaidUpgradeHash = arguments.prepaidUpgradeHash, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { return @@ -2087,7 +2167,8 @@ private final class GiftViewSheetContent: CombinedComponent { let upgradeDescription = Child(BalancedTextComponent.self) let upgradePerks = Child(List.self) let upgradeKeepName = Child(PlainButtonComponent.self) - + let upgradePriceButton = Child(PlainButtonComponent.self) + let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: []) let giftCompositionExternalState = GiftCompositionComponent.ExternalState() @@ -2373,6 +2454,16 @@ private final class GiftViewSheetContent: CombinedComponent { var headerComponents: [() -> Void] = [] + 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 tableLargeMonospaceFont = Font.monospace(16.0) + + let tableTextColor = theme.list.itemPrimaryTextColor + let tableLinkColor = theme.list.itemAccentColor + if let headerSubject { let animation = animation.update( component: GiftCompositionComponent( @@ -3062,15 +3153,6 @@ 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 tableLargeMonospaceFont = Font.monospace(16.0) - - let tableTextColor = theme.list.itemPrimaryTextColor - let tableLinkColor = theme.list.itemAccentColor var tableItems: [TableComponent.Item] = [] var isWearing = state.pendingWear @@ -3867,6 +3949,7 @@ private final class GiftViewSheetContent: CombinedComponent { transition: context.transition ) context.add(table + .clipsToBounds(true) .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0)) .appear(.default(alpha: true)) .disappear(ComponentTransition.Disappear({ view, transition, completion in @@ -4176,14 +4259,18 @@ private final class GiftViewSheetContent: CombinedComponent { if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme) } + var buttonTitleItems: [AnyComponentWithIdentity] = [] var upgradeString = strings.Gift_Upgrade_Upgrade if !incoming { if let gift = state.starGiftsMap[giftId], let upgradeStars = gift.upgradeStars { let priceString = presentationStringsFormattedNumber(Int32(clamping: upgradeStars), environment.dateTimeFormat.groupingSeparator) upgradeString = strings.Gift_Upgrade_GiftUpgrade(" # \(priceString)").string } - } else if let upgradeForm = state.upgradeForm, let price = upgradeForm.invoice.prices.first?.amount { - let priceString = presentationStringsFormattedNumber(Int32(clamping: price), environment.dateTimeFormat.groupingSeparator) + } else if let upgradeStars = state.effectiveUpgradePrice?.stars { + let priceString = presentationStringsFormattedNumber(Int32(clamping: upgradeStars), environment.dateTimeFormat.groupingSeparator) + upgradeString = strings.Gift_Upgrade_GiftUpgrade(" # \(priceString)").string + } else if let upgradeForm = state.upgradeForm, let upgradeStars = upgradeForm.invoice.prices.first?.amount { + let priceString = presentationStringsFormattedNumber(Int32(clamping: upgradeStars), environment.dateTimeFormat.groupingSeparator) upgradeString = strings.Gift_Upgrade_UpgradeFor(" # \(priceString)").string } let buttonTitle = subject.arguments?.upgradeStars != nil ? strings.Gift_Upgrade_Confirm : upgradeString @@ -4194,12 +4281,59 @@ private final class GiftViewSheetContent: CombinedComponent { buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string)) buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string)) } + + if let nextUpgradePrice = state.nextUpgradePrice { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let upgradeTimeout = nextUpgradePrice.date - currentTime + + buttonTitleItems.append(AnyComponentWithIdentity(id: "static_label", component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))))) + + let minutes = Int(upgradeTimeout / 60) + let seconds = Int(upgradeTimeout % 60) + + let rawString = strings.Gift_Upgrade_PriceWillDecrease + var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = [] + var startIndex = rawString.startIndex + while true { + if let range = rawString.range(of: "{", range: startIndex ..< rawString.endIndex) { + if range.lowerBound != startIndex { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .text(String(rawString[startIndex ..< range.lowerBound])))) + } + + startIndex = range.upperBound + if let endRange = rawString.range(of: "}", range: startIndex ..< rawString.endIndex) { + let controlString = rawString[range.upperBound ..< endRange.lowerBound] + if controlString == "m" { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .number(minutes, minDigits: 2))) + } else if controlString == "s" { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .number(seconds, minDigits: 2))) + } + + startIndex = endRange.upperBound + } + } else { + break + } + } + if startIndex != rawString.endIndex { + buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .text(String(rawString[startIndex ..< rawString.endIndex])))) + } + + buttonTitleItems.append(AnyComponentWithIdentity(id: "timer", component: AnyComponent(AnimatedTextComponent( + font: Font.with(size: 11.0, weight: .medium, traits: .monospacedNumbers), + color: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), + items: buttonAnimatedTitleItems + )))) + } else { + buttonTitleItems.append(AnyComponentWithIdentity(id: "static_label", component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))))) + } + buttonChild = button.update( component: ButtonComponent( background: buttonBackground, content: AnyComponentWithIdentity( id: AnyHashable("upgrade"), - component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) + component: AnyComponent(VStack(buttonTitleItems, spacing: 1.0)) ), isEnabled: true, displaysProgress: state.inProgress, @@ -4211,7 +4345,7 @@ private final class GiftViewSheetContent: CombinedComponent { } }), availableSize: buttonSize, - transition: context.transition + transition: .spring(duration: 0.2) ) } else if upgraded, let upgradeMessageIdId = subject.arguments?.upgradeMessageId, let originalMessageId = subject.arguments?.messageId { let upgradeMessageId = MessageId(peerId: originalMessageId.peerId, namespace: originalMessageId.namespace, id: upgradeMessageIdId) @@ -4430,6 +4564,40 @@ private final class GiftViewSheetContent: CombinedComponent { originY += buttonChild.size.height originY += 7.0 + if showUpgradePreview { + originY += 20.0 + + if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme { + state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: theme.actionSheet.controlAccentColor)!, theme) + } + + let attributedString = NSMutableAttributedString(string: "See how price will decrease >", font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor) + if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 { + attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string)) + } + + let upgradePriceButton = upgradePriceButton.update( + component: PlainButtonComponent( + content: AnyComponent( + MultilineTextComponent(text: .plain(attributedString)) + ), + action: { [weak state] in + state?.openUpgradePricePreview() + }, + animateScale: false + ), + environment: {}, + availableSize: buttonChild.size, + transition: .immediate + ) + context.add(upgradePriceButton + .position(CGPoint(x: buttonFrame.midX, y: originY)) + .appear(.default(scale: true, alpha: true)) + .disappear(.default(scale: true, alpha: true)) + ) + originY += upgradePriceButton.size.height + } + context.add(buttons .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0)) ) diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/BUILD index e7197580ee..a86c1d099c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/BUILD @@ -30,6 +30,7 @@ swift_library( "//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath", "//submodules/Components/HierarchyTrackingLayer", "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift index ff8ca29892..047dfe85de 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift @@ -18,6 +18,7 @@ import Markdown import PremiumUI import LottieComponent import AnimatedTextComponent +import ProfileLevelRatingBarComponent private final class ProfileLevelInfoScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -84,10 +85,7 @@ private final class ProfileLevelInfoScreenComponent: Component { private let closeButton = ComponentView() private let peerAvatar = ComponentView() - - private let callIconBackground = ComponentView() - private let callIcon = ComponentView() - + private let title = ComponentView() private let levelInfo = ComponentView() private var secondaryDescriptionText: ComponentView? @@ -97,9 +95,7 @@ private final class ProfileLevelInfoScreenComponent: Component { private let bottomPanelContainer: UIView private let actionButton = ComponentView() - - private let bottomOverscrollLimit: CGFloat - + private var isFirstTimeApplyingModalFactor: Bool = true private var ignoreScrolling: Bool = false @@ -115,9 +111,7 @@ private final class ProfileLevelInfoScreenComponent: Component { private var cachedChevronImage: UIImage? private var cachedCloseImage: UIImage? - override init(frame: CGRect) { - self.bottomOverscrollLimit = 200.0 - + override init(frame: CGRect) { self.dimView = UIView() self.backgroundLayer = SimpleLayer() @@ -496,7 +490,8 @@ private final class ProfileLevelInfoScreenComponent: Component { rightLabel: currentLevel < 0 ? "Negative rating" : nextLevel.flatMap { environment.strings.ProfileLevelInfo_LevelIndex(Int32($0)) } ?? "", badgeValue: badgeText, badgeTotal: badgeTextSuffix, - level: Int(currentLevel) + level: Int(currentLevel), + icon: .rating )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 110.0) diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD new file mode 100644 index 0000000000..b61bba252c --- /dev/null +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD @@ -0,0 +1,32 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ProfileLevelRatingBarComponent", + module_name = "ProfileLevelRatingBarComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/HierarchyTrackingLayer", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/TelegramUI/Components/LottieComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelRatingBarBadge.swift b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarBadge.swift similarity index 94% rename from submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelRatingBarBadge.swift rename to submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarBadge.swift index c79a7af616..154f6d8867 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelRatingBarBadge.swift +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarBadge.swift @@ -3,7 +3,6 @@ import UIKit import Display import TelegramPresentationData import ComponentFlow -import RoundedRectWithTailPath import AnimatedTextComponent import MultilineTextComponent import LottieComponent @@ -17,18 +16,26 @@ final class ProfileLevelRatingBarBadge: Component { } } + enum Icon { + case rating + case stars + } + let theme: PresentationTheme let title: String let suffix: String? + let icon: Icon init( theme: PresentationTheme, title: String, - suffix: String? + suffix: String?, + icon: Icon ) { self.theme = theme self.title = title self.suffix = suffix + self.icon = icon } static func ==(lhs: ProfileLevelRatingBarBadge, rhs: ProfileLevelRatingBarBadge) -> Bool { @@ -41,6 +48,9 @@ final class ProfileLevelRatingBarBadge: Component { if lhs.suffix != rhs.suffix { return false } + if lhs.icon != rhs.icon { + return false + } return true } @@ -141,10 +151,15 @@ final class ProfileLevelRatingBarBadge: Component { labelsTransition.animateAlpha(view: self.badgeIcon, from: 0.0, to: 1.0) } - if component.title.isEmpty { - self.badgeIcon.image = UIImage(bundleImageName: "Peer Info/ProfileLevelWarningIcon")?.withRenderingMode(.alwaysTemplate) - } else { - self.badgeIcon.image = UIImage(bundleImageName: "Peer Info/ProfileLevelProgressIcon")?.withRenderingMode(.alwaysTemplate) + switch component.icon { + case .rating: + if component.title.isEmpty { + self.badgeIcon.image = UIImage(bundleImageName: "Peer Info/ProfileLevelWarningIcon")?.withRenderingMode(.alwaysTemplate) + } else { + self.badgeIcon.image = UIImage(bundleImageName: "Peer Info/ProfileLevelProgressIcon")?.withRenderingMode(.alwaysTemplate) + } + case .stars: + self.badgeIcon.image = UIImage(bundleImageName: "Premium/SendStarsStarSliderIcon")?.withRenderingMode(.alwaysTemplate) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelRatingBarComponent.swift b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarComponent.swift similarity index 92% rename from submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelRatingBarComponent.swift rename to submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarComponent.swift index ce10d04682..8ba5e86144 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelRatingBarComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/Sources/ProfileLevelRatingBarComponent.swift @@ -7,15 +7,20 @@ import MultilineTextComponent import BundleIconComponent import HierarchyTrackingLayer -final class ProfileLevelRatingBarComponent: Component { - final class TransitionHint { - let animate: Bool +public final class ProfileLevelRatingBarComponent: Component { + public final class TransitionHint { + public let animate: Bool - init(animate: Bool) { + public init(animate: Bool) { self.animate = animate } } + public enum Icon { + case rating + case stars + } + let theme: PresentationTheme let value: CGFloat let leftLabel: String @@ -23,15 +28,19 @@ final class ProfileLevelRatingBarComponent: Component { let badgeValue: String let badgeTotal: String? let level: Int + let icon: Icon + let inversed: Bool - init( + public init( theme: PresentationTheme, value: CGFloat, leftLabel: String, rightLabel: String, badgeValue: String, badgeTotal: String?, - level: Int + level: Int, + icon: Icon, + inversed: Bool = false ) { self.theme = theme self.value = value @@ -40,9 +49,11 @@ final class ProfileLevelRatingBarComponent: Component { self.badgeValue = badgeValue self.badgeTotal = badgeTotal self.level = level + self.icon = icon + self.inversed = inversed } - static func ==(lhs: ProfileLevelRatingBarComponent, rhs: ProfileLevelRatingBarComponent) -> Bool { + public static func ==(lhs: ProfileLevelRatingBarComponent, rhs: ProfileLevelRatingBarComponent) -> Bool { if lhs.theme !== rhs.theme { return false } @@ -64,6 +75,12 @@ final class ProfileLevelRatingBarComponent: Component { if lhs.level != rhs.level { return false } + if lhs.icon != rhs.icon { + return false + } + if lhs.inversed != rhs.inversed { + return false + } return true } @@ -152,7 +169,7 @@ final class ProfileLevelRatingBarComponent: Component { } } - final class View: UIView { + public final class View: UIView { private let barBackground: UIImageView private let backgroundClippingContainer: UIView private let foregroundBarClippingContainer: UIView @@ -490,7 +507,9 @@ final class ProfileLevelRatingBarComponent: Component { let barBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - barHeight), size: CGSize(width: availableSize.width, height: barHeight)) transition.setFrame(view: self.barBackground, frame: barBackgroundFrame) - var barForegroundFrame = CGRect(origin: barBackgroundFrame.origin, size: CGSize(width: floorToScreenPixels(progressValue * barBackgroundFrame.width), height: barBackgroundFrame.height)) + let barForegroundOriginX = barBackgroundFrame.minX + let barForegroundWidth = floorToScreenPixels(progressValue * barBackgroundFrame.width) + var barForegroundFrame = CGRect(origin: CGPoint(x: barForegroundOriginX, y: barBackgroundFrame.minY), size: CGSize(width: barForegroundWidth, height: barBackgroundFrame.height)) var foregroundAlpha: CGFloat = 1.0 var foregroundContentsAlpha: CGFloat = 1.0 @@ -552,7 +571,7 @@ final class ProfileLevelRatingBarComponent: Component { self.barForeground.tintColor = badgeColor var effectiveBarForegroundFrame = barForegroundFrame - if currentIsNegativeRating { + if currentIsNegativeRating || component.inversed { effectiveBarForegroundFrame.size.width = barBackgroundFrame.maxX - barForegroundFrame.maxX effectiveBarForegroundFrame.origin.x = barBackgroundFrame.maxX - effectiveBarForegroundFrame.width } @@ -564,7 +583,23 @@ final class ProfileLevelRatingBarComponent: Component { transition.setAlpha(view: self.foregroundBarClippingContainer, alpha: foregroundAlpha) transition.setAlpha(view: self.foregroundClippingContainer, alpha: foregroundContentsAlpha) - let backgroundClippingFrame = CGRect(origin: CGPoint(x: barBackgroundFrame.minX + barForegroundFrame.width, y: barBackgroundFrame.minY), size: CGSize(width: barBackgroundFrame.width - barForegroundFrame.width, height: barBackgroundFrame.height)) + let backgroundClippingFrame: CGRect + if currentIsNegativeRating || component.inversed { + backgroundClippingFrame = CGRect( + x: barBackgroundFrame.minX, + y: barBackgroundFrame.minY, + width: max(0.0, effectiveBarForegroundFrame.minX - barBackgroundFrame.minX), + height: barBackgroundFrame.height + ) + } else { + backgroundClippingFrame = CGRect( + x: effectiveBarForegroundFrame.maxX, + y: barBackgroundFrame.minY, + width: max(0, barBackgroundFrame.maxX - effectiveBarForegroundFrame.maxX), + height: barBackgroundFrame.height + ) + } + transition.setPosition(view: self.backgroundClippingContainer, position: backgroundClippingFrame.center) transition.setBounds(view: self.backgroundClippingContainer, bounds: CGRect(origin: CGPoint(x: backgroundClippingFrame.minX - barBackgroundFrame.minX, y: 0.0), size: backgroundClippingFrame.size)) transition.setAlpha(view: self.backgroundClippingContainer, alpha: foregroundContentsAlpha) @@ -642,12 +677,21 @@ final class ProfileLevelRatingBarComponent: Component { foregroundRightLabelView.bounds = CGRect(origin: CGPoint(), size: rightLabelFrame.size) } + let icon: ProfileLevelRatingBarBadge.Icon + switch component.icon { + case .rating: + icon = .rating + case .stars: + icon = .stars + } + let badgeSize = self.badge.update( transition: transition.withUserData(ProfileLevelRatingBarBadge.TransitionHint(animateText: !labelsTransition.animation.isImmediate)), component: AnyComponent(ProfileLevelRatingBarBadge( theme: component.theme, title: component.level < 0 ? "" : "\(component.badgeValue)", - suffix: component.level < 0 ? nil : component.badgeTotal + suffix: component.level < 0 ? nil : component.badgeTotal, + icon: icon )), environment: {}, containerSize: CGSize(width: 200.0, height: 200.0) @@ -665,7 +709,8 @@ final class ProfileLevelRatingBarComponent: Component { apparentBadgeSize = badgeSize } - var badgeFrame = CGRect(origin: CGPoint(x: barBackgroundFrame.minX + barForegroundFrame.width - apparentBadgeSize.width * 0.5, y: barBackgroundFrame.minY - 18.0 - badgeSize.height), size: apparentBadgeSize) + let badgeOriginX = barBackgroundFrame.minX + barForegroundFrame.width + var badgeFrame = CGRect(origin: CGPoint(x: badgeOriginX - apparentBadgeSize.width * 0.5, y: barBackgroundFrame.minY - 18.0 - badgeSize.height), size: apparentBadgeSize) let badgeSideInset: CGFloat = 0.0 @@ -703,11 +748,11 @@ final class ProfileLevelRatingBarComponent: Component { } } - func makeView() -> View { + public func makeView() -> View { return View(frame: CGRect()) } - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + 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) } } From 94ee23649d7f7db0d15e6fd32844c51704aef650 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 9 Sep 2025 20:39:17 +0400 Subject: [PATCH 08/13] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 +- .../Sources/AccountContext.swift | 1 + .../Sources/SheetComponent.swift | 46 ++++- .../ChatMessageGiftBubbleContentNode.swift | 14 +- .../Sources/EmojiStatusComponent.swift | 3 +- .../Sources/GiftOptionsScreen.swift | 17 +- .../Sources/GiftSetupScreen.swift | 29 +++ .../Sources/GiftTransferAlertController.swift | 2 +- .../Sources/GiftViewScreen.swift | 188 ++++++++++++++++-- .../Sources/TableComponent.swift | 31 ++- .../Sources/StarsBalanceComponent.swift | 2 +- .../Sources/StarsWithdrawalScreen.swift | 39 ++-- .../Sources/StoryPeerListItemComponent.swift | 56 +++++- submodules/TelegramUI/Sources/OpenUrl.swift | 9 +- .../Sources/SharedAccountContext.swift | 4 +- 15 files changed, 387 insertions(+), 56 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a457c76e14..50663aaf9f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15017,4 +15017,4 @@ Sorry for the inconvenience."; "Stars.Purchase.RemoveOriginalDetailsStarGiftInfo" = "Buy Stars to remove original details of your gift."; - +"Notification.StarGift.TitleTo" = "Gift for %@"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 5aecfa6c41..3556d9473e 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -703,6 +703,7 @@ public enum PeerInfoControllerMode { case recommendedChannels case myProfile case gifts + case upgradableGifts case myProfileGifts case groupsInCommon case monoforum(EnginePeer.Id) diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index 1d8c0f4788..812c580b44 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -60,6 +60,7 @@ public final class SheetComponent: C } public let content: AnyComponent + public let headerContent: AnyComponent? public let backgroundColor: BackgroundColor public let followContentSizeChanges: Bool public let clipsContent: Bool @@ -73,6 +74,7 @@ public final class SheetComponent: C public init( content: AnyComponent, + headerContent: AnyComponent? = nil, backgroundColor: BackgroundColor, followContentSizeChanges: Bool = false, clipsContent: Bool = false, @@ -85,6 +87,7 @@ public final class SheetComponent: C willDismiss: @escaping () -> Void = {} ) { self.content = content + self.headerContent = headerContent self.backgroundColor = backgroundColor self.followContentSizeChanges = followContentSizeChanges self.clipsContent = clipsContent @@ -101,6 +104,9 @@ public final class SheetComponent: C if lhs.content != rhs.content { return false } + if lhs.headerContent != rhs.headerContent { + return false + } if lhs.backgroundColor != rhs.backgroundColor { return false } @@ -159,6 +165,7 @@ public final class SheetComponent: C private let backgroundView: UIView private var effectView: UIVisualEffectView? private let contentView: ComponentView + private var headerView: ComponentView? private var isAnimatingOut: Bool = false private var previousIsDisplaying: Bool = false @@ -272,10 +279,12 @@ public final class SheetComponent: C } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let headerView = self.headerView?.view, headerView.bounds.contains(self.convert(point, to: headerView)) { + return super.hitTest(point, with: event) + } if !self.backgroundView.bounds.contains(self.convert(point, to: self.backgroundView)) { return self.dimView } - return super.hitTest(point, with: event) } @@ -288,6 +297,10 @@ public final class SheetComponent: C transition.animateView(allowUserInteraction: true, { self.scrollView.center = targetPosition }) + + if let headerContent = self.headerView { + headerContent.view?.layer.animateAlpha(from: 0.1, to: 0.0, duration: 0.15) + } } private func animateOut(initialVelocity: CGFloat? = nil, completion: @escaping () -> Void) { @@ -300,6 +313,10 @@ public final class SheetComponent: C self.isUserInteractionEnabled = false self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + if let headerContent = self.headerView { + headerContent.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } + guard let contentView = self.contentView.view else { return } @@ -404,6 +421,33 @@ public final class SheetComponent: C } transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) + if let headerContent = component.headerContent { + let headerView: ComponentView + if let current = self.headerView { + headerView = current + } else { + headerView = ComponentView() + self.headerView = headerView + } + + let headerSize = headerView.update( + transition: transition, + component: headerContent, + environment: {}, + containerSize: CGSize(width: contentSize.width, height: 44.0) + ) + let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - headerSize.width) / 2.0), y: self.backgroundView.frame.minY - headerSize.height - 10.0), size: headerSize) + if let headerView = headerView.view { + if headerView.superview == nil { + self.scrollView.addSubview(headerView) + } + transition.setFrame(view: headerView, frame: headerFrame) + } + } else if let headerView = self.headerView { + self.headerView = nil + headerView.view?.removeFromSuperview() + } + let previousContentSize = self.scrollView.contentSize let updateContentSize = { self.scrollView.contentSize = contentSize diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 6bc942ba62..1a19cf132f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -558,7 +558,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { isStarGift = true var authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" - + let isSelfGift = item.message.id.peerId == item.context.account.peerId let isChannelGift = item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel || channelPeerId != nil if isSelfGift { @@ -569,8 +569,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } else { if let senderPeerId, let name = item.message.peers[senderPeerId].flatMap(EnginePeer.init)?.compactDisplayTitle { authorName = name + title = item.presentationData.strings.Notification_StarGift_Title(authorName).string + } else if !incoming, let name = item.message.peers[item.message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle { + title = item.presentationData.strings.Notification_StarGift_TitleTo(name).string + } else { + title = item.presentationData.strings.Gift_View_Unknown_Title } - title = item.presentationData.strings.Notification_StarGift_Title(authorName).string } } if let giftText, !giftText.isEmpty { @@ -647,7 +651,11 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { buttonTitle = item.presentationData.strings.Notification_StarGift_Unpack buttonIcon = "GiftUnpack" } else { - buttonTitle = item.presentationData.strings.Notification_StarGift_View + if isPrepaidUpgrade && !incoming { + buttonTitle = "" + } else { + buttonTitle = item.presentationData.strings.Notification_StarGift_View + } } } case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _): diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index 2a3ce4872f..5537ee1d8f 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -41,6 +41,7 @@ public final class EmojiStatusComponent: Component { public enum SizeType { case compact case large + case smaller } public enum Content: Equatable { @@ -357,7 +358,7 @@ public final class EmojiStatusComponent: Component { case let .verified(fillColor, foregroundColor, sizeType): let imageNamePrefix: String switch sizeType { - case .compact: + case .compact, .smaller: imageNamePrefix = "Chat List/PeerVerifiedIcon" case .large: imageNamePrefix = "Peer Info/VerifiedIcon" diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index cbbbcc7441..011862206f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -506,14 +506,27 @@ final class GiftOptionsScreenComponent: Component { } isSoldOut = true } else if let _ = gift.availability { + let text: String + if let perUserLimit = gift.perUserLimit { + text = "\(perUserLimit.remains) left" + } else { + text = environment.strings.Gift_Options_Gift_Limited + } ribbon = GiftItemComponent.Ribbon( - text: environment.strings.Gift_Options_Gift_Limited, + text: text, color: .blue ) } if !isSoldOut && gift.flags.contains(.requiresPremium) { + let text: String + if component.context.isPremium, let perUserLimit = gift.perUserLimit { + //TODO:localize + text = "\(perUserLimit.remains) left" + } else { + text = environment.strings.Gift_Options_Gift_Premium + } ribbon = GiftItemComponent.Ribbon( - text: environment.strings.Gift_Options_Gift_Premium, + text: text, color: .orange ) outline = .orange diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index 16d5bb870c..dcb9c2fab0 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -457,6 +457,35 @@ final class GiftSetupScreenComponent: Component { controllers.append(chatController) } navigationController.setViewControllers(controllers, animated: true) + + + if case let .starGift(starGift, _) = component.subject, let perUserLimit = starGift.perUserLimit { + Queue.mainQueue().after(0.5) { + //TODO:localize + let remains = max(0, perUserLimit.remains - 1) + let text: String + if remains == 0 { + text = "You've reached your limit on this gift." + } else { + text = "You can send **\(remains)** more." + } + let tooltipController = UndoOverlayController( + presentationData: presentationData, + content: .sticker( + context: context, + file: starGift.file, + loop: true, + title: "Gift Sent!", + text: text, + undoText: nil, + customAction: nil + ), + position: .top, + action: { _ in return true } + ) + (navigationController.viewControllers.last as? ViewController)?.present(tooltipController, in: .current) + } + } } if let completion { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift index 73e10e24e2..74ca22c545 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift @@ -138,7 +138,7 @@ private final class GiftTransferAlertContentNode: AlertContentNode { return ("URL", url) } ), textAlignment: .center) - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor) + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor.withAlphaComponent(0.9)) self.actionNodesSeparator.backgroundColor = theme.separatorColor for actionNode in self.actionNodes { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 27dc1c1eb9..9f66a57b0e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -537,7 +537,7 @@ private final class GiftViewSheetContent: CombinedComponent { action: { [weak navigationController] action in if case .undo = action, let navigationController, let giftsPeerId { let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: giftsPeerId)) - |> deliverOnMainQueue).start(next: { [weak navigationController] peer in + |> deliverOnMainQueue).start(next: { [weak navigationController] peer in guard let peer, let navigationController else { return } @@ -564,7 +564,7 @@ private final class GiftViewSheetContent: CombinedComponent { } func convertToStars() { - guard let controller = self.getController() as? GiftViewScreen, let starsContext = context.starsContext, let arguments = self.subject.arguments, let reference = arguments.reference, let fromPeerName = arguments.fromPeerName, let convertStars = arguments.convertStars, let navigationController = controller.navigationController as? NavigationController else { + guard let controller = self.getController() as? GiftViewScreen, let starsContext = context.starsContext, let arguments = self.subject.arguments, let reference = arguments.reference, let fromPeerName = arguments.fromPeerCompactName, let convertStars = arguments.convertStars, let navigationController = controller.navigationController as? NavigationController else { return } @@ -2036,7 +2036,7 @@ private final class GiftViewSheetContent: CombinedComponent { } func commitPrepaidUpgrade() { - guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let prepaidUpgradeHash = arguments.prepaidUpgradeHash, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { + guard let arguments = self.subject.arguments, let controller = self.getController() as? GiftViewScreen, let peerId = arguments.peerId, let prepaidUpgradeHash = arguments.prepaidUpgradeHash, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { return } guard case let .generic(gift) = arguments.gift else { @@ -2046,6 +2046,7 @@ private final class GiftViewSheetContent: CombinedComponent { return } let context = self.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let proceed: () -> Void = { [weak self, weak starsContext] in guard let self else { return @@ -2068,7 +2069,7 @@ private final class GiftViewSheetContent: CombinedComponent { } self.upgradeDisposable = (signal - |> deliverOnMainQueue).start(next: { [weak self, weak starsContext] result in + |> deliverOnMainQueue).start(next: { [weak self, weak controller, weak starsContext] result in guard let self else { return } @@ -2076,12 +2077,54 @@ private final class GiftViewSheetContent: CombinedComponent { starsContext?.load(force: true) } + let navigationController = controller?.navigationController as? NavigationController let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in + |> deliverOnMainQueue).start(next: { [weak self, weak navigationController] peer in guard let self, let peer else { return } self.openPeer(peer, gifts: false, dismiss: true) + + Queue.mainQueue().after(0.5) { + if let lastController = navigationController?.viewControllers.last as? ViewController { + let resultController = UndoOverlayController( + presentationData: presentationData, + content: .sticker( + context: context, + file: gift.file, + loop: false, + title: nil, + text: "Upgrade sent!", + undoText: "Gift More", + customAction: nil + ), + elevatedLayout: !(lastController is ChatController), + action: { [weak navigationController] action in + if case .undo = action, let navigationController { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak navigationController] peer in + guard let peer, let navigationController else { + return + } + if let controller = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .upgradableGifts, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + navigationController.pushViewController(controller, animated: true) + } + }) + } + return true + } + ) + lastController.present(resultController, in: .current) + } + } }) }, error: { _ in @@ -2092,8 +2135,8 @@ private final class GiftViewSheetContent: CombinedComponent { let _ = (self.optionsPromise.get() |> filter { $0 != nil } |> take(1) - |> deliverOnMainQueue).startStandalone(next: { [weak self] options in - guard let self, let controller = self.getController() else { + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak controller] options in + guard let self, let controller else { return } let purchaseController = self.context.sharedContext.makeStarsPurchaseScreen( @@ -4322,7 +4365,8 @@ private final class GiftViewSheetContent: CombinedComponent { buttonTitleItems.append(AnyComponentWithIdentity(id: "timer", component: AnyComponent(AnimatedTextComponent( font: Font.with(size: 11.0, weight: .medium, traits: .monospacedNumbers), color: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), - items: buttonAnimatedTitleItems + items: buttonAnimatedTitleItems, + noDelay: true )))) } else { buttonTitleItems.append(AnyComponentWithIdentity(id: "static_label", component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))))) @@ -4347,7 +4391,7 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: buttonSize, transition: .spring(duration: 0.2) ) - } else if upgraded, let upgradeMessageIdId = subject.arguments?.upgradeMessageId, let originalMessageId = subject.arguments?.messageId { + } else if upgraded, let arguments = subject.arguments, let upgradeMessageIdId = arguments.upgradeMessageId, let originalMessageId = arguments.messageId, !arguments.upgradeSeparate { let upgradeMessageId = MessageId(peerId: originalMessageId.peerId, namespace: originalMessageId.namespace, id: upgradeMessageIdId) let buttonTitle = strings.Gift_View_ViewUpgraded buttonChild = button.update( @@ -4564,13 +4608,13 @@ private final class GiftViewSheetContent: CombinedComponent { originY += buttonChild.size.height originY += 7.0 - if showUpgradePreview { + if showUpgradePreview, let _ = state.nextUpgradePrice { originY += 20.0 if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme { state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: theme.actionSheet.controlAccentColor)!, theme) } - + //TODO:localize let attributedString = NSMutableAttributedString(string: "See how price will decrease >", font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor) if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 { attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string)) @@ -4642,6 +4686,43 @@ final class GiftViewSheetComponent: CombinedComponent { let environment = context.environment[EnvironmentType.self] let controller = environment.controller + var headerContent: AnyComponent? + if let arguments = context.component.subject.arguments, case .unique = arguments.gift, let fromPeerId = arguments.fromPeerId, let fromPeerName = arguments.fromPeerName, arguments.fromPeerId != context.component.context.account.peerId { + let dateString = stringForMediumDate(timestamp: arguments.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat, withTime: false) + + let rawString = "**\(fromPeerName)** sent you this gift on **\(dateString)**." + let attributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: .white), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: .white), linkAttribute: { _ in return nil })) + + let context = context.component.context + headerContent = AnyComponent( + PlainButtonComponent(content: AnyComponent(HeaderContentComponent(attributedText: attributedString)), action: { + if let controller = controller(), let navigationController = controller.navigationController as? NavigationController { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: fromPeerId)) + |> deliverOnMainQueue).start(next: { [weak navigationController] peer in + guard let peer, let navigationController 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 sheet = sheet.update( component: SheetComponent( content: AnyComponent(GiftViewSheetContent( @@ -4650,10 +4731,12 @@ final class GiftViewSheetComponent: CombinedComponent { animateOut: animateOut, getController: controller )), + headerContent: headerContent, backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, clipsContent: true, hasDimView: false, + autoAnimateOut: false, externalState: sheetExternalState, animateOut: animateOut, onPan: { @@ -4740,7 +4823,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { case upgradePreview([StarGift.UniqueGift.Attribute], String) case wearPreview(StarGift.UniqueGift) - var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?, prepaidUpgradeHash: String?, upgradeSeparate: Bool, dropOriginalDetailsStars: Int64?)? { + var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, fromPeerCompactName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?, prepaidUpgradeHash: String?, upgradeSeparate: Bool, dropOriginalDetailsStars: Int64?)? { switch self { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { @@ -4754,7 +4837,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { } else { reference = .message(messageId: message.id) } - return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate, nil) + return (message.id.peerId, senderId ?? message.author?.id, message.author?.debugDisplayTitle, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate, nil) case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars): var reference: StarGiftReference if let peerId, let savedId { @@ -4779,13 +4862,13 @@ public class GiftViewScreen: ViewControllerComponentContainer { if case let .unique(uniqueGift) = gift { resellAmounts = uniqueGift.resellAmounts } - return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate, nil, false, dropOriginalDetailsStars) + return (message.id.peerId, senderId ?? message.author?.id, message.author?.debugDisplayTitle, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate, nil, false, dropOriginalDetailsStars) default: return nil } } case let .uniqueGift(gift, _), let .wearPreview(gift): - return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil, nil, false, nil) + return (nil, nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil, nil, false, nil) case let .profileGift(peerId, gift): var messageId: EngineMessage.Id? if case let .message(messageIdValue) = gift.reference { @@ -4795,7 +4878,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { if case let .unique(uniqueGift) = gift.gift { resellAmounts = uniqueGift.resellAmounts } - return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash, gift.upgradeSeparate, gift.dropOriginalDetailsStars) + return (peerId, gift.fromPeer?.id, gift.fromPeer?.debugDisplayTitle, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash, gift.upgradeSeparate, gift.dropOriginalDetailsStars) case .soldOutGift: return nil case .upgradePreview: @@ -5240,6 +5323,79 @@ private final class PeerCellComponent: Component { } } +final class HeaderContentComponent: Component { + let attributedText: NSAttributedString + + init( + attributedText: NSAttributedString + ) { + self.attributedText = attributedText + } + + static func ==(lhs: HeaderContentComponent, rhs: HeaderContentComponent) -> Bool { + if lhs.attributedText != rhs.attributedText { + return false + } + return true + } + + final class View: UIView { + private var component: HeaderContentComponent? + + private let backgroundView: BlurredBackgroundView + private let title = ComponentView() + + override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: UIColor.black.withAlphaComponent(0.2)) + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: HeaderContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent(text: .plain(component.attributedText), horizontalAlignment: .center) + ), + environment: {}, + containerSize: availableSize + ) + + let padding: CGFloat = 10.0 + let size = CGSize(width: titleSize.width + padding * 2.0, height: 19.0) + + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0) - UIScreenPixel), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + self.backgroundView.update(size: size, cornerRadius: size.height / 2.0, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size)) + + 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) + } +} + final class ButtonContentComponent: Component { let context: AccountContext let text: String diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift index 7c9bf666be..c7aaaa3ae9 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/TableComponent.swift @@ -70,6 +70,7 @@ final class TableComponent: CombinedComponent { } final class State: ComponentState { + var cachedLeftColumnImage: (UIImage, PresentationTheme)? var cachedBorderImage: (UIImage, PresentationTheme)? } @@ -78,7 +79,7 @@ final class TableComponent: CombinedComponent { } public static var body: Body { - let leftColumnBackground = Child(Rectangle.self) + let leftColumnBackground = Child(Image.self) let lastBackground = Child(Rectangle.self) let verticalBorder = Child(Rectangle.self) let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self) @@ -194,25 +195,39 @@ final class TableComponent: CombinedComponent { ) } + let borderRadius: CGFloat = 10.0 + let leftColumnImage: UIImage + if let (currentImage, theme) = context.state.cachedLeftColumnImage, theme === context.component.theme { + leftColumnImage = currentImage + } else { + leftColumnImage = generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in + let bounds = CGRect(origin: .zero, size: CGSize(width: size.width + borderRadius, height: size.height)) + context.clear(bounds) + + let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) + context.setFillColor(secondaryBackgroundColor.cgColor) + context.addPath(path) + context.fillPath() + })!.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10) + context.state.cachedLeftColumnImage = (leftColumnImage, context.component.theme) + } + let leftColumnBackground = leftColumnBackground.update( - component: Rectangle(color: secondaryBackgroundColor), + component: Image(image: leftColumnImage), availableSize: CGSize(width: leftColumnWidth, height: innerTotalHeight), transition: context.transition ) - context.add( - leftColumnBackground - .position(CGPoint(x: leftColumnWidth / 2.0, y: innerTotalHeight / 2.0)) + 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 = 10.0 borderImage = generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in let bounds = CGRect(origin: .zero, size: size) - context.setFillColor(backgroundColor.cgColor) - context.fill(bounds) + context.clear(bounds) let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil) context.setBlendMode(.clear) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift index 015c4cf330..d028a8d5f6 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift @@ -180,7 +180,7 @@ final class StarsBalanceComponent: Component { let formattedLabel: String switch component.currency { case .ton: - formattedLabel = formatTonAmountText(component.count.value, dateTimeFormat: component.dateTimeFormat) + formattedLabel = formatTonAmountText(component.count.value, dateTimeFormat: component.dateTimeFormat, maxDecimalPositions: 3) case .stars: formattedLabel = formatStarsAmountText(component.count, dateTimeFormat: component.dateTimeFormat) } diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 34945bf30b..0faf4a9d5e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -466,7 +466,7 @@ private final class SheetContent: CombinedComponent { case .ton: if let value = state.amount?.value, value > 0 { let tonValue = Int64(Float(value) * Float(resaleConfiguration.starGiftCommissionTonPermille) / 1000.0) - let tonString = formatTonAmountText(tonValue, dateTimeFormat: environment.dateTimeFormat, maxDecimalPositions: nil) + " TON" + let tonString = formatTonAmountText(tonValue, dateTimeFormat: environment.dateTimeFormat, maxDecimalPositions: 3) + " TON" amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SellGift_AmountInfo(tonString).string, attributes: amountMarkdownAttributes, textAlignment: .natural)) if let tonUsdRate = withdrawConfiguration.tonUsdRate { @@ -1230,7 +1230,13 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(clamping: minAmount))).string if case .starGiftResell = self.mode { - text = presentationData.strings.Stars_SellGiftMinAmountToast_Text("\(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(clamping: minAmount)))").string + switch currency { + case .stars: + text = presentationData.strings.Stars_SellGiftMinAmountToast_Text("\(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(clamping: minAmount)))").string + case .ton: + let amountString = formatTonAmountText(minAmount, dateTimeFormat: presentationData.dateTimeFormat) + " TON" + text = presentationData.strings.Stars_SellGiftMinAmountToast_Text(amountString).string + } } else if case let .suggestedPost(mode, _, _, _) = self.mode { let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) switch currency { @@ -1253,7 +1259,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { let resultController = UndoOverlayController( presentationData: presentationData, content: .image( - image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!, + image: currency == .ton ? generateTintedImage(image: UIImage(bundleImageName: "Premium/TonGift"), color: .white)! : UIImage(bundleImageName: "Premium/Stars/StarLarge")!, title: nil, text: text, round: false, @@ -1314,9 +1320,9 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { } case .ton: let scale: Int64 = 1_000_000_000 // 10⁹ (one “nano”) - if let dot = text.firstIndex(of: ".") { + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, let dot = text.firstIndex(of: decimalSeparator) { // Slices for the parts on each side of the dot - var wholeSlice = String(text[.. Bool { var acceptZero = false - if self.minValue <= 0 { + if case .ton = self.currency, self.minValue < 1_000_000_000 { acceptZero = true } @@ -1367,7 +1373,7 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { return false default: if case .ton = self.currency { - if c == "." { + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, c == decimalSeparator { return false } } @@ -1376,7 +1382,7 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { }) { return false } - if newText.count(where: { $0 == "." }) > 1 { + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, newText.count(where: { $0 == decimalSeparator }) > 1 { return false } @@ -1390,7 +1396,7 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { } case .ton: var fixedText = false - if let index = newText.firstIndex(of: ".") { + if let decimalSeparator = self.dateTimeFormat.decimalSeparator.first, let index = newText.firstIndex(of: decimalSeparator) { let fractionalString = newText[newText.index(after: index)...] if fractionalString.count > 2 { newText = String(newText[newText.startIndex ..< newText.index(index, offsetBy: 3)]) @@ -1398,7 +1404,16 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { } } - if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0") && !newText.hasPrefix("0.")) { + if newText == self.dateTimeFormat.decimalSeparator { + if !acceptZero { + newText.removeFirst() + } else { + newText = "0\(newText)" + } + fixedText = true + } + + if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0") && !newText.hasPrefix("0\(self.dateTimeFormat.decimalSeparator)")) { newText.removeFirst() fixedText = true } @@ -1416,7 +1431,7 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { case .stars: textField.text = "\(self.maxValue)" case .ton: - textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: PresentationDateTimeFormat(timeFormat: self.dateTimeFormat.timeFormat, dateFormat: self.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))" + textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: PresentationDateTimeFormat(timeFormat: self.dateTimeFormat.timeFormat, dateFormat: self.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: self.dateTimeFormat.decimalSeparator, groupingSeparator: ""), maxDecimalPositions: nil))" } self.onTextChanged(text: self.textField.text ?? "") self.animateError() @@ -1639,7 +1654,7 @@ private final class AmountFieldComponent: Component { self.tonFormatter = nil self.textField.delegate = self.starsFormatter case .ton: - self.textField.keyboardType = .numbersAndPunctuation + self.textField.keyboardType = .decimalPad if self.tonFormatter == nil { self.tonFormatter = AmountFieldStarsFormatter( textField: self.textField, diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index eab96745c9..1ee23877b7 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -14,6 +14,7 @@ import AsyncDisplayKit import StoryContainerScreen import MultilineTextComponent import HierarchyTrackingLayer +import EmojiStatusComponent private func calculateCircleIntersection(center: CGPoint, otherCenter: CGPoint, radius: CGFloat) -> (point1Angle: CGFloat, point2Angle: CGFloat)? { let distanceVector = CGPoint(x: otherCenter.x - center.x, y: otherCenter.y - center.y) @@ -501,6 +502,7 @@ public final class StoryPeerListItemComponent: Component { private let indicatorShapeSeenLayer: SimpleShapeLayer private let indicatorShapeUnseenLayer: SimpleShapeLayer private let title = ComponentView() + private var verifiedIconView: ComponentHostView? private let composeTitle = ComponentView() private var component: StoryPeerListItemComponent? @@ -885,7 +887,59 @@ public final class StoryPeerListItemComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width + 12.0, height: 100.0) ) - let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5) + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 2.0) * effectiveScale), size: titleSize) + + var totalTitleWidth = titleSize.width + + var currentVerifiedIconContent: EmojiStatusComponent.Content? + if component.peer.isVerified { + currentVerifiedIconContent = .verified(fillColor: component.theme.list.itemCheckColors.fillColor, foregroundColor: component.theme.list.itemCheckColors.foregroundColor, sizeType: .smaller) + } + if let currentVerifiedIconContent { + let verifiedIconView: ComponentHostView + if let current = self.verifiedIconView { + verifiedIconView = current + } else { + verifiedIconView = ComponentHostView() + verifiedIconView.isUserInteractionEnabled = false + self.verifiedIconView = verifiedIconView + self.button.addSubview(verifiedIconView) + } + + let containerSize = CGSize(width: 12.0, height: 12.0) + let verifiedIconComponent = EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: currentVerifiedIconContent, + size: containerSize, + isVisibleForAnimations: component.context.sharedContext.energyUsageSettings.loopEmoji, + action: nil + ) + + let iconSize = verifiedIconView.update( + transition: .immediate, + component: AnyComponent(verifiedIconComponent), + environment: {}, + containerSize: containerSize + ) + totalTitleWidth += iconSize.width + 1.0 + var titleScale = effectiveScale + if titleScale < 1.0 { + titleScale = min(1.0, titleScale + 0.02) + } else if titleScale > 1.01 { + titleScale = max(1.0, titleScale * 0.96) + } + let verifiedIconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - totalTitleWidth) * 0.5) + (titleSize.width + 1.0) * titleScale + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 2.0 + UIScreenPixel) * effectiveScale), size: iconSize) + titleTransition.setPosition(view: verifiedIconView, position: verifiedIconFrame.center) + verifiedIconView.bounds = CGRect(origin: CGPoint(), size: verifiedIconFrame.size) + titleTransition.setScale(view: verifiedIconView, scale: effectiveScale) + titleTransition.setAlpha(view: verifiedIconView, alpha: component.expandedAlphaFraction) + } else if let verifiedIconView = self.verifiedIconView { + self.verifiedIconView = nil + verifiedIconView.removeFromSuperview() + } + + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - totalTitleWidth) * 0.5) + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 2.0) * effectiveScale), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { titleView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 12c79166cd..a6f24f2535 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -1137,7 +1137,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur } if isInternetUrl { - if parsedUrl.host == "t.me" || parsedUrl.host == "telegram.me" { + if parsedUrl.host == "t.me" || parsedUrl.host == "telegram.me" || parsedUrl.host == "telegram.dog" { handleInternalUrl(parsedUrl.absoluteString) } else { let settings = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings, ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]), context.sharedContext.accountManager.accessChallengeData()) @@ -1159,11 +1159,6 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur return settings } -// var isCompact = false -// if let metrics = navigationController?.validLayout?.metrics, case .compact = metrics.widthClass { -// isCompact = true -// } - let _ = (settings |> deliverOnMainQueue).startStandalone(next: { settings in var isTonSite = false @@ -1216,7 +1211,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur } if parsedUrl.scheme == "http" || parsedUrl.scheme == "https" { - let nativeHosts = ["t.me", "telegram.me"] + let nativeHosts = ["t.me", "telegram.me", "telegram.dog"] if let host = parsedUrl.host, nativeHosts.contains(host) { continueHandling() } else { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 99ce9bc265..2e6bec97dd 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3948,7 +3948,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation forumTopicThread = thread case .recommendedChannels: switchToRecommendedChannels = true - case .gifts: + case .gifts, .upgradableGifts: switchToGifts = true case .groupsInCommon: switchToGroupsInCommon = true @@ -3986,7 +3986,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation forumTopicThread = thread case .myProfile: isMyProfile = true - case .gifts: + case .gifts, .upgradableGifts: switchToGifts = true case .myProfileGifts: isMyProfile = true From 9d97a6219514c326d29c853f21873a86a1a4eb80 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 9 Sep 2025 22:12:19 +0400 Subject: [PATCH 09/13] Update localization --- .../Telegram-iOS/en.lproj/Localizable.strings | 22 +++++++++++++++++++ .../Sources/GiftOptionsScreen.swift | 5 ++--- .../Sources/GiftSetupScreen.swift | 7 +++--- .../Sources/GiftUpgradeCostScreen.swift | 13 +++++------ .../Sources/GiftViewScreen.swift | 4 ++-- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 50663aaf9f..e6d926d6f2 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15018,3 +15018,25 @@ Sorry for the inconvenience."; "Stars.Purchase.RemoveOriginalDetailsStarGiftInfo" = "Buy Stars to remove original details of your gift."; "Notification.StarGift.TitleTo" = "Gift for %@"; + +"Gift.Upgrade.Gift.Sent.Text" = "Upgrade sent!"; +"Gift.Upgrade.Gift.Sent.GiftMore" = "Gift More"; + +"Gift.Options.Gift.Limited.Left_1" = "%@ left"; +"Gift.Options.Gift.Limited.Left_any" = "%@ left"; + +"Gift.Options.Gift.Premium.Left_1" = "%@ left"; +"Gift.Options.Gift.Premium.Left_any" = "%@ left"; + +"Gift.Send.Limited.Success.Title" = "Gift Sent!"; +"Gift.Send.Limited.Success.Text_1" = "You can send **%@** more."; +"Gift.Send.Limited.Success.Text_any" = "You can send **%@** more."; +"Gift.Send.Limited.Success.Text.None" = "You've reached your limit on this gift."; + +"Gift.UpgradeCost.Title" = "Upgrade Cost"; +"Gift.UpgradeCost.Stars_1" = "%@ Star"; +"Gift.UpgradeCost.Stars_any" = "%@ Stars"; +"Gift.UpgradeCost.Description" = "Users who upgrade their gifts first get collectibles with shorter numbers."; +"Gift.UpgradeCost.AdditionalDescription" = "Users who upgrade their gifts first get collectibles with shorter numbers."; +"Gift.UpgradeCost.Done" = "Understood"; + diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 011862206f..9795605e14 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -508,7 +508,7 @@ final class GiftOptionsScreenComponent: Component { } else if let _ = gift.availability { let text: String if let perUserLimit = gift.perUserLimit { - text = "\(perUserLimit.remains) left" + text = environment.strings.Gift_Options_Gift_Limited_Left(perUserLimit.remains) } else { text = environment.strings.Gift_Options_Gift_Limited } @@ -520,8 +520,7 @@ final class GiftOptionsScreenComponent: Component { if !isSoldOut && gift.flags.contains(.requiresPremium) { let text: String if component.context.isPremium, let perUserLimit = gift.perUserLimit { - //TODO:localize - text = "\(perUserLimit.remains) left" + text = environment.strings.Gift_Options_Gift_Premium_Left(perUserLimit.remains) } else { text = environment.strings.Gift_Options_Gift_Premium } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index dcb9c2fab0..93f311d3c5 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -461,13 +461,12 @@ final class GiftSetupScreenComponent: Component { if case let .starGift(starGift, _) = component.subject, let perUserLimit = starGift.perUserLimit { Queue.mainQueue().after(0.5) { - //TODO:localize let remains = max(0, perUserLimit.remains - 1) let text: String if remains == 0 { - text = "You've reached your limit on this gift." + text = presentationData.strings.Gift_Send_Limited_Success_Text_None } else { - text = "You can send **\(remains)** more." + text = presentationData.strings.Gift_Send_Limited_Success_Text(remains) } let tooltipController = UndoOverlayController( presentationData: presentationData, @@ -475,7 +474,7 @@ final class GiftSetupScreenComponent: Component { context: context, file: starGift.file, loop: true, - title: "Gift Sent!", + title: presentationData.strings.Gift_Send_Limited_Success_Title, text: text, undoText: nil, customAction: nil diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift index 6819bfd15c..d23c7b7d7f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeCostScreen.swift @@ -373,7 +373,7 @@ private final class GiftUpgradeCostScreenComponent: Component { let titleSize = self.title.update( transition: transition, - component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Upgrade Cost", font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_UpgradeCost_Title, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) @@ -398,14 +398,13 @@ private final class GiftUpgradeCostScreenComponent: Component { value = CGFloat(effectiveValue - endStars) / CGFloat(startStars - endStars) } - //TODO:localize let barSize = self.bar.update( transition: transition, component: AnyComponent(ProfileLevelRatingBarComponent( theme: environment.theme, value: 1.0 - value, - leftLabel: "\(component.upgradePreview.prices.first?.stars ?? 0) Stars", - rightLabel: "\(component.upgradePreview.prices.last?.stars ?? 0) Stars", + leftLabel: environment.strings.Gift_UpgradeCost_Stars(Int32(clamping: component.upgradePreview.prices.first?.stars ?? 0)), + rightLabel: environment.strings.Gift_UpgradeCost_Stars(Int32(clamping: component.upgradePreview.prices.last?.stars ?? 0)), badgeValue: "\(self.effectiveUpgradePrice?.stars ?? 0)", badgeTotal: "", level: 0, @@ -428,7 +427,7 @@ private final class GiftUpgradeCostScreenComponent: Component { transition: transition, component: AnyComponent(BalancedTextComponent( text: .plain(NSAttributedString( - string: "Users who upgrade their gifts first get collectibles with shorter numbers.", + string: environment.strings.Gift_UpgradeCost_Description, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor, paragraphAlignment: .center @@ -492,7 +491,7 @@ private final class GiftUpgradeCostScreenComponent: Component { transition: transition, component: AnyComponent(BalancedTextComponent( text: .plain(NSAttributedString( - string: "Upgrade cost drops every minute.", + string: environment.strings.Gift_UpgradeCost_AdditionalDescription, font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor, paragraphAlignment: .center @@ -514,7 +513,7 @@ private final class GiftUpgradeCostScreenComponent: Component { contentHeight += additionalDescriptionSize.height + 15.0 - let actionButtonTitle: String = "Understood" + let actionButtonTitle: String = environment.strings.Gift_UpgradeCost_Done var buttonTitle: [AnyComponentWithIdentity] = [] let playButtonAnimation = ActionSlot() diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 9f66a57b0e..4303252cba 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -2094,8 +2094,8 @@ private final class GiftViewSheetContent: CombinedComponent { file: gift.file, loop: false, title: nil, - text: "Upgrade sent!", - undoText: "Gift More", + text: presentationData.strings.Gift_Upgrade_Gift_Sent_Text, + undoText: presentationData.strings.Gift_Upgrade_Gift_Sent_GiftMore, customAction: nil ), elevatedLayout: !(lastController is ChatController), From 2c3a49780f3bf7861004ef5bcdf4b92b49b5f183 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 10 Sep 2025 15:58:38 +0400 Subject: [PATCH 10/13] Fix story verification icon --- .../Sources/StoryPeerListItemComponent.swift | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index 1ee23877b7..2d1ca06a6e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -485,6 +485,7 @@ public final class StoryPeerListItemComponent: Component { private let extractedBackgroundView: UIImageView private let button: HighlightTrackingButton + private let titleContainer: UIView fileprivate var composeLayer: StoryComposeLayer? fileprivate let avatarContent: PortalSourceView @@ -514,6 +515,9 @@ public final class StoryPeerListItemComponent: Component { self.button = HighlightTrackingButton() + self.titleContainer = UIView() + self.titleContainer.isUserInteractionEnabled = false + self.extractedContainerNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() self.extractedBackgroundView = UIImageView() @@ -560,6 +564,8 @@ public final class StoryPeerListItemComponent: Component { self.avatarContent.addSubview(self.avatarContainer) self.button.addSubview(self.avatarContent) + self.button.addSubview(self.titleContainer) + self.avatarContent.layer.addSublayer(self.indicatorColorSeenLayer) self.avatarContent.layer.addSublayer(self.indicatorColorUnseenLayer) self.indicatorMaskSeenLayer.addSublayer(self.indicatorShapeSeenLayer) @@ -878,6 +884,18 @@ public final class StoryPeerListItemComponent: Component { } } + let iconContainerSize = CGSize(width: 12.0, height: 12.0) + let iconSpacing: CGFloat = 1.0 + + var currentVerifiedIconContent: EmojiStatusComponent.Content? + if component.peer.isVerified { + currentVerifiedIconContent = .verified(fillColor: component.theme.list.itemCheckColors.fillColor, foregroundColor: component.theme.list.itemCheckColors.foregroundColor, sizeType: .smaller) + } + var titleConstrainedSize = CGSize(width: availableSize.width + 12.0, height: 100.0) + if let _ = currentVerifiedIconContent { + titleConstrainedSize.width -= iconContainerSize.width + iconSpacing + } + let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( @@ -885,15 +903,10 @@ public final class StoryPeerListItemComponent: Component { maximumNumberOfLines: 1 )), environment: {}, - containerSize: CGSize(width: availableSize.width + 12.0, height: 100.0) + containerSize: titleConstrainedSize ) var totalTitleWidth = titleSize.width - - var currentVerifiedIconContent: EmojiStatusComponent.Content? - if component.peer.isVerified { - currentVerifiedIconContent = .verified(fillColor: component.theme.list.itemCheckColors.fillColor, foregroundColor: component.theme.list.itemCheckColors.foregroundColor, sizeType: .smaller) - } if let currentVerifiedIconContent { let verifiedIconView: ComponentHostView if let current = self.verifiedIconView { @@ -902,16 +915,14 @@ public final class StoryPeerListItemComponent: Component { verifiedIconView = ComponentHostView() verifiedIconView.isUserInteractionEnabled = false self.verifiedIconView = verifiedIconView - self.button.addSubview(verifiedIconView) + self.titleContainer.addSubview(verifiedIconView) } - - let containerSize = CGSize(width: 12.0, height: 12.0) let verifiedIconComponent = EmojiStatusComponent( context: component.context, animationCache: component.context.animationCache, animationRenderer: component.context.animationRenderer, content: currentVerifiedIconContent, - size: containerSize, + size: iconContainerSize, isVisibleForAnimations: component.context.sharedContext.energyUsageSettings.loopEmoji, action: nil ) @@ -920,38 +931,36 @@ public final class StoryPeerListItemComponent: Component { transition: .immediate, component: AnyComponent(verifiedIconComponent), environment: {}, - containerSize: containerSize + containerSize: iconContainerSize ) - totalTitleWidth += iconSize.width + 1.0 - var titleScale = effectiveScale - if titleScale < 1.0 { - titleScale = min(1.0, titleScale + 0.02) - } else if titleScale > 1.01 { - titleScale = max(1.0, titleScale * 0.96) - } - let verifiedIconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - totalTitleWidth) * 0.5) + (titleSize.width + 1.0) * titleScale + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 2.0 + UIScreenPixel) * effectiveScale), size: iconSize) - titleTransition.setPosition(view: verifiedIconView, position: verifiedIconFrame.center) - verifiedIconView.bounds = CGRect(origin: CGPoint(), size: verifiedIconFrame.size) - titleTransition.setScale(view: verifiedIconView, scale: effectiveScale) - titleTransition.setAlpha(view: verifiedIconView, alpha: component.expandedAlphaFraction) + totalTitleWidth += iconSize.width + iconSpacing + + let verifiedIconFrame = CGRect(origin: CGPoint(x: totalTitleWidth - iconSize.width, y: UIScreenPixel), size: iconSize) + titleTransition.setFrame(view: verifiedIconView, frame: verifiedIconFrame) } else if let verifiedIconView = self.verifiedIconView { self.verifiedIconView = nil verifiedIconView.removeFromSuperview() } - let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - totalTitleWidth) * 0.5) + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 2.0) * effectiveScale), size: titleSize) + let titleFrame = CGRect(origin: .zero, size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { titleView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) titleView.isUserInteractionEnabled = false - self.button.addSubview(titleView) + self.titleContainer.addSubview(titleView) } titleTransition.setPosition(view: titleView, position: titleFrame.center) titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - titleTransition.setScale(view: titleView, scale: effectiveScale) - titleTransition.setAlpha(view: titleView, alpha: component.expandedAlphaFraction) } + let titleContainerSize = CGSize(width: totalTitleWidth, height: titleSize.height) + let titleContainerFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleContainerSize.width) * 0.5) + (effectiveWidth - availableSize.width) * 0.5, y: indicatorFrame.midY + (indicatorFrame.height * 0.5 + 2.0) * effectiveScale), size: titleContainerSize) + + titleTransition.setPosition(view: self.titleContainer, position: titleContainerFrame.center) + self.titleContainer.bounds = CGRect(origin: CGPoint(), size: titleContainerFrame.size) + titleTransition.setScale(view: self.titleContainer, scale: effectiveScale) + titleTransition.setAlpha(view: self.titleContainer, alpha: component.expandedAlphaFraction) + if let ringAnimation = component.ringAnimation { var progressTransition = transition let progressLayer: StoryProgressLayer From f7901af416277d2e31ba76fb5e91db2ee09fe6cf Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 11 Sep 2025 02:29:29 +0400 Subject: [PATCH 11/13] Fix ton balance update on gift purchase --- .../GiftViewScreen/Sources/GiftViewScreen.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 4303252cba..8d479d0f2e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1549,19 +1549,14 @@ private final class GiftViewSheetContent: CombinedComponent { if let buyGift = controller.buyGift { buyGiftImpl = { slug, peerId, price in return buyGift(slug, peerId, price) - |> afterCompleted { - context.starsContext?.load(force: true) - } } } else { buyGiftImpl = { slug, peerId, price in return self.context.engine.payments.buyStarGift(slug: slug, peerId: peerId, price: price) - |> afterCompleted { - context.starsContext?.load(force: true) - } } } + let finalPrice = acceptedPrice ?? resellAmount self.buyDisposable = (buyGiftImpl(uniqueGift.slug, recipientPeerId, acceptedPrice ?? resellAmount) |> deliverOnMainQueue).start( error: { [weak self] error in @@ -1683,7 +1678,12 @@ private final class GiftViewSheetContent: CombinedComponent { self.updated(transition: .spring(duration: 0.4)) Queue.mainQueue().after(0.5) { - context.starsContext?.load(force: true) + switch finalPrice.currency { + case .stars: + context.starsContext?.load(force: true) + case .ton: + context.tonContext?.load(force: true) + } } } ) From 3e71e2428b9f062327b228759ced99d1d4d44a2e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 12 Sep 2025 02:17:36 +0400 Subject: [PATCH 12/13] Fix profile buttons display with reduced transparency on --- .../Gifts/GiftViewScreen/Sources/GiftViewScreen.swift | 11 ----------- .../Sources/PeerInfoHeaderNavigationButton.swift | 4 ++++ .../PeerInfoScreen/Sources/PeerInfoHeaderNode.swift | 3 +++ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 8d479d0f2e..ef71136b45 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1895,7 +1895,6 @@ private final class GiftViewSheetContent: CombinedComponent { controller.showBalance = false - let context = self.context let upgradeGiftImpl: ((Int64?, Bool) -> Signal) if let upgradeGift = controller.upgradeGift { guard let reference = arguments.reference else { @@ -1903,11 +1902,6 @@ private final class GiftViewSheetContent: CombinedComponent { } upgradeGiftImpl = { formId, keepOriginalInfo in return upgradeGift(formId, reference, keepOriginalInfo) - |> afterCompleted { - if formId != nil { - context.starsContext?.load(force: true) - } - } } } else { guard let reference = arguments.reference else { @@ -1915,11 +1909,6 @@ private final class GiftViewSheetContent: CombinedComponent { } upgradeGiftImpl = { formId, keepOriginalInfo in return self.context.engine.payments.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo) - |> afterCompleted { - if formId != nil { - context.starsContext?.load(force: true) - } - } } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift index 43a402e294..3da02f5c74 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift @@ -178,6 +178,10 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { } self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + + if isReduceTransparencyEnabled() { + self.backgroundNode.isHidden = true + } } @objc private func pressed() { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index bd65f84488..44ba9d974b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -2339,6 +2339,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateFrameAdditive(node: self.buttonsBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonRightOrigin.y), size: CGSize(width: width, height: buttonSize.height + 40.0))) self.buttonsBackgroundNode.update(size: self.buttonsBackgroundNode.bounds.size, transition: transition) self.buttonsBackgroundNode.updateColor(color: contentButtonBackgroundColor, enableBlur: true, transition: transition) + if isReduceTransparencyEnabled() { + self.buttonsBackgroundNode.alpha = 0.1 + } for buttonKey in buttonKeys.reversed() { let buttonNode: PeerInfoHeaderButtonNode From 3e6c43251173afc8bac591d88ea3a679d5249092 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 12 Sep 2025 10:56:05 +0200 Subject: [PATCH 13/13] Cherry-pick group call messages --- MODULE.bazel.lock | 2 +- submodules/TelegramApi/Sources/Api0.swift | 6 +- submodules/TelegramApi/Sources/Api27.swift | 194 +++++++++----- submodules/TelegramApi/Sources/Api39.swift | 108 +++++--- .../Sources/PresentationGroupCall.swift | 31 +++ .../Account/AccountIntermediateState.swift | 21 +- .../TelegramCore/Sources/ForumChannels.swift | 12 +- .../State/AccountStateManagementUtils.swift | 52 ++-- .../Sources/State/AccountStateManager.swift | 14 + .../TelegramEngine/Calls/GroupCalls.swift | 245 ++++++++++++++++++ .../Messages/TelegramEngineMessages.swift | 4 + 11 files changed, 558 insertions(+), 131 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c8f2a8c8b2..ed86535e8b 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -159,7 +159,7 @@ "moduleExtensions": { "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { "general": { - "bzlTransitiveDigest": "xcBTf2+GaloFpg7YEh/Bv+1yAczRkiCt3DGws4K7kSk=", + "bzlTransitiveDigest": "RjubjYIojbv0PxTpnoknalV9QzT9asbV7elDuN7m2A4=", "usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 269a36125f..3a94b801ff 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1069,8 +1069,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) } dict[-232346616] = { return Api.Update.parse_updateChannelMessageViews($0) } dict[-1738720581] = { return Api.Update.parse_updateChannelParticipant($0) } - dict[422509539] = { return Api.Update.parse_updateChannelPinnedTopic($0) } - dict[-31881726] = { return Api.Update.parse_updateChannelPinnedTopics($0) } dict[636691703] = { return Api.Update.parse_updateChannelReadMessagesContents($0) } dict[277713951] = { return Api.Update.parse_updateChannelTooLong($0) } dict[-1937192669] = { return Api.Update.parse_updateChannelUserTyping($0) } @@ -1109,6 +1107,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1747565759] = { return Api.Update.parse_updateGroupCall($0) } dict[-1535694705] = { return Api.Update.parse_updateGroupCallChainBlocks($0) } dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) } + dict[-917002394] = { return Api.Update.parse_updateGroupCallEncryptedMessage($0) } + dict[-1761933248] = { return Api.Update.parse_updateGroupCallMessage($0) } dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) } dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } dict[1442983757] = { return Api.Update.parse_updateLangPack($0) } @@ -1141,6 +1141,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) } dict[1538885128] = { return Api.Update.parse_updatePinnedChannelMessages($0) } dict[-99664734] = { return Api.Update.parse_updatePinnedDialogs($0) } + dict[1748708434] = { return Api.Update.parse_updatePinnedForumTopic($0) } + dict[-554613808] = { return Api.Update.parse_updatePinnedForumTopics($0) } dict[-309990731] = { return Api.Update.parse_updatePinnedMessages($0) } dict[1751942566] = { return Api.Update.parse_updatePinnedSavedDialogs($0) } dict[-298113238] = { return Api.Update.parse_updatePrivacy($0) } diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 32ede26785..d0d6ab7e37 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -569,8 +569,6 @@ public extension Api { case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32) case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32) case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) - case updateChannelPinnedTopic(flags: Int32, channelId: Int64, topicId: Int32) - case updateChannelPinnedTopics(flags: Int32, channelId: Int64, order: [Int32]?) case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, savedPeerId: Api.Peer?, messages: [Int32]) case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?) case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) @@ -609,6 +607,8 @@ public extension Api { case updateGroupCall(flags: Int32, chatId: Int64?, call: Api.GroupCall) case updateGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, blocks: [Buffer], nextOffset: Int32) case updateGroupCallConnection(flags: Int32, params: Api.DataJSON) + case updateGroupCallEncryptedMessage(call: Api.InputGroupCall, fromId: Api.Peer, encryptedMessage: Buffer) + case updateGroupCallMessage(call: Api.InputGroupCall, fromId: Api.Peer, message: Api.TextWithEntities) case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) case updateLangPack(difference: Api.LangPackDifference) @@ -641,6 +641,8 @@ public extension Api { case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) case updatePinnedChannelMessages(flags: Int32, channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) case updatePinnedDialogs(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) + case updatePinnedForumTopic(flags: Int32, peer: Api.Peer, topicId: Int32) + case updatePinnedForumTopics(flags: Int32, peer: Api.Peer, order: [Int32]?) case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) case updatePinnedSavedDialogs(flags: Int32, order: [Api.DialogPeer]?) case updatePrivacy(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) @@ -959,26 +961,6 @@ public extension Api { if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} serializeInt32(qts, buffer: buffer, boxed: false) break - case .updateChannelPinnedTopic(let flags, let channelId, let topicId): - if boxed { - buffer.appendInt32(422509539) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(topicId, buffer: buffer, boxed: false) - break - case .updateChannelPinnedTopics(let flags, let channelId, let order): - if boxed { - buffer.appendInt32(-31881726) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order!.count)) - for item in order! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - break case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let savedPeerId, let messages): if boxed { buffer.appendInt32(636691703) @@ -1317,6 +1299,22 @@ public extension Api { serializeInt32(flags, buffer: buffer, boxed: false) params.serialize(buffer, true) break + case .updateGroupCallEncryptedMessage(let call, let fromId, let encryptedMessage): + if boxed { + buffer.appendInt32(-917002394) + } + call.serialize(buffer, true) + fromId.serialize(buffer, true) + serializeBytes(encryptedMessage, buffer: buffer, boxed: false) + break + case .updateGroupCallMessage(let call, let fromId, let message): + if boxed { + buffer.appendInt32(-1761933248) + } + call.serialize(buffer, true) + fromId.serialize(buffer, true) + message.serialize(buffer, true) + break case .updateGroupCallParticipants(let call, let participants, let version): if boxed { buffer.appendInt32(-219423922) @@ -1589,6 +1587,26 @@ public extension Api { item.serialize(buffer, true) }} break + case .updatePinnedForumTopic(let flags, let peer, let topicId): + if boxed { + buffer.appendInt32(1748708434) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + break + case .updatePinnedForumTopics(let flags, let peer, let order): + if boxed { + buffer.appendInt32(-554613808) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order!.count)) + for item in order! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + break case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): if boxed { buffer.appendInt32(-309990731) @@ -2021,10 +2039,6 @@ public extension Api { return ("updateChannelMessageViews", [("channelId", channelId as Any), ("id", id as Any), ("views", views as Any)]) case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): return ("updateChannelParticipant", [("flags", flags as Any), ("channelId", channelId as Any), ("date", date as Any), ("actorId", actorId as Any), ("userId", userId as Any), ("prevParticipant", prevParticipant as Any), ("newParticipant", newParticipant as Any), ("invite", invite as Any), ("qts", qts as Any)]) - case .updateChannelPinnedTopic(let flags, let channelId, let topicId): - return ("updateChannelPinnedTopic", [("flags", flags as Any), ("channelId", channelId as Any), ("topicId", topicId as Any)]) - case .updateChannelPinnedTopics(let flags, let channelId, let order): - return ("updateChannelPinnedTopics", [("flags", flags as Any), ("channelId", channelId as Any), ("order", order as Any)]) case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let savedPeerId, let messages): return ("updateChannelReadMessagesContents", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("savedPeerId", savedPeerId as Any), ("messages", messages as Any)]) case .updateChannelTooLong(let flags, let channelId, let pts): @@ -2101,6 +2115,10 @@ public extension Api { return ("updateGroupCallChainBlocks", [("call", call as Any), ("subChainId", subChainId as Any), ("blocks", blocks as Any), ("nextOffset", nextOffset as Any)]) case .updateGroupCallConnection(let flags, let params): return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)]) + case .updateGroupCallEncryptedMessage(let call, let fromId, let encryptedMessage): + return ("updateGroupCallEncryptedMessage", [("call", call as Any), ("fromId", fromId as Any), ("encryptedMessage", encryptedMessage as Any)]) + case .updateGroupCallMessage(let call, let fromId, let message): + return ("updateGroupCallMessage", [("call", call as Any), ("fromId", fromId as Any), ("message", message as Any)]) case .updateGroupCallParticipants(let call, let participants, let version): return ("updateGroupCallParticipants", [("call", call as Any), ("participants", participants as Any), ("version", version as Any)]) case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): @@ -2165,6 +2183,10 @@ public extension Api { return ("updatePinnedChannelMessages", [("flags", flags as Any), ("channelId", channelId as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updatePinnedDialogs(let flags, let folderId, let order): return ("updatePinnedDialogs", [("flags", flags as Any), ("folderId", folderId as Any), ("order", order as Any)]) + case .updatePinnedForumTopic(let flags, let peer, let topicId): + return ("updatePinnedForumTopic", [("flags", flags as Any), ("peer", peer as Any), ("topicId", topicId as Any)]) + case .updatePinnedForumTopics(let flags, let peer, let order): + return ("updatePinnedForumTopics", [("flags", flags as Any), ("peer", peer as Any), ("order", order as Any)]) case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): return ("updatePinnedMessages", [("flags", flags as Any), ("peer", peer as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updatePinnedSavedDialogs(let flags, let order): @@ -2872,42 +2894,6 @@ public extension Api { return nil } } - public static func parse_updateChannelPinnedTopic(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelPinnedTopic(flags: _1!, channelId: _2!, topicId: _3!) - } - else { - return nil - } - } - public static func parse_updateChannelPinnedTopics(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelPinnedTopics(flags: _1!, channelId: _2!, order: _3) - } - else { - return nil - } - } public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() @@ -3582,6 +3568,50 @@ public extension Api { return nil } } + public static func parse_updateGroupCallEncryptedMessage(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCallEncryptedMessage(call: _1!, fromId: _2!, encryptedMessage: _3!) + } + else { + return nil + } + } + public static func parse_updateGroupCallMessage(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.TextWithEntities? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCallMessage(call: _1!, fromId: _2!, message: _3!) + } + else { + return nil + } + } public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? { var _1: Api.InputGroupCall? if let signature = reader.readInt32() { @@ -4156,6 +4186,46 @@ public extension Api { return nil } } + public static func parse_updatePinnedForumTopic(_ reader: BufferReader) -> Update? { + 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: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedForumTopic(flags: _1!, peer: _2!, topicId: _3!) + } + else { + return nil + } + } + public static func parse_updatePinnedForumTopics(_ reader: BufferReader) -> Update? { + 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: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedForumTopics(flags: _1!, peer: _2!, order: _3) + } + else { + return nil + } + } public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index 20b2443b6a..baca4c5d6f 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -3414,27 +3414,6 @@ public extension Api.functions.channels { }) } } -public extension Api.functions.channels { - static func reorderPinnedForumTopics(flags: Int32, channel: Api.InputChannel, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(693150095) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.reorderPinnedForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} public extension Api.functions.channels { static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -3839,23 +3818,6 @@ public extension Api.functions.channels { }) } } -public extension Api.functions.channels { - static func updatePinnedForumTopic(channel: Api.InputChannel, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1814925350) - channel.serialize(buffer, true) - serializeInt32(topicId, buffer: buffer, boxed: false) - pinned.serialize(buffer, true) - return (FunctionDescription(name: "channels.updatePinnedForumTopic", parameters: [("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("pinned", String(describing: pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} public extension Api.functions.channels { static func updateUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -7724,6 +7686,27 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func reorderPinnedForumTopics(flags: Int32, peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(242762224) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reorderPinnedForumTopics", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func reorderPinnedSavedDialogs(flags: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -9183,6 +9166,23 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func updatePinnedForumTopic(peer: Api.InputPeer, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(392032849) + peer.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + pinned.serialize(buffer, true) + return (FunctionDescription(name: "messages.updatePinnedForumTopic", parameters: [("peer", String(describing: peer)), ("topicId", String(describing: topicId)), ("pinned", String(describing: pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func updatePinnedMessage(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -10796,6 +10796,38 @@ public extension Api.functions.phone { }) } } +public extension Api.functions.phone { + static func sendGroupCallEncryptedMessage(call: Api.InputGroupCall, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-441473683) + call.serialize(buffer, true) + serializeBytes(encryptedMessage, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.sendGroupCallEncryptedMessage", parameters: [("call", String(describing: call)), ("encryptedMessage", String(describing: encryptedMessage))]), 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.phone { + static func sendGroupCallMessage(call: Api.InputGroupCall, message: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-614432696) + call.serialize(buffer, true) + message.serialize(buffer, true) + return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("call", String(describing: call)), ("message", String(describing: message))]), 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.phone { static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 5d79763057..b3b34302d0 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -822,6 +822,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private let e2eContext: ConferenceCallE2EContext? + private var messagesContext: GroupCallMessagesContext? { + didSet { + if let messagesContext = self.messagesContext { + self.messagesStatePromise.set(messagesContext.state) + } + } + } + private let messagesStatePromise = Promise(GroupCallMessagesContext.State(messages: [])) + public var messagesState: Signal { + return self.messagesStatePromise.get() + } + private var lastErrorAlertTimestamp: Double = 0.0 init( @@ -911,6 +923,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.e2eContext = nil } + if let initialCall { + self.messagesContext = accountContext.engine.messages.groupCallMessages( + callId: initialCall.description.id, + reference: .id(id: initialCall.description.id, accessHash: initialCall.description.accessHash), + e2eContext: self.e2eContext + ) + } + var sharedAudioContext = sharedAudioContext if sharedAudioContext == nil { var useSharedAudio = !isStream @@ -2959,6 +2979,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } public func setIsMuted(action: PresentationGroupCallMuteAction) { + if "".isEmpty { + self.messagesContext?.send(text: "test\(UInt32.random(in: 0 ... UInt32.max))", entities: []) + return + } + if self.isMutedValue == action { return } @@ -3959,6 +3984,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } }) } + + public func sendMessage(text: String, entities: [MessageTextEntity]) { + if let messagesContext = self.messagesContext { + messagesContext.send(text: text, entities: entities) + } + } } public final class TelegramE2EEncryptionProviderImpl: TelegramE2EEncryptionProvider { diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 756178bc4a..df16ffedc2 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -118,6 +118,8 @@ enum AccountStateMutationOperation { case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32) case UpdateGroupCall(peerId: PeerId?, call: Api.GroupCall) case UpdateGroupCallChainBlocks(id: Int64, accessHash: Int64, subChainId: Int32, blocks: [Data], nextOffset: Int32) + case UpdateGroupCallMessage(id: Int64, authorId: PeerId, text: Api.TextWithEntities) + case UpdateGroupCallOpaqueMessage(id: Int64, authorId: PeerId, data: Data) case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) case UpdateAttachMenuBots case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String) @@ -409,6 +411,14 @@ struct AccountMutableState { self.addOperation(.UpdateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks, nextOffset: nextOffset)) } + mutating func updateGroupCallMessage(id: Int64, authorId: PeerId, text: Api.TextWithEntities) { + self.addOperation(.UpdateGroupCallMessage(id: id, authorId: authorId, text: text)) + } + + mutating func updateGroupCallOpaqueMessage(id: Int64, authorId: PeerId, data: Data) { + self.addOperation(.UpdateGroupCallOpaqueMessage(id: id, authorId: authorId, data: data)) + } + mutating func updateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) { self.addOperation(.UpdateAutoremoveTimeout(peer: peer, value: value)) } @@ -719,7 +729,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: break case let .AddMessages(messages, location): for message in messages { @@ -855,6 +865,7 @@ struct AccountReplayedFinalState { let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] + let groupCallMessageUpdates: [GroupCallMessageUpdate] let storyUpdates: [InternalStoryUpdate] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] @@ -882,6 +893,7 @@ struct AccountFinalStateEvents { let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] + let groupCallMessageUpdates: [GroupCallMessageUpdate] let storyUpdates: [InternalStoryUpdate] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] @@ -903,10 +915,10 @@ struct AccountFinalStateEvents { let addedConferenceInvitationMessagesIds: [MessageId] var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty + return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty } - init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) { + init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds @@ -916,6 +928,7 @@ struct AccountFinalStateEvents { self.updatedCalls = updatedCalls self.addedCallSignalingData = addedCallSignalingData self.updatedGroupCallParticipants = updatedGroupCallParticipants + self.groupCallMessageUpdates = groupCallMessageUpdates self.storyUpdates = storyUpdates self.updatedPeersNearby = updatedPeersNearby self.isContactUpdates = isContactUpdates @@ -948,6 +961,7 @@ struct AccountFinalStateEvents { self.updatedCalls = state.updatedCalls self.addedCallSignalingData = state.addedCallSignalingData self.updatedGroupCallParticipants = state.updatedGroupCallParticipants + self.groupCallMessageUpdates = state.groupCallMessageUpdates self.storyUpdates = state.storyUpdates self.updatedPeersNearby = state.updatedPeersNearby self.isContactUpdates = state.isContactUpdates @@ -1016,6 +1030,7 @@ struct AccountFinalStateEvents { updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, + groupCallMessageUpdates: self.groupCallMessageUpdates + other.groupCallMessageUpdates, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 51f6ea64c1..7fc609da77 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -499,8 +499,8 @@ func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, } } } else { - return account.postbox.transaction { transaction -> Api.InputChannel? in - guard let inputChannel = transaction.getPeer(id).flatMap(apiInputChannel) else { + return account.postbox.transaction { transaction -> Api.InputPeer? in + guard let inputChannel = transaction.getPeer(id).flatMap(apiInputPeer) else { return nil } @@ -509,14 +509,14 @@ func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, return inputChannel } |> castError(SetForumChannelTopicPinnedError.self) - |> mapToSignal { inputChannel -> Signal in - guard let inputChannel = inputChannel else { + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { return .fail(.generic) } - return account.network.request(Api.functions.channels.reorderPinnedForumTopics( + return account.network.request(Api.functions.messages.reorderPinnedForumTopics( flags: 1 << 0, - channel: inputChannel, + peer: inputPeer, order: threadIds.map(Int32.init(clamping:)) )) |> mapError { _ -> SetForumChannelTopicPinnedError in diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 41e635d980..4bd29bab42 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -115,10 +115,9 @@ private func peerIdsRequiringLocalChatStateFromUpdates(_ updates: [Api.Update]) case let .updateChannelTooLong(_, channelId, _): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) peerIds.insert(peerId) - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if order == nil { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - peerIds.insert(peerId) + peerIds.insert(peerId.peerId) } case let .updateFolderPeers(folderPeers, _, _): for peer in folderPeers { @@ -357,10 +356,9 @@ private func peerIdsRequiringLocalChatStateFromDifference(_ difference: Api.upda case let .updateChannelTooLong(_, channelId, _): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) peerIds.insert(peerId) - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if order == nil { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - peerIds.insert(peerId) + peerIds.insert(peerId.peerId) } default: break @@ -384,10 +382,9 @@ private func peerIdsRequiringLocalChatStateFromDifference(_ difference: Api.upda case let .updateChannelTooLong(_, channelId, _): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) peerIds.insert(peerId) - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if order == nil { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - peerIds.insert(peerId) + peerIds.insert(peerId.peerId) } default: break @@ -767,15 +764,15 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] { } else { updatesByChannel[peerId]!.append(update) } - case let .updateChannelPinnedTopic(_, channelId, _): - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + case let .updatePinnedForumTopic(_, peerId, _): + let peerId = peerId.peerId if updatesByChannel[peerId] == nil { updatesByChannel[peerId] = [update] } else { updatesByChannel[peerId]!.append(update) } - case let .updateChannelPinnedTopics(_, channelId, _): - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + case let .updatePinnedForumTopics(_, peerId, _): + let peerId = peerId.peerId if updatesByChannel[peerId] == nil { updatesByChannel[peerId] = [update] } else { @@ -922,11 +919,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: channelsToPoll[peerId] = channelPts } } - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if let order = order { - updatedState.addUpdatePinnedTopicOrder(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadIds: order.map(Int64.init)) + updatedState.addUpdatePinnedTopicOrder(peerId: peerId.peerId, threadIds: order.map(Int64.init)) } else { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + let peerId = peerId.peerId if case .none = channelsToPoll[peerId] { channelsToPoll[peerId] = nil } @@ -1555,9 +1552,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: } else { updatedState.addUpdatePinnedSavedItemIds(operation: .sync) } - case let .updateChannelPinnedTopic(flags, channelId, topicId): + case let .updatePinnedForumTopic(flags, peerId, topicId): let isPinned = (flags & (1 << 0)) != 0 - updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned) + updatedState.addUpdatePinnedTopic(peerId: peerId.peerId, threadId: Int64(topicId), isPinned: isPinned) case let .updateReadMessagesContents(_, messages, _, _, date): updatedState.addReadMessagesContents((nil, nil, messages), date: date) case let .updateChannelReadMessagesContents(_, channelId, topMsgId, savedPeerId, messages): @@ -1687,6 +1684,14 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: if case let .inputGroupCall(id, accessHash) = call { updatedState.updateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks.map { $0.makeData() }, nextOffset: nextOffset) } + case let .updateGroupCallMessage(call, fromId, message): + if case let .inputGroupCall(id, _) = call { + updatedState.updateGroupCallMessage(id: id, authorId: fromId.peerId, text: message) + } + case let .updateGroupCallEncryptedMessage(call, fromId, encryptedMessage): + if case let .inputGroupCall(id, _) = call { + updatedState.updateGroupCallOpaqueMessage(id: id, authorId: fromId.peerId, data: encryptedMessage.makeData()) + } case let .updatePeerHistoryTTL(_, peer, ttl): updatedState.updateAutoremoveTimeout(peer: peer, value: CachedPeerAutoremoveTimeout.Value(ttl)) case let .updateLangPackTooLong(langCode): @@ -3613,7 +3618,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -3730,6 +3735,7 @@ func replayFinalState( var updatedCalls: [Api.PhoneCall] = [] var addedCallSignalingData: [(Int64, Data)] = [] var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [] + var groupCallMessageUpdates: [GroupCallMessageUpdate] = [] var storyUpdates: [InternalStoryUpdate] = [] var updatedPeersNearby: [PeerNearby]? var isContactUpdates: [(PeerId, Bool)] = [] @@ -4871,6 +4877,13 @@ func replayFinalState( callId, .state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version)) )) + case let .UpdateGroupCallMessage(callId, authorId, text): + switch text { + case let .textWithEntities(text, entities): + groupCallMessageUpdates.append(GroupCallMessageUpdate(callId: callId, update: .newPlaintextMessage(authorId: authorId, text: text, entities: messageTextEntitiesFromApiEntities(entities)))) + } + case let .UpdateGroupCallOpaqueMessage(callId, authorId, data): + groupCallMessageUpdates.append(GroupCallMessageUpdate(callId: callId, update: .newOpaqueMessage(authorId: authorId, data: data))) case let .UpdateGroupCall(peerId, call): switch call { case .groupCall: @@ -5834,6 +5847,7 @@ func replayFinalState( updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, + groupCallMessageUpdates: groupCallMessageUpdates, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index 59a730582b..3f61deae1c 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -305,6 +305,11 @@ public final class AccountStateManager { return self.groupCallParticipantUpdatesPipe.signal() } + private let groupCallMessageUpdatesPipe = ValuePipe<[GroupCallMessageUpdate]>() + public var groupCallMessageUpdates: Signal<[GroupCallMessageUpdate], NoError> { + return self.groupCallMessageUpdatesPipe.signal() + } + private let deletedMessagesPipe = ValuePipe<[DeletedMessageId]>() public var deletedMessages: Signal<[DeletedMessageId], NoError> { return self.deletedMessagesPipe.signal() @@ -1129,6 +1134,9 @@ public final class AccountStateManager { if !events.updatedGroupCallParticipants.isEmpty { strongSelf.groupCallParticipantUpdatesPipe.putNext(events.updatedGroupCallParticipants) } + if !events.groupCallMessageUpdates.isEmpty { + strongSelf.groupCallMessageUpdatesPipe.putNext(events.groupCallMessageUpdates) + } if !events.storyUpdates.isEmpty { strongSelf.storyUpdatesPipe.putNext(events.storyUpdates) } @@ -1924,6 +1932,12 @@ public final class AccountStateManager { } } + var groupCallMessageUpdates: Signal<[GroupCallMessageUpdate], NoError> { + return self.impl.signalWith { impl, subscriber in + return impl.groupCallMessageUpdates.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) + } + } + public var deletedMessages: Signal<[DeletedMessageId], NoError> { return self.impl.signalWith { impl, subscriber in return impl.deletedMessages.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index b20cb92f1c..b6d444c2d9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -3373,3 +3373,248 @@ func _internal_refreshInlineGroupCall(account: Account, messageId: MessageId) -> |> ignoreValues } } + +struct GroupCallMessageUpdate { + enum Update { + case newPlaintextMessage(authorId: PeerId, text: String, entities: [MessageTextEntity]) + case newOpaqueMessage(authorId: PeerId, data: Data) + } + + var callId: Int64 + var update: Update + + init(callId: Int64, update: Update) { + self.callId = callId + self.update = update + } +} + +public final class GroupCallMessagesContext { + public final class Message: Equatable { + public let id: Int64 + public let author: EnginePeer? + public let text: String + public let entities: [MessageTextEntity] + + public init(id: Int64, author: EnginePeer?, text: String, entities: [MessageTextEntity]) { + self.id = id + self.author = author + self.text = text + self.entities = entities + } + + public static func ==(lhs: Message, rhs: Message) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs === rhs { + return true + } + if lhs.author != rhs.author { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.entities != rhs.entities { + return false + } + return true + } + } + + public struct State: Equatable { + public var messages: [Message] + + public init(messages: [Message]) { + self.messages = messages + } + } + + private final class Impl { + let queue: Queue + let account: Account + let callId: Int64 + let reference: InternalGroupCallReference + let e2eContext: ConferenceCallE2EContext? + + var nextId: Int64 = 1 + + var state: State { + didSet { + self.stateValue.set(self.state) + } + } + let stateValue = ValuePromise() + + var updatesDisposable: Disposable? + let sendMessageDisposables = DisposableSet() + + init(queue: Queue, account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?) { + self.queue = queue + self.account = account + self.callId = callId + self.reference = reference + self.e2eContext = e2eContext + + self.state = State(messages: []) + self.stateValue.set(self.state) + + self.updatesDisposable = (account.stateManager.groupCallMessageUpdates + |> deliverOn(self.queue)).startStrict(next: { [weak self] updates in + guard let self else { + return + } + var addedMessages: [(authorId: PeerId, text: String, entities: [MessageTextEntity])] = [] + var addedOpaqueMessages: [(authorId: PeerId, data: Data)] = [] + for update in updates { + if update.callId != self.callId { + continue + } + switch update.update { + case let .newPlaintextMessage(authorId, text, entities): + addedMessages.append((authorId, text, entities)) + case let .newOpaqueMessage(authorId, data): + addedOpaqueMessages.append((authorId, data)) + } + } + + if !addedMessages.isEmpty || !addedOpaqueMessages.isEmpty { + let _ = (self.account.postbox.transaction { transaction -> [Message] in + var messages: [Message] = [] + if let e2eContext = self.e2eContext { + let decryptedMessages = e2eContext.state.with({ state -> [Data?] in + guard let state = state.state else { + return [] + } + var result: [Data?] = [] + for addedOpaqueMessage in addedOpaqueMessages { + result.append(state.decrypt(message: addedOpaqueMessage.data, userId: addedOpaqueMessage.authorId.id._internalGetInt64Value())) + } + return result + }) + for i in 0 ..< addedOpaqueMessages.count { + let addedOpaqueMessage = addedOpaqueMessages[i] + var decryptedMessage: Data? + if i < decryptedMessages.count { + decryptedMessage = decryptedMessages[i] + } + guard let decryptedMessage else { + continue + } + guard let text = String(data: decryptedMessage, encoding: .utf8) else { + continue + } + + let messageId = self.nextId + self.nextId += 1 + messages.append(Message( + id: messageId, + author: transaction.getPeer(addedOpaqueMessage.authorId).flatMap(EnginePeer.init), + text: text, + entities: [] + )) + } + } else { + for addedMessage in addedMessages { + let messageId = self.nextId + self.nextId += 1 + messages.append(Message( + id: messageId, + author: transaction.getPeer(addedMessage.authorId).flatMap(EnginePeer.init), + text: addedMessage.text, + entities: addedMessage.entities + )) + } + } + return messages + } + |> deliverOn(self.queue)).startStandalone(next: { [weak self] messages in + guard let self else { + return + } + var state = self.state + state.messages.append(contentsOf: messages) + self.state = state + }) + } + }) + } + + deinit { + self.updatesDisposable?.dispose() + self.sendMessageDisposables.dispose() + } + + func send(text: String, entities: [MessageTextEntity]) { + let accountPeerId = self.account.peerId + let _ = (self.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(accountPeerId) + } + |> deliverOn(self.queue)).startStandalone(next: { [weak self] accountPeer in + guard let self else { + return + } + + let messageId = self.nextId + self.nextId += 1 + + var state = self.state + state.messages.append(Message( + id: messageId, + author: accountPeer.flatMap(EnginePeer.init), + text: text, + entities: entities + )) + self.state = state + + if let e2eContext = self.e2eContext { + let messageData = text.data(using: .utf8)! + let encryptedMessage = e2eContext.state.with({ state -> Data? in + guard let state = state.state else { + return nil + } + return state.encrypt(message: messageData, channelId: 2, plaintextPrefixLength: 0) + }) + if let encryptedMessage { + self.sendMessageDisposables.add(self.account.network.request(Api.functions.phone.sendGroupCallEncryptedMessage( + call: self.reference.apiInputGroupCall, + encryptedMessage: Buffer(data: encryptedMessage) + )).startStrict()) + } + } else { + self.sendMessageDisposables.add(self.account.network.request(Api.functions.phone.sendGroupCallMessage( + call: self.reference.apiInputGroupCall, + message: .textWithEntities( + text: text, + entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()) + ) + )).startStrict()) + } + }) + } + } + + private let queue: Queue + private let impl: QueueLocalObject + + public var state: Signal { + return self.impl.signalWith { impl, subscriber in + return impl.stateValue.get().startStandalone(next: subscriber.putNext) + } + } + + init(account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?) { + let queue = Queue(name: "GroupCallMessagesContext") + self.queue = queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, account: account, callId: callId, reference: reference, e2eContext: e2eContext) + }) + } + + public func send(text: String, entities: [MessageTextEntity]) { + self.impl.with { impl in + impl.send(text: text, entities: entities) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 204e060b55..b8e432e461 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1631,6 +1631,10 @@ public extension TelegramEngine { public func refreshGlobalPostSearchState() -> Signal { return _internal_refreshGlobalPostSearchState(account: self.account) } + + public func groupCallMessages(callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?) -> GroupCallMessagesContext { + return GroupCallMessagesContext(account: self.account, callId: callId, reference: reference, e2eContext: e2eContext) + } } }