Implement suggestion of changes.

This commit is contained in:
John Preston 2025-06-26 10:42:01 +04:00
parent 881aed50ea
commit 1ecd7aa7cf
15 changed files with 134 additions and 82 deletions

Binary file not shown.

View file

@ -4423,9 +4423,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_suggest_options_title" = "Suggest a Message";
"lng_suggest_options_change" = "Suggest Changes";
"lng_suggest_options_stars_offer" = "Offer Stars";
"lng_suggest_options_stars_request" = "Request Stars";
"lng_suggest_options_stars_price" = "Enter Price in Stars";
"lng_suggest_options_stars_price_about" = "Choose how many Stars to pay to publish this message.";
"lng_suggest_options_ton_offer" = "Offer TON";
"lng_suggest_options_ton_request" = "Request TON";
"lng_suggest_options_ton_price" = "Enter Price in TON";
"lng_suggest_options_ton_price_about" = "Choose how many TON to pay to publish this message.";
"lng_suggest_options_date" = "Time";
@ -4447,10 +4449,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_suggest_action_agree_date" = "The post will be automatically published on {channel} {date}.";
"lng_suggest_action_your_charged" = "You have been charged {amount}.";
"lng_suggest_action_his_charged" = "{from} have been charged {amount}.";
"lng_suggest_action_agree_receive" = "{channel} will receive the Stars once the post has been live for 24 hours.";
"lng_suggest_action_agree_removed" = "If {channel} removes the post before it has been live for 24 hours, the Stars will be refunded.";
"lng_suggest_action_your_not_enough" = "**Transaction failed** because you didn't have enough Stars.";
"lng_suggest_action_his_not_enough" = "**Transaction failed** because the user didn't have enough Stars.";
"lng_suggest_action_agree_receive_stars" = "{channel} will receive the Stars once the post has been live for 24 hours.";
"lng_suggest_action_agree_receive_ton" = "{channel} will receive TON once the post has been live for 24 hours.";
"lng_suggest_action_agree_removed_stars" = "If {channel} removes the post before it has been live for 24 hours, the Stars will be refunded.";
"lng_suggest_action_agree_removed_ton" = "If {channel} removes the post before it has been live for 24 hours, TON will be refunded.";
"lng_suggest_action_your_not_enough_stars" = "**Transaction failed** because you didn't have enough Stars.";
"lng_suggest_action_your_not_enough_ton" = "**Transaction failed** because you didn't have enough TON.";
"lng_suggest_action_his_not_enough_stars" = "**Transaction failed** because the user didn't have enough Stars.";
"lng_suggest_action_his_not_enough_ton" = "**Transaction failed** because the user didn't have enough TON.";
"lng_suggest_action_declined" = "{from} rejected the message.";
"lng_suggest_action_declined_reason" = "{from} rejected the message with the comment.";
"lng_suggest_change_price" = "{from} suggests a new price for the message.";

View file

@ -26,6 +26,7 @@
<file alias="hours.tgs">../../animations/hours.tgs</file>
<file alias="phone.tgs">../../animations/phone.tgs</file>
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
<file alias="diamond.tgs">../../animations/diamond.tgs</file>
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
<file alias="search.tgs">../../animations/search.tgs</file>

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/components/scheduled_messages.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_todo_list.h"
#include "data/data_web_page.h"
@ -62,76 +63,39 @@ mtpRequestId SuggestMessage(
const auto session = &item->history()->session();
const auto api = &session->api();
const auto text = textWithEntities.text;
const auto sentEntities = EntitiesToMTP(
session,
textWithEntities.entities,
ConvertOption::SkipLocal);
const auto emptyFlag = MTPmessages_SendMessage::Flag(0);
auto replyTo = FullReplyTo{
const auto thread = item->history()->amMonoforumAdmin()
? item->savedSublist()
: (Data::Thread*)item->history();
auto action = SendAction(thread, options);
action.replyTo = FullReplyTo{
.messageId = item->fullId(),
.monoforumPeerId = (item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId()),
};
const auto flags = emptyFlag
| MTPmessages_SendMessage::Flag::f_reply_to
| MTPmessages_SendMessage::Flag::f_suggested_post
| (webpage.removed
? MTPmessages_SendMessage::Flag::f_no_webpage
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_SendMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_SendMessage::Flag::f_entities
: emptyFlag)
| (options.starsApproved
? MTPmessages_SendMessage::Flag::f_allow_paid_stars
: emptyFlag);
const auto randomId = base::RandomValue<uint64>();
return api->request(MTPmessages_SendMessage(
MTP_flags(flags),
item->history()->peer->input,
ReplyToForMTP(item->history(), replyTo),
MTP_string(text),
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTPint(), // schedule_date
MTPInputPeer(), // send_as
MTPInputQuickReplyShortcut(), // quick_reply_shortcut
MTPlong(), // effect
MTP_long(options.starsApproved),
Api::SuggestToMTP(options.suggest)
)).done([=](
const MTPUpdates &result,
[[maybe_unused]] mtpRequestId requestId) {
const auto apply = [=] { api->applyUpdates(result); };
if constexpr (WithId<DoneCallback>) {
done(apply, requestId);
} else if constexpr (WithoutId<DoneCallback>) {
done(apply);
} else if constexpr (WithoutCallback<DoneCallback>) {
done();
apply();
} else {
t_bad_callback(done);
}
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
auto message = MessageToSend(std::move(action));
message.textWithTags = TextWithTags{
textWithEntities.text,
TextUtilities::ConvertEntitiesToTextTags(textWithEntities.entities)
};
message.webPage = webpage;
api->sendMessage(std::move(message));
const auto requestId = -1;
crl::on_main(session, [=] {
const auto type = u"MESSAGE_NOT_MODIFIED"_q;
if constexpr (ErrorWithId<FailCallback>) {
fail(error.type(), requestId);
fail(type, requestId);
} else if constexpr (ErrorWithoutId<FailCallback>) {
fail(error.type());
fail(type);
} else if constexpr (WithoutCallback<FailCallback>) {
fail();
} else {
t_bad_callback(fail);
}
}).send();
});
return requestId;
}
template <typename DoneCallback, typename FailCallback>
@ -253,7 +217,7 @@ mtpRequestId SuggestMessageOrMedia(
MTPstring()); // query
}
}
if (inputMedia || (!webpage.removed && !webpage.url.isEmpty())) {
if (inputMedia) {
return SuggestMedia(
item,
textWithEntities,

View file

@ -268,11 +268,12 @@ void SuggestApprovalDate(
close);
};
using namespace HistoryView;
const auto admin = item->history()->amMonoforumAdmin();
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.done = done,
.value = suggestion->date,
.mode = SuggestMode::Change,
.mode = (admin ? SuggestMode::ChangeAdmin : SuggestMode::ChangeUser),
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@ -306,6 +307,7 @@ void SuggestApprovalPrice(
close);
};
using namespace HistoryView;
const auto admin = item->history()->amMonoforumAdmin();
auto dateBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.session = &controller->session(),
.done = done,
@ -316,7 +318,7 @@ void SuggestApprovalPrice(
.ton = uint32(suggestion->price.ton() ? 1 : 0),
.date = suggestion->date,
},
.mode = SuggestMode::Change,
.mode = (admin ? SuggestMode::ChangeAdmin : SuggestMode::ChangeUser),
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));

View file

@ -3661,7 +3661,19 @@ void ApiWrap::editMedia(
if (list.files.empty()) return;
auto &file = list.files.front();
const auto to = FileLoadTaskOptions(action);
auto to = FileLoadTaskOptions(action);
const auto existing = to.replaceMediaOf
? session().data().message(action.history->peer, to.replaceMediaOf)
: nullptr;
if (existing && existing->computeSuggestionActions()
== SuggestionActions::AcceptAndDecline) {
to.replyTo.messageId = {
action.history->peer->id,
to.replaceMediaOf
};
to.replyTo.monoforumPeerId = existing->sublistPeerId();
to.replaceMediaOf = MsgId();
}
_fileLoader->addTask(std::make_unique<FileLoadTask>(
&session(),
file.path,

View file

@ -232,12 +232,14 @@ EditCaptionBox::EditCaptionBox(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item,
TextWithTags &&text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Ui::PreparedList &&list,
Fn<void()> saved)
: _controller(controller)
, _historyItem(item)
, _suggest(suggest)
, _isAllowedEditMedia(item->allowsEditMedia())
, _albumType(ComputeAlbumType(item))
, _controls(base::make_unique_q<Ui::VerticalLayout>(this))
@ -271,6 +273,7 @@ void EditCaptionBox::StartMediaReplace(
not_null<Window::SessionController*> controller,
FullMsgId itemId,
TextWithTags text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Fn<void()> saved) {
@ -284,6 +287,7 @@ void EditCaptionBox::StartMediaReplace(
controller,
item,
std::move(text),
suggest,
spoilered,
invertCaption,
std::move(list),
@ -300,6 +304,7 @@ void EditCaptionBox::StartMediaReplace(
FullMsgId itemId,
Ui::PreparedList &&list,
TextWithTags text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Fn<void()> saved) {
@ -335,6 +340,7 @@ void EditCaptionBox::StartMediaReplace(
controller,
item,
std::move(text),
suggest,
spoilered,
invertCaption,
std::move(list),
@ -347,6 +353,7 @@ void EditCaptionBox::StartPhotoEdit(
std::shared_ptr<Data::PhotoMedia> media,
FullMsgId itemId,
TextWithTags text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Fn<void()> saved) {
@ -365,6 +372,7 @@ void EditCaptionBox::StartPhotoEdit(
controller,
item,
std::move(text),
suggest,
spoilered,
invertCaption,
std::move(list),
@ -1001,6 +1009,7 @@ void EditCaptionBox::save() {
};
auto options = Api::SendOptions();
options.suggest = _suggest;
options.scheduled = item->isScheduled() ? item->date() : 0;
options.shortcutId = item->shortcutId();
options.invertCaption = _mediaEditManager.invertCaption();

View file

@ -39,6 +39,7 @@ public:
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item,
TextWithTags &&text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Ui::PreparedList &&list,
@ -49,6 +50,7 @@ public:
not_null<Window::SessionController*> controller,
FullMsgId itemId,
TextWithTags text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Fn<void()> saved);
@ -57,6 +59,7 @@ public:
FullMsgId itemId,
Ui::PreparedList &&list,
TextWithTags text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Fn<void()> saved);
@ -65,6 +68,7 @@ public:
std::shared_ptr<Data::PhotoMedia> media,
FullMsgId itemId,
TextWithTags text,
SuggestPostOptions suggest,
bool spoilered,
bool invertCaption,
Fn<void()> saved);
@ -111,6 +115,7 @@ private:
const not_null<Window::SessionController*> _controller;
const not_null<HistoryItem*> _historyItem;
const SuggestPostOptions _suggest;
const bool _isAllowedEditMedia;
const Ui::AlbumType _albumType;

View file

@ -6236,7 +6236,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
this,
_from,
Data::GiftType::Ton,
data.vamount().v);
data.vcrypto_amount().v);
}, [&](const MTPDmessageActionPrizeStars &data) {
_media = std::make_unique<Data::MediaGiftBox>(
this,

View file

@ -2271,7 +2271,11 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
}
if (editDraft && editDraft->suggest) {
using namespace HistoryView;
applySuggestOptions(editDraft->suggest, SuggestMode::Change);
applySuggestOptions(
editDraft->suggest,
(_history->amMonoforumAdmin()
? SuggestMode::ChangeAdmin
: SuggestMode::ChangeUser));
} else {
cancelSuggestPost();
}
@ -3030,6 +3034,7 @@ bool HistoryWidget::updateReplaceMediaButton() {
controller(),
{ _history->peer->id, _editMsgId },
_field->getTextWithTags(),
suggestOptions(),
_mediaEditManager.spoilered(),
_mediaEditManager.invertCaption(),
crl::guard(_list, [=] { cancelEdit(); }));
@ -6276,6 +6281,7 @@ bool HistoryWidget::confirmSendingFiles(
{ _history->peer->id, _editMsgId },
std::move(list),
_field->getTextWithTags(),
suggestOptions(),
_mediaEditManager.spoilered(),
_mediaEditManager.invertCaption(),
crl::guard(_list, [=] { cancelEdit(); }));
@ -7412,6 +7418,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
_photoEditMedia,
{ _history->peer->id, _editMsgId },
_field->getTextWithTags(),
suggestOptions(),
_mediaEditManager.spoilered(),
_mediaEditManager.invertCaption(),
crl::guard(_list, [=] { cancelEdit(); }));

View file

@ -420,9 +420,13 @@ void FieldHeader::init() {
if (_preview.parsed) {
_editOptionsRequests.fire({});
} else if (isEditingMessage()) {
_jumpToItemRequests.fire(FullReplyTo{
.messageId = _editMsgId.current()
});
if (_suggestOptions) {
_suggestOptions->edit();
} else {
_jumpToItemRequests.fire(FullReplyTo{
.messageId = _editMsgId.current()
});
}
} else if (reply && (e->modifiers() & Qt::ControlModifier)) {
_jumpToItemRequests.fire_copy(reply);
} else if (reply || readyToForward()) {
@ -789,7 +793,7 @@ void FieldHeader::editMessage(
_inPhotoEditOver.stop();
}
if (id && suggest) {
applySuggestOptions(suggest, SuggestMode::Change);
applySuggestOptions(suggest, SuggestMode::ChangeAdmin);
} else {
cancelSuggestPost();
}
@ -1227,6 +1231,7 @@ bool ComposeControls::confirmMediaEdit(Ui::PreparedList &list) {
_editingId,
std::move(list),
_field->getTextWithTags(),
_header->suggestOptions(),
queryToEdit.spoilered,
queryToEdit.options.invertCaption,
crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
@ -1466,6 +1471,7 @@ void ComposeControls::init() {
_photoEditMedia,
_editingId,
_field->getTextWithTags(),
_header->suggestOptions(),
queryToEdit.spoilered,
queryToEdit.options.invertCaption,
crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
@ -3093,6 +3099,7 @@ bool ComposeControls::updateReplaceMediaButton() {
_regularWindow,
_editingId,
_field->getTextWithTags(),
_header->suggestOptions(),
queryToEdit.spoilered,
queryToEdit.options.invertCaption,
crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));

View file

@ -95,13 +95,17 @@ void ChooseSuggestPriceBox(
state->buttons.push_back({
.text = Ui::Text::String(
st::semiboldTextStyle,
tr::lng_suggest_options_stars_offer(tr::now)),
(args.mode == SuggestMode::ChangeAdmin
? tr::lng_suggest_options_stars_request(tr::now)
: tr::lng_suggest_options_stars_offer(tr::now))),
.active = !state->ton.current(),
});
state->buttons.push_back({
.text = Ui::Text::String(
st::semiboldTextStyle,
tr::lng_suggest_options_ton_offer(tr::now)),
(args.mode == SuggestMode::ChangeAdmin
? tr::lng_suggest_options_ton_request(tr::now)
: tr::lng_suggest_options_ton_offer(tr::now))),
.active = state->ton.current(),
});

View file

@ -29,7 +29,8 @@ namespace HistoryView {
enum class SuggestMode {
New,
Change,
ChangeUser,
ChangeAdmin,
};
struct SuggestTimeBoxArgs {

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "boxes/gift_premium_box.h" // ResolveGiftCode
#include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
@ -47,7 +48,7 @@ PremiumGift::PremiumGift(
PremiumGift::~PremiumGift() = default;
int PremiumGift::top() {
return starGift()
return (starGift() || tonGift())
? st::msgServiceStarGiftStickerTop
: st::msgServiceGiftBoxStickerTop;
}
@ -57,7 +58,7 @@ int PremiumGift::width() {
}
QSize PremiumGift::size() {
return starGift()
return (starGift() || tonGift())
? QSize(
st::msgServiceStarGiftStickerSize,
st::msgServiceStarGiftStickerSize)
@ -68,7 +69,10 @@ QSize PremiumGift::size() {
TextWithEntities PremiumGift::title() {
using namespace Ui::Text;
if (starGift()) {
if (tonGift()) {
AssertIsDebug();
return { QString::number(_data.count / 1'000'000'000LL) + u" TON"_q };
} else if (starGift()) {
const auto peer = _parent->history()->peer;
return peer->isSelf()
? tr::lng_action_gift_self_subtitle(tr::now, WithEntities)
@ -114,7 +118,10 @@ TextWithEntities PremiumGift::title() {
}
TextWithEntities PremiumGift::subtitle() {
if (starGift()) {
if (tonGift()) {
AssertIsDebug();
return { u"Use TON to suggest posts to channels."_q };
} else if (starGift()) {
const auto toChannel = _data.channel
&& _parent->history()->peer->isServiceUser();
return !_data.message.empty()
@ -242,6 +249,14 @@ bool PremiumGift::buttonMinistars() {
}
ClickHandlerPtr PremiumGift::createViewLink() {
if (tonGift()) {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto window = my.sessionWindow.get()) {
window->showSettings(Settings::CreditsId());
}
});
}
if (auto link = OpenStarGiftLink(_parent->data())) {
return link;
}
@ -401,6 +416,17 @@ int PremiumGift::credits() const {
void PremiumGift::ensureStickerCreated() const {
if (_sticker) {
return;
} else if (tonGift()) {
const auto document = ChatHelpers::GenerateLocalTgsSticker(
&_parent->history()->session(),
"diamond");
const auto sticker = document->sticker();
Assert(sticker != nullptr);
_sticker.emplace(_parent, document, false, _parent);
_sticker->setPlayingOnce(true);
_sticker->initSize(st::msgServiceStarGiftStickerSize);
_parent->repaint();
return;
} else if (const auto document = _data.document) {
const auto sticker = document->sticker();
Assert(sticker != nullptr);

View file

@ -143,8 +143,12 @@ auto GenerateSuggestDecisionMedia(
TextWithEntities(
).append(Emoji(kWarning)).append(' ').append(
(sublistPeer->isSelf()
? tr::lng_suggest_action_your_not_enough
: tr::lng_suggest_action_his_not_enough)(
? (decision->price.ton()
? tr::lng_suggest_action_your_not_enough_ton
: tr::lng_suggest_action_your_not_enough_stars)
: (decision->price.ton()
? tr::lng_suggest_action_his_not_enough_ton
: tr::lng_suggest_action_his_not_enough_stars))(
tr::now,
Ui::Text::RichLangValue)),
st::chatSuggestInfoFullMargin,
@ -237,7 +241,9 @@ auto GenerateSuggestDecisionMedia(
pushText(
TextWithEntities(
).append(Emoji(kHourglass)).append(' ').append(
tr::lng_suggest_action_agree_receive(
(price.ton()
? tr::lng_suggest_action_agree_receive_ton
: tr::lng_suggest_action_agree_receive_stars)(
tr::now,
lt_channel,
Ui::Text::Bold(broadcast->name()),
@ -247,7 +253,9 @@ auto GenerateSuggestDecisionMedia(
pushText(
TextWithEntities(
).append(Emoji(kReload)).append(' ').append(
tr::lng_suggest_action_agree_removed(
(price.ton()
? tr::lng_suggest_action_agree_removed_ton
: tr::lng_suggest_action_agree_removed_stars)(
tr::now,
lt_channel,
Ui::Text::Bold(broadcast->name()),