Update API scheme on layer 206. Re-Suggest.

This commit is contained in:
John Preston 2025-06-20 13:13:17 +04:00
parent ec28eea7f0
commit e29dcf7489
12 changed files with 278 additions and 51 deletions

View file

@ -11,19 +11,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "core/click_handler_types.h"
#include "data/data_session.h"
#include "history/view/history_view_suggest_options.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwindow.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/layers/generic_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace Api {
namespace {
@ -116,19 +121,22 @@ void SendDecline(
void RequestApprovalDate(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
const auto id = item->fullId();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](TimeId result) {
SendApproval(controller, item, result);
if (const auto item = controller->session().data().message(id)) {
SendApproval(controller, item, result);
}
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
auto dateBox = Box(Ui::ChooseDateTimeBox, Ui::ChooseDateTimeBoxArgs{
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(),
.done = done,
.min = [] { return base::unixtime::now() + 1; },
.time = (base::unixtime::now() + 86400),
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@ -177,6 +185,137 @@ void RequestDeclineComment(
}));
}
struct SendSuggestState {
SendPaymentHelper sendPayment;
};
void SendSuggest(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item,
std::shared_ptr<SendSuggestState> state,
Fn<void(SuggestPostOptions&)> modify,
Fn<void()> done = nullptr,
int starsApproved = 0) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto id = item->fullId();
const auto withPaymentApproved = [=](int stars) {
if (const auto item = controller->session().data().message(id)) {
SendSuggest(controller, item, state, modify, done, stars);
}
};
const auto checked = state->sendPayment.check(
controller->uiShow(),
item->history()->peer,
1,
starsApproved,
withPaymentApproved);
if (!checked) {
return;
}
const auto isForward = item->Get<HistoryMessageForwarded>();
auto action = SendAction(item->history());
action.options.suggest.exists = 1;
action.options.suggest.date = suggestion->date;
action.options.suggest.stars = suggestion->stars;
action.options.starsApproved = starsApproved;
action.replyTo.monoforumPeerId = item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId();
action.replyTo.messageId = item->fullId();
modify(action.options.suggest);
controller->session().api().forwardMessages({
.items = { item },
.options = (isForward
? Data::ForwardOptions::PreserveInfo
: Data::ForwardOptions::NoSenderNames),
}, action);
if (const auto onstack = done) {
onstack();
}
}
void SuggestApprovalDate(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto id = item->fullId();
const auto state = std::make_shared<SendSuggestState>();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](TimeId result) {
const auto item = controller->session().data().message(id);
if (!item) {
return;
}
const auto close = [=] {
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
SendSuggest(
controller,
item,
state,
[=](SuggestPostOptions &options) { options.date = result; },
close);
};
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.title = tr::lng_suggest_menu_edit_time(),
.submit = tr::lng_profile_suggest_button(),
.done = done,
.value = suggestion->date,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
}
void SuggestApprovalPrice(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto id = item->fullId();
const auto state = std::make_shared<SendSuggestState>();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](SuggestPostOptions result) {
const auto item = controller->session().data().message(id);
if (!item) {
return;
}
const auto close = [=] {
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
SendSuggest(
controller,
item,
state,
[=](SuggestPostOptions &options) { options = result; },
close);
};
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.session = &controller->session(),
.done = done,
.value = {
.exists = true,
.stars = uint32(suggestion->stars),
.date = suggestion->date,
},
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
}
} // namespace
std::shared_ptr<ClickHandler> AcceptClickHandler(
@ -231,7 +370,23 @@ std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
if (!item) {
return;
}
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
window->widget(),
st::popupMenuWithIcons);
menu->addAction(tr::lng_suggest_menu_edit_message(tr::now), [=] {
}, &st::menuIconEdit);
menu->addAction(tr::lng_suggest_menu_edit_price(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalPrice(window, item);
}
}, &st::menuIconTagSell);
menu->addAction(tr::lng_suggest_menu_edit_time(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalDate(window, item);
}
}, &st::menuIconSchedule);
menu->popup(QCursor::pos());
});
}

View file

@ -3410,6 +3410,9 @@ void ApiWrap::forwardMessages(
if (sendAs) {
sendFlags |= SendFlag::f_send_as;
}
if (action.options.suggest) {
sendFlags |= SendFlag::f_suggested_post;
}
const auto kGeneralId = Data::ForumTopic::kGeneralId;
const auto topicRootId = action.replyTo.topicRootId;
const auto topMsgId = (topicRootId == kGeneralId)
@ -3422,7 +3425,7 @@ void ApiWrap::forwardMessages(
const auto monoforumPeer = monoforumPeerId
? session().data().peer(monoforumPeerId).get()
: nullptr;
if (monoforumPeer) {
if (monoforumPeer || (action.options.suggest && action.replyTo)) {
sendFlags |= SendFlag::f_reply_to;
}
@ -3454,14 +3457,17 @@ void ApiWrap::forwardMessages(
MTP_vector<MTPlong>(randomIds),
peer->input,
MTP_int(topMsgId),
(monoforumPeer
(action.options.suggest
? ReplyToForMTP(history, action.replyTo)
: monoforumPeer
? MTP_inputReplyToMonoForum(monoforumPeer->input)
: MTPInputReplyTo()),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTPint(), // video_timestamp
MTP_long(starsPaid)
MTP_long(starsPaid),
Api::SuggestToMTP(action.options.suggest)
)).done([=](const MTPUpdates &result) {
if (!scheduled) {
this->updates().checkForSentToScheduled(result);

View file

@ -1769,7 +1769,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
? Flag::f_quick_reply_shortcut
: Flag(0))
| (starsPaid ? Flag::f_allow_paid_stars : Flag())
| (sublistPeer ? Flag::f_reply_to : Flag());
| (sublistPeer ? Flag::f_reply_to : Flag())
| (options.suggest ? Flag::f_suggested_post : Flag());
threadHistory->sendRequestId = api.request(
MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
@ -1785,7 +1786,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
MTP_inputPeerEmpty(), // send_as
Data::ShortcutIdToMTP(session, options.shortcutId),
MTP_int(videoTimestamp.value_or(0)),
MTP_long(starsPaid)
MTP_long(starsPaid),
Api::SuggestToMTP(options.suggest)
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
threadHistory->session().api().applyUpdates(updates);
state->requests.remove(reqId);

View file

@ -981,7 +981,7 @@ HistoryWidget::HistoryWidget(
action.replyTo.messageId);
if (action.replaceMediaOf) {
} else if (action.options.scheduled) {
cancelReply(lastKeyboardUsed);
cancelReplyOrSuggest(lastKeyboardUsed);
crl::on_main(this, [=, history = action.history] {
controller->showSection(
std::make_shared<HistoryView::ScheduledMemento>(history));
@ -989,7 +989,7 @@ HistoryWidget::HistoryWidget(
} else {
fastShowAtEnd(action.history);
if (!_justMarkingAsRead
&& cancelReply(lastKeyboardUsed)
&& cancelReplyOrSuggest(lastKeyboardUsed)
&& !action.clearDraft) {
saveCloudDraft();
}
@ -8758,6 +8758,12 @@ bool HistoryWidget::lastForceReplyReplied() const {
== FullMsgId(_peer->id, _history->lastKeyboardId));
}
bool HistoryWidget::cancelReplyOrSuggest(bool lastKeyboardUsed) {
const auto ok1 = cancelReply(lastKeyboardUsed);
const auto ok2 = cancelSuggestPost();
return ok1 || ok2;
}
bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
bool wasReply = false;
if (_replyTo) {
@ -8804,7 +8810,7 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
}
void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) {
if (cancelReply(lastKeyboardUsed)) {
if (cancelReplyOrSuggest(lastKeyboardUsed)) {
saveCloudDraft();
}
}
@ -8989,7 +8995,7 @@ bool HistoryWidget::updateCanSendMessage() {
_canSendMessages = newCanSendMessages;
_canSendTexts = newCanSendTexts;
if (!_canSendMessages) {
cancelReply();
cancelReplyOrSuggest();
}
refreshSuggestPostToggle();
refreshScheduledToggle();
@ -9064,8 +9070,9 @@ void HistoryWidget::escape() {
}
} else if (_autocomplete && !_autocomplete->isHidden()) {
_autocomplete->hideAnimated();
} else if (_replyTo && _field->getTextWithTags().text.isEmpty()) {
cancelReply();
} else if ((_replyTo || _suggestOptions)
&& _field->getTextWithTags().empty()) {
cancelReplyOrSuggest();
} else if (auto &voice = _voiceRecordBar; voice->isActive()) {
voice->showDiscardBox(nullptr, anim::type::normal);
} else {

View file

@ -218,6 +218,7 @@ public:
[[nodiscard]] SuggestPostOptions suggestOptions() const;
bool lastForceReplyReplied(const FullMsgId &replyTo) const;
bool lastForceReplyReplied() const;
bool cancelReplyOrSuggest(bool lastKeyboardUsed = false);
bool cancelReply(bool lastKeyboardUsed = false);
bool cancelSuggestPost();
void cancelEdit();

View file

@ -668,7 +668,9 @@ void ServicePreMessage::paint(
const auto top = g.top() - height - st::msgMargin.bottom();
const auto position = QPoint(left, top);
p.translate(position);
media->draw(p, context.translated(-position).withSelection({}));
media->draw(p, context.selected()
? context.translated(-position)
: context.translated(-position).withSelection({}));
p.translate(-position);
} else {
const auto top = g.top() - height - st::msgMargin.top();

View file

@ -25,23 +25,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
namespace HistoryView {
namespace {
struct EditOptionsArgs {
int starsLimit = 0;
QString channelName;
SuggestPostOptions values;
Fn<void(SuggestPostOptions)> save;
};
void EditOptionsBox(
void ChooseSuggestTimeBox(
not_null<Ui::GenericBox*> box,
EditOptionsArgs &&args) {
SuggestTimeBoxArgs &&args) {
const auto now = base::unixtime::now();
const auto min = args.session->appConfig().suggestedPostDelayMin() + 60;
const auto max = args.session->appConfig().suggestedPostDelayMax();
const auto value = args.value
? std::clamp(args.value, now + min, now + max)
: (now + 86400);
Ui::ChooseDateTimeBox(box, {
.title = std::move(args.title),
.submit = std::move(args.submit),
.done = std::move(args.done),
.min = [=] { return now + min; },
.time = value,
.max = [=] { return now + max; },
});
}
void ChooseSuggestPriceBox(
not_null<Ui::GenericBox*> box,
SuggestPriceBoxArgs &&args) {
struct State {
rpl::variable<TimeId> date;
};
const auto state = box->lifetime().make_state<State>();
state->date = args.values.date;
state->date = args.value.date;
const auto limit = args.session->appConfig().suggestedPostStarsMax();
box->setTitle(tr::lng_suggest_options_title());
@ -57,8 +70,8 @@ void EditOptionsBox(
wrap,
st::editTagField,
tr::lng_paid_cost_placeholder(),
args.values.stars ? QString::number(args.values.stars) : QString(),
args.starsLimit);
args.value.stars ? QString::number(args.value.stars) : QString(),
limit);
const auto field = owned.data();
wrap->widthValue() | rpl::start_with_next([=](int width) {
field->move(0, 0);
@ -102,14 +115,12 @@ void EditOptionsBox(
strong->closeBox();
}
};
auto dateBox = Box(Ui::ChooseDateTimeBox, Ui::ChooseDateTimeBoxArgs{
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = args.session,
.title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(),
.done = done,
.min = [] { return base::unixtime::now() + 1; },
.time = (state->date.current()
? state->date.current()
: (base::unixtime::now() + 86400)),
.value = state->date.current(),
});
*weak = dateBox.data();
box->uiShow()->show(std::move(dateBox));
@ -120,15 +131,15 @@ void EditOptionsBox(
AssertIsDebug()//tr::lng_suggest_options_offer
const auto save = [=] {
const auto now = uint32(field->getLastText().toULongLong());
if (now > args.starsLimit) {
if (now > limit) {
field->showError();
return;
}
const auto weak = Ui::MakeWeak(box);
args.save({ .stars = now, .date = state->date.current()});
if (const auto strong = weak.data()) {
strong->closeBox();
}
args.done({
.exists = true,
.stars = now,
.date = state->date.current(),
});
};
QObject::connect(field, &Ui::NumberInput::submitted, box, save);
@ -139,8 +150,6 @@ void EditOptionsBox(
});
}
} // namespace
SuggestOptions::SuggestOptions(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
@ -179,18 +188,19 @@ void SuggestOptions::paintBar(QPainter &p, int x, int y, int outerWidth) {
}
void SuggestOptions::edit() {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto apply = [=](SuggestPostOptions values) {
_values = values;
updateTexts();
_updates.fire({});
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
const auto broadcast = _peer->monoforumBroadcast();
const auto &appConfig = _peer->session().appConfig();
_controller->show(Box(EditOptionsBox, EditOptionsArgs{
.starsLimit = appConfig.suggestedPostStarsMax(),
.channelName = (broadcast ? broadcast : _peer.get())->shortName(),
.values = _values,
.save = apply,
*weak = _controller->show(Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.session = &_peer->session(),
.done = apply,
.value = _values,
}));
}

View file

@ -9,12 +9,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_common.h"
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Main {
class Session;
} // namespace Main
namespace Window {
class SessionController;
} // namespace Window
namespace HistoryView {
struct SuggestTimeBoxArgs {
not_null<Main::Session*> session;
rpl::producer<QString> title;
rpl::producer<QString> submit;
Fn<void(TimeId)> done;
TimeId value = 0;
};
void ChooseSuggestTimeBox(
not_null<Ui::GenericBox*> box,
SuggestTimeBoxArgs &&args);
struct SuggestPriceBoxArgs {
not_null<Main::Session*> session;
bool updating = false;
Fn<void(SuggestPostOptions)> done;
SuggestPostOptions value;
};
void ChooseSuggestPriceBox(
not_null<Ui::GenericBox*> box,
SuggestPriceBoxArgs &&args);
class SuggestOptions final {
public:
SuggestOptions(

View file

@ -134,7 +134,12 @@ void MediaGeneric::draw(Painter &p, const PaintContext &context) const {
const auto radius = st::msgServiceGiftBoxRadius;
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
p.drawRoundedRect(QRect(0, 0, width(), height()), radius, radius);
const auto rect = QRect(0, 0, width(), height());
p.drawRoundedRect(rect, radius, radius);
//if (context.selected()) {
// p.setBrush(context.st->serviceTextPalette().selectBg);
// p.drawRoundedRect(rect, radius, radius);
//}
}
auto translated = 0;

View file

@ -166,6 +166,14 @@ int AppConfig::suggestedPostStarsMax() const {
return get<int>(u"stars_suggested_post_amount_max"_q, 100'000);
}
int AppConfig::suggestedPostDelayMin() const {
return get<int>(u"stars_suggested_post_future_min"_q, 300);
}
int AppConfig::suggestedPostDelayMax() const {
return get<int>(u"appConfig.stars_suggested_post_future_max"_q, 2678400);
}
void AppConfig::refresh(bool force) {
if (_requestId || !_api) {
if (force) {

View file

@ -89,6 +89,8 @@ public:
[[nodiscard]] int todoListItemTextLimit() const;
[[nodiscard]] int suggestedPostStarsMax() const;
[[nodiscard]] int suggestedPostDelayMin() const;
[[nodiscard]] int suggestedPostDelayMax() const;
void refresh(bool force = false);

View file

@ -2194,7 +2194,7 @@ messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
messages.sendMessage#fe05dc9a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long suggested_post:flags.22?SuggestedPost = Updates;
messages.sendMedia#ac55d9c1 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long suggested_post:flags.22?SuggestedPost = Updates;
messages.forwardMessages#38f0188c flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer top_msg_id:flags.9?int reply_to:flags.22?InputReplyTo schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long = Updates;
messages.forwardMessages#978928ca flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer top_msg_id:flags.9?int reply_to:flags.22?InputReplyTo schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long suggested_post:flags.23?SuggestedPost = Updates;
messages.reportSpam#cf1592db peer:InputPeer = Bool;
messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings;
messages.report#fc78af9b peer:InputPeer id:Vector<int> option:bytes message:string = ReportResult;