From 1684465e04c2232a26f46f5f9731bc4543782dbc Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Tue, 25 Feb 2025 18:59:16 +0400 Subject: [PATCH] Add sending paid stories replies. --- Telegram/Resources/langs/lang.strings | 6 +- .../history/history_item_helpers.cpp | 19 +- .../history/history_item_helpers.h | 8 +- .../SourceFiles/history/history_widget.cpp | 118 ++++------- Telegram/SourceFiles/history/history_widget.h | 21 +- .../history_view_compose_controls.cpp | 15 +- .../history/view/history_view_element.cpp | 3 + .../history/view/history_view_message.cpp | 12 +- .../view/history_view_replies_section.cpp | 138 +++++++++++-- .../view/history_view_replies_section.h | 14 +- .../media/stories/media_stories_reply.cpp | 192 +++++++++++++++--- .../media/stories/media_stories_reply.h | 16 +- .../ui/chat/attach/attach_prepare.cpp | 21 ++ .../ui/chat/attach/attach_prepare.h | 15 ++ 14 files changed, 442 insertions(+), 156 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8f6a32ad8..294adcadb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2167,14 +2167,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?"; "lng_action_payment_refunded" = "{peer} refunded {amount}"; "lng_action_paid_message_sent#one" = "You paid {count} Star to {action}"; -"lng_action_paid_message_sent#other" = "You paid {count} Star to {action}"; -"lng_action_paid_message_group#one" = "{from} paid {count} Star to {action}"; -"lng_action_paid_message_group#other" = "{from} paid {count} Star to {action}"; +"lng_action_paid_message_sent#other" = "You paid {count} Stars to {action}"; "lng_action_paid_message_one" = "send a message"; "lng_action_paid_message_some#one" = "send {count} message"; "lng_action_paid_message_some#other" = "send {count} messages"; "lng_action_paid_message_got#one" = "You received {count} Star from {name}"; "lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; +"lng_you_paid_stars#one" = "You paid {count} Star."; +"lng_you_paid_stars#other" = "You paid {count} Stars."; "lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_view_all" = "View all"; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 49fcebcb2..5c601a700 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -382,13 +382,15 @@ bool SendPaymentHelper::check( not_null<PeerData*> peer, int messagesCount, int starsApproved, - Fn<void(int)> resend) { + Fn<void(int)> resend, + PaidConfirmStyles styles) { return check( navigation->uiShow(), peer, messagesCount, starsApproved, - std::move(resend)); + std::move(resend), + styles); } bool SendPaymentHelper::check( @@ -396,8 +398,10 @@ bool SendPaymentHelper::check( not_null<PeerData*> peer, int messagesCount, int starsApproved, - Fn<void(int)> resend) { - _lifetime.destroy(); + Fn<void(int)> resend, + PaidConfirmStyles styles) { + clear(); + const auto details = ComputePaymentDetails(peer, messagesCount); if (!details) { _resend = [=] { resend(starsApproved); }; @@ -426,12 +430,17 @@ bool SendPaymentHelper::check( } else if (const auto stars = details->stars; stars > starsApproved) { ShowSendPaidConfirm(show, peer, *details, [=] { resend(stars); - }); + }, styles); return false; } return true; } +void SendPaymentHelper::clear() { + _lifetime.destroy(); + _resend = nullptr; +} + void RequestDependentMessageItem( not_null<HistoryItem*> item, PeerId peerId, diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index c5dc8795e..dfe2c737c 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -179,13 +179,17 @@ public: not_null<PeerData*> peer, int messagesCount, int starsApproved, - Fn<void(int)> resend); + Fn<void(int)> resend, + PaidConfirmStyles styles = {}); [[nodiscard]] bool check( std::shared_ptr<Main::SessionShow> show, not_null<PeerData*> peer, int messagesCount, int starsApproved, - Fn<void(int)> resend); + Fn<void(int)> resend, + PaidConfirmStyles styles = {}); + + void clear(); private: Fn<void()> _resend; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e72412884..964c08888 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -225,16 +225,6 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_"; } // namespace -struct HistoryWidget::SendingFiles { - std::vector<Ui::PreparedGroup> groups; - Ui::SendFilesWay way; - TextWithTags caption; - Api::SendOptions options; - int totalCount = 0; - bool sendComment = false; - bool ctrlShiftEnter = false; -}; - HistoryWidget::HistoryWidget( QWidget *parent, not_null<Window::SessionController*> controller) @@ -898,17 +888,6 @@ HistoryWidget::HistoryWidget( } }, lifetime()); - if (!session().credits().loaded()) { - session().credits().loadedValue( - ) | rpl::filter( - rpl::mappers::_1 - ) | rpl::take(1) | rpl::start_with_next([=] { - if (const auto callback = base::take(_resendOnFullUpdated)) { - callback(); - } - }, lifetime()); - } - using Type = Data::DefaultNotify; rpl::merge( session().data().notifySettings().defaultUpdates(Type::User), @@ -2400,7 +2379,7 @@ void HistoryWidget::showHistory( setHistory(nullptr); _list = nullptr; _peer = nullptr; - _resendOnFullUpdated = nullptr; + _sendPayment.clear(); _topicsRequested.clear(); _canSendMessages = false; _canSendTexts = false; @@ -4412,7 +4391,7 @@ void HistoryWidget::send(Api::SendOptions options) { if (showSendMessageError( message.textWithTags, ignoreSlowmodeCountdown, - crl::guard(this, withPaymentApproved), + withPaymentApproved, options.starsApproved)) { return; } @@ -4735,6 +4714,19 @@ FullMsgId HistoryWidget::cornerButtonsCurrentId() { : FullMsgId(); } +bool HistoryWidget::checkSendPayment( + int messagesCount, + int starsApproved, + Fn<void(int)> withPaymentApproved) { + return _peer + && _sendPayment.check( + controller(), + _peer, + messagesCount, + starsApproved, + std::move(withPaymentApproved)); +} + void HistoryWidget::checkSuggestToGigagroup() { const auto group = _peer ? _peer->asMegagroup() : nullptr; if (!group || !group->owner().suggestToGigagroup(group)) { @@ -5890,7 +5882,7 @@ Data::ForumTopic *HistoryWidget::resolveReplyToTopic() { bool HistoryWidget::showSendMessageError( const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, - Fn<void(int starsApproved)> resend, + Fn<void(int starsApproved)> withPaymentApproved, int starsApproved) { if (!_canSendMessages) { return false; @@ -5908,25 +5900,12 @@ bool HistoryWidget::showSendMessageError( Data::ShowSendErrorToast(controller(), _peer, error); return true; } - return resend - && !checkSendPayment(request.messagesCount, starsApproved, resend); -} -bool HistoryWidget::checkSendPayment( - int messagesCount, - int starsApproved, - Fn<void(int starsApproved)> resend) { - const auto details = ComputePaymentDetails(_peer, messagesCount); - if (!details) { - _resendOnFullUpdated = [=] { resend(starsApproved); }; - return false; - } else if (const auto stars = details->stars; stars > starsApproved) { - ShowSendPaidConfirm(controller(), _peer, *details, [=] { - resend(stars); - }); - return false; - } - return true; + return withPaymentApproved + && !checkSendPayment( + request.messagesCount, + starsApproved, + withPaymentApproved); } bool HistoryWidget::confirmSendingFiles(const QStringList &files) { @@ -6012,11 +5991,11 @@ bool HistoryWidget::confirmSendingFiles( } void HistoryWidget::sendingFilesConfirmed( - Ui::PreparedList &&list, - Ui::SendFilesWay way, - TextWithTags &&caption, - Api::SendOptions options, - bool ctrlShiftEnter) { + Ui::PreparedList &&list, + Ui::SendFilesWay way, + TextWithTags &&caption, + Api::SendOptions options, + bool ctrlShiftEnter) { Expects(list.filesToProcess.empty()); const auto compress = way.sendImagesAsPhotos(); @@ -6024,55 +6003,51 @@ void HistoryWidget::sendingFilesConfirmed( return; } - const auto filesCount = int(list.files.size()); auto groups = DivideByGroups( std::move(list), way, _peer->slowmodeApplied()); - const auto sendComment = !caption.text.isEmpty() - && (groups.size() != 1 || !groups.front().sentWithCaption()); - sendingFilesConfirmed(std::make_shared<SendingFiles>(SendingFiles{ - .groups = std::move(groups), - .way = way, - .caption = std::move(caption), - .options = options, - .totalCount = filesCount + (sendComment ? 1 : 0), - .sendComment = sendComment, - .ctrlShiftEnter = ctrlShiftEnter, - })); + auto bundle = PrepareFilesBundle( + std::move(groups), + way, + std::move(caption), + ctrlShiftEnter); + sendingFilesConfirmed(std::move(bundle), options); } void HistoryWidget::sendingFilesConfirmed( - std::shared_ptr<SendingFiles> args) { + std::shared_ptr<Ui::PreparedBundle> bundle, + Api::SendOptions options) { const auto withPaymentApproved = [=](int approved) { - args->options.starsApproved = approved; - sendingFilesConfirmed(args); + auto copy = options; + copy.starsApproved = approved; + sendingFilesConfirmed(bundle, copy); }; const auto checked = checkSendPayment( - args->totalCount, - args->options.starsApproved, + bundle->totalCount, + options.starsApproved, withPaymentApproved); if (!checked) { return; } - const auto compress = args->way.sendImagesAsPhotos(); + const auto compress = bundle->way.sendImagesAsPhotos(); const auto type = compress ? SendMediaType::Photo : SendMediaType::File; - auto action = prepareSendAction(args->options); + auto action = prepareSendAction(options); action.clearDraft = false; - if (args->sendComment) { + if (bundle->sendComment) { auto message = Api::MessageToSend(action); - message.textWithTags = base::take(args->caption); + message.textWithTags = base::take(bundle->caption); session().api().sendMessage(std::move(message)); } - for (auto &group : args->groups) { + for (auto &group : bundle->groups) { const auto album = (group.type != Ui::AlbumType::None) ? std::make_shared<SendingAlbum>() : nullptr; session().api().sendFiles( std::move(group.list), type, - base::take(args->caption), + base::take(bundle->caption), album, action); } @@ -8545,9 +8520,6 @@ void HistoryWidget::fullInfoUpdated() { updateControlsVisibility(); updateControlsGeometry(); } - if (const auto callback = base::take(_resendOnFullUpdated)) { - callback(); - } } void HistoryWidget::handlePeerUpdate() { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index f52dd3086..2b956c2c1 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_compose_media_edit_manager.h" #include "history/view/history_view_corner_buttons.h" #include "history/history_drag_area.h" +#include "history/history_item_helpers.h" #include "history/history_view_highlight_manager.h" #include "history/history_view_top_toast.h" #include "history/history.h" @@ -69,6 +70,7 @@ class PinnedBar; class GroupCallBar; class RequestsBar; struct PreparedList; +struct PreparedBundle; class SendFilesWay; class SendAsButton; class SpoilerAnimation; @@ -320,7 +322,6 @@ private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; using VoiceToSend = HistoryView::Controls::VoiceToSend; - struct SendingFiles; enum ScrollChangeType { ScrollChangeNone, @@ -359,6 +360,11 @@ private: bool cornerButtonsUnreadMayBeShown() override; bool cornerButtonsHas(HistoryView::CornerButtonType type) override; + [[nodiscard]] bool checkSendPayment( + int messagesCount, + int starsApproved, + Fn<void(int)> withPaymentApproved); + void checkSuggestToGigagroup(); void processReply(); void setReplyFieldsFromProcessing(); @@ -471,12 +477,8 @@ private: bool showSendMessageError( const TextWithTags &textWithTags, bool ignoreSlowmodeCountdown, - Fn<void(int starsApproved)> resend = nullptr, + Fn<void(int starsApproved)> withPaymentApproved = nullptr, int starsApproved = 0); - bool checkSendPayment( - int messagesCount, - int starsApproved, - Fn<void(int starsApproved)> resend); void sendingFilesConfirmed( Ui::PreparedList &&list, @@ -484,7 +486,9 @@ private: TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter); - void sendingFilesConfirmed(std::shared_ptr<SendingFiles> args); + void sendingFilesConfirmed( + std::shared_ptr<Ui::PreparedBundle> bundle, + Api::SendOptions options); void uploadFile(const QByteArray &fileContent, SendMediaType type); void itemRemoved(not_null<const HistoryItem*> item); @@ -769,7 +773,6 @@ private: std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete; std::unique_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions; object_ptr<Support::Autocomplete> _supportAutocomplete; - Fn<void()> _resendOnFullUpdated; UserData *_inlineBot = nullptr; QString _inlineBotUsername; @@ -874,6 +877,8 @@ private: int _topDelta = 0; + SendPaymentHelper _sendPayment; + rpl::event_stream<> _cancelRequests; }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 1f88adeec..dc323ba7d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1722,13 +1722,20 @@ void ComposeControls::updateFieldPlaceholder() { } _field->setPlaceholder([&] { + const auto peer = _history ? _history->peer.get() : nullptr; if (_fieldCustomPlaceholder) { return rpl::duplicate(_fieldCustomPlaceholder); } else if (isEditingMessage()) { return tr::lng_edit_message_text(); - } else if (!_history) { + } else if (!peer) { return tr::lng_message_ph(); - } else if (const auto channel = _history->peer->asChannel()) { + } else if (const auto stars = peer->starsPerMessageChecked()) { + return tr::lng_message_paid_ph( + lt_amount, + tr::lng_prize_credits_amount( + lt_count, + rpl::single(stars * 1.))); + } else if (const auto channel = peer->asChannel()) { if (channel->isBroadcast()) { return session().data().notifySettings().silentPosts(channel) ? tr::lng_broadcast_silent_ph() @@ -3120,6 +3127,7 @@ void ComposeControls::initWebpageProcess() { | Data::PeerUpdate::Flag::Notifications | Data::PeerUpdate::Flag::MessagesTTL | Data::PeerUpdate::Flag::FullInfo + | Data::PeerUpdate::Flag::StarsPerMessage ) | rpl::filter([peer = _history->peer](const Data::PeerUpdate &update) { return (update.peer.get() == peer); }) | rpl::map([](const Data::PeerUpdate &update) { @@ -3135,6 +3143,9 @@ void ComposeControls::initWebpageProcess() { if (flags & Data::PeerUpdate::Flag::MessagesTTL) { updateMessagesTTLShown(); } + if (flags & Data::PeerUpdate::Flag::StarsPerMessage) { + updateFieldPlaceholder(); + } if (flags & Data::PeerUpdate::Flag::FullInfo) { if (updateBotCommandShown()) { updateControlsVisibility(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 4dddabdfb..44e9e658f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -384,6 +384,9 @@ QString DateTooltipText(not_null<Element*> view) { if (item->isScheduled() && item->isSilent()) { dateText += '\n' + QChar(0xD83D) + QChar(0xDD15); } + if (const auto stars = item->out() ? item->starsPaid() : 0) { + dateText += '\n' + tr::lng_you_paid_stars(tr::now, lt_count, stars); + } return dateText; } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 19d3ea733..b9a4b0a1e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -476,22 +476,12 @@ void Message::initPaidInformation() { lt_action, action(), Ui::Text::WithEntities) - : history()->peer->isUser() - ? tr::lng_action_paid_message_got( + : tr::lng_action_paid_message_got( tr::now, lt_count, info.stars, lt_name, Ui::Text::Link(item->from()->shortName(), 1), - Ui::Text::WithEntities) - : tr::lng_action_paid_message_group( - tr::now, - lt_count, - info.stars, - lt_from, - Ui::Text::Link(item->from()->shortName(), 1), - lt_action, - action(), Ui::Text::WithEntities), }; if (!item->out()) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 3ffc1348e..88f2b44a5 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -733,8 +733,8 @@ void RepliesWidget::setupComposeControls() { }, lifetime()); _composeControls->sendVoiceRequests( - ) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) { - sendVoice(std::move(data)); + ) | rpl::start_with_next([=](const ComposeControls::VoiceToSend &data) { + sendVoice(data); }, lifetime()); _composeControls->sendCommandRequests( @@ -1070,25 +1070,59 @@ void RepliesWidget::sendingFilesConfirmed( std::move(list), way, _history->peer->slowmodeApplied()); - const auto type = way.sendImagesAsPhotos() - ? SendMediaType::Photo - : SendMediaType::File; + auto bundle = PrepareFilesBundle( + std::move(groups), + way, + std::move(caption), + ctrlShiftEnter); + sendingFilesConfirmed(std::move(bundle), options); +} + +bool RepliesWidget::checkSendPayment( + int messagesCount, + int starsApproved, + Fn<void(int)> withPaymentApproved) { + return _sendPayment.check( + controller(), + _history->peer, + messagesCount, + starsApproved, + std::move(withPaymentApproved)); +} + +void RepliesWidget::sendingFilesConfirmed( + std::shared_ptr<Ui::PreparedBundle> bundle, + Api::SendOptions options) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendingFilesConfirmed(bundle, copy); + }; + const auto checked = checkSendPayment( + bundle->totalCount, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + + const auto compress = bundle->way.sendImagesAsPhotos(); + const auto type = compress ? SendMediaType::Photo : SendMediaType::File; auto action = prepareSendAction(options); action.clearDraft = false; - if ((groups.size() != 1 || !groups.front().sentWithCaption()) - && !caption.text.isEmpty()) { + if (bundle->sendComment) { auto message = Api::MessageToSend(action); - message.textWithTags = base::take(caption); + message.textWithTags = base::take(bundle->caption); session().api().sendMessage(std::move(message)); } - for (auto &group : groups) { + for (auto &group : bundle->groups) { const auto album = (group.type != Ui::AlbumType::None) ? std::make_shared<SendingAlbum>() : nullptr; session().api().sendFiles( std::move(group.list), type, - base::take(caption), + base::take(bundle->caption), album, action); } @@ -1227,7 +1261,20 @@ void RepliesWidget::send() { send({}); } -void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) { +void RepliesWidget::sendVoice(const ComposeControls::VoiceToSend &data) { + const auto withPaymentApproved = [=](int approved) { + auto copy = data; + copy.options.starsApproved = approved; + sendVoice(copy); + }; + const auto checked = checkSendPayment( + 1, + data.options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(data.options); session().api().sendVoiceMessage( data.bytes, @@ -1254,19 +1301,32 @@ void RepliesWidget::send(Api::SendOptions options) { message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); message.webPage = _composeControls->webPageDraft(); - const auto error = GetErrorForSending( - _history->peer, - { - .topicRootId = _topic ? _topic->rootId() : MsgId(0), - .forward = &_composeControls->forwardItems(), - .text = &message.textWithTags, - .ignoreSlowmodeCountdown = (options.scheduled != 0), - }); + auto request = SendingErrorRequest{ + .topicRootId = _topic ? _topic->rootId() : MsgId(0), + .forward = &_composeControls->forwardItems(), + .text = &message.textWithTags, + .ignoreSlowmodeCountdown = (options.scheduled != 0), + }; + request.messagesCount = ComputeSendingMessagesCount(_history, request); + const auto error = GetErrorForSending(_history->peer, request); if (error) { Data::ShowSendErrorToast(controller(), _history->peer, error); return; } - + if (!options.scheduled) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + send(copy); + }; + const auto checked = checkSendPayment( + request.messagesCount, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + } session().api().sendMessage(std::move(message)); _composeControls->clear(); @@ -1420,6 +1480,18 @@ bool RepliesWidget::sendExistingDocument( || ShowSendPremiumError(controller(), document)) { return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = messageToSend; + copy.action.options.starsApproved = approved; + sendExistingDocument(document, std::move(copy), localId); + }; + const auto checked = checkSendPayment( + 1, + messageToSend.action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } Api::SendExistingDocument( std::move(messageToSend), @@ -1448,6 +1520,19 @@ bool RepliesWidget::sendExistingPhoto( return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendExistingPhoto(photo, copy); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } + Api::SendExistingPhoto( Api::MessageToSend(prepareSendAction(options)), photo); @@ -1478,6 +1563,19 @@ void RepliesWidget::sendInlineResult( not_null<UserData*> bot, Api::SendOptions options, std::optional<MsgId> localMessageId) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendInlineResult(result, bot, copy, localMessageId); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(options); action.generateLocal = true; session().api().sendInlineResult( diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 7f3a6f4dc..d03f5c834 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_memento.h" #include "history/view/history_view_corner_buttons.h" #include "history/view/history_view_list_widget.h" +#include "history/history_item_helpers.h" #include "history/history_view_swipe_data.h" #include "data/data_messages.h" #include "base/timer.h" @@ -38,6 +39,7 @@ class PlainShadow; class FlatButton; class PinnedBar; struct PreparedList; +struct PreparedBundle; class SendFilesWay; } // namespace Ui @@ -207,6 +209,11 @@ private: void checkActivation() override; void doSetInnerFocus() override; + [[nodiscard]] bool checkSendPayment( + int messagesCount, + int starsApproved, + Fn<void(int)> withPaymentApproved); + void onScroll(); void updateInnerVisibleArea(); void updateControlsGeometry(); @@ -251,7 +258,7 @@ private: Api::SendOptions options) const; void send(); void send(Api::SendOptions options); - void sendVoice(Controls::VoiceToSend &&data); + void sendVoice(const Controls::VoiceToSend &data); void edit( not_null<HistoryItem*> item, Api::SendOptions options, @@ -308,6 +315,9 @@ private: TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter); + void sendingFilesConfirmed( + std::shared_ptr<Ui::PreparedBundle> bundle, + Api::SendOptions options); bool sendExistingDocument( not_null<DocumentData*> document, @@ -380,6 +390,8 @@ private: HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal; + SendPaymentHelper _sendPayment; + int _lastScrollTop = 0; int _topicReopenBarHeight = 0; int _scrollTopDelta = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index ad06525c3..21ae90050 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -15,11 +15,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/premium_limits_box.h" #include "boxes/send_files_box.h" +#include "boxes/share_box.h" // ShareBoxStyleOverrides #include "chat_helpers/compose/compose_show.h" #include "chat_helpers/tabbed_selector.h" #include "core/file_utilities.h" #include "core/mime_type.h" #include "data/stickers/data_custom_emoji.h" +#include "data/data_changes.h" #include "data/data_chat_participant_status.h" #include "data/data_document.h" #include "data/data_message_reaction_id.h" @@ -28,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "history/view/controls/compose_controls_common.h" #include "history/view/controls/history_view_compose_controls.h" +#include "history/view/history_view_schedule_box.h" // ScheduleBoxStyleArgs #include "history/history_item_helpers.h" #include "history/history.h" #include "inline_bots/inline_bot_result.h" @@ -36,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/stories/media_stories_controller.h" #include "media/stories/media_stories_stealth.h" #include "menu/menu_send.h" +#include "settings/settings_credits_graphics.h" // DarkCreditsEntryBoxStyle #include "storage/localimageloader.h" #include "storage/storage_account.h" #include "storage/storage_media_prepare.h" @@ -52,14 +56,19 @@ namespace { [[nodiscard]] rpl::producer<QString> PlaceholderText( const std::shared_ptr<ChatHelpers::Show> &show, - rpl::producer<bool> isComment) { + rpl::producer<bool> isComment, + rpl::producer<int> starsPerMessage) { return rpl::combine( show->session().data().stories().stealthModeValue(), - std::move(isComment) - ) | rpl::map([](Data::StealthMode value, bool isComment) { - return std::tuple(value.enabledTill, isComment); + std::move(isComment), + std::move(starsPerMessage) + ) | rpl::map([]( + Data::StealthMode value, + bool isComment, + int starsPerMessage) { + return std::tuple(value.enabledTill, isComment, starsPerMessage); }) | rpl::distinct_until_changed( - ) | rpl::map([](TimeId till, bool isComment) { + ) | rpl::map([](TimeId till, bool isComment, int starsPerMessage) { return rpl::single( rpl::empty ) | rpl::then( @@ -71,7 +80,13 @@ namespace { }) | rpl::then( rpl::single(0) ) | rpl::map([=](TimeId left) { - return left + return starsPerMessage + ? tr::lng_message_paid_ph( + lt_amount, + tr::lng_prize_credits_amount( + lt_count, + rpl::single(starsPerMessage * 1.))) + : left ? tr::lng_stealth_mode_countdown( lt_left, rpl::single(TimeLeftText(left))) @@ -128,7 +143,8 @@ ReplyArea::ReplyArea(not_null<Controller*> controller) .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), .customPlaceholder = PlaceholderText( _controller->uiShow(), - rpl::deferred([=] { return _isComment.value(); })), + rpl::deferred([=] { return _isComment.value(); }), + rpl::deferred([=] { return _starsForMessage.value(); })), .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { @@ -199,7 +215,7 @@ bool ReplyArea::sendReaction(const Data::ReactionId &id) { } } return !message.textWithTags.empty() - && send(std::move(message), {}, true); + && send(std::move(message), true); } void ReplyArea::send(Api::SendOptions options) { @@ -209,29 +225,45 @@ void ReplyArea::send(Api::SendOptions options) { message.textWithTags = _controls->getTextWithAppliedMarkdown(); message.webPage = webPageDraft; - send(std::move(message), options); + send(std::move(message)); } bool ReplyArea::send( Api::MessageToSend message, - Api::SendOptions options, bool skipToast) { - if (!options.scheduled && showSlowmodeError()) { + if (!message.action.options.scheduled && showSlowmodeError()) { return false; } - const auto error = GetErrorForSending( - _data.peer, - { - .topicRootId = MsgId(0), - .text = &message.textWithTags, - .ignoreSlowmodeCountdown = (options.scheduled != 0), - }); + auto request = SendingErrorRequest{ + .topicRootId = MsgId(0), + .text = &message.textWithTags, + .ignoreSlowmodeCountdown = (message.action.options.scheduled != 0), + }; + request.messagesCount = ComputeSendingMessagesCount( + message.action.history, + request); + const auto error = GetErrorForSending(_data.peer, request); if (error) { Data::ShowSendErrorToast(_controller->uiShow(), _data.peer, error); return false; } + if (!message.action.options.scheduled) { + const auto withPaymentApproved = [=](int approved) { + auto copy = message; + copy.action.options.starsApproved = approved; + send(copy); + }; + const auto checked = checkSendPayment( + request.messagesCount, + message.action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } + } + session().api().sendMessage(std::move(message)); finishSending(skipToast); @@ -239,7 +271,40 @@ bool ReplyArea::send( return true; } -void ReplyArea::sendVoice(VoiceToSend &&data) { +bool ReplyArea::checkSendPayment( + int messagesCount, + int starsApproved, + Fn<void(int)> withPaymentApproved) { + const auto st1 = ::Settings::DarkCreditsEntryBoxStyle(); + const auto st2 = st1.shareBox.get(); + const auto st3 = st2 ? st2->scheduleBox.get() : nullptr; + return _data.peer + && _sendPayment.check( + _controller->uiShow(), + _data.peer, + messagesCount, + starsApproved, + std::move(withPaymentApproved), + { + .label = st3 ? st3->chooseDateTimeArgs.labelStyle : nullptr, + .checkbox = st2 ? st2->checkbox : nullptr, + }); +} + +void ReplyArea::sendVoice(const VoiceToSend &data) { + const auto withPaymentApproved = [=](int approved) { + auto copy = data; + copy.options.starsApproved = approved; + sendVoice(copy); + }; + const auto checked = checkSendPayment( + 1, + data.options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(data.options); session().api().sendVoiceMessage( data.bytes, @@ -269,6 +334,18 @@ bool ReplyArea::sendExistingDocument( || Window::ShowSendPremiumError(show, document)) { return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = messageToSend; + copy.action.options.starsApproved = approved; + sendExistingDocument(document, std::move(copy), localId); + }; + const auto checked = checkSendPayment( + 1, + messageToSend.action.options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } Api::SendExistingDocument(std::move(messageToSend), document, localId); @@ -296,6 +373,18 @@ bool ReplyArea::sendExistingPhoto( } else if (showSlowmodeError()) { return false; } + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendExistingPhoto(photo, copy); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return false; + } Api::SendExistingPhoto( Api::MessageToSend(prepareSendAction(options)), @@ -322,6 +411,19 @@ void ReplyArea::sendInlineResult( not_null<UserData*> bot, Api::SendOptions options, std::optional<MsgId> localMessageId) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendInlineResult(result, bot, copy, localMessageId); + }; + const auto checked = checkSendPayment( + 1, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + auto action = prepareSendAction(options); action.generateLocal = true; session().api().sendInlineResult( @@ -564,25 +666,47 @@ void ReplyArea::sendingFilesConfirmed( std::move(list), way, _data.peer->slowmodeApplied()); - const auto type = way.sendImagesAsPhotos() - ? SendMediaType::Photo - : SendMediaType::File; + auto bundle = PrepareFilesBundle( + std::move(groups), + way, + std::move(caption), + ctrlShiftEnter); + sendingFilesConfirmed(std::move(bundle), options); +} + +void ReplyArea::sendingFilesConfirmed( + std::shared_ptr<Ui::PreparedBundle> bundle, + Api::SendOptions options) { + const auto withPaymentApproved = [=](int approved) { + auto copy = options; + copy.starsApproved = approved; + sendingFilesConfirmed(bundle, copy); + }; + const auto checked = checkSendPayment( + bundle->totalCount, + options.starsApproved, + withPaymentApproved); + if (!checked) { + return; + } + + const auto compress = bundle->way.sendImagesAsPhotos(); + const auto type = compress ? SendMediaType::Photo : SendMediaType::File; auto action = prepareSendAction(options); action.clearDraft = false; - if ((groups.size() != 1 || !groups.front().sentWithCaption()) - && !caption.text.isEmpty()) { + if (bundle->sendComment) { auto message = Api::MessageToSend(action); - message.textWithTags = base::take(caption); + message.textWithTags = base::take(bundle->caption); session().api().sendMessage(std::move(message)); } - for (auto &group : groups) { + for (auto &group : bundle->groups) { const auto album = (group.type != Ui::AlbumType::None) ? std::make_shared<SendingAlbum>() : nullptr; session().api().sendFiles( std::move(group.list), type, - base::take(caption), + base::take(bundle->caption), album, action); } @@ -618,8 +742,8 @@ void ReplyArea::initActions() { }, _lifetime); _controls->sendVoiceRequests( - ) | rpl::start_with_next([=](VoiceToSend &&data) { - sendVoice(std::move(data)); + ) | rpl::start_with_next([=](const VoiceToSend &data) { + sendVoice(data); }, _lifetime); _controls->attachRequests( @@ -697,6 +821,16 @@ void ReplyArea::show( _controls->clear(); } return; + } else if (const auto peer = _data.peer) { + using Flag = Data::PeerUpdate::Flag; + _starsForMessage = peer->session().changes().peerFlagsValue( + peer, + Flag::StarsPerMessage | Flag::FullInfo + ) | rpl::map([=] { + return peer->starsPerMessageChecked(); + }); + } else { + _starsForMessage = 0; } invalidate_weak_ptrs(&_shownPeerGuard); const auto peer = data.peer; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index d3cdec47d..bb7fe15f0 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/weak_ptr.h" +#include "history/history_item_helpers.h" class History; enum class SendMediaType; @@ -44,6 +45,7 @@ struct Details; namespace Ui { struct PreparedList; +struct PreparedBundle; class SendFilesWay; class RpWidget; } // namespace Ui @@ -90,9 +92,13 @@ private: bool send( Api::MessageToSend message, - Api::SendOptions options, bool skipToast = false); + [[nodiscard]] bool checkSendPayment( + int messagesCount, + int starsApproved, + Fn<void(int)> withPaymentApproved); + void uploadFile(const QByteArray &fileContent, SendMediaType type); bool confirmSendingFiles( QImage &&image, @@ -116,6 +122,9 @@ private: TextWithTags &&caption, Api::SendOptions options, bool ctrlShiftEnter); + void sendingFilesConfirmed( + std::shared_ptr<Ui::PreparedBundle> bundle, + Api::SendOptions options); void finishSending(bool skipToast = false); bool sendExistingDocument( @@ -141,7 +150,7 @@ private: [[nodiscard]] Api::SendAction prepareSendAction( Api::SendOptions options) const; void send(Api::SendOptions options); - void sendVoice(VoiceToSend &&data); + void sendVoice(const VoiceToSend &data); void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos); [[nodiscard]] Fn<SendMenu::Details()> sendMenuDetails() const; @@ -151,6 +160,7 @@ private: const not_null<Controller*> _controller; rpl::variable<bool> _isComment; + rpl::variable<int> _starsForMessage; const std::unique_ptr<HistoryView::ComposeControls> _controls; std::unique_ptr<Cant> _cant; @@ -160,6 +170,8 @@ private: bool _chooseAttachRequest = false; rpl::variable<bool> _choosingAttach; + SendPaymentHelper _sendPayment; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 3c5e0e707..fb968607b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -266,6 +266,27 @@ bool PreparedList::hasSpoilerMenu(bool compress) const { return allAreVideo || (allAreMedia && compress); } +std::shared_ptr<PreparedBundle> PrepareFilesBundle( + std::vector<PreparedGroup> groups, + SendFilesWay way, + TextWithTags caption, + bool ctrlShiftEnter) { + auto totalCount = 0; + for (const auto &group : groups) { + totalCount += group.list.files.size(); + } + const auto sendComment = !caption.text.isEmpty() + && (groups.size() != 1 || !groups.front().sentWithCaption()); + return std::make_shared<PreparedBundle>(PreparedBundle{ + .groups = std::move(groups), + .way = way, + .caption = std::move(caption), + .totalCount = totalCount + (sendComment ? 1 : 0), + .sendComment = sendComment, + .ctrlShiftEnter = ctrlShiftEnter, + }); +} + int MaxAlbumItems() { return kMaxAlbumCount; } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index b244f321b..311903a00 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "editor/photo_editor_common.h" +#include "ui/chat/attach/attach_send_files_way.h" #include "ui/rect_part.h" #include <QtCore/QSemaphore> @@ -153,6 +154,20 @@ struct PreparedGroup { SendFilesWay way, bool slowmode); +struct PreparedBundle { + std::vector<PreparedGroup> groups; + SendFilesWay way; + TextWithTags caption; + int totalCount = 0; + bool sendComment = false; + bool ctrlShiftEnter = false; +}; +[[nodiscard]] std::shared_ptr<PreparedBundle> PrepareFilesBundle( + std::vector<PreparedGroup> groups, + SendFilesWay way, + TextWithTags caption, + bool ctrlShiftEnter); + [[nodiscard]] int MaxAlbumItems(); [[nodiscard]] bool ValidateThumbDimensions(int width, int height);