Update API scheme, new paid.

This commit is contained in:
John Preston 2025-02-18 19:50:55 +04:00
parent 852ab19760
commit 960cf7a34b
22 changed files with 302 additions and 290 deletions

View file

@ -3650,6 +3650,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_send_anonymous_ph" = "Send anonymously...";
"lng_story_reply_ph" = "Reply privately...";
"lng_story_comment_ph" = "Comment story...";
"lng_message_paid_ph" = "Message for {amount}";
"lng_send_text_no" = "Text not allowed.";
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
"lng_send_text_type_and_last" = "{types} and {last}";
@ -4822,7 +4823,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_slowmode_seconds#one" = "{count} second";
"lng_slowmode_seconds#other" = "{count} seconds";
"lng_payment_for_message" = "Pay {cost} for 1 Message";
"lng_payment_confirm_title" = "Confirm payment";
"lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message.";
"lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message.";

View file

@ -25,6 +25,7 @@ struct SendOptions {
TimeId scheduled = 0;
BusinessShortcutId shortcutId = 0;
EffectId effectId = 0;
int starsApproved = 0;
bool silent = false;
bool handleSupportSwitch = false;
bool invertCaption = false;

View file

@ -37,7 +37,7 @@ Polls::Polls(not_null<ApiWrap*> api)
void Polls::create(
const PollData &data,
const SendAction &action,
SendAction action,
Fn<void()> done,
Fn<void()> fail) {
_session->api().sendAction(action);
@ -59,7 +59,9 @@ void Polls::create(
history->startSavingCloudDraft(topicRootId);
}
const auto silentPost = ShouldSendSilent(peer, action.options);
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
@ -73,6 +75,7 @@ void Polls::create(
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
const auto sendAs = action.options.sendAs;

View file

@ -27,7 +27,7 @@ public:
void create(
const PollData &data,
const SendAction &action,
SendAction action,
Fn<void()> done,
Fn<void()> fail);
void sendVotes(

View file

@ -95,8 +95,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name()
: QString();
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
@ -113,6 +114,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
@ -165,7 +167,7 @@ void SendExistingMedia(
? (*localMessageId)
: session->data().nextLocalMessageId());
const auto randomId = base::RandomValue<uint64>();
const auto &action = message.action;
auto &action = message.action;
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMedia::Flags(0);
@ -195,8 +197,9 @@ void SendExistingMedia(
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
}
const auto captionText = caption.text;
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
@ -213,6 +216,7 @@ void SendExistingMedia(
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
@ -351,7 +355,7 @@ bool SendDice(MessageToSend &message) {
message.action.generateLocal = true;
const auto &action = message.action;
auto &action = message.action;
api->sendAction(action);
const auto newId = FullMsgId(
@ -390,8 +394,11 @@ bool SendDice(MessageToSend &message) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}

View file

@ -3883,8 +3883,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars;
mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
}
@ -4020,7 +4023,7 @@ void ApiWrap::sendBotStart(
void ApiWrap::sendInlineResult(
not_null<UserData*> bot,
not_null<InlineBots::Result*> data,
const SendAction &action,
SendAction action,
std::optional<MsgId> localMessageId,
Fn<void(bool)> done) {
sendAction(action);
@ -4060,8 +4063,11 @@ void ApiWrap::sendInlineResult(
if (action.options.hideViaBot) {
sendFlags |= SendFlag::f_hide_via;
}
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
action.options.starsApproved);
if (starsPaid) {
action.options.starsApproved -= starsPaid;
sendFlags |= SendFlag::f_allow_paid_stars;
}
@ -4244,7 +4250,12 @@ void ApiWrap::sendMediaWithRandomId(
Api::ConvertOption::SkipLocal);
const auto updateRecentStickers = Api::HasAttachedStickers(media);
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0)
@ -4304,7 +4315,7 @@ void ApiWrap::sendMultiPaidMedia(
Expects(album->options.price > 0);
const auto groupId = album->groupId;
const auto &options = album->options;
auto &options = album->options;
const auto randomId = album->items.front().randomId;
auto medias = album->items | ranges::view::transform([](
const SendingAlbum::Item &part) {
@ -4322,7 +4333,12 @@ void ApiWrap::sendMultiPaidMedia(
_session,
caption.entities,
Api::ConvertOption::SkipLocal);
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
using Flag = MTPmessages_SendMedia::Flag;
const auto flags = Flag(0)
@ -4452,7 +4468,12 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
const auto history = sample->history();
const auto replyTo = sample->replyTo();
const auto sendAs = album->options.sendAs;
const auto starsPaid = history->peer->commitStarsForMessage();
const auto starsPaid = std::min(
history->peer->starsPerMessageChecked(),
album->options.starsApproved);
if (starsPaid) {
album->options.starsApproved -= starsPaid;
}
using Flag = MTPmessages_SendMultiMedia::Flag;
const auto flags = Flag(0)
| (replyTo ? Flag::f_reply_to : Flag(0))

View file

@ -368,7 +368,7 @@ public:
void sendInlineResult(
not_null<UserData*> bot,
not_null<InlineBots::Result*> data,
const SendAction &action,
SendAction action,
std::optional<MsgId> localMessageId,
Fn<void(bool)> done = nullptr);
void sendMessageFail(

View file

@ -1568,7 +1568,12 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
: topicRootId;
const auto peer = thread->peer();
const auto threadHistory = thread->owningHistory();
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
options.starsApproved -= starsPaid;
}
histories.sendRequest(threadHistory, requestType, [=](
Fn<void()> finish) {
const auto session = &threadHistory->session();

View file

@ -880,48 +880,6 @@ void ChannelData::setStarsPerMessage(int stars) {
}
}
int ChannelData::starsForMessageLocked() const {
if (const auto info = mgInfo.get()) {
return info->_starsForMessageLocked;
}
return 0;
}
void ChannelData::lockStarsForMessage() {
const auto info = mgInfo.get();
if (!info || info->_starsForMessageLocked == info->_starsPerMessage) {
return;
}
cancelStarsForMessage();
if (info->_starsPerMessage) {
info->_starsForMessageLocked = info->_starsPerMessage;
session().credits().lock(StarsAmount(info->_starsPerMessage));
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
}
}
int ChannelData::commitStarsForMessage() {
const auto info = mgInfo.get();
if (!info) {
return 0;
} else if (const auto stars = base::take(info->_starsForMessageLocked)) {
session().credits().withdrawLocked(StarsAmount(stars));
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
return stars;
}
return 0;
}
void ChannelData::cancelStarsForMessage() {
const auto info = mgInfo.get();
if (!info) {
return;
} else if (const auto stars = base::take(info->_starsForMessageLocked)) {
session().credits().unlock(StarsAmount(stars));
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
}
}
int ChannelData::peerGiftsCount() const {
return _peerGiftsCount;
}

View file

@ -153,7 +153,6 @@ private:
Data::ChatBotCommands _botCommands;
std::unique_ptr<Data::Forum> _forum;
int _starsPerMessage = 0;
int _starsForMessageLocked = 0;
friend class ChannelData;
@ -464,10 +463,6 @@ public:
void setStarsPerMessage(int stars);
[[nodiscard]] int starsPerMessage() const;
[[nodiscard]] int starsForMessageLocked() const;
void lockStarsForMessage();
[[nodiscard]] int commitStarsForMessage();
void cancelStarsForMessage();
[[nodiscard]] int peerGiftsCount() const;
void setPeerGiftsCount(int count);

View file

@ -17,10 +17,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "storage/storage_account.h"
#include "ui/boxes/confirm_box.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
#include "window/window_session_controller.h"
#include "styles/style_widgets.h"
namespace {
@ -425,4 +430,59 @@ void ShowSendErrorToast(
});
}
void ShowSendPaidConfirm(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
SendError error,
Fn<void()> confirmed) {
return ShowSendPaidConfirm(navigation->uiShow(), peer, error, confirmed);
}
void ShowSendPaidConfirm(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
Data::SendError error,
Fn<void()> confirmed) {
const auto session = &peer->session();
if (session->local().isPeerTrustedPayForMessage(peer->id)) {
confirmed();
return;
}
//const auto messages = error.paidMessages;
const auto stars = error.paidStars;
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
const auto trust = std::make_shared<QPointer<Ui::Checkbox>>();
const auto proceed = [=](Fn<void()> close) {
if ((*trust)->checked()) {
session->local().markPeerTrustedPayForMessage(peer->id);
}
confirmed();
close();
};
Ui::ConfirmBox(box, {
.text = tr::lng_payment_confirm_text(
tr::now,
lt_count,
stars,
lt_name,
Ui::Text::Bold(peer->shortName()),
Ui::Text::RichLangValue).append(' ').append(
tr::lng_payment_confirm_sure(
tr::now,
lt_count,
stars,
Ui::Text::RichLangValue)),
.confirmed = proceed,
.confirmText = tr::lng_payment_confirm_button(),
.title = tr::lng_payment_confirm_title(),
});
const auto skip = st::defaultCheckbox.margin.top();
*trust = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_payment_confirm_dont_ask(tr::now)),
st::boxRowPadding + QMargins(0, skip, 0, skip));
}));
}
} // namespace Data

View file

@ -189,17 +189,26 @@ struct SendError {
struct Args {
QString text;
int paidStars = 0;
int paidMessages = 0;
int boostsToLift = 0;
bool resolving = false;
bool premiumToLift = false;
};
SendError(Args &&args)
: text(std::move(args.text))
, paidStars(args.paidStars)
, paidMessages(args.paidMessages)
, boostsToLift(args.boostsToLift)
, resolving(args.resolving)
, premiumToLift(args.premiumToLift) {
}
QString text;
int paidStars = 0;
int paidMessages = 0;
int boostsToLift = 0;
bool resolving = false;
bool premiumToLift = false;
[[nodiscard]] SendError value_or(SendError other) const {
@ -244,4 +253,15 @@ void ShowSendErrorToast(
not_null<PeerData*> peer,
SendError error);
void ShowSendPaidConfirm(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
SendError error,
Fn<void()> confirmed);
void ShowSendPaidConfirm(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
SendError error,
Fn<void()> confirmed);
} // namespace Data

View file

@ -1459,38 +1459,13 @@ int PeerData::starsPerMessage() const {
return 0;
}
int PeerData::starsForMessageLocked() const {
if (const auto user = asUser()) {
return user->starsForMessageLocked();
} else if (const auto channel = asChannel()) {
return channel->starsForMessageLocked();
}
return 0;
}
void PeerData::lockStarsForMessage() {
if (const auto user = asUser()) {
user->lockStarsForMessage();
} else if (const auto channel = asChannel()) {
channel->lockStarsForMessage();
}
}
int PeerData::commitStarsForMessage() {
if (const auto user = asUser()) {
return user->commitStarsForMessage();
} else if (const auto channel = asChannel()) {
return channel->commitStarsForMessage();
}
return 0;
}
void PeerData::cancelStarsForMessage() {
if (const auto user = asUser()) {
user->cancelStarsForMessage();
} else if (const auto channel = asChannel()) {
channel->cancelStarsForMessage();
int PeerData::starsPerMessageChecked() const {
if (const auto channel = asChannel()) {
return (channel->adminRights() || channel->amCreator())
? 0
: channel->starsPerMessage();
}
return starsPerMessage();
}
Data::GroupCall *PeerData::groupCall() const {

View file

@ -269,10 +269,7 @@ public:
[[nodiscard]] bool canManageGroupCall() const;
[[nodiscard]] int starsPerMessage() const;
[[nodiscard]] int starsForMessageLocked() const;
void lockStarsForMessage();
[[nodiscard]] int commitStarsForMessage();
void cancelStarsForMessage();
[[nodiscard]] int starsPerMessageChecked() const;
[[nodiscard]] UserData *asBot();
[[nodiscard]] const UserData *asBot() const;

View file

@ -552,38 +552,6 @@ void UserData::setStarsPerMessage(int stars) {
}
}
int UserData::starsForMessageLocked() const {
return _starsForMessageLocked;
}
void UserData::lockStarsForMessage() {
if (_starsPerMessage == _starsForMessageLocked) {
return;
}
cancelStarsForMessage();
if (_starsPerMessage) {
_starsForMessageLocked = _starsPerMessage;
session().credits().lock(StarsAmount(_starsPerMessage));
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
}
}
int UserData::commitStarsForMessage() {
if (const auto stars = base::take(_starsForMessageLocked)) {
session().credits().withdrawLocked(StarsAmount(stars));
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
return stars;
}
return 0;
}
void UserData::cancelStarsForMessage() {
if (const auto stars = base::take(_starsForMessageLocked)) {
session().credits().unlock(StarsAmount(stars));
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
}
}
bool UserData::canAddContact() const {
return canShareThisContact() && !isContact();
}

View file

@ -184,10 +184,6 @@ public:
void setStarsPerMessage(int stars);
[[nodiscard]] int starsPerMessage() const;
[[nodiscard]] int starsForMessageLocked() const;
void lockStarsForMessage();
[[nodiscard]] int commitStarsForMessage();
void cancelStarsForMessage();
[[nodiscard]] bool canShareThisContact() const;
[[nodiscard]] bool canAddContact() const;
@ -279,7 +275,6 @@ private:
int _commonChatsCount = 0;
int _peerGiftsCount = 0;
int _starsPerMessage = 0;
int _starsForMessageLocked = 0;
ContactStatus _contactStatus = ContactStatus::Unknown;
CallsStatus _callsStatus = CallsStatus::Unknown;

View file

@ -93,10 +93,33 @@ Data::SendError GetErrorForSending(
return tr::lng_forward_cant(tr::now);
}
}
if (peer->slowmodeApplied()) {
const auto count = (hasText ? 1 : 0)
const auto countMessages = [&] {
auto result = 0;
if (hasText) {
auto sending = TextWithEntities();
auto left = TextWithEntities{
request.text->text,
TextUtilities::ConvertTextTagsToEntities(request.text->tags)
};
auto prepareFlags = Ui::ItemTextOptions(
thread->owningHistory(),
peer->session().user()).flags;
TextUtilities::PrepareForSending(left, prepareFlags);
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
++result;
}
if (!result) {
++result;
}
}
return result
+ (request.story ? 1 : 0)
+ (request.mediaMessage ? 1 : 0)
+ (request.forward ? int(request.forward->size()) : 0);
};
if (peer->slowmodeApplied()) {
const auto count = countMessages();
if (const auto history = peer->owner().historyLoaded(peer)) {
if (!request.ignoreSlowmodeCountdown
&& (history->latestSendingMessage() != nullptr)
@ -135,6 +158,28 @@ Data::SendError GetErrorForSending(
}
}
if (const auto user = peer->asUser()) {
if (user->hasStarsPerMessage()
&& !user->messageMoneyRestrictionsKnown()) {
user->updateFull();
return Data::SendError({ .resolving = true });
}
} else if (const auto channel = peer->asChannel()) {
if (!channel->isFullLoaded()) {
channel->updateFull();
return Data::SendError({ .resolving = true });
}
}
if (!peer->session().credits().loaded()) {
peer->session().credits().load();
return Data::SendError({ .resolving = true });
} else if (const auto perMessage = peer->starsPerMessageChecked()) {
const auto count = countMessages();
return Data::SendError({
.paidStars = count * perMessage,
.paidMessages = count,
});
}
return {};
}

View file

@ -115,6 +115,7 @@ struct SendingErrorRequest {
const Data::Story *story = nullptr;
const TextWithTags *text = nullptr;
bool ignoreSlowmodeCountdown = false;
bool mediaMessage = false;
};
[[nodiscard]] Data::SendError GetErrorForSending(
not_null<PeerData*> peer,

View file

@ -266,7 +266,6 @@ HistoryWidget::HistoryWidget(
tr::lng_channel_mute(tr::now).toUpper(),
st::historyComposeButton)
, _reportMessages(this, QString(), st::historyComposeButton)
, _payForMessage(this, QString(), st::historyComposeButton)
, _attachToggle(this, st::historyAttach)
, _tabbedSelectorToggle(this, st::historyAttachEmoji)
, _botKeyboardShow(this, st::historyBotKeyboardShow)
@ -384,7 +383,6 @@ HistoryWidget::HistoryWidget(
_muteUnmute->addClickHandler([=] { toggleMuteUnmute(); });
setupGiftToChannelButton();
_reportMessages->addClickHandler([=] { reportSelectedMessages(); });
_payForMessage->addClickHandler([=] { payForMessage(); });
_field->submits(
) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) {
sendWithModifiers(modifiers);
@ -525,7 +523,6 @@ HistoryWidget::HistoryWidget(
_joinChannel->hide();
_muteUnmute->hide();
_reportMessages->hide();
_payForMessage->hide();
initVoiceRecordBar();
@ -816,7 +813,7 @@ HistoryWidget::HistoryWidget(
const auto was = (_sendAs != nullptr);
refreshSendAsToggle();
if (was != (_sendAs != nullptr) || _peer->starsPerMessage()) {
if (was != (_sendAs != nullptr)) {
updateControlsVisibility();
updateControlsGeometry();
orderWidgets();
@ -839,8 +836,10 @@ HistoryWidget::HistoryWidget(
return;
}
}
if ((flags & PeerUpdateFlag::BotStartToken)
|| (flags & PeerUpdateFlag::StarsPerMessage)) {
if (flags & PeerUpdateFlag::StarsPerMessage) {
updateFieldPlaceholder();
}
if (flags & PeerUpdateFlag::BotStartToken) {
updateControlsVisibility();
updateControlsGeometry();
}
@ -878,7 +877,9 @@ HistoryWidget::HistoryWidget(
}
if (flags & PeerUpdateFlag::FullInfo) {
fullInfoUpdated();
if (const auto channel = _peer->asChannel()) {
if (_peer->starsPerMessageChecked()) {
session().credits().load();
} else if (const auto channel = _peer->asChannel()) {
if (channel->allowedReactions().paidEnabled) {
session().credits().load();
}
@ -886,6 +887,15 @@ HistoryWidget::HistoryWidget(
}
}, lifetime());
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),
@ -1100,19 +1110,8 @@ void HistoryWidget::initVoiceRecordBar() {
}, lifetime());
_voiceRecordBar->sendVoiceRequests(
) | rpl::start_with_next([=](const auto &data) {
if (!canWriteMessage() || data.bytes.isEmpty() || !_history) {
return;
}
auto action = prepareSendAction(data.options);
session().api().sendVoiceMessage(
data.bytes,
data.waveform,
data.duration,
data.video,
action);
_voiceRecordBar->clearListenState();
) | rpl::start_with_next([=](const VoiceToSend &data) {
sendVoice(data);
}, lifetime());
_voiceRecordBar->cancelRequests(
@ -1965,7 +1964,6 @@ void HistoryWidget::setInnerFocus() {
|| isRecording()
|| isJoinChannel()
|| isBotStart()
|| isPayForMessage()
|| isBlocked()
|| (!_canSendTexts && !_editMsgId)) {
if (_scroll->isHidden()) {
@ -2388,10 +2386,8 @@ void HistoryWidget::showHistory(
setHistory(nullptr);
_list = nullptr;
if (_peer) {
_peer->cancelStarsForMessage();
}
_peer = nullptr;
_resendOnFullUpdated = nullptr;
_topicsRequested.clear();
_canSendMessages = false;
_canSendTexts = false;
@ -3035,7 +3031,6 @@ bool HistoryWidget::canWriteMessage() const {
&& !isJoinChannel()
&& !isMuteUnmute()
&& !isBotStart()
&& !isPayForMessage()
&& !isSearching();
}
@ -3096,7 +3091,6 @@ void HistoryWidget::updateControlsVisibility() {
|| isJoinChannel()
|| isMuteUnmute()
|| isBotStart()
|| isPayForMessage()
|| isReportMessages()))) {
const auto toggle = [&](Ui::FlatButton *shown) {
const auto toggleOne = [&](not_null<Ui::FlatButton*> button) {
@ -3108,7 +3102,6 @@ void HistoryWidget::updateControlsVisibility() {
}
};
toggleOne(_reportMessages);
toggleOne(_payForMessage);
toggleOne(_joinChannel);
toggleOne(_muteUnmute);
toggleOne(_botStart);
@ -3122,8 +3115,6 @@ void HistoryWidget::updateControlsVisibility() {
toggle(_reportMessages);
} else if (isBlocked()) {
toggle(_unblock);
} else if (isPayForMessage()) {
toggle(_payForMessage);
} else if (isJoinChannel()) {
toggle(_joinChannel);
} else if (isMuteUnmute()) {
@ -3186,7 +3177,6 @@ void HistoryWidget::updateControlsVisibility() {
_joinChannel->hide();
_muteUnmute->hide();
_reportMessages->hide();
_payForMessage->hide();
_send->show();
updateSendButtonType();
@ -3300,7 +3290,6 @@ void HistoryWidget::updateControlsVisibility() {
_joinChannel->hide();
_muteUnmute->hide();
_reportMessages->hide();
_payForMessage->hide();
_attachToggle->hide();
if (_silent) {
_silent->hide();
@ -4352,6 +4341,36 @@ Api::SendAction HistoryWidget::prepareSendAction(
return result;
}
void HistoryWidget::sendVoice(const VoiceToSend &data) {
if (!canWriteMessage() || data.bytes.isEmpty() || !_history) {
return;
}
const auto withPaymentApproved = [=](int approved) {
auto copy = data;
copy.options.starsApproved = approved;
sendVoice(copy);
};
const auto ignoreSlowmodeCountdown = data.options.scheduled != 0;
if (showSendMessageError(
{},
ignoreSlowmodeCountdown,
crl::guard(this, withPaymentApproved),
data.options.starsApproved,
true)) {
return;
}
auto action = prepareSendAction(data.options);
session().api().sendVoiceMessage(
data.bytes,
data.waveform,
data.duration,
data.video,
action);
_voiceRecordBar->clearListenState();
}
void HistoryWidget::send(Api::SendOptions options) {
if (!_history) {
return;
@ -4360,9 +4379,7 @@ void HistoryWidget::send(Api::SendOptions options) {
return;
} else if (!options.scheduled && showSlowmodeError()) {
return;
}
if (_voiceRecordBar->isListenState()) {
} else if (_voiceRecordBar->isListenState()) {
_voiceRecordBar->requestToSendWithOptions(options);
return;
}
@ -4376,9 +4393,16 @@ void HistoryWidget::send(Api::SendOptions options) {
message.webPage = _preview->draft();
const auto ignoreSlowmodeCountdown = (options.scheduled != 0);
const auto withPaymentApproved = [=](int approved) {
auto copy = options;
copy.starsApproved = approved;
send(copy);
};
if (showSendMessageError(
message.textWithTags,
ignoreSlowmodeCountdown)) {
ignoreSlowmodeCountdown,
crl::guard(this, withPaymentApproved),
options.starsApproved)) {
return;
}
@ -4541,53 +4565,7 @@ void HistoryWidget::reportSelectedMessages() {
}
}
void HistoryWidget::payForMessage() {
if (!_peer || !session().credits().loaded()) {
return;
} else if (!_peer->starsPerMessage() || _peer->starsForMessageLocked()) {
updateControlsVisibility();
} else if (session().local().isPeerTrustedPayForMessage(_peer->id)) {
payForMessageSure();
} else {
const auto peer = _peer;
const auto count = peer->starsPerMessage();
controller()->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto trust = std::make_shared<QPointer<Ui::Checkbox>>();
const auto confirmed = [=](Fn<void()> close) {
payForMessageSure((*trust)->checked());
close();
};
Ui::ConfirmBox(box, {
.text = tr::lng_payment_confirm_text(
tr::now,
lt_count,
count,
lt_name,
Ui::Text::Bold(peer->shortName()),
Ui::Text::RichLangValue).append(' ').append(
tr::lng_payment_confirm_sure(
tr::now,
lt_count,
count,
Ui::Text::RichLangValue)),
.confirmed = confirmed,
.confirmText = tr::lng_payment_confirm_button(),
.title = tr::lng_payment_confirm_title(),
});
const auto skip = st::defaultCheckbox.margin.top();
*trust = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_payment_confirm_dont_ask(tr::now)),
st::boxRowPadding + QMargins(0, skip, 0, skip));
}));
}
}
void HistoryWidget::payForMessageSure(bool trust) {
if (trust) {
session().local().markPeerTrustedPayForMessage(_peer->id);
}
const auto required = _peer->starsPerMessage();
if (!required) {
return;
@ -4595,7 +4573,6 @@ void HistoryWidget::payForMessageSure(bool trust) {
const auto done = [=](Settings::SmallBalanceResult result) {
if (result == Settings::SmallBalanceResult::Success
|| result == Settings::SmallBalanceResult::Already) {
_peer->lockStarsForMessage();
if (canWriteMessage()) {
setInnerFocus();
}
@ -5154,40 +5131,6 @@ bool HistoryWidget::isSearching() const {
return _composeSearch != nullptr;
}
bool HistoryWidget::isPayForMessage() const {
const auto stars = _peer ? _peer->starsPerMessage() : 0;
const auto locked = _peer ? _peer->starsForMessageLocked() : 0;
if (!stars || locked) {
return false;
} else if (const auto channel = _peer->asChannel()) {
if (channel->amCreator() || channel->adminRights()) {
return false;
}
}
const auto creating = !_payForMessageStars.current();
_payForMessageStars = stars;
if (creating) {
session().credits().load();
auto text = _payForMessageStars.value(
) | rpl::map([session = &session()](int stars) {
return tr::lng_payment_for_message(
tr::now,
lt_cost,
Ui::CreditsEmojiSmall(session).append(
Lang::FormatCountDecimal(stars)),
Ui::Text::WithEntities);
});
Ui::SetButtonMarkedLabel(
_payForMessage,
std::move(text),
&session(),
st::historyComposeButtonText);
}
return true;
}
bool HistoryWidget::showRecordButton() const {
return (_recordAvailability != Webrtc::RecordAvailability::None)
&& !_voiceRecordBar->isListenState()
@ -5245,7 +5188,6 @@ bool HistoryWidget::updateCmdStartShown() {
&& _peer->asChannel()->mgInfo->botStatus > 0))) {
if (!isBotStart()
&& !isBlocked()
&& !isPayForMessage()
&& !_keyboard->hasMarkup()
&& !_keyboard->forceReply()
&& !_editMsgId) {
@ -5659,7 +5601,7 @@ void HistoryWidget::moveFieldControls() {
// (_botMenu.button) (_attachToggle|_replaceMedia) (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel
// (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send
// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages|_payForMessage)
// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages)
auto buttonsBottom = bottom - _attachToggle->height();
auto left = st::historySendRight;
@ -5733,7 +5675,6 @@ void HistoryWidget::moveFieldControls() {
_joinChannel->setGeometry(fullWidthButtonRect);
_muteUnmute->setGeometry(fullWidthButtonRect);
_reportMessages->setGeometry(fullWidthButtonRect);
_payForMessage->setGeometry(fullWidthButtonRect);
if (_sendRestriction) {
_sendRestriction->setGeometry(fullWidthButtonRect);
}
@ -5824,14 +5765,21 @@ void HistoryWidget::updateFieldPlaceholder() {
}
_field->setPlaceholder([&]() -> rpl::producer<QString> {
const auto peer = _history ? _history->peer.get() : nullptr;
if (_editMsgId) {
return tr::lng_edit_message_text();
} else if (!_history) {
} else if (!peer) {
return tr::lng_message_ph();
} else if ((_kbShown || _keyboard->forceReply())
&& !_keyboard->placeholder().isEmpty()) {
return rpl::single(_keyboard->placeholder());
} 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()) {
const auto topic = resolveReplyToTopic();
const auto topicRootId = topic
? topic->rootId()
@ -5948,7 +5896,10 @@ Data::ForumTopic *HistoryWidget::resolveReplyToTopic() {
bool HistoryWidget::showSendMessageError(
const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown) {
bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> resend,
int starsApproved,
bool mediaMessage) {
if (!_canSendMessages) {
return false;
}
@ -5960,8 +5911,17 @@ bool HistoryWidget::showSendMessageError(
.forward = &_forwardPanel->items(),
.text = &textWithTags,
.ignoreSlowmodeCountdown = ignoreSlowmodeCountdown,
.mediaMessage = mediaMessage,
});
if (!error) {
if (resend && error.resolving) {
_resendOnFullUpdated = [=] { resend(starsApproved); };
return true;
} else if (resend && error.paidStars > starsApproved) {
Data::ShowSendPaidConfirm(controller(), _peer, error, [=] {
resend(error.paidStars);
});
return true;
} else if (!error) {
return false;
}
Data::ShowSendErrorToast(controller(), _peer, error);
@ -6174,25 +6134,18 @@ void HistoryWidget::handleHistoryChange(not_null<const History*> history) {
const auto joinChannel = isJoinChannel();
const auto muteUnmute = isMuteUnmute();
const auto reportMessages = isReportMessages();
const auto payForMessage = isPayForMessage();
const auto update = false
|| (_reportMessages->isHidden() == reportMessages)
|| (!reportMessages && _unblock->isHidden() == unblock)
|| (!reportMessages
&& !unblock
&& _payForMessage->isHidden() == payForMessage)
|| (!reportMessages
&& !unblock
&& !payForMessage
&& _botStart->isHidden() == botStart)
|| (!reportMessages
&& !unblock
&& !payForMessage
&& !botStart
&& _joinChannel->isHidden() == joinChannel)
|| (!reportMessages
&& !unblock
&& !payForMessage
&& !botStart
&& !joinChannel
&& _muteUnmute->isHidden() == muteUnmute);
@ -6556,7 +6509,6 @@ void HistoryWidget::updateHistoryGeometry(
} else if (!editingMessage()
&& (isSearching()
|| isBlocked()
|| isPayForMessage()
|| isBotStart()
|| isJoinChannel()
|| isMuteUnmute()
@ -6866,7 +6818,6 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
if (!isSearching()
&& !isBotStart()
&& !isBlocked()
&& !isPayForMessage()
&& _canSendMessages
&& (wasVisible
|| (_replyTo && _replyEditMsg)
@ -8528,6 +8479,9 @@ void HistoryWidget::fullInfoUpdated() {
updateControlsVisibility();
updateControlsGeometry();
}
if (const auto callback = base::take(_resendOnFullUpdated)) {
callback();
}
}
void HistoryWidget::handlePeerUpdate() {
@ -8743,7 +8697,6 @@ void HistoryWidget::updateTopBarSelection() {
|| (_list && _list->wasSelectedText())
|| isRecording()
|| isBotStart()
|| isPayForMessage()
|| isBlocked()
|| (!_canSendTexts && !_editMsgId)) {
_list->setFocus();

View file

@ -115,6 +115,7 @@ class TTLButton;
class WebpageProcessor;
class CharactersLimitLabel;
class PhotoEditSpoilerManager;
struct VoiceToSend;
} // namespace HistoryView::Controls
class BotKeyboard;
@ -318,6 +319,7 @@ protected:
private:
using TabbedPanel = ChatHelpers::TabbedPanel;
using TabbedSelector = ChatHelpers::TabbedSelector;
using VoiceToSend = HistoryView::Controls::VoiceToSend;
enum ScrollChangeType {
ScrollChangeNone,
@ -401,6 +403,7 @@ private:
[[nodiscard]] Api::SendAction prepareSendAction(
Api::SendOptions options) const;
void sendVoice(const VoiceToSend &data);
void send(Api::SendOptions options);
void sendWithModifiers(Qt::KeyboardModifiers modifiers);
void sendScheduled(Api::SendOptions initialOptions);
@ -422,7 +425,6 @@ private:
[[nodiscard]] int computeMaxFieldHeight() const;
void toggleMuteUnmute();
void reportSelectedMessages();
void payForMessage();
void payForMessageSure(bool trust = false);
void showKeyboardHideButton();
void toggleKeyboard(bool manual = true);
@ -468,7 +470,10 @@ private:
std::optional<bool> compress) const;
bool showSendMessageError(
const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown);
bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> resend = nullptr,
int starsApproved = 0,
bool mediaMessage = false);
void sendingFilesConfirmed(
Ui::PreparedList &&list,
@ -642,7 +647,6 @@ private:
[[nodiscard]] bool isJoinChannel() const;
[[nodiscard]] bool isMuteUnmute() const;
[[nodiscard]] bool isReportMessages() const;
[[nodiscard]] bool isPayForMessage() const;
bool updateCmdStartShown();
void updateSendButtonType();
[[nodiscard]] bool showRecordButton() const;
@ -761,6 +765,7 @@ 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;
@ -781,8 +786,6 @@ private:
QPointer<Ui::IconButton> _giftToChannelIn;
QPointer<Ui::IconButton> _giftToChannelOut;
object_ptr<Ui::FlatButton> _reportMessages;
object_ptr<Ui::FlatButton> _payForMessage;
mutable rpl::variable<int> _payForMessageStars;
struct {
object_ptr<Ui::RoundButton> button = { nullptr };
QString text;

View file

@ -111,29 +111,33 @@ namespace Media::Stories {
const auto threadPeer = thread->peer();
const auto threadHistory = thread->owningHistory();
const auto randomId = base::RandomValue<uint64>();
auto sendFlags = MTPmessages_SendMedia::Flags(0);
using SendFlag = MTPmessages_SendMedia::Flag;
auto sendFlags = SendFlag(0) | SendFlag(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
sendFlags |= SendFlag::f_reply_to;
}
const auto silentPost = ShouldSendSilent(threadPeer, options);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
sendFlags |= SendFlag::f_silent;
}
if (options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
sendFlags |= SendFlag::f_schedule_date;
}
if (options.shortcutId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
sendFlags |= SendFlag::f_quick_reply_shortcut;
}
if (options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
sendFlags |= SendFlag::f_effect;
}
if (options.invertCaption) {
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
sendFlags |= SendFlag::f_invert_media;
}
const auto starsPaid = peer->commitStarsForMessage();
const auto starsPaid = std::min(
peer->starsPerMessageChecked(),
options.starsApproved);
if (starsPaid) {
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
options.starsApproved -= starsPaid;
sendFlags |= SendFlag::f_allow_paid_stars;
}
const auto done = [=] {
if (!--state->requests) {

View file

@ -220,7 +220,7 @@ inputPeerNotifySettings#cacb6ae2 flags:# show_previews:flags.0?Bool silent:flags
peerNotifySettings#99622c0c flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool stories_ios_sound:flags.8?NotificationSound stories_android_sound:flags.9?NotificationSound stories_other_sound:flags.10?NotificationSound = PeerNotifySettings;
peerSettings#a8639d72 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string charge_paid_message_stars:flags.14?long = PeerSettings;
peerSettings#d8c39ec flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string charge_paid_message_stars:flags.14?long registration_month:flags.15?string phone_country:flags.16?string location_country:flags.17?string = PeerSettings;
wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
@ -1480,6 +1480,7 @@ inputInvoiceChatInviteSubscription#34e793f1 hash:string = InputInvoice;
inputInvoiceStarGift#e8625e92 flags:# hide_name:flags.0?true include_upgrade:flags.2?true peer:InputPeer gift_id:long message:flags.1?TextWithEntities = InputInvoice;
inputInvoiceStarGiftUpgrade#4d818d5d flags:# keep_original_details:flags.0?true stargift:InputSavedStarGift = InputInvoice;
inputInvoiceStarGiftTransfer#4a5f5bd9 stargift:InputSavedStarGift to_id:InputPeer = InputInvoice;
inputInvoicePremiumGiftStars#dabab2ef flags:# user_id:InputUser months:int message:flags.0?TextWithEntities = InputInvoice;
payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice;
@ -1495,7 +1496,7 @@ inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = In
inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose;
inputStorePaymentStarsGiveaway#751f08fa flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true stars:long boost_peer:InputPeer additional_peers:flags.1?Vector<InputPeer> countries_iso2:flags.2?Vector<string> prize_description:flags.4?string random_id:long until_date:int currency:string amount:long users:int = InputStorePaymentPurpose;
premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption;
premiumGiftOption#79c059f7 flags:# months:int currency:string amount:long bot_url:flags.1?string store_product:flags.0?string = PremiumGiftOption;
paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod;