Add sending paid stories replies.

This commit is contained in:
John Preston 2025-02-25 18:59:16 +04:00
parent fe2df96953
commit 1684465e04
14 changed files with 442 additions and 156 deletions

View file

@ -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_set_chat_intro" = "{from} added the message below for all empty chats. How?";
"lng_action_payment_refunded" = "{peer} refunded {amount}"; "lng_action_payment_refunded" = "{peer} refunded {amount}";
"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}"; "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_sent#other" = "You paid {count} Stars 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_one" = "send a message"; "lng_action_paid_message_one" = "send a message";
"lng_action_paid_message_some#one" = "send {count} message"; "lng_action_paid_message_some#one" = "send {count} message";
"lng_action_paid_message_some#other" = "send {count} messages"; "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#one" = "You received {count} Star from {name}";
"lng_action_paid_message_got#other" = "You received {count} Stars 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_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all"; "lng_similar_channels_view_all" = "View all";

View file

@ -382,13 +382,15 @@ bool SendPaymentHelper::check(
not_null<PeerData*> peer, not_null<PeerData*> peer,
int messagesCount, int messagesCount,
int starsApproved, int starsApproved,
Fn<void(int)> resend) { Fn<void(int)> resend,
PaidConfirmStyles styles) {
return check( return check(
navigation->uiShow(), navigation->uiShow(),
peer, peer,
messagesCount, messagesCount,
starsApproved, starsApproved,
std::move(resend)); std::move(resend),
styles);
} }
bool SendPaymentHelper::check( bool SendPaymentHelper::check(
@ -396,8 +398,10 @@ bool SendPaymentHelper::check(
not_null<PeerData*> peer, not_null<PeerData*> peer,
int messagesCount, int messagesCount,
int starsApproved, int starsApproved,
Fn<void(int)> resend) { Fn<void(int)> resend,
_lifetime.destroy(); PaidConfirmStyles styles) {
clear();
const auto details = ComputePaymentDetails(peer, messagesCount); const auto details = ComputePaymentDetails(peer, messagesCount);
if (!details) { if (!details) {
_resend = [=] { resend(starsApproved); }; _resend = [=] { resend(starsApproved); };
@ -426,12 +430,17 @@ bool SendPaymentHelper::check(
} else if (const auto stars = details->stars; stars > starsApproved) { } else if (const auto stars = details->stars; stars > starsApproved) {
ShowSendPaidConfirm(show, peer, *details, [=] { ShowSendPaidConfirm(show, peer, *details, [=] {
resend(stars); resend(stars);
}); }, styles);
return false; return false;
} }
return true; return true;
} }
void SendPaymentHelper::clear() {
_lifetime.destroy();
_resend = nullptr;
}
void RequestDependentMessageItem( void RequestDependentMessageItem(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
PeerId peerId, PeerId peerId,

View file

@ -179,13 +179,17 @@ public:
not_null<PeerData*> peer, not_null<PeerData*> peer,
int messagesCount, int messagesCount,
int starsApproved, int starsApproved,
Fn<void(int)> resend); Fn<void(int)> resend,
PaidConfirmStyles styles = {});
[[nodiscard]] bool check( [[nodiscard]] bool check(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
int messagesCount, int messagesCount,
int starsApproved, int starsApproved,
Fn<void(int)> resend); Fn<void(int)> resend,
PaidConfirmStyles styles = {});
void clear();
private: private:
Fn<void()> _resend; Fn<void()> _resend;

View file

@ -225,16 +225,6 @@ const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
} // namespace } // 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( HistoryWidget::HistoryWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
@ -898,17 +888,6 @@ HistoryWidget::HistoryWidget(
} }
}, lifetime()); }, 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; using Type = Data::DefaultNotify;
rpl::merge( rpl::merge(
session().data().notifySettings().defaultUpdates(Type::User), session().data().notifySettings().defaultUpdates(Type::User),
@ -2400,7 +2379,7 @@ void HistoryWidget::showHistory(
setHistory(nullptr); setHistory(nullptr);
_list = nullptr; _list = nullptr;
_peer = nullptr; _peer = nullptr;
_resendOnFullUpdated = nullptr; _sendPayment.clear();
_topicsRequested.clear(); _topicsRequested.clear();
_canSendMessages = false; _canSendMessages = false;
_canSendTexts = false; _canSendTexts = false;
@ -4412,7 +4391,7 @@ void HistoryWidget::send(Api::SendOptions options) {
if (showSendMessageError( if (showSendMessageError(
message.textWithTags, message.textWithTags,
ignoreSlowmodeCountdown, ignoreSlowmodeCountdown,
crl::guard(this, withPaymentApproved), withPaymentApproved,
options.starsApproved)) { options.starsApproved)) {
return; return;
} }
@ -4735,6 +4714,19 @@ FullMsgId HistoryWidget::cornerButtonsCurrentId() {
: FullMsgId(); : 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() { void HistoryWidget::checkSuggestToGigagroup() {
const auto group = _peer ? _peer->asMegagroup() : nullptr; const auto group = _peer ? _peer->asMegagroup() : nullptr;
if (!group || !group->owner().suggestToGigagroup(group)) { if (!group || !group->owner().suggestToGigagroup(group)) {
@ -5890,7 +5882,7 @@ Data::ForumTopic *HistoryWidget::resolveReplyToTopic() {
bool HistoryWidget::showSendMessageError( bool HistoryWidget::showSendMessageError(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown, bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> resend, Fn<void(int starsApproved)> withPaymentApproved,
int starsApproved) { int starsApproved) {
if (!_canSendMessages) { if (!_canSendMessages) {
return false; return false;
@ -5908,25 +5900,12 @@ bool HistoryWidget::showSendMessageError(
Data::ShowSendErrorToast(controller(), _peer, error); Data::ShowSendErrorToast(controller(), _peer, error);
return true; return true;
} }
return resend
&& !checkSendPayment(request.messagesCount, starsApproved, resend);
}
bool HistoryWidget::checkSendPayment( return withPaymentApproved
int messagesCount, && !checkSendPayment(
int starsApproved, request.messagesCount,
Fn<void(int starsApproved)> resend) { starsApproved,
const auto details = ComputePaymentDetails(_peer, messagesCount); withPaymentApproved);
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;
} }
bool HistoryWidget::confirmSendingFiles(const QStringList &files) { bool HistoryWidget::confirmSendingFiles(const QStringList &files) {
@ -6012,11 +5991,11 @@ bool HistoryWidget::confirmSendingFiles(
} }
void HistoryWidget::sendingFilesConfirmed( void HistoryWidget::sendingFilesConfirmed(
Ui::PreparedList &&list, Ui::PreparedList &&list,
Ui::SendFilesWay way, Ui::SendFilesWay way,
TextWithTags &&caption, TextWithTags &&caption,
Api::SendOptions options, Api::SendOptions options,
bool ctrlShiftEnter) { bool ctrlShiftEnter) {
Expects(list.filesToProcess.empty()); Expects(list.filesToProcess.empty());
const auto compress = way.sendImagesAsPhotos(); const auto compress = way.sendImagesAsPhotos();
@ -6024,55 +6003,51 @@ void HistoryWidget::sendingFilesConfirmed(
return; return;
} }
const auto filesCount = int(list.files.size());
auto groups = DivideByGroups( auto groups = DivideByGroups(
std::move(list), std::move(list),
way, way,
_peer->slowmodeApplied()); _peer->slowmodeApplied());
const auto sendComment = !caption.text.isEmpty() auto bundle = PrepareFilesBundle(
&& (groups.size() != 1 || !groups.front().sentWithCaption()); std::move(groups),
sendingFilesConfirmed(std::make_shared<SendingFiles>(SendingFiles{ way,
.groups = std::move(groups), std::move(caption),
.way = way, ctrlShiftEnter);
.caption = std::move(caption), sendingFilesConfirmed(std::move(bundle), options);
.options = options,
.totalCount = filesCount + (sendComment ? 1 : 0),
.sendComment = sendComment,
.ctrlShiftEnter = ctrlShiftEnter,
}));
} }
void HistoryWidget::sendingFilesConfirmed( void HistoryWidget::sendingFilesConfirmed(
std::shared_ptr<SendingFiles> args) { std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options) {
const auto withPaymentApproved = [=](int approved) { const auto withPaymentApproved = [=](int approved) {
args->options.starsApproved = approved; auto copy = options;
sendingFilesConfirmed(args); copy.starsApproved = approved;
sendingFilesConfirmed(bundle, copy);
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
args->totalCount, bundle->totalCount,
args->options.starsApproved, options.starsApproved,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
} }
const auto compress = args->way.sendImagesAsPhotos(); const auto compress = bundle->way.sendImagesAsPhotos();
const auto type = compress ? SendMediaType::Photo : SendMediaType::File; const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
auto action = prepareSendAction(args->options); auto action = prepareSendAction(options);
action.clearDraft = false; action.clearDraft = false;
if (args->sendComment) { if (bundle->sendComment) {
auto message = Api::MessageToSend(action); auto message = Api::MessageToSend(action);
message.textWithTags = base::take(args->caption); message.textWithTags = base::take(bundle->caption);
session().api().sendMessage(std::move(message)); session().api().sendMessage(std::move(message));
} }
for (auto &group : args->groups) { for (auto &group : bundle->groups) {
const auto album = (group.type != Ui::AlbumType::None) const auto album = (group.type != Ui::AlbumType::None)
? std::make_shared<SendingAlbum>() ? std::make_shared<SendingAlbum>()
: nullptr; : nullptr;
session().api().sendFiles( session().api().sendFiles(
std::move(group.list), std::move(group.list),
type, type,
base::take(args->caption), base::take(bundle->caption),
album, album,
action); action);
} }
@ -8545,9 +8520,6 @@ void HistoryWidget::fullInfoUpdated() {
updateControlsVisibility(); updateControlsVisibility();
updateControlsGeometry(); updateControlsGeometry();
} }
if (const auto callback = base::take(_resendOnFullUpdated)) {
callback();
}
} }
void HistoryWidget::handlePeerUpdate() { void HistoryWidget::handlePeerUpdate() {

View file

@ -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/controls/history_view_compose_media_edit_manager.h"
#include "history/view/history_view_corner_buttons.h" #include "history/view/history_view_corner_buttons.h"
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_item_helpers.h"
#include "history/history_view_highlight_manager.h" #include "history/history_view_highlight_manager.h"
#include "history/history_view_top_toast.h" #include "history/history_view_top_toast.h"
#include "history/history.h" #include "history/history.h"
@ -69,6 +70,7 @@ class PinnedBar;
class GroupCallBar; class GroupCallBar;
class RequestsBar; class RequestsBar;
struct PreparedList; struct PreparedList;
struct PreparedBundle;
class SendFilesWay; class SendFilesWay;
class SendAsButton; class SendAsButton;
class SpoilerAnimation; class SpoilerAnimation;
@ -320,7 +322,6 @@ private:
using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedPanel = ChatHelpers::TabbedPanel;
using TabbedSelector = ChatHelpers::TabbedSelector; using TabbedSelector = ChatHelpers::TabbedSelector;
using VoiceToSend = HistoryView::Controls::VoiceToSend; using VoiceToSend = HistoryView::Controls::VoiceToSend;
struct SendingFiles;
enum ScrollChangeType { enum ScrollChangeType {
ScrollChangeNone, ScrollChangeNone,
@ -359,6 +360,11 @@ private:
bool cornerButtonsUnreadMayBeShown() override; bool cornerButtonsUnreadMayBeShown() override;
bool cornerButtonsHas(HistoryView::CornerButtonType type) override; bool cornerButtonsHas(HistoryView::CornerButtonType type) override;
[[nodiscard]] bool checkSendPayment(
int messagesCount,
int starsApproved,
Fn<void(int)> withPaymentApproved);
void checkSuggestToGigagroup(); void checkSuggestToGigagroup();
void processReply(); void processReply();
void setReplyFieldsFromProcessing(); void setReplyFieldsFromProcessing();
@ -471,12 +477,8 @@ private:
bool showSendMessageError( bool showSendMessageError(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown, bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> resend = nullptr, Fn<void(int starsApproved)> withPaymentApproved = nullptr,
int starsApproved = 0); int starsApproved = 0);
bool checkSendPayment(
int messagesCount,
int starsApproved,
Fn<void(int starsApproved)> resend);
void sendingFilesConfirmed( void sendingFilesConfirmed(
Ui::PreparedList &&list, Ui::PreparedList &&list,
@ -484,7 +486,9 @@ private:
TextWithTags &&caption, TextWithTags &&caption,
Api::SendOptions options, Api::SendOptions options,
bool ctrlShiftEnter); 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 uploadFile(const QByteArray &fileContent, SendMediaType type);
void itemRemoved(not_null<const HistoryItem*> item); void itemRemoved(not_null<const HistoryItem*> item);
@ -769,7 +773,6 @@ private:
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete; std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
std::unique_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions; std::unique_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions;
object_ptr<Support::Autocomplete> _supportAutocomplete; object_ptr<Support::Autocomplete> _supportAutocomplete;
Fn<void()> _resendOnFullUpdated;
UserData *_inlineBot = nullptr; UserData *_inlineBot = nullptr;
QString _inlineBotUsername; QString _inlineBotUsername;
@ -874,6 +877,8 @@ private:
int _topDelta = 0; int _topDelta = 0;
SendPaymentHelper _sendPayment;
rpl::event_stream<> _cancelRequests; rpl::event_stream<> _cancelRequests;
}; };

View file

@ -1722,13 +1722,20 @@ void ComposeControls::updateFieldPlaceholder() {
} }
_field->setPlaceholder([&] { _field->setPlaceholder([&] {
const auto peer = _history ? _history->peer.get() : nullptr;
if (_fieldCustomPlaceholder) { if (_fieldCustomPlaceholder) {
return rpl::duplicate(_fieldCustomPlaceholder); return rpl::duplicate(_fieldCustomPlaceholder);
} else if (isEditingMessage()) { } else if (isEditingMessage()) {
return tr::lng_edit_message_text(); return tr::lng_edit_message_text();
} else if (!_history) { } else if (!peer) {
return tr::lng_message_ph(); 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()) { if (channel->isBroadcast()) {
return session().data().notifySettings().silentPosts(channel) return session().data().notifySettings().silentPosts(channel)
? tr::lng_broadcast_silent_ph() ? tr::lng_broadcast_silent_ph()
@ -3120,6 +3127,7 @@ void ComposeControls::initWebpageProcess() {
| Data::PeerUpdate::Flag::Notifications | Data::PeerUpdate::Flag::Notifications
| Data::PeerUpdate::Flag::MessagesTTL | Data::PeerUpdate::Flag::MessagesTTL
| Data::PeerUpdate::Flag::FullInfo | Data::PeerUpdate::Flag::FullInfo
| Data::PeerUpdate::Flag::StarsPerMessage
) | rpl::filter([peer = _history->peer](const Data::PeerUpdate &update) { ) | rpl::filter([peer = _history->peer](const Data::PeerUpdate &update) {
return (update.peer.get() == peer); return (update.peer.get() == peer);
}) | rpl::map([](const Data::PeerUpdate &update) { }) | rpl::map([](const Data::PeerUpdate &update) {
@ -3135,6 +3143,9 @@ void ComposeControls::initWebpageProcess() {
if (flags & Data::PeerUpdate::Flag::MessagesTTL) { if (flags & Data::PeerUpdate::Flag::MessagesTTL) {
updateMessagesTTLShown(); updateMessagesTTLShown();
} }
if (flags & Data::PeerUpdate::Flag::StarsPerMessage) {
updateFieldPlaceholder();
}
if (flags & Data::PeerUpdate::Flag::FullInfo) { if (flags & Data::PeerUpdate::Flag::FullInfo) {
if (updateBotCommandShown()) { if (updateBotCommandShown()) {
updateControlsVisibility(); updateControlsVisibility();

View file

@ -384,6 +384,9 @@ QString DateTooltipText(not_null<Element*> view) {
if (item->isScheduled() && item->isSilent()) { if (item->isScheduled() && item->isSilent()) {
dateText += '\n' + QChar(0xD83D) + QChar(0xDD15); 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; return dateText;
} }

View file

@ -476,22 +476,12 @@ void Message::initPaidInformation() {
lt_action, lt_action,
action(), action(),
Ui::Text::WithEntities) Ui::Text::WithEntities)
: history()->peer->isUser() : tr::lng_action_paid_message_got(
? tr::lng_action_paid_message_got(
tr::now, tr::now,
lt_count, lt_count,
info.stars, info.stars,
lt_name, lt_name,
Ui::Text::Link(item->from()->shortName(), 1), 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), Ui::Text::WithEntities),
}; };
if (!item->out()) { if (!item->out()) {

View file

@ -733,8 +733,8 @@ void RepliesWidget::setupComposeControls() {
}, lifetime()); }, lifetime());
_composeControls->sendVoiceRequests( _composeControls->sendVoiceRequests(
) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) { ) | rpl::start_with_next([=](const ComposeControls::VoiceToSend &data) {
sendVoice(std::move(data)); sendVoice(data);
}, lifetime()); }, lifetime());
_composeControls->sendCommandRequests( _composeControls->sendCommandRequests(
@ -1070,25 +1070,59 @@ void RepliesWidget::sendingFilesConfirmed(
std::move(list), std::move(list),
way, way,
_history->peer->slowmodeApplied()); _history->peer->slowmodeApplied());
const auto type = way.sendImagesAsPhotos() auto bundle = PrepareFilesBundle(
? SendMediaType::Photo std::move(groups),
: SendMediaType::File; 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); auto action = prepareSendAction(options);
action.clearDraft = false; action.clearDraft = false;
if ((groups.size() != 1 || !groups.front().sentWithCaption()) if (bundle->sendComment) {
&& !caption.text.isEmpty()) {
auto message = Api::MessageToSend(action); auto message = Api::MessageToSend(action);
message.textWithTags = base::take(caption); message.textWithTags = base::take(bundle->caption);
session().api().sendMessage(std::move(message)); session().api().sendMessage(std::move(message));
} }
for (auto &group : groups) { for (auto &group : bundle->groups) {
const auto album = (group.type != Ui::AlbumType::None) const auto album = (group.type != Ui::AlbumType::None)
? std::make_shared<SendingAlbum>() ? std::make_shared<SendingAlbum>()
: nullptr; : nullptr;
session().api().sendFiles( session().api().sendFiles(
std::move(group.list), std::move(group.list),
type, type,
base::take(caption), base::take(bundle->caption),
album, album,
action); action);
} }
@ -1227,7 +1261,20 @@ void RepliesWidget::send() {
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); auto action = prepareSendAction(data.options);
session().api().sendVoiceMessage( session().api().sendVoiceMessage(
data.bytes, data.bytes,
@ -1254,19 +1301,32 @@ void RepliesWidget::send(Api::SendOptions options) {
message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
message.webPage = _composeControls->webPageDraft(); message.webPage = _composeControls->webPageDraft();
const auto error = GetErrorForSending( auto request = SendingErrorRequest{
_history->peer, .topicRootId = _topic ? _topic->rootId() : MsgId(0),
{ .forward = &_composeControls->forwardItems(),
.topicRootId = _topic ? _topic->rootId() : MsgId(0), .text = &message.textWithTags,
.forward = &_composeControls->forwardItems(), .ignoreSlowmodeCountdown = (options.scheduled != 0),
.text = &message.textWithTags, };
.ignoreSlowmodeCountdown = (options.scheduled != 0), request.messagesCount = ComputeSendingMessagesCount(_history, request);
}); const auto error = GetErrorForSending(_history->peer, request);
if (error) { if (error) {
Data::ShowSendErrorToast(controller(), _history->peer, error); Data::ShowSendErrorToast(controller(), _history->peer, error);
return; 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)); session().api().sendMessage(std::move(message));
_composeControls->clear(); _composeControls->clear();
@ -1420,6 +1480,18 @@ bool RepliesWidget::sendExistingDocument(
|| ShowSendPremiumError(controller(), document)) { || ShowSendPremiumError(controller(), document)) {
return false; 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( Api::SendExistingDocument(
std::move(messageToSend), std::move(messageToSend),
@ -1448,6 +1520,19 @@ bool RepliesWidget::sendExistingPhoto(
return false; 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::SendExistingPhoto(
Api::MessageToSend(prepareSendAction(options)), Api::MessageToSend(prepareSendAction(options)),
photo); photo);
@ -1478,6 +1563,19 @@ void RepliesWidget::sendInlineResult(
not_null<UserData*> bot, not_null<UserData*> bot,
Api::SendOptions options, Api::SendOptions options,
std::optional<MsgId> localMessageId) { 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); auto action = prepareSendAction(options);
action.generateLocal = true; action.generateLocal = true;
session().api().sendInlineResult( session().api().sendInlineResult(

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/section_memento.h" #include "window/section_memento.h"
#include "history/view/history_view_corner_buttons.h" #include "history/view/history_view_corner_buttons.h"
#include "history/view/history_view_list_widget.h" #include "history/view/history_view_list_widget.h"
#include "history/history_item_helpers.h"
#include "history/history_view_swipe_data.h" #include "history/history_view_swipe_data.h"
#include "data/data_messages.h" #include "data/data_messages.h"
#include "base/timer.h" #include "base/timer.h"
@ -38,6 +39,7 @@ class PlainShadow;
class FlatButton; class FlatButton;
class PinnedBar; class PinnedBar;
struct PreparedList; struct PreparedList;
struct PreparedBundle;
class SendFilesWay; class SendFilesWay;
} // namespace Ui } // namespace Ui
@ -207,6 +209,11 @@ private:
void checkActivation() override; void checkActivation() override;
void doSetInnerFocus() override; void doSetInnerFocus() override;
[[nodiscard]] bool checkSendPayment(
int messagesCount,
int starsApproved,
Fn<void(int)> withPaymentApproved);
void onScroll(); void onScroll();
void updateInnerVisibleArea(); void updateInnerVisibleArea();
void updateControlsGeometry(); void updateControlsGeometry();
@ -251,7 +258,7 @@ private:
Api::SendOptions options) const; Api::SendOptions options) const;
void send(); void send();
void send(Api::SendOptions options); void send(Api::SendOptions options);
void sendVoice(Controls::VoiceToSend &&data); void sendVoice(const Controls::VoiceToSend &data);
void edit( void edit(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Api::SendOptions options, Api::SendOptions options,
@ -308,6 +315,9 @@ private:
TextWithTags &&caption, TextWithTags &&caption,
Api::SendOptions options, Api::SendOptions options,
bool ctrlShiftEnter); bool ctrlShiftEnter);
void sendingFilesConfirmed(
std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options);
bool sendExistingDocument( bool sendExistingDocument(
not_null<DocumentData*> document, not_null<DocumentData*> document,
@ -380,6 +390,8 @@ private:
HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal; HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal;
SendPaymentHelper _sendPayment;
int _lastScrollTop = 0; int _lastScrollTop = 0;
int _topicReopenBarHeight = 0; int _topicReopenBarHeight = 0;
int _scrollTopDelta = 0; int _scrollTopDelta = 0;

View file

@ -15,11 +15,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/premium_limits_box.h" #include "boxes/premium_limits_box.h"
#include "boxes/send_files_box.h" #include "boxes/send_files_box.h"
#include "boxes/share_box.h" // ShareBoxStyleOverrides
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "core/mime_type.h" #include "core/mime_type.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_changes.h"
#include "data/data_chat_participant_status.h" #include "data/data_chat_participant_status.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_message_reaction_id.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 "data/data_user.h"
#include "history/view/controls/compose_controls_common.h" #include "history/view/controls/compose_controls_common.h"
#include "history/view/controls/history_view_compose_controls.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_item_helpers.h"
#include "history/history.h" #include "history/history.h"
#include "inline_bots/inline_bot_result.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_controller.h"
#include "media/stories/media_stories_stealth.h" #include "media/stories/media_stories_stealth.h"
#include "menu/menu_send.h" #include "menu/menu_send.h"
#include "settings/settings_credits_graphics.h" // DarkCreditsEntryBoxStyle
#include "storage/localimageloader.h" #include "storage/localimageloader.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
#include "storage/storage_media_prepare.h" #include "storage/storage_media_prepare.h"
@ -52,14 +56,19 @@ namespace {
[[nodiscard]] rpl::producer<QString> PlaceholderText( [[nodiscard]] rpl::producer<QString> PlaceholderText(
const std::shared_ptr<ChatHelpers::Show> &show, const std::shared_ptr<ChatHelpers::Show> &show,
rpl::producer<bool> isComment) { rpl::producer<bool> isComment,
rpl::producer<int> starsPerMessage) {
return rpl::combine( return rpl::combine(
show->session().data().stories().stealthModeValue(), show->session().data().stories().stealthModeValue(),
std::move(isComment) std::move(isComment),
) | rpl::map([](Data::StealthMode value, bool isComment) { std::move(starsPerMessage)
return std::tuple(value.enabledTill, isComment); ) | rpl::map([](
Data::StealthMode value,
bool isComment,
int starsPerMessage) {
return std::tuple(value.enabledTill, isComment, starsPerMessage);
}) | rpl::distinct_until_changed( }) | rpl::distinct_until_changed(
) | rpl::map([](TimeId till, bool isComment) { ) | rpl::map([](TimeId till, bool isComment, int starsPerMessage) {
return rpl::single( return rpl::single(
rpl::empty rpl::empty
) | rpl::then( ) | rpl::then(
@ -71,7 +80,13 @@ namespace {
}) | rpl::then( }) | rpl::then(
rpl::single(0) rpl::single(0)
) | rpl::map([=](TimeId left) { ) | 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( ? tr::lng_stealth_mode_countdown(
lt_left, lt_left,
rpl::single(TimeLeftText(left))) rpl::single(TimeLeftText(left)))
@ -128,7 +143,8 @@ ReplyArea::ReplyArea(not_null<Controller*> controller)
.stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(),
.customPlaceholder = PlaceholderText( .customPlaceholder = PlaceholderText(
_controller->uiShow(), _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), .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now),
.voiceLockFromBottom = true, .voiceLockFromBottom = true,
.features = { .features = {
@ -199,7 +215,7 @@ bool ReplyArea::sendReaction(const Data::ReactionId &id) {
} }
} }
return !message.textWithTags.empty() return !message.textWithTags.empty()
&& send(std::move(message), {}, true); && send(std::move(message), true);
} }
void ReplyArea::send(Api::SendOptions options) { void ReplyArea::send(Api::SendOptions options) {
@ -209,29 +225,45 @@ void ReplyArea::send(Api::SendOptions options) {
message.textWithTags = _controls->getTextWithAppliedMarkdown(); message.textWithTags = _controls->getTextWithAppliedMarkdown();
message.webPage = webPageDraft; message.webPage = webPageDraft;
send(std::move(message), options); send(std::move(message));
} }
bool ReplyArea::send( bool ReplyArea::send(
Api::MessageToSend message, Api::MessageToSend message,
Api::SendOptions options,
bool skipToast) { bool skipToast) {
if (!options.scheduled && showSlowmodeError()) { if (!message.action.options.scheduled && showSlowmodeError()) {
return false; return false;
} }
const auto error = GetErrorForSending( auto request = SendingErrorRequest{
_data.peer, .topicRootId = MsgId(0),
{ .text = &message.textWithTags,
.topicRootId = MsgId(0), .ignoreSlowmodeCountdown = (message.action.options.scheduled != 0),
.text = &message.textWithTags, };
.ignoreSlowmodeCountdown = (options.scheduled != 0), request.messagesCount = ComputeSendingMessagesCount(
}); message.action.history,
request);
const auto error = GetErrorForSending(_data.peer, request);
if (error) { if (error) {
Data::ShowSendErrorToast(_controller->uiShow(), _data.peer, error); Data::ShowSendErrorToast(_controller->uiShow(), _data.peer, error);
return false; 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)); session().api().sendMessage(std::move(message));
finishSending(skipToast); finishSending(skipToast);
@ -239,7 +271,40 @@ bool ReplyArea::send(
return true; 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); auto action = prepareSendAction(data.options);
session().api().sendVoiceMessage( session().api().sendVoiceMessage(
data.bytes, data.bytes,
@ -269,6 +334,18 @@ bool ReplyArea::sendExistingDocument(
|| Window::ShowSendPremiumError(show, document)) { || Window::ShowSendPremiumError(show, document)) {
return false; 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); Api::SendExistingDocument(std::move(messageToSend), document, localId);
@ -296,6 +373,18 @@ bool ReplyArea::sendExistingPhoto(
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return false; 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::SendExistingPhoto(
Api::MessageToSend(prepareSendAction(options)), Api::MessageToSend(prepareSendAction(options)),
@ -322,6 +411,19 @@ void ReplyArea::sendInlineResult(
not_null<UserData*> bot, not_null<UserData*> bot,
Api::SendOptions options, Api::SendOptions options,
std::optional<MsgId> localMessageId) { 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); auto action = prepareSendAction(options);
action.generateLocal = true; action.generateLocal = true;
session().api().sendInlineResult( session().api().sendInlineResult(
@ -564,25 +666,47 @@ void ReplyArea::sendingFilesConfirmed(
std::move(list), std::move(list),
way, way,
_data.peer->slowmodeApplied()); _data.peer->slowmodeApplied());
const auto type = way.sendImagesAsPhotos() auto bundle = PrepareFilesBundle(
? SendMediaType::Photo std::move(groups),
: SendMediaType::File; 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); auto action = prepareSendAction(options);
action.clearDraft = false; action.clearDraft = false;
if ((groups.size() != 1 || !groups.front().sentWithCaption()) if (bundle->sendComment) {
&& !caption.text.isEmpty()) {
auto message = Api::MessageToSend(action); auto message = Api::MessageToSend(action);
message.textWithTags = base::take(caption); message.textWithTags = base::take(bundle->caption);
session().api().sendMessage(std::move(message)); session().api().sendMessage(std::move(message));
} }
for (auto &group : groups) { for (auto &group : bundle->groups) {
const auto album = (group.type != Ui::AlbumType::None) const auto album = (group.type != Ui::AlbumType::None)
? std::make_shared<SendingAlbum>() ? std::make_shared<SendingAlbum>()
: nullptr; : nullptr;
session().api().sendFiles( session().api().sendFiles(
std::move(group.list), std::move(group.list),
type, type,
base::take(caption), base::take(bundle->caption),
album, album,
action); action);
} }
@ -618,8 +742,8 @@ void ReplyArea::initActions() {
}, _lifetime); }, _lifetime);
_controls->sendVoiceRequests( _controls->sendVoiceRequests(
) | rpl::start_with_next([=](VoiceToSend &&data) { ) | rpl::start_with_next([=](const VoiceToSend &data) {
sendVoice(std::move(data)); sendVoice(data);
}, _lifetime); }, _lifetime);
_controls->attachRequests( _controls->attachRequests(
@ -697,6 +821,16 @@ void ReplyArea::show(
_controls->clear(); _controls->clear();
} }
return; 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); invalidate_weak_ptrs(&_shownPeerGuard);
const auto peer = data.peer; const auto peer = data.peer;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "history/history_item_helpers.h"
class History; class History;
enum class SendMediaType; enum class SendMediaType;
@ -44,6 +45,7 @@ struct Details;
namespace Ui { namespace Ui {
struct PreparedList; struct PreparedList;
struct PreparedBundle;
class SendFilesWay; class SendFilesWay;
class RpWidget; class RpWidget;
} // namespace Ui } // namespace Ui
@ -90,9 +92,13 @@ private:
bool send( bool send(
Api::MessageToSend message, Api::MessageToSend message,
Api::SendOptions options,
bool skipToast = false); bool skipToast = false);
[[nodiscard]] bool checkSendPayment(
int messagesCount,
int starsApproved,
Fn<void(int)> withPaymentApproved);
void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFile(const QByteArray &fileContent, SendMediaType type);
bool confirmSendingFiles( bool confirmSendingFiles(
QImage &&image, QImage &&image,
@ -116,6 +122,9 @@ private:
TextWithTags &&caption, TextWithTags &&caption,
Api::SendOptions options, Api::SendOptions options,
bool ctrlShiftEnter); bool ctrlShiftEnter);
void sendingFilesConfirmed(
std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options);
void finishSending(bool skipToast = false); void finishSending(bool skipToast = false);
bool sendExistingDocument( bool sendExistingDocument(
@ -141,7 +150,7 @@ private:
[[nodiscard]] Api::SendAction prepareSendAction( [[nodiscard]] Api::SendAction prepareSendAction(
Api::SendOptions options) const; Api::SendOptions options) const;
void send(Api::SendOptions options); void send(Api::SendOptions options);
void sendVoice(VoiceToSend &&data); void sendVoice(const VoiceToSend &data);
void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos); void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos);
[[nodiscard]] Fn<SendMenu::Details()> sendMenuDetails() const; [[nodiscard]] Fn<SendMenu::Details()> sendMenuDetails() const;
@ -151,6 +160,7 @@ private:
const not_null<Controller*> _controller; const not_null<Controller*> _controller;
rpl::variable<bool> _isComment; rpl::variable<bool> _isComment;
rpl::variable<int> _starsForMessage;
const std::unique_ptr<HistoryView::ComposeControls> _controls; const std::unique_ptr<HistoryView::ComposeControls> _controls;
std::unique_ptr<Cant> _cant; std::unique_ptr<Cant> _cant;
@ -160,6 +170,8 @@ private:
bool _chooseAttachRequest = false; bool _chooseAttachRequest = false;
rpl::variable<bool> _choosingAttach; rpl::variable<bool> _choosingAttach;
SendPaymentHelper _sendPayment;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };

View file

@ -266,6 +266,27 @@ bool PreparedList::hasSpoilerMenu(bool compress) const {
return allAreVideo || (allAreMedia && compress); 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() { int MaxAlbumItems() {
return kMaxAlbumCount; return kMaxAlbumCount;
} }

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "editor/photo_editor_common.h" #include "editor/photo_editor_common.h"
#include "ui/chat/attach/attach_send_files_way.h"
#include "ui/rect_part.h" #include "ui/rect_part.h"
#include <QtCore/QSemaphore> #include <QtCore/QSemaphore>
@ -153,6 +154,20 @@ struct PreparedGroup {
SendFilesWay way, SendFilesWay way,
bool slowmode); 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]] int MaxAlbumItems();
[[nodiscard]] bool ValidateThumbDimensions(int width, int height); [[nodiscard]] bool ValidateThumbDimensions(int width, int height);