From b2351194d4c2c4e2574bd4160135800596cc872e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 24 Feb 2025 17:11:08 +0400 Subject: [PATCH] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 6 +- .../Sources/AccountContext.swift | 35 +- .../Sources/ShareController.swift | 2 +- .../GameUI/Sources/GameControllerNode.swift | 2 +- .../Sources/InviteLinkEditController.swift | 7 +- .../Sources/InviteLinkViewController.swift | 12 +- .../ChannelPermissionsController.swift | 10 +- .../IncomingMessagePrivacyScreen.swift | 14 +- .../Sources/ShareController.swift | 34 +- .../ShareItems/Sources/ShareItems.swift | 14 +- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api35.swift | 46 ++- submodules/TelegramApi/Sources/Api38.swift | 20 + .../StandaloneSendMessage.swift | 44 +- .../SyncCore/SyncCore_CachedChannelData.swift | 1 + .../TelegramEngine/Data/PeersData.swift | 16 +- ...OutgoingMessageWithChatContextResult.swift | 9 +- .../Messages/TelegramEngineMessages.swift | 18 +- .../TelegramEngine/Payments/StarGifts.swift | 3 +- .../TelegramEngine/Peers/AddPeerMember.swift | 14 +- .../Peers/UpdateCachedPeerData.swift | 4 + .../Sources/ChatMessageBubbleItemNode.swift | 8 +- .../ChatMessagePaymentAlertController.swift | 19 +- .../Sources/ChatUserInfoItem.swift | 14 +- .../Sources/GiftOptionsScreen.swift | 53 ++- .../LegacyCamera/Sources/LegacyCamera.swift | 2 +- .../Sources/LegacyMessageInputPanel.swift | 1 + .../Sources/MediaEditorScreen.swift | 1 + .../Sources/StoryPreviewComponent.swift | 1 + .../Sources/MessageInputPanelComponent.swift | 104 +++-- .../Components/SendInviteLinkScreen/BUILD | 1 + .../Sources/SendInviteLinkScreen.swift | 109 ++++- .../Sources/ShareExtensionContext.swift | 24 +- .../Sources/StarsWithdrawalScreen.swift | 4 +- .../Stories/StoryContainerScreen/BUILD | 1 + .../Sources/StoryChatContent.swift | 46 ++- .../Sources/StoryContent.swift | 8 +- .../StoryItemSetContainerComponent.swift | 17 +- ...StoryItemSetContainerViewSendMessage.swift | 322 ++++++++++----- .../Chat/ChatControllerLoadDisplayNode.swift | 8 +- .../Chat/ChatControllerMediaRecording.swift | 6 +- .../Chat/ChatControllerPaidMessage.swift | 8 +- .../TelegramUI/Sources/ChatController.swift | 6 +- .../ChatControllerForwardMessages.swift | 383 ++++++++++-------- .../Sources/ChatTextInputPanelNode.swift | 3 +- 45 files changed, 959 insertions(+), 503 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index f189e1295f..57ed87606b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13819,12 +13819,12 @@ Sorry for the inconvenience."; "GroupInfo.Permissions.ChargeForMessages" = "Charge for Messages"; "GroupInfo.Permissions.ChargeForMessagesInfo" = "If you turn this on, regular members of the group will have to pay Stars to send messages."; "GroupInfo.Permissions.MessagePrice" = "SET YOUR PRICE PER MESSAGE"; -"GroupInfo.Permissions.MessagePriceInfo" = "Your group will receive 85% of the selected fee (%1$@) for each incoming message."; +"GroupInfo.Permissions.MessagePriceInfo" = "Your group will receive %1$@% of the selected fee (%2$@) for each incoming message."; "Privacy.Messages.ChargeForMessages" = "Charge for Messages"; "Privacy.Messages.ChargeForMessagesInfo" = "Charge a fee for messages from people outide your contacts or those you haven't messaged first."; "Privacy.Messages.MessagePrice" = "SET YOUR PRICE PER MESSAGE"; -"Privacy.Messages.MessagePriceInfo" = "Your will receive 85% of the selected fee (%1$@) for each incoming message."; +"Privacy.Messages.MessagePriceInfo" = "Your will receive %1$@% of the selected fee (%2$@) for each incoming message."; "Privacy.Messages.RemoveFeeHeader" = "EXCEPTIONS"; "Privacy.Messages.RemoveFee" = "Remove Fee"; @@ -13874,3 +13874,5 @@ Sorry for the inconvenience."; "Gift.Send.PayWithStars.Info" = "Your balance is **%@**. [Get More Stars >]()"; "Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()"; + +"Chat.InputTextPaidMessagePlaceholder" = "Message for %@"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 686709c540..57f228240a 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1353,20 +1353,43 @@ public struct StickersSearchConfiguration { public struct StarsSubscriptionConfiguration { static var defaultValue: StarsSubscriptionConfiguration { - return StarsSubscriptionConfiguration(maxFee: 2500, usdWithdrawRate: 1200) + return StarsSubscriptionConfiguration( + maxFee: 2500, + usdWithdrawRate: 1200, + paidMessageMaxAmount: 10000, + paidMessageCommissionPermille: 850 + ) } - public let maxFee: Int64? - public let usdWithdrawRate: Int64? + public let maxFee: Int64 + public let usdWithdrawRate: Int64 + public let paidMessageMaxAmount: Int64 + public let paidMessageCommissionPermille: Int32 - fileprivate init(maxFee: Int64?, usdWithdrawRate: Int64?) { + fileprivate init( + maxFee: Int64, + usdWithdrawRate: Int64, + paidMessageMaxAmount: Int64, + paidMessageCommissionPermille: Int32 + ) { self.maxFee = maxFee self.usdWithdrawRate = usdWithdrawRate + self.paidMessageMaxAmount = paidMessageMaxAmount + self.paidMessageCommissionPermille = paidMessageCommissionPermille } public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration { - if let data = appConfiguration.data, let value = data["stars_subscription_amount_max"] as? Double, let usdRate = data["stars_usd_withdraw_rate_x1000"] as? Double { - return StarsSubscriptionConfiguration(maxFee: Int64(value), usdWithdrawRate: Int64(usdRate)) + if let data = appConfiguration.data { + let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee + let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate + let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount + let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille + return StarsSubscriptionConfiguration( + maxFee: maxFee, + usdWithdrawRate: usdWithdrawRate, + paidMessageMaxAmount: paidMessageMaxAmount, + paidMessageCommissionPermille: paidMessageCommissionPermille + ) } else { return .defaultValue } diff --git a/submodules/AccountContext/Sources/ShareController.swift b/submodules/AccountContext/Sources/ShareController.swift index 122c247bda..6e33940d3e 100644 --- a/submodules/AccountContext/Sources/ShareController.swift +++ b/submodules/AccountContext/Sources/ShareController.swift @@ -76,5 +76,5 @@ public enum ShareControllerSubject { case image([ImageRepresentationWithReference]) case media(AnyMediaReference, MediaParameters?) case mapMedia(TelegramMediaMap) - case fromExternal(([PeerId], [PeerId: Int64], String, ShareControllerAccountContext, Bool) -> Signal) + case fromExternal(Int, ([PeerId], [PeerId: Int64], [PeerId: StarsAmount], String, ShareControllerAccountContext, Bool) -> Signal) } diff --git a/submodules/GameUI/Sources/GameControllerNode.swift b/submodules/GameUI/Sources/GameControllerNode.swift index c0274d25b1..06923b10d1 100644 --- a/submodules/GameUI/Sources/GameControllerNode.swift +++ b/submodules/GameUI/Sources/GameControllerNode.swift @@ -144,7 +144,7 @@ final class GameControllerNode: ViewControllerTracingNode { if eventName == "share_game" || eventName == "share_score" { if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty { if eventName == "share_score" { - self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, threadIds, text, account, _ in + self.present(ShareController(context: self.context, subject: .fromExternal(1, { [weak self] peerIds, threadIds, requireStars, text, account, _ in if let strongSelf = self, let message = strongSelf.message, let account = account as? ShareControllerAppAccountContext { let signals = peerIds.map { TelegramEngine(account: account.context.account).messages.forwardGameWithScore(messageId: message.id, to: $0, threadId: threadIds[$0], as: nil) } return .single(.preparing(false)) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift index e1b84e2123..ceb9f8df51 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift @@ -493,13 +493,10 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: if state.subscriptionEnabled { var label: String = "" if let subscriptionFee = state.subscriptionFee, subscriptionFee > StarsAmount.zero { - var usdRate = 0.012 - if let usdWithdrawRate = configuration.usdWithdrawRate { - usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - } + let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0 label = presentationData.strings.InviteLink_Create_FeePerMonth("≈\(formatTonUsdValue(subscriptionFee.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))").string } - entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, configuration.maxFee.flatMap({ StarsAmount(value: $0, nanos: 0) }))) + entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, StarsAmount(value: configuration.maxFee, nanos: 0))) } let infoText: String if let _ = invite, state.subscriptionEnabled { diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index c1191eb94c..e303fe8b3b 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -594,10 +594,7 @@ public final class InviteLinkViewController: ViewController { guard let peer else { return } - var usdRate = 0.012 - if let usdWithdrawRate = configuration.usdWithdrawRate { - usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - } + let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0 let subscriptionController = context.sharedContext.makeStarsSubscriptionScreen(context: context, peer: peer, pricing: pricing, importer: importer, usdRate: usdRate) self?.controller?.push(subscriptionController) }) @@ -834,11 +831,8 @@ public final class InviteLinkViewController: ViewController { context.account.postbox.loadedPeerWithId(adminId) ) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, requestsState, creatorPeer in if let strongSelf = self { - var usdRate = 0.012 - if let usdWithdrawRate = configuration.usdWithdrawRate { - usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - } - + let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0 + var entries: [InviteLinkViewEntry] = [] entries.append(.link(presentationData.theme, invite)) diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index b1c1d646c5..f41f3921d1 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -720,7 +720,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen entries.append(.conversionInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_BroadcastConvertInfo(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.groupingSeparator)).string)) } - if channel.hasPermission(.banMembers) { + if cachedData.flags.contains(.paidMessagesAvailable) && channel.hasPermission(.banMembers) { let sendPaidMessageStars = state.modifiedStarsAmount?.value ?? (cachedData.sendPaidMessageStars?.value ?? 0) let chargeEnabled = sendPaidMessageStars > 0 entries.append(.chargeForMessages(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessages, chargeEnabled)) @@ -728,15 +728,13 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen if chargeEnabled { var price: String = "" - var usdRate = 0.012 - if let usdWithdrawRate = configuration.usdWithdrawRate { - usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - } + let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0 + price = "≈\(formatTonUsdValue(sendPaidMessageStars, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))" entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice)) entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, price)) - entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo(price).string)) + entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo("\(configuration.paidMessageCommissionPermille / 10)", price).string)) } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 0009407f80..e7b6151db8 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -50,7 +50,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { case footer(value: GlobalPrivacySettings.NonContactChatsPrivacy) case priceHeader case price(value: Int64, price: String) - case priceInfo(value: String) + case priceInfo(commission: Int32, value: String) case exceptionsHeader case exceptions(count: Int) case exceptionsInfo @@ -153,8 +153,8 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 1, maxValue: 10000, value: value, price: price, sectionId: self.section, updated: { value in arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0))) }) - case let .priceInfo(value): - return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo(value).string), sectionId: self.section) + case let .priceInfo(commission, value): + return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo("\(commission)", value).string), sectionId: self.section) case .exceptionsHeader: return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_RemoveFeeHeader, sectionId: self.section) case let .exceptions(count): @@ -184,14 +184,12 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD entries.append(.footer(value: state.updatedValue)) entries.append(.priceHeader) - var usdRate = 0.012 - if let usdWithdrawRate = configuration.usdWithdrawRate { - usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 - } + let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0 + let price = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))" entries.append(.price(value: amount.value, price: price)) - entries.append(.priceInfo(value: price)) + entries.append(.priceInfo(commission: configuration.paidMessageCommissionPermille / 10, value: price)) entries.append(.exceptionsHeader) entries.append(.exceptions(count: state.disableFor.count)) entries.append(.exceptionsInfo) diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index def38e6cfa..f2152f33bb 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -676,6 +676,8 @@ public final class ShareController: ViewController { messageCount = messages.count } else if case let .image(images) = self.subject { messageCount = images.count + } else if case let .fromExternal(count, _) = self.subject { + messageCount = count } var mediaParameters: ShareControllerSubject.MediaParameters? @@ -1253,18 +1255,18 @@ public final class ShareController: ViewController { } ) |> take(1) - |> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: Int64]) in + |> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: StarsAmount]) in var result: [EnginePeer.Id: EnginePeer?] = [:] - var requiresStars: [EnginePeer.Id: Int64] = [:] + var requiresStars: [EnginePeer.Id: StarsAmount] = [:] for peerId in peerIds { if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer { result[peerId] = EnginePeer(peer) if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { - requiresStars[peerId] = cachedData.sendPaidMessageStars?.value + requiresStars[peerId] = cachedData.sendPaidMessageStars } } else if let channel = peer as? TelegramChannel { - requiresStars[peerId] = channel.sendPaidMessageStars?.value + requiresStars[peerId] = channel.sendPaidMessageStars } } } @@ -1284,7 +1286,7 @@ public final class ShareController: ViewController { subject = selectedValue.subject } - func transformMessages(_ messages: [StandaloneSendEnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: Int64?) -> [StandaloneSendEnqueueMessage] { + func transformMessages(_ messages: [StandaloneSendEnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: StarsAmount?) -> [StandaloneSendEnqueueMessage] { return messages.map { message in var message = message if !showNames { @@ -1296,7 +1298,7 @@ public final class ShareController: ViewController { if silently { message.isSilent = true } - message.sendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) } + message.sendPaidMessageStars = sendPaidMessageStars return message } } @@ -1839,8 +1841,8 @@ public final class ShareController: ViewController { messages: messagesToEnqueue )) } - case let .fromExternal(f): - return f(peerIds, topicIds, text, strongSelf.currentContext, silently) + case let .fromExternal(_, f): + return f(peerIds, topicIds, requiresStars, text, strongSelf.currentContext, silently) |> map { state -> ShareState in switch state { case let .preparing(long): @@ -1907,18 +1909,18 @@ public final class ShareController: ViewController { } ) |> take(1) - |> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: Int64]) in + |> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: StarsAmount]) in var result: [EnginePeer.Id: EnginePeer?] = [:] - var requiresStars: [EnginePeer.Id: Int64] = [:] + var requiresStars: [EnginePeer.Id: StarsAmount] = [:] for peerId in peerIds { if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer { result[peerId] = EnginePeer(peer) if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { - requiresStars[peerId] = cachedData.sendPaidMessageStars?.value + requiresStars[peerId] = cachedData.sendPaidMessageStars } } else if let channel = peer as? TelegramChannel { - requiresStars[peerId] = channel.sendPaidMessageStars?.value + requiresStars[peerId] = channel.sendPaidMessageStars } } } @@ -1938,7 +1940,7 @@ public final class ShareController: ViewController { subject = selectedValue.subject } - func transformMessages(_ messages: [EnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: Int64?) -> [EnqueueMessage] { + func transformMessages(_ messages: [EnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: StarsAmount?) -> [EnqueueMessage] { return messages.map { message in return message.withUpdatedAttributes({ attributes in var attributes = attributes @@ -1949,7 +1951,7 @@ public final class ShareController: ViewController { attributes.append(NotificationInfoMessageAttribute(flags: .muted)) } if let sendPaidMessageStars { - attributes.append(PaidStarsMessageAttribute(stars: StarsAmount(value: sendPaidMessageStars, nanos: 0), postponeSending: false)) + attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) } return attributes }) @@ -2321,8 +2323,8 @@ public final class ShareController: ViewController { messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId]) shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messagesToEnqueue)) } - case let .fromExternal(f): - return f(peerIds, topicIds, text, currentContext, silently) + case let .fromExternal(_, f): + return f(peerIds, topicIds, requiresStars, text, currentContext, silently) |> map { state -> ShareState in switch state { case let .preparing(long): diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 84c74e819c..d063a8e0cf 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -409,7 +409,7 @@ public func preparedShareItems(postbox: Postbox, network: Network, to peerId: Pe }) } -public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, auxiliaryMethods: AccountAuxiliaryMethods, to peerIds: [PeerId], threadIds: [PeerId: Int64], items: [PreparedShareItemContent], silently: Bool, additionalText: String) -> Signal { +public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, auxiliaryMethods: AccountAuxiliaryMethods, to peerIds: [PeerId], threadIds: [PeerId: Int64], requireStars: [PeerId: StarsAmount], items: [PreparedShareItemContent], silently: Bool, additionalText: String) -> Signal { var messages: [StandaloneSendEnqueueMessage] = [] var groupingKey: Int64? var mediaTypes: (photo: Int, video: Int, music: Int, other: Int) = (0, 0, 0, 0) @@ -498,6 +498,16 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net var peerSignals: Signal = .single(0.0) for peerId in peerIds { + var peerMessages = messages + if let amount = requireStars[peerId] { + var updatedMessages: [StandaloneSendEnqueueMessage] = [] + for message in peerMessages { + var message = message + message.sendPaidMessageStars = amount + updatedMessages.append(message) + } + peerMessages = updatedMessages + } peerSignals = peerSignals |> then(standaloneSendEnqueueMessages( accountPeerId: accountPeerId, postbox: postbox, @@ -506,7 +516,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net auxiliaryMethods: auxiliaryMethods, peerId: peerId, threadId: threadIds[peerId], - messages: messages + messages: peerMessages ) |> mapToSignal { status -> Signal in switch status { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 26afc9bdca..42feaf44c6 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1390,7 +1390,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) } dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) } dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) } - dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) } + dict[-418915641] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) } dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) } dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) } dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) } diff --git a/submodules/TelegramApi/Sources/Api35.swift b/submodules/TelegramApi/Sources/Api35.swift index c8dbfa6fa7..6c7f89773e 100644 --- a/submodules/TelegramApi/Sources/Api35.swift +++ b/submodules/TelegramApi/Sources/Api35.swift @@ -1,16 +1,21 @@ public extension Api.payments { enum SavedStarGifts: TypeConstructorDescription { - case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) + case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, pinnedToTop: [Int64]?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users): + case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users): if boxed { - buffer.appendInt32(-1779201615) + buffer.appendInt32(-418915641) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {chatNotificationsEnabled!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(pinnedToTop!.count)) + for item in pinnedToTop! { + serializeInt64(item, buffer: buffer, boxed: false) + }} buffer.appendInt32(481674261) buffer.appendInt32(Int32(gifts.count)) for item in gifts { @@ -33,8 +38,8 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users): - return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) + case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users): + return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("pinnedToTop", pinnedToTop as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)]) } } @@ -47,29 +52,34 @@ public extension Api.payments { if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.Bool } } - var _4: [Api.SavedStarGift]? + var _4: [Int64]? + if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } + var _5: [Api.SavedStarGift]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self) } - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } - var _6: [Api.Chat]? + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + var _7: [Api.Chat]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _7: [Api.User]? + var _8: [Api.User]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = _6 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!) + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, pinnedToTop: _4, gifts: _5!, nextOffset: _6, chats: _7!, users: _8!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index a7f8b72dae..9c10a1bc0b 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -9717,6 +9717,26 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func toggleStarGiftsPinnedToTop(peer: Api.InputPeer, stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(353626032) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stargift.count)) + for item in stargift { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.toggleStarGiftsPinnedToTop", parameters: [("peer", String(describing: peer)), ("stargift", String(describing: stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.payments { static func transferStarGift(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index b41bf14e64..4fac57c73d 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -129,6 +129,7 @@ public func standaloneSendEnqueueMessages( struct MessageResult { var result: PendingMessageUploadedContentResult var media: [Media] + var attributes: [MessageAttribute] } let signals: [Signal] = messages.map { message in @@ -178,7 +179,10 @@ public func standaloneSendEnqueueMessages( if message.isSilent { attributes.append(NotificationInfoMessageAttribute(flags: .muted)) } - + if let sendPaidMessageStars = message.sendPaidMessageStars { + attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } + let content = messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: { _, _, _, _ in return .single(nil) }, messageMediaPreuploadManager: MessageMediaPreuploadManager(), revalidationContext: MediaReferenceRevalidationContext(), forceReupload: false, isGrouped: false, passFetchProgress: true, forceNoBigParts: false, peerId: peerId, messageId: nil, attributes: attributes, text: text, media: media) @@ -191,7 +195,7 @@ public func standaloneSendEnqueueMessages( } return contentResult |> map { contentResult in - return MessageResult(result: contentResult, media: media) + return MessageResult(result: contentResult, media: media, attributes: attributes) } } @@ -201,7 +205,7 @@ public func standaloneSendEnqueueMessages( } |> mapToSignal { contentResults -> Signal in var progressSum: Float = 0.0 - var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media])] = [] + var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media], attributes: [MessageAttribute])] = [] var allDone = true for result in contentResults { switch result.result { @@ -209,13 +213,13 @@ public func standaloneSendEnqueueMessages( allDone = false progressSum += value.progress case let .content(content): - allResults.append((content, result.media)) + allResults.append((content, result.media, result.attributes)) } } if allDone { var sendSignals: [Signal] = [] - for (content, media) in allResults { + for (content, media, attributes) in allResults { var text: String = "" switch content.content { case let .text(textValue): @@ -235,7 +239,7 @@ public func standaloneSendEnqueueMessages( peerId: peerId, content: content, text: text, - attributes: [], + attributes: attributes, media: media, threadId: threadId )) @@ -328,6 +332,7 @@ private func sendUploadedMessageContent( var videoTimestamp: Int32? var sendAsPeerId: PeerId? var bubbleUpEmojiOrStickersets = false + var allowPaidStars: Int64? var flags: Int32 = 0 @@ -365,6 +370,8 @@ private func sendUploadedMessageContent( } else if let attribute = attribute as? ForwardVideoTimestampAttribute { flags |= Int32(1 << 20) videoTimestamp = attribute.timestamp + } else if let attribute = attribute as? PaidStarsMessageAttribute { + allowPaidStars = attribute.stars.value } } @@ -390,6 +397,11 @@ private func sendUploadedMessageContent( flags |= (1 << 13) } + if let _ = allowPaidStars { + flags |= 1 << 21 + } + + let dependencyTag: PendingMessageRequestDependencyTag? = nil//(messageId: messageId) let sendMessageRequest: Signal, MTRpcError> @@ -415,7 +427,7 @@ private func sendUploadedMessageContent( } } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -437,7 +449,7 @@ private func sendUploadedMessageContent( } } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): var topMsgId: Int32? @@ -447,7 +459,7 @@ private func sendUploadedMessageContent( } if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) @@ -473,7 +485,7 @@ private func sendUploadedMessageContent( } } - sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, allowPaidStars: nil)) + sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, allowPaidStars: allowPaidStars)) |> map(NetworkRequestResult.result) case .messageScreenshot: let replyTo: Api.InputReplyTo @@ -585,6 +597,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M var replyToStoryId: StoryId? var scheduleTime: Int32? var sendAsPeerId: PeerId? + var allowPaidStars: Int64? var flags: Int32 = 0 flags |= (1 << 7) @@ -609,6 +622,8 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M scheduleTime = attribute.scheduleTime } else if let attribute = attribute as? SendAsMessageAttribute { sendAsPeerId = attribute.peerId + } else if let attribute = attribute as? PaidStarsMessageAttribute { + allowPaidStars = attribute.stars.value } } @@ -622,6 +637,11 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M flags |= (1 << 13) } + if let _ = allowPaidStars { + flags |= 1 << 21 + } + + let sendMessageRequest: Signal switch content { case let .text(text): @@ -641,7 +661,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil) } - sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars)) |> `catch` { _ -> Signal in return .complete() } @@ -662,7 +682,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil) } - sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars)) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index 45408d889e..89e671f7f9 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -25,6 +25,7 @@ public struct CachedChannelFlags: OptionSet { public static let paidMediaAllowed = CachedChannelFlags(rawValue: 1 << 11) public static let canViewStarsRevenue = CachedChannelFlags(rawValue: 1 << 12) public static let starGiftsAvailable = CachedChannelFlags(rawValue: 1 << 13) + public static let paidMessagesAvailable = CachedChannelFlags(rawValue: 1 << 14) } public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 71ed10b3ac..5242e9432f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -375,22 +375,18 @@ public extension TelegramEngine.EngineData.Item { } var key: PostboxViewKey { - return .cachedPeerData(peerId: self.id) + return .peer(peerId: self.id, components: [.cachedData]) } func extract(view: PostboxView) -> Result { - guard let view = view as? CachedPeerDataView else { + guard let view = view as? PeerView else { preconditionFailure() } - guard let cachedPeerData = view.cachedPeerData else { - return nil - } - switch cachedPeerData { - case let user as CachedUserData: - return user.sendPaidMessageStars - case let channel as CachedChannelData: + if let cachedPeerData = view.cachedData as? CachedUserData { + return cachedPeerData.sendPaidMessageStars + } else if let channel = peerViewMainPeer(view) as? TelegramChannel { return channel.sendPaidMessageStars - default: + } else { return nil } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index c93f33e990..3302c7fafd 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -2,15 +2,15 @@ import Foundation import Postbox import SwiftSignalKit -func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> Bool { - guard let message = _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) else { +func _internal_enqueueOutgoingMessageWithChatContextResult(account: Account, to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, sendPaidMessageStars: StarsAmount?, postpone: Bool, correlationId: Int64?) -> Bool { + guard let message = _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId) else { return false } let _ = enqueueMessages(account: account, peerId: peerId, messages: [message]).start() return true } -func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { +func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, sendPaidMessageStars: StarsAmount?, postpone: Bool, correlationId: Int64?) -> EnqueueMessage? { var replyToMessageId = replyToMessageId if replyToMessageId == nil, let threadId = threadId { replyToMessageId = EngineMessageReplySubject(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: MessageId.Id(clamping: threadId)), quote: nil) @@ -32,6 +32,9 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: if silentPosting { attributes.append(NotificationInfoMessageAttribute(flags: .muted)) } + if let sendPaidMessageStars { + attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: postpone)) + } switch result.message { case let .auto(caption, entities, replyMarkup): if let entities = entities { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index cda12a4577..2ae4018184 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -247,13 +247,14 @@ public extension TelegramEngine { storyId: StoryId? = nil, content: EngineOutgoingMessageContent, silentPosting: Bool = false, - scheduleTime: Int32? = nil + scheduleTime: Int32? = nil, + sendPaidMessageStars: StarsAmount? = nil ) -> Signal<[MessageId?], NoError> { var message: EnqueueMessage? if case let .preparedInlineMessage(preparedInlineMessage) = content { - message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: preparedInlineMessage.botId, result: preparedInlineMessage.result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil) + message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: preparedInlineMessage.botId, result: preparedInlineMessage.result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: false, correlationId: nil) } else if case let .contextResult(results, result) = content { - message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: nil) + message = self.outgoingMessageWithChatContextResult(to: peerId, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: storyId, hideVia: true, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: false, correlationId: nil) } else { var attributes: [MessageAttribute] = [] if silentPosting { @@ -262,6 +263,9 @@ public extension TelegramEngine { if let scheduleTime = scheduleTime { attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) } + if let sendPaidMessageStars { + attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } var text: String = "" var mediaReference: AnyMediaReference? @@ -301,12 +305,12 @@ public extension TelegramEngine { ) } - public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject? = nil, replyToStoryId: StoryId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, correlationId: Int64? = nil) -> Bool { - return _internal_enqueueOutgoingMessageWithChatContextResult(account: self.account, to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) + public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject? = nil, replyToStoryId: StoryId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, sendPaidMessageStars: StarsAmount?, postpone: Bool = false, correlationId: Int64? = nil) -> Bool { + return _internal_enqueueOutgoingMessageWithChatContextResult(account: self.account, to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId) } - public func outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { - return _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) + public func outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: EngineMessageReplySubject?, replyToStoryId: StoryId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, sendPaidMessageStars: StarsAmount?, postpone: Bool, correlationId: Int64?) -> EnqueueMessage? { + return _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone, correlationId: correlationId) } public func setMessageReactions( diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 4f4c241a45..653c79d8f4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -1064,7 +1064,8 @@ private final class ProfileGiftsContextImpl { } return postbox.transaction { transaction -> ([ProfileGiftsContext.State.StarGift], Int32, String?, Bool?) in switch result { - case let .savedStarGifts(_, count, apiNotificationsEnabled, apiGifts, nextOffset, chats, users): + case let .savedStarGifts(_, count, apiNotificationsEnabled, pinnedToTop, apiGifts, nextOffset, chats, users): + let _ = pinnedToTop let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift index 27ad703429..d303743a59 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift @@ -99,8 +99,8 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId } }) } - - return TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in + + let result = TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in switch invitee { case let .missingInvitee(flags, userId): guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else { @@ -113,6 +113,10 @@ func _internal_addGroupMember(account: Account, peerId: PeerId, memberId: PeerId ) } }) + + let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: result.forbiddenPeers.map { $0.peer.id }).startStandalone() + + return result } |> mapError { _ -> AddGroupMemberError in } |> mapToSignal { result -> Signal in @@ -186,6 +190,8 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer switch result { case let .invitedUsers(updates, missingInvitees): if case let .missingInvitee(flags, _) = missingInvitees.first { + let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: [memberPeer.id]).startStandalone() + return .fail(.restricted(TelegramForbiddenInvitePeer( peer: EnginePeer(memberPeer), canInviteWithPremium: (flags & (1 << 0)) != 0, @@ -302,7 +308,7 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P account.viewTracker.forceUpdateCachedPeerData(peerId: peerId) return account.postbox.transaction { transaction -> TelegramInvitePeersResult in - return TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in + let result = TelegramInvitePeersResult(forbiddenPeers: missingInviteesValue.compactMap { invitee -> TelegramForbiddenInvitePeer? in switch invitee { case let .missingInvitee(flags, userId): guard let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) else { @@ -315,6 +321,8 @@ func _internal_addChannelMembers(account: Account, peerId: PeerId, memberIds: [P ) } }) + let _ = _internal_updateIsPremiumRequiredToContact(account: account, peerIds: result.forbiddenPeers.map { $0.peer.id }).startStandalone() + return result } |> castError(AddChannelMemberError.self) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index c21d258bb2..9c68e06e69 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -620,6 +620,10 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee if (flags2 & Int32(1 << 19)) != 0 { channelFlags.insert(.starGiftsAvailable) } + if (flags2 & Int32(1 << 20)) != 0 { + channelFlags.insert(.paidMessagesAvailable) + } + let sendAsPeerId = defaultSendAs?.peerId let linkedDiscussionPeerId: PeerId? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 67f242a7ef..11c5a8cc1b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2877,7 +2877,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI bottomBubbleAttributes = contentPropertiesAndLayouts[i + 1].3 } - if i == 0 { + if i == 0 || (i == 1 && contentPropertiesAndLayouts[0].1.isDetached) { topPosition = firstNodeTopPosition } else { topPosition = .Neighbour(topBubbleAttributes.isAttachment, topBubbleAttributes.neighborType, topBubbleAttributes.neighborSpacing) @@ -3002,11 +3002,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } let (size, apply) = finalize(maxContentWidth) - var containerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentNodeOriginY), size: size) - if size.height == 33.0 && detachedContentNodesHeight > 0.0 { - //TODO:unmock - containerFrame = containerFrame.offsetBy(dx: 0.0, dy: 2.0) - } + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentNodeOriginY), size: size) contentNodeFramesPropertiesAndApply.append((containerFrame, properties, contentGroupId == nil, apply)) if contentProperties.neighborType == .media && unlockButtonPosition == nil { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift index 26dd0e00ff..ec77b0a7c1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift @@ -328,23 +328,34 @@ private class ChatMessagePaymentAlertController: AlertController { self.context = context self.presentationData = presentationData self.parentNavigationController = navigationController - + super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + + self.willDismiss = { [weak self] in + guard let self else { + return + } + self.animateOut() + } } required public init(coder aDecoder: NSCoder) { preconditionFailure() } - override func dismissAnimated() { - super.dismissAnimated() - + private func animateOut() { if let view = self.balance.view { view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false) view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) } } + override func dismissAnimated() { + super.dismissAnimated() + + self.animateOut() + } + override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) diff --git a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift index 3de9d47c32..ef8946b103 100644 --- a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift @@ -251,6 +251,9 @@ public final class ChatUserInfoItemNode: ListViewItemNode { let infoConstrainedSize = CGSize(width: constrainedWidth * 0.7, height: CGFloat.greatestFiniteMagnitude) + var maxTitleWidth: CGFloat = 0.0 + var maxValueWidth: CGFloat = 0.0 + var registrationDateText: String? let registrationDateTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)? let registrationDateValueLayoutAndApply: (TextNodeLayout, () -> TextNode)? @@ -272,7 +275,8 @@ public final class ChatUserInfoItemNode: ListViewItemNode { backgroundSize.height += verticalSpacing backgroundSize.height += registrationDateValueLayoutAndApply?.0.size.height ?? 0 - backgroundSize.width = max(backgroundSize.width, horizontalContentInset * 2.0 + (registrationDateTitleLayoutAndApply?.0.size.width ?? 0) + attributeSpacing + (registrationDateValueLayoutAndApply?.0.size.width ?? 0)) + maxTitleWidth = max(maxTitleWidth, (registrationDateTitleLayoutAndApply?.0.size.width ?? 0)) + maxValueWidth = max(maxValueWidth, (registrationDateValueLayoutAndApply?.0.size.width ?? 0)) } else { registrationDateTitleLayoutAndApply = nil registrationDateValueLayoutAndApply = nil @@ -297,7 +301,8 @@ public final class ChatUserInfoItemNode: ListViewItemNode { backgroundSize.height += verticalSpacing backgroundSize.height += phoneCountryValueLayoutAndApply?.0.size.height ?? 0 - backgroundSize.width = max(backgroundSize.width, horizontalContentInset * 2.0 + (phoneCountryTitleLayoutAndApply?.0.size.width ?? 0) + attributeSpacing + (phoneCountryValueLayoutAndApply?.0.size.width ?? 0)) + maxTitleWidth = max(maxTitleWidth, (phoneCountryTitleLayoutAndApply?.0.size.width ?? 0)) + maxValueWidth = max(maxValueWidth, (phoneCountryValueLayoutAndApply?.0.size.width ?? 0)) } else { phoneCountryTitleLayoutAndApply = nil phoneCountryValueLayoutAndApply = nil @@ -322,12 +327,15 @@ public final class ChatUserInfoItemNode: ListViewItemNode { backgroundSize.height += verticalSpacing backgroundSize.height += locationCountryValueLayoutAndApply?.0.size.height ?? 0 - backgroundSize.width = max(backgroundSize.width, horizontalContentInset * 2.0 + (locationCountryTitleLayoutAndApply?.0.size.width ?? 0) + attributeSpacing + (locationCountryValueLayoutAndApply?.0.size.width ?? 0)) + maxTitleWidth = max(maxTitleWidth, (locationCountryTitleLayoutAndApply?.0.size.width ?? 0)) + maxValueWidth = max(maxValueWidth, (locationCountryValueLayoutAndApply?.0.size.width ?? 0)) } else { locationCountryTitleLayoutAndApply = nil locationCountryValueLayoutAndApply = nil } + backgroundSize.width = horizontalContentInset * 3.0 + maxTitleWidth + attributeSpacing + maxValueWidth + let (groupsLayout, groupsApply) = makeGroupsLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "No groups in common", font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) backgroundSize.height += verticalSpacing * 2.0 + paragraphSpacing backgroundSize.height += groupsLayout.size.height diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index ee6a362431..d45f5fc0b9 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -502,9 +502,7 @@ final class GiftOptionsScreenComponent: Component { return } - //TODO:unmock let context = component.context - let alertController = giftTransferAlertController( context: context, gift: transferGift, @@ -517,36 +515,53 @@ final class GiftOptionsScreenComponent: Component { guard let controller, let navigationController = controller.navigationController as? NavigationController else { return } - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is ContactSelectionController) && !($0 is GiftOptionsScreen) } + if peer.id.namespace == Namespaces.Peer.CloudChannel { - if let controller = context.sharedContext.makePeerInfoController( - context: context, - updatedPresentationData: nil, - peer: peer._asPeer(), - mode: .gifts, - avatarInitiallyExpanded: false, - fromChat: false, - requestsContext: nil - ) { - controllers.append(controller) - } - } else { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } var foundController = false for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation { + if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId { + foundController = true + break + } + } + if !foundController { + if let controller = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .gifts, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + controllers.append(controller) + } + } + navigationController.setViewControllers(controllers, animated: true) + } else { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { chatController.hintPlayNextOutgoingGift() foundController = true break } } if !foundController { - let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) chatController.hintPlayNextOutgoingGift() controllers.append(chatController) } + navigationController.setViewControllers(controllers, animated: true) + } + + if let completion = component.completion { + completion() } - navigationController.setViewControllers(controllers, animated: true) } ) controller.present(alertController, in: .window(.root)) diff --git a/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift b/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift index 7d8aa898ee..be2b344db3 100644 --- a/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift @@ -253,7 +253,7 @@ public func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedM nativeGenerator(_1, _2, _3, nil) }) if let parentController = parentController { - parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, _, text, account, silently in + parentController.present(ShareController(context: context, subject: .fromExternal(1, { peerIds, _, _, text, account, silently in guard let account = account as? ShareControllerAppAccountContext else { return .single(.done) } diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index 79423fca6d..a0a6034849 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -214,6 +214,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { strings: presentationData.strings, style: .media, placeholder: .plain(presentationData.strings.MediaPicker_AddCaption), + sendPaidMessageStars: nil, maxLength: Int(self.context.userLimits.maxCaptionLength), queryTypes: [.mention, .hashtag], alwaysDarkWhenHasText: false, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 15b3ca4613..86ed3d9a89 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1365,6 +1365,7 @@ final class MediaEditorScreenComponent: Component { strings: environment.strings, style: .editor, placeholder: .plain(environment.strings.Story_Editor_InputPlaceholderAddCaption), + sendPaidMessageStars: nil, maxLength: Int(component.context.userLimits.maxStoryCaptionLength), queryTypes: [.mention, .hashtag], alwaysDarkWhenHasText: false, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift index 569e8f4273..900b4f8ba0 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift @@ -251,6 +251,7 @@ final class StoryPreviewComponent: Component { strings: presentationData.strings, style: .story, placeholder: .plain(presentationData.strings.Story_InputPlaceholderReplyPrivately), + sendPaidMessageStars: nil, maxLength: nil, queryTypes: [], alwaysDarkWhenHasText: false, diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 41b5f840ab..85a47443dd 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -160,6 +160,7 @@ public final class MessageInputPanelComponent: Component { public let strings: PresentationStrings public let style: Style public let placeholder: Placeholder + public let sendPaidMessageStars: StarsAmount? public let maxLength: Int? public let queryTypes: ContextQueryTypes public let alwaysDarkWhenHasText: Bool @@ -218,6 +219,7 @@ public final class MessageInputPanelComponent: Component { strings: PresentationStrings, style: Style, placeholder: Placeholder, + sendPaidMessageStars: StarsAmount?, maxLength: Int?, queryTypes: ContextQueryTypes, alwaysDarkWhenHasText: Bool, @@ -276,6 +278,7 @@ public final class MessageInputPanelComponent: Component { self.style = style self.nextInputMode = nextInputMode self.placeholder = placeholder + self.sendPaidMessageStars = sendPaidMessageStars self.maxLength = maxLength self.queryTypes = queryTypes self.alwaysDarkWhenHasText = alwaysDarkWhenHasText @@ -346,6 +349,9 @@ public final class MessageInputPanelComponent: Component { if lhs.placeholder != rhs.placeholder { return false } + if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars { + return false + } if lhs.maxLength != rhs.maxLength { return false } @@ -849,43 +855,75 @@ public final class MessageInputPanelComponent: Component { ) let isEditing = self.textFieldExternalState.isEditing || component.forceIsEditing - var placeholderItems: [AnimatedTextComponent.Item] = [] - switch component.placeholder { - case let .plain(string): - placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(0 as Int), content: .text(string))) - case let .counter(items): - for item in items { - switch item.content { - case let .text(string): - placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .text(string))) - case let .number(value, minDigits): - placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .number(value, minDigits: minDigits))) + let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate + let placeholderSize: CGSize + if case let .plain(string) = component.placeholder, string.contains("#") { + let attributedPlaceholder = NSMutableAttributedString(string: string, font:Font.regular(17.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.3)) + if let range = attributedPlaceholder.string.range(of: "#") { + attributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(component.theme)!, range: NSRange(range, in: attributedPlaceholder.string)) + attributedPlaceholder.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff, alpha: 0.3), range: NSRange(range, in: attributedPlaceholder.string)) + attributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedPlaceholder.string)) + } + + placeholderSize = self.placeholder.update( + transition: placeholderTransition, + component: AnyComponent(MultilineTextComponent(text: .plain(attributedPlaceholder))), + environment: {}, + containerSize: availableTextFieldSize + ) + + let vibrancyAttributedPlaceholder = NSMutableAttributedString(string: string, font:Font.regular(17.0), textColor: UIColor.black) + if let range = vibrancyAttributedPlaceholder.string.range(of: "#") { + vibrancyAttributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(component.theme)!, range: NSRange(range, in: vibrancyAttributedPlaceholder.string)) + vibrancyAttributedPlaceholder.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(range, in: vibrancyAttributedPlaceholder.string)) + vibrancyAttributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: vibrancyAttributedPlaceholder.string)) + } + + let _ = self.vibrancyPlaceholder.update( + transition: placeholderTransition, + component: AnyComponent(MultilineTextComponent(text: .plain(attributedPlaceholder))), + environment: {}, + containerSize: availableTextFieldSize + ) + } else { + var placeholderItems: [AnimatedTextComponent.Item] = [] + switch component.placeholder { + case let .plain(string): + placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(0 as Int), content: .text(string))) + case let .counter(items): + for item in items { + switch item.content { + case let .text(string): + placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .text(string))) + case let .number(value, minDigits): + placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .number(value, minDigits: minDigits))) + } } } + + placeholderSize = self.placeholder.update( + transition: placeholderTransition, + component: AnyComponent(AnimatedTextComponent( + font: Font.regular(17.0), + color: UIColor(rgb: 0xffffff, alpha: 0.3), + items: placeholderItems + )), + environment: {}, + containerSize: availableTextFieldSize + ) + + let _ = self.vibrancyPlaceholder.update( + transition: placeholderTransition, + component: AnyComponent(AnimatedTextComponent( + font: Font.regular(17.0), + color: .black, + items: placeholderItems + )), + environment: {}, + containerSize: availableTextFieldSize + ) } - let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate - let placeholderSize = self.placeholder.update( - transition: placeholderTransition, - component: AnyComponent(AnimatedTextComponent( - font: Font.regular(17.0), - color: UIColor(rgb: 0xffffff, alpha: 0.3), - items: placeholderItems - )), - environment: {}, - containerSize: availableTextFieldSize - ) - - let _ = self.vibrancyPlaceholder.update( - transition: placeholderTransition, - component: AnyComponent(AnimatedTextComponent( - font: Font.regular(17.0), - color: .black, - items: placeholderItems - )), - environment: {}, - containerSize: availableTextFieldSize - ) if !isEditing && component.setMediaRecordingActive == nil { insets.right = defaultInsets.left } diff --git a/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD b/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD index 0c0e555fad..e95817f52a 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD @@ -31,6 +31,7 @@ swift_library( "//submodules/PeerPresenceStatusManager", "//submodules/UndoUI", "//submodules/AnimatedAvatarSetNode", + "//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift index 158d1accb0..3dfb23c301 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift @@ -17,6 +17,7 @@ import UndoUI import AnimatedAvatarSetNode import AvatarNode import TelegramStringFormatting +import ChatMessagePaymentAlertController private final class SendInviteLinkScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -26,19 +27,22 @@ private final class SendInviteLinkScreenComponent: Component { let link: String? let peers: [TelegramForbiddenInvitePeer] let peerPresences: [EnginePeer.Id: EnginePeer.Presence] + let sendPaidMessageStars: [EnginePeer.Id: StarsAmount] init( context: AccountContext, peer: EnginePeer, link: String?, peers: [TelegramForbiddenInvitePeer], - peerPresences: [EnginePeer.Id: EnginePeer.Presence] + peerPresences: [EnginePeer.Id: EnginePeer.Presence], + sendPaidMessageStars: [EnginePeer.Id: StarsAmount] ) { self.context = context self.peer = peer self.link = link self.peers = peers self.peerPresences = peerPresences + self.sendPaidMessageStars = sendPaidMessageStars } static func ==(lhs: SendInviteLinkScreenComponent, rhs: SendInviteLinkScreenComponent) -> Bool { @@ -54,6 +58,9 @@ private final class SendInviteLinkScreenComponent: Component { if lhs.peerPresences != rhs.peerPresences { return false } + if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars { + return false + } return true } @@ -266,6 +273,38 @@ private final class SendInviteLinkScreenComponent: Component { } } + private func presentPaidMessageAlertIfNeeded(peers: [EnginePeer], requiresStars: [EnginePeer.Id: StarsAmount], completion: @escaping () -> Void) { + guard let component = self.component else { + completion() + return + } + var totalAmount: StarsAmount = .zero + for peer in peers { + if let amount = requiresStars[peer.id] { + totalAmount = totalAmount + amount + } + } + if totalAmount.value > 0 { + let controller = chatMessagePaymentAlertController( + context: component.context, + presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, + updatedPresentationData: nil, + peers: peers, + count: 1, + amount: totalAmount, + totalAmount: totalAmount, + hasCheck: false, + navigationController: self.environment?.controller()?.navigationController as? NavigationController, + completion: { _ in + completion() + } + ) + self.environment?.controller()?.present(controller, in: .window(.root)) + } else { + completion() + } + } + func update(component: SendInviteLinkScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let environment = environment[ViewControllerComponentContainer.Environment.self].value let themeUpdated = self.environment?.theme !== environment.theme @@ -851,20 +890,37 @@ private final class SendInviteLinkScreenComponent: Component { } else if let link = component.link { let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } - let _ = enqueueMessagesToMultiplePeers(account: component.context.account, peerIds: Array(self.selectedItems), threadIds: [:], messages: [.message(text: link, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start() - let text: String - if selectedPeers.count == 1 { - text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string - } else if selectedPeers.count == 2 { - text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string - } else { - text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string - } - - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) - - controller.dismiss() + self.presentPaidMessageAlertIfNeeded( + peers: selectedPeers.map { $0.peer }, + requiresStars: component.sendPaidMessageStars, + completion: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + for peerId in Array(self.selectedItems) { + var messageAttributes: [EngineMessage.Attribute] = [] + if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } + let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() + } + + let text: String + if selectedPeers.count == 1 { + text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else if selectedPeers.count == 2 { + text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else { + text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) + + controller.dismiss() + } + ) } else { controller.dismiss() } @@ -1083,16 +1139,21 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer { self.link = link self.peers = peers - super.init(context: context, component: SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: [:]), navigationBarAppearance: .none) + super.init(context: context, component: SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: [:], sendPaidMessageStars: [:]), navigationBarAppearance: .none) self.statusBar.statusBarStyle = .Ignore self.navigationPresentation = .flatModal self.blocksBackgroundWhenInOverlay = true - self.presenceDisposable = (context.engine.data.subscribe(EngineDataMap( - peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:)) - )) - |> deliverOnMainQueue).start(next: { [weak self] presences in + self.presenceDisposable = (context.engine.data.subscribe( + EngineDataMap( + peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:)) + ), + EngineDataMap( + peers.map(\.peer.id).map(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.init(id:)) + ) + ) + |> deliverOnMainQueue).start(next: { [weak self] presences, sendPaidMessageStars in guard let self else { return } @@ -1102,7 +1163,13 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer { parsedPresences[id] = presence } } - self.updateComponent(component: AnyComponent(SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: parsedPresences)), transition: .immediate) + var parsedSendPaidMessageStars: [EnginePeer.Id: StarsAmount] = [:] + for (id, sendPaidMessageStars) in sendPaidMessageStars { + if let sendPaidMessageStars { + parsedSendPaidMessageStars[id] = sendPaidMessageStars + } + } + self.updateComponent(component: AnyComponent(SendInviteLinkScreenComponent(context: context, peer: peer, link: link, peers: peers, peerPresences: parsedPresences, sendPaidMessageStars: parsedSendPaidMessageStars)), transition: .immediate) }) } diff --git a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift index f33b368637..a02526928e 100644 --- a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift @@ -525,8 +525,8 @@ public class ShareRootControllerImpl { } |> runOn(Queue.mainQueue()) } - let sentItems: ([PeerId], [PeerId: Int64], [PreparedShareItemContent], ShareControllerAccountContext, Bool, String) -> Signal = { peerIds, threadIds, contents, account, silently, additionalText in - let sentItems = sentShareItems(accountPeerId: account.accountPeerId, postbox: account.stateManager.postbox, network: account.stateManager.network, stateManager: account.stateManager, auxiliaryMethods: makeTelegramAccountAuxiliaryMethods(uploadInBackground: nil), to: peerIds, threadIds: threadIds, items: contents, silently: silently, additionalText: additionalText) + let sentItems: ([PeerId], [PeerId: Int64], [PeerId: StarsAmount], [PreparedShareItemContent], ShareControllerAccountContext, Bool, String) -> Signal = { peerIds, threadIds, requireStars, contents, account, silently, additionalText in + let sentItems = sentShareItems(accountPeerId: account.accountPeerId, postbox: account.stateManager.postbox, network: account.stateManager.network, stateManager: account.stateManager, auxiliaryMethods: makeTelegramAccountAuxiliaryMethods(uploadInBackground: nil), to: peerIds, threadIds: threadIds, requireStars: requireStars, items: contents, silently: silently, additionalText: additionalText) |> `catch` { _ -> Signal< Float, NoError> in return .complete() @@ -537,8 +537,20 @@ public class ShareRootControllerImpl { } |> then(.single(.done)) } - - let shareController = ShareController(environment: environment, currentContext: context, subject: .fromExternal({ peerIds, threadIds, additionalText, account, silently in + + var itemCount = 1 + + if let extensionItems = self?.getExtensionContext()?.inputItems as? [NSExtensionItem] { + for item in extensionItems { + if let attachments = item.attachments { + itemCount = 0 + for _ in attachments { + itemCount += 1 + } + } + } + } + let shareController = ShareController(environment: environment, currentContext: context, subject: .fromExternal(itemCount, { peerIds, threadIds, requireStars, additionalText, account, silently in if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, !inputItems.isEmpty, !peerIds.isEmpty { let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)! return preparedShareItems(postbox: account.stateManager.postbox, network: account.stateManager.network, to: peerIds[0], dataItems: rawSignals) @@ -564,11 +576,11 @@ public class ShareRootControllerImpl { return requestUserInteraction(value) |> castError(ShareControllerError.self) |> mapToSignal { contents -> Signal in - return sentItems(peerIds, threadIds, contents, account, silently, additionalText) + return sentItems(peerIds, threadIds, requireStars, contents, account, silently, additionalText) |> castError(ShareControllerError.self) } case let .done(contents): - return sentItems(peerIds, threadIds, contents, account, silently, additionalText) + return sentItems(peerIds, threadIds, requireStars, contents, account, silently, additionalText) |> castError(ShareControllerError.self) } } diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 921272ce37..190070b927 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -124,9 +124,9 @@ private final class SheetContent: CombinedComponent { minAmount = StarsAmount(value: 1, nanos: 0) maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) } - var usdRate = 0.012 + if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero { - usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 + let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" } else { amountLabel = nil diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index c923c845a9..540e1b1bc4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -98,6 +98,7 @@ swift_library( "//submodules/TelegramUI/Components/SliderContextItem", "//submodules/TelegramUI/Components/InteractiveTextComponent", "//submodules/TelegramUI/Components/SaveProgressScreen", + "//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index dabc123237..3c41e12feb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -219,7 +219,8 @@ public final class StoryContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: nil, - appliedBoosts: nil + appliedBoosts: nil, + sendPaidMessageStars: cachedUserData.sendPaidMessageStars ) } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -230,7 +231,8 @@ public final class StoryContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: cachedChannelData.boostsToUnrestrict, - appliedBoosts: cachedChannelData.appliedBoosts + appliedBoosts: cachedChannelData.appliedBoosts, + sendPaidMessageStars: cachedChannelData.sendPaidMessageStars ) } else { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -241,7 +243,8 @@ public final class StoryContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: nil, - appliedBoosts: nil + appliedBoosts: nil, + sendPaidMessageStars: nil ) } } else { @@ -253,7 +256,8 @@ public final class StoryContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: nil, - appliedBoosts: nil + appliedBoosts: nil, + sendPaidMessageStars: nil ) } let state = stateView.value?.get(Stories.PeerState.self) @@ -1182,7 +1186,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext { TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: storyId.peerId), TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: storyId.peerId), - TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: storyId.peerId) + TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: storyId.peerId), + TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: storyId.peerId) ), item |> mapToSignal { item -> Signal<(Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]), NoError> in return context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile], [StoryId: EngineStoryItem?]) in @@ -1253,7 +1258,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext { return } - let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data + let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts, sendPaidMessageStars) = data let (item, peers, allEntityFiles, forwardInfoStories) = itemAndPeers guard let peer else { @@ -1270,7 +1275,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: boostsToUnrestrict, - appliedBoosts: appliedBoosts + appliedBoosts: appliedBoosts, + sendPaidMessageStars: sendPaidMessageStars ) for (storyId, story) in forwardInfoStories { @@ -1436,9 +1442,11 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { TelegramEngine.EngineData.Item.NotificationSettings.Global.Result, TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result, TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result, - TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result) + TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result, + TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.Result + ) - init(data: (TelegramEngine.EngineData.Item.Peer.Peer.Result, TelegramEngine.EngineData.Item.Peer.Presence.Result, TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable.Result, TelegramEngine.EngineData.Item.Peer.CanViewStats.Result, TelegramEngine.EngineData.Item.Peer.NotificationSettings.Result, TelegramEngine.EngineData.Item.NotificationSettings.Global.Result, TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result, TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result, TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result)) { + init(data: (TelegramEngine.EngineData.Item.Peer.Peer.Result, TelegramEngine.EngineData.Item.Peer.Presence.Result, TelegramEngine.EngineData.Item.Peer.AreVoiceMessagesAvailable.Result, TelegramEngine.EngineData.Item.Peer.CanViewStats.Result, TelegramEngine.EngineData.Item.Peer.NotificationSettings.Result, TelegramEngine.EngineData.Item.NotificationSettings.Global.Result, TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.Result, TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict.Result, TelegramEngine.EngineData.Item.Peer.AppliedBoosts.Result, TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.Result)) { self.data = data } } @@ -1544,7 +1552,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging(id: peerId), TelegramEngine.EngineData.Item.Peer.BoostsToUnrestrict(id: peerId), - TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: peerId) + TelegramEngine.EngineData.Item.Peer.AppliedBoosts(id: peerId), + TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: peerId) ) |> map { PeerData(data: $0) }) self.currentPeerData = currentPeerData @@ -1563,7 +1572,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { self.listState = state let stateValue: StoryContentContextState - if let focusedIndex, let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts) = data?.data, let peer { + if let focusedIndex, let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts, sendPaidMessageStars) = data?.data, let peer { let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) let additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, @@ -1573,7 +1582,8 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: boostsToUnrestrict, - appliedBoosts: appliedBoosts + appliedBoosts: appliedBoosts, + sendPaidMessageStars: sendPaidMessageStars ) let item = state.items[focusedIndex] @@ -2462,7 +2472,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: nil, - appliedBoosts: nil + appliedBoosts: nil, + sendPaidMessageStars: cachedUserData.sendPaidMessageStars ) } else if let cachedChannelData = cachedPeerDataView.cachedPeerData as? CachedChannelData { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -2473,7 +2484,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: cachedChannelData.boostsToUnrestrict, - appliedBoosts: cachedChannelData.appliedBoosts + appliedBoosts: cachedChannelData.appliedBoosts, + sendPaidMessageStars: cachedChannelData.sendPaidMessageStars ) } else { additionalPeerData = StoryContentContextState.AdditionalPeerData( @@ -2484,7 +2496,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: nil, - appliedBoosts: nil + appliedBoosts: nil, + sendPaidMessageStars: nil ) } } @@ -2497,7 +2510,8 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, preferHighQualityStories: preferHighQualityStories, boostsToUnrestrict: nil, - appliedBoosts: nil + appliedBoosts: nil, + sendPaidMessageStars: nil ) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index 90479b4c0e..b4e4c11784 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -161,6 +161,7 @@ public final class StoryContentContextState { public let preferHighQualityStories: Bool public let boostsToUnrestrict: Int32? public let appliedBoosts: Int32? + public let sendPaidMessageStars: StarsAmount? public init( isMuted: Bool, @@ -170,7 +171,8 @@ public final class StoryContentContextState { isPremiumRequiredForMessaging: Bool, preferHighQualityStories: Bool, boostsToUnrestrict: Int32?, - appliedBoosts: Int32? + appliedBoosts: Int32?, + sendPaidMessageStars: StarsAmount? ) { self.isMuted = isMuted self.areVoiceMessagesAvailable = areVoiceMessagesAvailable @@ -180,6 +182,7 @@ public final class StoryContentContextState { self.preferHighQualityStories = preferHighQualityStories self.boostsToUnrestrict = boostsToUnrestrict self.appliedBoosts = appliedBoosts + self.sendPaidMessageStars = sendPaidMessageStars } public static func == (lhs: StoryContentContextState.AdditionalPeerData, rhs: StoryContentContextState.AdditionalPeerData) -> Bool { @@ -207,6 +210,9 @@ public final class StoryContentContextState { if lhs.appliedBoosts != rhs.appliedBoosts { return false } + if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars { + return false + } return true } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 97f1f3c842..2f0f3c2f74 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -2823,7 +2823,12 @@ public final class StoryItemSetContainerComponent: Component { inputPlaceholder = .counter(items) } else { - inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately) + if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { + let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat + inputPlaceholder = .plain(component.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), dateTimeFormat.groupingSeparator))").string) + } else { + inputPlaceholder = .plain(isGroup ? component.strings.Story_InputPlaceholderReplyInGroup : component.strings.Story_InputPlaceholderReplyPrivately) + } } let startTime22 = CFAbsoluteTimeGetCurrent() @@ -2867,6 +2872,7 @@ public final class StoryItemSetContainerComponent: Component { strings: component.strings, style: .story, placeholder: inputPlaceholder, + sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars, maxLength: 4096, queryTypes: [.mention, .hashtag, .emoji], alwaysDarkWhenHasText: component.metrics.widthClass == .regular, @@ -4647,6 +4653,10 @@ public final class StoryItemSetContainerComponent: Component { case .stars: break } + + if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } let message: EnqueueMessage = .message( text: text, @@ -4693,7 +4703,6 @@ public final class StoryItemSetContainerComponent: Component { }) } } - if self.displayLikeReactions { if component.slice.item.storyItem.myReaction == updateReaction.reaction { action() @@ -4704,7 +4713,9 @@ public final class StoryItemSetContainerComponent: Component { } } else { self.sendMessageContext.performWithPossibleStealthModeConfirmation(view: self, action: { - action() + self.sendMessageContext.presentPaidMessageAlertIfNeeded(view: self, completion: { + action() + }) }) } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 0677c3dd74..7140300641 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -50,6 +50,7 @@ import LocationUI import ReactionSelectionNode import StoryQualityUpgradeSheetScreen import AudioWaveform +import ChatMessagePaymentAlertController private var ObjCKey_DeinitWatcher: Int? @@ -491,6 +492,30 @@ final class StoryItemSetContainerSendMessage { view.updateIsProgressPaused() } + func presentPaidMessageAlertIfNeeded(view: StoryItemSetContainerComponent.View, completion: @escaping () -> Void) { + guard let component = view.component, let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars else { + completion() + return + } + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) + + let controller = chatMessagePaymentAlertController( + context: component.context, + presentationData: presentationData, + updatedPresentationData: nil, + peers: [component.slice.effectivePeer], + count: 1, + amount: sendPaidMessageStars, + totalAmount: nil, + hasCheck: false, + navigationController: component.controller()?.navigationController as? NavigationController, + completion: { _ in + completion() + } + ) + component.controller()?.present(controller, in: .window(.root)) + } + func performWithPossibleStealthModeConfirmation(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) { guard let component = view.component, component.stealthModeTimeout != nil else { action() @@ -512,7 +537,6 @@ final class StoryItemSetContainerSendMessage { let timestamp = Int32(Date().timeIntervalSince1970) if noticeCount < 1, let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp { - let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) @@ -575,53 +599,64 @@ final class StoryItemSetContainerSendMessage { let controller = component.controller() as? StoryContainerScreen - if let recordedAudioPreview = self.recordedAudioPreview, case let .audio(audio) = recordedAudioPreview { - self.recordedAudioPreview = nil - - let waveformBuffer = audio.waveform.makeBitstream() - - let messages: [EnqueueMessage] = [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: audio.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: Int(audio.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])] - - let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start() - - view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) - } else if self.hasRecordedVideoPreview, let videoRecorderValue = self.videoRecorderValue { - videoRecorderValue.send() - self.hasRecordedVideoPreview = false - self.videoRecorder.set(.single(nil)) - view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) - } else { - switch inputPanelView.getSendMessageInput() { - case let .text(text): - if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - let entities = generateChatInputTextEntities(text) - let _ = (component.context.engine.messages.enqueueOutgoingMessage( - to: peerId, - replyTo: nil, - storyId: focusedStoryId, - content: .text(text.string, entities), - silentPosting: silentPosting, - scheduleTime: scheduleTime - ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in - Queue.mainQueue().after(0.3) { - if let self, let view { - self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in + guard let self, let view else { + return + } + if let recordedAudioPreview = self.recordedAudioPreview, case let .audio(audio) = recordedAudioPreview { + self.recordedAudioPreview = nil + + let waveformBuffer = audio.waveform.makeBitstream() + + var messageAttributes: [MessageAttribute] = [] + if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } + + let messages: [EnqueueMessage] = [.message(text: "", attributes: messageAttributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: audio.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: Int(audio.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])] + + let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start() + + view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) + } else if self.hasRecordedVideoPreview, let videoRecorderValue = self.videoRecorderValue { + videoRecorderValue.send() + self.hasRecordedVideoPreview = false + self.videoRecorder.set(.single(nil)) + view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) + } else { + switch inputPanelView.getSendMessageInput() { + case let .text(text): + if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + let entities = generateChatInputTextEntities(text) + let _ = (component.context.engine.messages.enqueueOutgoingMessage( + to: peerId, + replyTo: nil, + storyId: focusedStoryId, + content: .text(text.string, entities), + silentPosting: silentPosting, + scheduleTime: scheduleTime, + sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars + ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + Queue.mainQueue().after(0.3) { + if let self, let view { + self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }, isScheduled: scheduleTime != nil) + } } + }) + component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id)) + inputPanelView.clearSendMessageInput(updateState: true) + + self.currentInputMode = .text + if hasFirstResponder(view) { + view.endEditing(true) + } else { + view.state?.updated(transition: .spring(duration: 0.3)) } - }) - component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id)) - inputPanelView.clearSendMessageInput(updateState: true) - - self.currentInputMode = .text - if hasFirstResponder(view) { - view.endEditing(true) - } else { - view.state?.updated(transition: .spring(duration: 0.3)) + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } - controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } } - } + }) }) } @@ -660,26 +695,32 @@ final class StoryItemSetContainerSendMessage { }) } - let _ = (component.context.engine.messages.enqueueOutgoingMessage( - to: peerId, - replyTo: nil, - storyId: focusedStoryId, - content: .file(fileReference) - ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in - Queue.mainQueue().after(0.3) { - if let self, let view { - self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) - } + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in + guard let self, let view else { + return } + let _ = (component.context.engine.messages.enqueueOutgoingMessage( + to: peerId, + replyTo: nil, + storyId: focusedStoryId, + content: .file(fileReference), + sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars + ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + Queue.mainQueue().after(0.3) { + if let self, let view { + self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) + } + } + }) + + self.currentInputMode = .text + if hasFirstResponder(view) { + view.endEditing(true) + } else { + view.state?.updated(transition: .spring(duration: 0.3)) + } + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) }) - - self.currentInputMode = .text - if hasFirstResponder(view) { - view.endEditing(true) - } else { - view.state?.updated(transition: .spring(duration: 0.3)) - } - controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) }) } @@ -714,37 +755,48 @@ final class StoryItemSetContainerSendMessage { }) } - let _ = (component.context.engine.messages.enqueueOutgoingMessage( - to: peerId, - replyTo: nil, - storyId: focusedStoryId, - content: .contextResult(results, result) - ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in - Queue.mainQueue().after(0.3) { - if let self, let view { - self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) - } + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in + guard let self, let view else { + return } + let _ = (component.context.engine.messages.enqueueOutgoingMessage( + to: peerId, + replyTo: nil, + storyId: focusedStoryId, + content: .contextResult(results, result), + sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars + ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in + Queue.mainQueue().after(0.3) { + if let self, let view { + self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 }) + } + } + }) + + self.currentInputMode = .text + if hasFirstResponder(view) { + view.endEditing(true) + } else { + view.state?.updated(transition: .spring(duration: 0.3)) + } + controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) }) - - self.currentInputMode = .text - if hasFirstResponder(view) { - view.endEditing(true) - } else { - view.state?.updated(transition: .spring(duration: 0.3)) - } - controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } func enqueueGifData(view: StoryItemSetContainerComponent.View, data: Data) { guard let component = view.component else { return } - let peer = component.slice.effectivePeer - let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in - if let self, let view { - self.sendMessages(view: view, peer: peer, messages: [message]) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let self else { + return } + let peer = component.slice.effectivePeer + let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in + if let self, let view { + self.sendMessages(view: view, peer: peer, messages: [message]) + } + }) }) } @@ -794,7 +846,12 @@ final class StoryItemSetContainerSendMessage { let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes, alternativeRepresentations: []) let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - self.sendMessages(view: view, peer: peer, messages: [message], silentPosting: false) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let self else { + return + } + self.sendMessages(view: view, peer: peer, messages: [message], silentPosting: false) + }) } }) } @@ -846,7 +903,12 @@ final class StoryItemSetContainerSendMessage { guard let self, let view else { return } - self.sendMessages(view: view, peer: peer, messages: [updatedMessage]) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in + guard let self, let view else { + return + } + self.sendMessages(view: view, peer: peer, messages: [updatedMessage]) + }) }) }, displaySlowmodeTooltip: { [weak self] view, rect in //self?.interfaceInteraction?.displaySlowmodeTooltip(view, rect) @@ -896,9 +958,14 @@ final class StoryItemSetContainerSendMessage { guard let self, let view else { return } - self.sendMessages(view: view, peer: peer, messages: [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) - - HapticFeedback().tap() + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in + guard let self, let view else { + return + } + self.sendMessages(view: view, peer: peer, messages: [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) + + HapticFeedback().tap() + }) }) } }) @@ -1593,7 +1660,11 @@ final class StoryItemSetContainerSendMessage { guard let view, let component = view.component else { return } - let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + var messageAttributes: [MessageAttribute] = [] + if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } + let message: EnqueueMessage = .message(text: "", attributes: messageAttributes, inlineStickers: [:], mediaReference: mediaReference, threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: [message.withUpdatedReplyToMessageId(nil)]) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in if let self, let view { @@ -1636,7 +1707,12 @@ final class StoryItemSetContainerSendMessage { return } let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - self.sendMessages(view: view, peer: peer, messages: [message]) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let self else { + return + } + self.sendMessages(view: view, peer: peer, messages: [message]) + }) }) completion(controller, controller.mediaPickerContext) @@ -1705,7 +1781,12 @@ final class StoryItemSetContainerSendMessage { } } - self.sendMessages(view: view, peer: peer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let self else { + return + } + self.sendMessages(view: view, peer: peer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) + }) } else if let peer = peers.first { let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError> switch peer { @@ -1760,7 +1841,12 @@ final class StoryItemSetContainerSendMessage { } enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let self else { + return + } + self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) + }) } else { let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in guard let self, let view else { @@ -1779,7 +1865,12 @@ final class StoryItemSetContainerSendMessage { } enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let self else { + return + } + self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) + }) } }), completed: nil, cancelled: nil) component.controller()?.push(contactController) @@ -2187,7 +2278,12 @@ final class StoryItemSetContainerSendMessage { } if !messages.isEmpty { - strongSelf.sendMessages(view: view, peer: peer, messages: messages) + strongSelf.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.sendMessages(view: view, peer: peer, messages: messages) + }) } } })) @@ -2248,7 +2344,12 @@ final class StoryItemSetContainerSendMessage { if !inputText.string.isEmpty { self.clearInputText(view: view) } - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in + guard let self else { + return + } + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) + }) } ) } @@ -2268,7 +2369,19 @@ final class StoryItemSetContainerSendMessage { guard let self, let view, let component = view.component else { return } - if component.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peer.id, threadId: nil, botId: results.botId, result: result, replyToMessageId: replyMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: storyId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime) { + if component.context.engine.messages.enqueueOutgoingMessageWithChatContextResult( + to: peer.id, + threadId: nil, + botId: results.botId, + result: result, + replyToMessageId: replyMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, + replyToStoryId: storyId, + hideVia: hideVia, + silentPosting: silentPosting, + scheduleTime: scheduleTime, + sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars, + postpone: false + ) { } if let attachmentController = self.attachmentController { @@ -2416,10 +2529,15 @@ final class StoryItemSetContainerSendMessage { guard let self, let view else { return } - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, parameters: parameters) - if !inputText.string.isEmpty { - self.clearInputText(view: view) - } + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in + guard let self, let view else { + return + } + self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, parameters: parameters) + if !inputText.string.isEmpty { + self.clearInputText(view: view) + } + }) }, recognizedQRCode: { _ in }, presentSchedulePicker: { [weak self, weak view] _, done in guard let self, let view else { @@ -2545,6 +2663,10 @@ final class StoryItemSetContainerSendMessage { attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) } } + var messageAttributes: [MessageAttribute] = [] + if let component = view.component, let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } return attributes } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 32a4f083db..a8b2117736 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -2604,8 +2604,12 @@ extension ChatControllerImpl { strongSelf.interfaceInteraction?.displaySlowmodeTooltip(node.view, rect) return false } - - strongSelf.enqueueChatContextResult(results, result) + strongSelf.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in + guard let strongSelf = self else { + return + } + strongSelf.enqueueChatContextResult(results, result, postpone: postpone) + }) return true }, sendBotCommand: { [weak self] botPeer, command in if let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift index 37fd817a05..455ea29bdf 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift @@ -250,10 +250,8 @@ extension ChatControllerImpl { } var sendImmediately = false - if let _ = self.presentationInterfaceState.sendPaidMessageStars { - if case .send = action { - updatedAction = .preview - } + if let _ = self.presentationInterfaceState.sendPaidMessageStars, case .send = action { + updatedAction = .preview sendImmediately = true } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift index 259e63a313..0e1b9de5e6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift @@ -23,10 +23,10 @@ extension ChatControllerImpl { if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars { let _ = (ApplicationSpecificNotice.dismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id) |> deliverOnMainQueue).start(next: { [weak self] dismissedAmount in - guard let self else { + guard let self, let starsContext = self.context.starsContext else { return } - if let dismissedAmount, dismissedAmount == sendPaidMessageStars.value { + if let dismissedAmount, dismissedAmount == sendPaidMessageStars.value, let currentState = starsContext.currentState, currentState.balance > sendPaidMessageStars { completion(true) self.displayPaidMessageUndo(count: count, amount: sendPaidMessageStars) } else { @@ -37,14 +37,14 @@ extension ChatControllerImpl { let controller = chatMessagePaymentAlertController( context: self.context, presentationData: presentationData, - updatedPresentationData: nil,//self.updatedPresentationData, + updatedPresentationData: nil, peers: [peer], count: count, amount: sendPaidMessageStars, totalAmount: nil, navigationController: self.navigationController as? NavigationController, completion: { [weak self] dontAskAgain in - guard let self, let starsContext = self.context.starsContext else { + guard let self else { return } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 3026a94e4f..430f87c869 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9515,7 +9515,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } - func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) { + func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true, postpone: Bool = false) { if !canSendMessagesToChat(self.presentationInterfaceState) { return } @@ -9534,7 +9534,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject - if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, threadId: self.chatLocation.threadId, botId: results.botId, result: result, replyToMessageId: replyMessageSubject?.subjectModel, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime) { + + let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars + if self.context.engine.messages.enqueueOutgoingMessageWithChatContextResult(to: peerId, threadId: self.chatLocation.threadId, botId: results.botId, result: result, replyToMessageId: replyMessageSubject?.subjectModel, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, sendPaidMessageStars: sendPaidMessageStars, postpone: postpone) { self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 401c692bec..e36b0510f5 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -13,8 +13,9 @@ import ChatInterfaceState import PremiumUI import ReactionSelectionNode import TopMessageReactions +import ChatMessagePaymentAlertController -extension ChatControllerImpl { +extension ChatControllerImpl { func forwardMessages(messageIds: [MessageId], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) { let _ = (self.context.engine.data.get(EngineDataMap( messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init) @@ -94,190 +95,246 @@ extension ChatControllerImpl { } } controller.multiplePeersSelected = { [weak self, weak controller] peers, peerMap, messageText, mode, forwardOptions, _ in - guard let strongSelf = self, let strongController = controller else { - return - } - strongController.dismiss() + let peerIds = peers.map { $0.id } - var result: [EnqueueMessage] = [] - if messageText.string.count > 0 { - let inputText = convertMarkdownToAttributes(messageText) - for text in breakChatInputText(trimChatInputText(inputText)) { - if text.length != 0 { - var attributes: [MessageAttribute] = [] - let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) - if !entities.isEmpty { - attributes.append(TextEntitiesMessageAttribute(entities: entities)) - } - result.append(.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - } - } - } - - var attributes: [MessageAttribute] = [] - attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true)) - - result.append(contentsOf: messages.map { message -> EnqueueMessage in - return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil) - }) - - let commit: ([EnqueueMessage]) -> Void = { result in + let _ = (context.engine.data.get( + EngineDataMap( + peerIds.map(TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars.init(id:)) + ) + ) + |> deliverOnMainQueue).start(next: { [weak self, weak controller] sendPaidMessageStars in guard let strongSelf = self else { return } - var result = result - - strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }).updatedSearch(nil) }) - - var correlationIds: [Int64] = [] - for i in 0 ..< result.count { - let correlationId = Int64.random(in: Int64.min ... Int64.max) - correlationIds.append(correlationId) - result[i] = result[i].withUpdatedCorrelationId(correlationId) + var count: Int32 = Int32(messages.count) + if messageText.string.count > 0 { + count += 1 } - - let targetPeersShouldDivertSignals: [Signal<(EnginePeer, Bool), NoError>] = peers.map { peer -> Signal<(EnginePeer, Bool), NoError> in - return strongSelf.shouldDivertMessagesToScheduled(targetPeer: peer, messages: result) - |> map { shouldDivert -> (EnginePeer, Bool) in - return (peer, shouldDivert) + var totalAmount: StarsAmount = .zero + for peer in peers { + if let maybeAmount = sendPaidMessageStars[peer.id], let amount = maybeAmount { + totalAmount = totalAmount + amount } } - let targetPeersShouldDivert: Signal<[(EnginePeer, Bool)], NoError> = combineLatest(targetPeersShouldDivertSignals) - let _ = (targetPeersShouldDivert - |> deliverOnMainQueue).startStandalone(next: { targetPeersShouldDivert in - guard let strongSelf = self else { + + let proceed = { [weak self, weak controller] in + guard let strongSelf = self, let strongController = controller else { return } - var displayConvertingTooltip = false + strongController.dismiss() - var displayPeers: [EnginePeer] = [] - for (peer, shouldDivert) in targetPeersShouldDivert { - var peerMessages = result - if shouldDivert { - displayConvertingTooltip = true - peerMessages = peerMessages.map { message -> EnqueueMessage in - return message.withUpdatedAttributes { attributes in - var attributes = attributes - attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60)) - return attributes + var result: [EnqueueMessage] = [] + if messageText.string.count > 0 { + let inputText = convertMarkdownToAttributes(messageText) + for text in breakChatInputText(trimChatInputText(inputText)) { + if text.length != 0 { + var attributes: [MessageAttribute] = [] + let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) } + result.append(.message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) } } - - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: peerMessages) - |> deliverOnMainQueue).startStandalone(next: { messageIds in - if let strongSelf = self { - let signals: [Signal] = messageIds.compactMap({ id -> Signal? in - guard let id = id else { - return nil - } - return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status, _ -> Signal in - if status != nil { - return .never() - } else { - return .single(true) - } - } - |> take(1) - }) - if strongSelf.shareStatusDisposable == nil { - strongSelf.shareStatusDisposable = MetaDisposable() - } - strongSelf.shareStatusDisposable?.set((combineLatest(signals) - |> deliverOnMainQueue).startStrict()) - } - }) - - if case let .secretChat(secretPeer) = peer { - if let peer = peerMap[secretPeer.regularPeerId] { - displayPeers.append(peer) - } - } else { - displayPeers.append(peer) - } - } - - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let text: String - var savedMessages = false - if displayPeers.count == 1, let peerId = displayPeers.first?.id, peerId == strongSelf.context.account.peerId { - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many - savedMessages = true - } else { - if displayPeers.count == 1, let peer = displayPeers.first { - var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - peerName = peerName.replacingOccurrences(of: "**", with: "") - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string - } else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last { - var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "") - var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "") - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string - } else if let peer = displayPeers.first { - var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - peerName = peerName.replacingOccurrences(of: "**", with: "") - text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string - } else { - text = "" - } } - let reactionItems: Signal<[ReactionItem], NoError> - if savedMessages && messages.count > 0 { - reactionItems = tagMessageReactions(context: strongSelf.context, subPeerId: nil) - } else { - reactionItems = .single([]) - } + var attributes: [MessageAttribute] = [] + attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true)) - let _ = (reactionItems - |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] reactionItems in - guard let strongSelf else { - return - } - - strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, position: savedMessages && messages.count > 0 ? .top : .bottom, animateInAsReplacement: true, action: { action in - if savedMessages, let self, action == .info { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { - return - } - guard let navigationController = self.navigationController as? NavigationController else { - return - } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) - }) - } - return false - }, additionalView: (savedMessages && messages.count > 0) ? chatShareToSavedMessagesAdditionalView(strongSelf, reactionItems: reactionItems, correlationIds: correlationIds) : nil), in: .current) + result.append(contentsOf: messages.map { message -> EnqueueMessage in + return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil) }) - if displayConvertingTooltip { + let commit: ([EnqueueMessage]) -> Void = { result in + guard let strongSelf = self else { + return + } + var result = result + + strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }).updatedSearch(nil) }) + + var correlationIds: [Int64] = [] + for i in 0 ..< result.count { + let correlationId = Int64.random(in: Int64.min ... Int64.max) + correlationIds.append(correlationId) + result[i] = result[i].withUpdatedCorrelationId(correlationId) + } + + let targetPeersShouldDivertSignals: [Signal<(EnginePeer, Bool), NoError>] = peers.map { peer -> Signal<(EnginePeer, Bool), NoError> in + return strongSelf.shouldDivertMessagesToScheduled(targetPeer: peer, messages: result) + |> map { shouldDivert -> (EnginePeer, Bool) in + return (peer, shouldDivert) + } + } + let targetPeersShouldDivert: Signal<[(EnginePeer, Bool)], NoError> = combineLatest(targetPeersShouldDivertSignals) + let _ = (targetPeersShouldDivert + |> deliverOnMainQueue).startStandalone(next: { targetPeersShouldDivert in + guard let strongSelf = self else { + return + } + + var displayConvertingTooltip = false + + var displayPeers: [EnginePeer] = [] + for (peer, shouldDivert) in targetPeersShouldDivert { + var peerMessages = result + if shouldDivert { + displayConvertingTooltip = true + peerMessages = peerMessages.map { message -> EnqueueMessage in + return message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60)) + return attributes + } + } + } + + if let maybeAmount = sendPaidMessageStars[peer.id], let amount = maybeAmount { + peerMessages = peerMessages.map { message -> EnqueueMessage in + return message.withUpdatedAttributes { attributes in + var attributes = attributes + attributes.append(PaidStarsMessageAttribute(stars: amount, postponeSending: false)) + return attributes + } + } + } + + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: peerMessages) + |> deliverOnMainQueue).startStandalone(next: { messageIds in + if let strongSelf = self { + let signals: [Signal] = messageIds.compactMap({ id -> Signal? in + guard let id = id else { + return nil + } + return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) + |> mapToSignal { status, _ -> Signal in + if status != nil { + return .never() + } else { + return .single(true) + } + } + |> take(1) + }) + if strongSelf.shareStatusDisposable == nil { + strongSelf.shareStatusDisposable = MetaDisposable() + } + strongSelf.shareStatusDisposable?.set((combineLatest(signals) + |> deliverOnMainQueue).startStrict()) + } + }) + + if case let .secretChat(secretPeer) = peer { + if let peer = peerMap[secretPeer.regularPeerId] { + displayPeers.append(peer) + } + } else { + displayPeers.append(peer) + } + } + + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + let text: String + var savedMessages = false + if displayPeers.count == 1, let peerId = displayPeers.first?.id, peerId == strongSelf.context.account.peerId { + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many + savedMessages = true + } else { + if displayPeers.count == 1, let peer = displayPeers.first { + var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + peerName = peerName.replacingOccurrences(of: "**", with: "") + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string + } else if displayPeers.count == 2, let firstPeer = displayPeers.first, let secondPeer = displayPeers.last { + var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "") + var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "") + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).string + } else if let peer = displayPeers.first { + var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + peerName = peerName.replacingOccurrences(of: "**", with: "") + text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(displayPeers.count - 1)").string : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(displayPeers.count - 1)").string + } else { + text = "" + } + } + + let reactionItems: Signal<[ReactionItem], NoError> + if savedMessages && messages.count > 0 { + reactionItems = tagMessageReactions(context: strongSelf.context, subPeerId: nil) + } else { + reactionItems = .single([]) + } + + let _ = (reactionItems + |> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] reactionItems in + guard let strongSelf else { + return + } + + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, position: savedMessages && messages.count > 0 ? .top : .bottom, animateInAsReplacement: true, action: { action in + if savedMessages, let self, action == .info { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true)) + }) + } + return false + }, additionalView: (savedMessages && messages.count > 0) ? chatShareToSavedMessagesAdditionalView(strongSelf, reactionItems: reactionItems, correlationIds: correlationIds) : nil), in: .current) + }) + + if displayConvertingTooltip { + } + }) } - }) - } - - switch mode { - case .generic: - commit(result) - case .silent: - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true) - commit(transformedMessages) - case .schedule: - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in - if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime) + + switch mode { + case .generic: + commit(result) + case .silent: + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true) + commit(transformedMessages) + case .schedule: + strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in + if let strongSelf = self { + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime) + commit(transformedMessages) + } + }) + case .whenOnline: + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleWhenOnlineTimestamp) commit(transformedMessages) } - }) - case .whenOnline: - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleWhenOnlineTimestamp) - commit(transformedMessages) - } + } + + if totalAmount.value > 0 { + let controller = chatMessagePaymentAlertController( + context: nil, + presentationData: strongSelf.presentationData, + updatedPresentationData: nil, + peers: peers, + count: count, + amount: totalAmount, + totalAmount: totalAmount, + hasCheck: false, + navigationController: strongSelf.navigationController as? NavigationController, + completion: { _ in + proceed() + } + ) + strongSelf.present(controller, in: .window(.root)) + } else { + proceed() + } + }) } controller.peerSelected = { [weak self, weak controller] peer, threadId in guard let strongSelf = self, let strongController = controller else { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index e8175ac0a6..f44aa7f749 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1927,8 +1927,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } else { if let sendPaidMessageStars = interfaceState.sendPaidMessageStars { - //TODO:localize - placeholder = "Message for # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))" + placeholder = interfaceState.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))").string placeholderHasStar = true } else { placeholder = interfaceState.strings.Conversation_InputTextPlaceholder