Support adding an offer to existing message.

This commit is contained in:
John Preston 2025-06-26 16:30:16 +04:00
parent b929e2a7b2
commit 4c1b962486
9 changed files with 144 additions and 72 deletions

View file

@ -4232,6 +4232,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_edit_msg" = "Edit";
"lng_context_add_factcheck" = "Add Fact Check";
"lng_context_edit_factcheck" = "Edit Fact Check";
"lng_context_add_offer" = "Add Offer";
"lng_context_forward_msg" = "Forward";
"lng_context_send_now_msg" = "Send Now";
"lng_context_reschedule" = "Reschedule";
@ -5363,6 +5364,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_polls_all" = "Sorry, sending polls is not allowed in this group.";
"lng_restricted_send_public_polls" = "Sorry, polls with visible votes can't be forwarded to channels.";
"lng_restricted_send_todo_lists" = "Sorry, To-Do lists can't be forwarded to channels.";
"lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel.";
"lng_restricted_send_voice_messages" = "{user} doesn't accept voice messages.";

View file

@ -37,7 +37,7 @@ namespace Api {
namespace {
void SendApproval(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
TimeId scheduleDate = 0) {
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
@ -50,8 +50,7 @@ void SendApproval(
}
const auto id = item->fullId();
const auto weak = base::make_weak(controller);
const auto session = &controller->session();
const auto session = &show->session();
const auto finish = [=] {
if (const auto item = session->data().message(id)) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
@ -71,15 +70,13 @@ void SendApproval(
session->api().applyUpdates(result);
finish();
}).fail([=](const MTP::Error &error) {
if (const auto window = weak.get()) {
window->showToast(error.type());
}
show->showToast(error.type());
finish();
}).send();
}
void SendDecline(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
const QString &comment) {
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
@ -92,8 +89,7 @@ void SendDecline(
}
const auto id = item->fullId();
const auto weak = base::make_weak(controller);
const auto session = &controller->session();
const auto session = &show->session();
const auto finish = [=] {
if (const auto item = session->data().message(id)) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
@ -114,21 +110,19 @@ void SendDecline(
session->api().applyUpdates(result);
finish();
}).fail([=](const MTP::Error &error) {
if (const auto window = weak.get()) {
window->showToast(error.type());
}
show->showToast(error.type());
finish();
}).send();
}
void RequestApprovalDate(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto id = item->fullId();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](TimeId result) {
if (const auto item = controller->session().data().message(id)) {
SendApproval(controller, item, result);
if (const auto item = show->session().data().message(id)) {
SendApproval(show, item, result);
}
if (const auto strong = weak->data()) {
strong->closeBox();
@ -136,19 +130,19 @@ void RequestApprovalDate(
};
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.session = &show->session(),
.done = done,
.mode = SuggestMode::New,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
show->show(std::move(dateBox));
}
void RequestDeclineComment(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto id = item->fullId();
controller->uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
show->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto callback = std::make_shared<Fn<void()>>();
Ui::ConfirmBox(box, {
.text = tr::lng_suggest_decline_text(
@ -169,11 +163,11 @@ void RequestDeclineComment(
reason->setFocusFast();
});
*callback = [=, weak = Ui::MakeWeak(box)] {
const auto item = controller->session().data().message(id);
const auto item = show->session().data().message(id);
if (!item) {
return;
}
SendDecline(controller, item, reason->getLastText().trimmed());
SendDecline(show, item, reason->getLastText().trimmed());
if (const auto strong = weak.data()) {
strong->closeBox();
}
@ -191,24 +185,21 @@ struct SendSuggestState {
SendPaymentHelper sendPayment;
};
void SendSuggest(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
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);
if (const auto item = show->session().data().message(id)) {
SendSuggest(show, item, state, modify, done, stars);
}
};
const auto checked = state->sendPayment.check(
controller->uiShow(),
show,
item->history()->peer,
1,
starsApproved,
@ -218,18 +209,23 @@ void SendSuggest(
}
const auto isForward = item->Get<HistoryMessageForwarded>();
auto action = SendAction(item->history());
action.options.suggest.exists = 1;
if (suggestion) {
action.options.suggest.date = suggestion->date;
action.options.suggest.priceWhole = suggestion->price.whole();
action.options.suggest.priceNano = suggestion->price.nano();
action.options.suggest.ton = suggestion->price.ton() ? 1 : 0;
}
modify(action.options.suggest);
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({
show->session().api().sendAction(action);
show->session().api().forwardMessages({
.items = { item },
.options = (isForward
? Data::ForwardOptions::PreserveInfo
@ -241,7 +237,7 @@ void SendSuggest(
}
void SuggestApprovalDate(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
@ -251,7 +247,7 @@ void SuggestApprovalDate(
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);
const auto item = show->session().data().message(id);
if (!item) {
return;
}
@ -261,7 +257,7 @@ void SuggestApprovalDate(
}
};
SendSuggest(
controller,
show,
item,
state,
[=](SuggestPostOptions &options) { options.date = result; },
@ -270,27 +266,25 @@ void SuggestApprovalDate(
using namespace HistoryView;
const auto admin = item->history()->amMonoforumAdmin();
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.session = &show->session(),
.done = done,
.value = suggestion->date,
.mode = (admin ? SuggestMode::ChangeAdmin : SuggestMode::ChangeUser),
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
show->show(std::move(dateBox));
}
void SuggestApprovalPrice(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
void SuggestOfferForMessage(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
SuggestPostOptions values,
HistoryView::SuggestMode mode) {
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);
const auto item = show->session().data().message(id);
if (!item) {
return;
}
@ -300,28 +294,39 @@ void SuggestApprovalPrice(
}
};
SendSuggest(
controller,
show,
item,
state,
[=](SuggestPostOptions &options) { options = result; },
close);
};
using namespace HistoryView;
const auto admin = item->history()->amMonoforumAdmin();
auto dateBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.session = &controller->session(),
auto priceBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.session = &show->session(),
.done = done,
.value = {
.value = values,
.mode = mode,
});
*weak = priceBox.data();
show->show(std::move(priceBox));
}
void SuggestApprovalPrice(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto admin = item->history()->amMonoforumAdmin();
using namespace HistoryView;
SuggestOfferForMessage(show, item, {
.exists = uint32(1),
.priceWhole = uint32(suggestion->price.whole()),
.priceNano = uint32(suggestion->price.nano()),
.ton = uint32(suggestion->price.ton() ? 1 : 0),
.date = suggestion->date,
},
.mode = (admin ? SuggestMode::ChangeAdmin : SuggestMode::ChangeUser),
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
}, admin ? SuggestMode::ChangeAdmin : SuggestMode::ChangeUser);
}
} // namespace
@ -340,13 +345,14 @@ std::shared_ptr<ClickHandler> AcceptClickHandler(
if (!item) {
return;
}
const auto show = controller->uiShow();
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
} else if (!suggestion->date) {
RequestApprovalDate(controller, item);
RequestApprovalDate(show, item);
} else {
SendApproval(controller, item);
SendApproval(show, item);
}
});
}
@ -360,7 +366,7 @@ std::shared_ptr<ClickHandler> DeclineClickHandler(
if (!controller) {
return;
}
RequestDeclineComment(controller, item);
RequestDeclineComment(controller->uiShow(), item);
});
}
@ -426,16 +432,27 @@ std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
}
menu->addAction(tr::lng_suggest_menu_edit_price(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalPrice(window, item);
SuggestApprovalPrice(window->uiShow(), item);
}
}, &st::menuIconTagSell);
menu->addAction(tr::lng_suggest_menu_edit_time(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalDate(window, item);
SuggestApprovalDate(window->uiShow(), item);
}
}, &st::menuIconSchedule);
menu->popup(QCursor::pos());
});
}
void AddOfferToMessage(
std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId) {
const auto session = &show->session();
const auto item = session->data().message(itemId);
if (!item || !HistoryView::CanAddOfferToMessage(item)) {
return;
}
SuggestOfferForMessage(show, item, {}, HistoryView::SuggestMode::New);
}
} // namespace Api

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class ClickHandler;
namespace Main {
class SessionShow;
} // namespace Main
namespace Api {
[[nodiscard]] std::shared_ptr<ClickHandler> AcceptClickHandler(
@ -18,4 +22,8 @@ namespace Api {
[[nodiscard]] std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
not_null<HistoryItem*> item);
void AddOfferToMessage(
std::shared_ptr<Main::SessionShow> show,
FullMsgId itemId);
} // namespace Api

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_helpers.h"
#include "history/view/controls/history_view_forward_panel.h"
#include "history/view/controls/history_view_draft_options.h"
#include "history/view/controls/history_view_suggest_options.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_web_page.h"
#include "history/view/reactions/history_view_reactions.h"
@ -76,6 +77,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "apiwrap.h"
#include "api/api_attached_stickers.h"
#include "api/api_suggest_post.h"
#include "api/api_toggling_media.h"
#include "api/api_who_reacted.h"
#include "api/api_views.h"
@ -2410,9 +2412,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
highlightId);
}, &st::menuIconViewReplies);
}
_menu->addAction(u"Add Offer"_q, [=] {
}, &st::menuIconDiscussion);
const auto t = base::unixtime::now();
const auto editItem = (albumPartItem && albumPartItem->allowsEdit(t))
? albumPartItem
@ -2812,6 +2811,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
forwardItem(itemId);
}, &st::menuIconForward);
}
if (HistoryView::CanAddOfferToMessage(item)) {
_menu->addAction(tr::lng_context_add_offer(tr::now), [=] {
Api::AddOfferToMessage(_controller->uiShow(), itemId);
}, &st::menuIconTagSell);
}
if (item->canDelete()) {
const auto callback = [=] { deleteItem(itemId); };
if (item->isUploading()) {
@ -3062,6 +3066,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
forwardAsGroup(itemId);
}, &st::menuIconForward);
}
if (HistoryView::CanAddOfferToMessage(item)) {
_menu->addAction(tr::lng_context_add_offer(tr::now), [=] {
Api::AddOfferToMessage(_controller->uiShow(), itemId);
}, &st::menuIconTagSell);
}
if (canDelete) {
const auto callback = [=] {
deleteAsGroup(itemId);

View file

@ -2695,11 +2695,26 @@ Data::SendError HistoryItem::errorTextForForward(
} else if (requiresInline && !Data::CanSend(to, kInline)) {
const auto forInline = Data::RestrictionError(peer, kInline);
return forInline ? forInline : tr::lng_forward_cant(tr::now);
} else if (_media
} else if (const auto specific = errorTextForForwardIgnoreRights(to)) {
return specific;
} else if (!Data::CanSend(to, requiredRight, false)) {
return tr::lng_forward_cant(tr::now);
}
return {};
}
Data::SendError HistoryItem::errorTextForForwardIgnoreRights(
not_null<Data::Thread*> to) const {
const auto peer = to->peer();
if (_media
&& _media->poll()
&& _media->poll()->publicVotes()
&& peer->isBroadcast()) {
return tr::lng_restricted_send_public_polls(tr::now);
} else if (_media
&& _media->todolist()
&& (peer->isBroadcast() || peer->isMonoforum())) {
return tr::lng_restricted_send_todo_lists(tr::now);
} else if (_media
&& _media->invoice()
&& _media->invoice()->isPaidMedia
@ -2707,8 +2722,6 @@ Data::SendError HistoryItem::errorTextForForward(
&& peer->isFullLoaded()
&& !peer->asBroadcast()->canPostPaidMedia()) {
return tr::lng_restricted_send_paid_media(tr::now);
} else if (!Data::CanSend(to, requiredRight, false)) {
return tr::lng_forward_cant(tr::now);
}
return {};
}

View file

@ -437,6 +437,8 @@ public:
[[nodiscard]] bool requiresSendInlineRight() const;
[[nodiscard]] Data::SendError errorTextForForward(
not_null<Data::Thread*> to) const;
[[nodiscard]] Data::SendError errorTextForForwardIgnoreRights(
not_null<Data::Thread*> to) const;
[[nodiscard]] const HistoryMessageTranslation *translation() const;
[[nodiscard]] bool translationShowRequiresCheck(LanguageId to) const;
bool translationShowRequiresRequest(LanguageId to);

View file

@ -2200,6 +2200,10 @@ void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
_pinnedClickedId = FullMsgId();
_minPinnedId = std::nullopt;
if (_history->isReadyFor(_showAtMsgId)) {
_history->forgetScrollState();
if (_migrated) {
_migrated->forgetScrollState();
}
historyLoaded();
} else {
firstLoadMessages();

View file

@ -14,7 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "info/channel_statistics/earn/earn_icons.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
@ -384,6 +386,19 @@ bool CanEditSuggestedMessage(not_null<HistoryItem*> item) {
return !media || media->allowsEditCaption();
}
bool CanAddOfferToMessage(not_null<HistoryItem*> item) {
const auto history = item->history();
const auto broadcast = history->peer->monoforumBroadcast();
return broadcast
&& !history->amMonoforumAdmin()
&& !item->Get<HistoryMessageSuggestedPost>()
&& !item->groupId()
&& item->isRegular()
&& !item->isService()
&& !item->errorTextForForwardIgnoreRights(
history->owner().history(broadcast)).has_value();
}
SuggestOptions::SuggestOptions(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,

View file

@ -56,6 +56,8 @@ void ChooseSuggestPriceBox(
[[nodiscard]] bool CanEditSuggestedMessage(not_null<HistoryItem*> item);
[[nodiscard]] bool CanAddOfferToMessage(not_null<HistoryItem*> item);
class SuggestOptions final {
public:
SuggestOptions(