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_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";

View file

@ -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,

View file

@ -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;

View file

@ -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() {

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/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;
};

View file

@ -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();

View file

@ -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;
}

View file

@ -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()) {

View file

@ -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(

View file

@ -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;

View file

@ -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;

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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);