Start suggesting changes to messages by editing.

This commit is contained in:
John Preston 2025-06-20 21:31:54 +04:00
parent 498116c3f6
commit dc19f2e76c
10 changed files with 496 additions and 138 deletions

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_media.h"
#include "api/api_text_entities.h"
#include "base/random.h"
#include "ui/boxes/confirm_box.h"
#include "data/business/data_shortcut_messages.h"
#include "data/components/scheduled_messages.h"
@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_web_page.h"
#include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mtproto/mtproto_response.h"
@ -46,6 +48,230 @@ template <typename T>
constexpr auto ErrorWithoutId
= is_callable_plain_v<T, QString>;
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMessage(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail) {
Expects(options.suggest.exists);
Expects(!options.scheduled);
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{
.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) {
if constexpr (ErrorWithId<FailCallback>) {
fail(error.type(), requestId);
} else if constexpr (ErrorWithoutId<FailCallback>) {
fail(error.type());
} else if constexpr (WithoutCallback<FailCallback>) {
fail();
} else {
t_bad_callback(fail);
}
}).send();
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMedia(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia) {
Expects(options.suggest.exists);
Expects(!options.scheduled);
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 updateRecentStickers = inputMedia
? Api::HasAttachedStickers(*inputMedia)
: false;
const auto emptyFlag = MTPmessages_SendMedia::Flag(0);
auto replyTo = FullReplyTo{
.messageId = item->fullId(),
.monoforumPeerId = (item->history()->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId()),
};
const auto flags = emptyFlag
| MTPmessages_SendMedia::Flag::f_reply_to
| MTPmessages_SendMedia::Flag::f_suggested_post
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_SendMedia::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_SendMedia::Flag::f_entities
: emptyFlag)
| (options.starsApproved
? MTPmessages_SendMedia::Flag::f_allow_paid_stars
: emptyFlag);
const auto randomId = base::RandomValue<uint64>();
return api->request(MTPmessages_SendMedia(
MTP_flags(flags),
item->history()->peer->input,
ReplyToForMTP(item->history(), replyTo),
inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
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);
}
if (updateRecentStickers) {
api->requestSpecialStickersForce(false, false, true);
}
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
if constexpr (ErrorWithId<FailCallback>) {
fail(error.type(), requestId);
} else if constexpr (ErrorWithoutId<FailCallback>) {
fail(error.type());
} else if constexpr (WithoutCallback<FailCallback>) {
fail();
} else {
t_bad_callback(fail);
}
}).send();
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId SuggestMessageOrMedia(
not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options,
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia) {
const auto wasMedia = item->media();
if (!inputMedia && wasMedia && wasMedia->allowsEditCaption()) {
if (const auto photo = wasMedia->photo()) {
inputMedia = MTP_inputMediaPhoto(
MTP_flags(0),
photo->mtpInput(),
MTPint()); // ttl_seconds
} else if (const auto document = wasMedia->document()) {
inputMedia = MTP_inputMediaDocument(
MTP_flags(0),
document->mtpInput(),
MTPInputPhoto(), // video_cover
MTPint(), // video_timestamp
MTPint(), // ttl_seconds
MTPstring()); // query
}
}
if (inputMedia || (!webpage.removed && !webpage.url.isEmpty())) {
return SuggestMedia(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail),
inputMedia);
}
return SuggestMessage(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail));
}
template <typename DoneCallback, typename FailCallback>
mtpRequestId EditMessage(
not_null<HistoryItem*> item,
@ -55,6 +281,18 @@ mtpRequestId EditMessage(
DoneCallback &&done,
FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
if (item->computeSuggestionActions()
== SuggestionActions::AcceptAndDecline) {
return SuggestMessageOrMedia(
item,
textWithEntities,
webpage,
options,
std::move(done),
std::move(fail),
inputMedia);
}
const auto session = &item->history()->session();
const auto api = &session->api();
@ -71,31 +309,31 @@ mtpRequestId EditMessage(
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
const auto flags = emptyFlag
| ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
| (options.scheduled
? MTPmessages_EditMessage::Flag::f_schedule_date
: emptyFlag)
| (item->isBusinessShortcut()
? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
: emptyFlag);
| ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message
: emptyFlag)
| ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag)
| (options.scheduled
? MTPmessages_EditMessage::Flag::f_schedule_date
: emptyFlag)
| (item->isBusinessShortcut()
? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
: emptyFlag);
const auto id = item->isScheduled()
? session->scheduledMessages().lookupId(item)

View file

@ -9,8 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/unixtime.h"
#include "chat_helpers/message_field.h"
#include "core/click_handler_types.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_saved_sublist.h"
#include "history/view/history_view_suggest_options.h"
#include "history/history.h"
#include "history/history_item.h"
@ -134,9 +137,8 @@ void RequestApprovalDate(
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &controller->session(),
.title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(),
.done = done,
.mode = SuggestMode::New,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@ -266,10 +268,9 @@ void SuggestApprovalDate(
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,
.mode = SuggestMode::Change,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@ -311,6 +312,7 @@ void SuggestApprovalPrice(
.stars = uint32(suggestion->stars),
.date = suggestion->date,
},
.mode = SuggestMode::Change,
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
@ -373,9 +375,47 @@ std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
window->widget(),
st::popupMenuWithIcons);
menu->addAction(tr::lng_suggest_menu_edit_message(tr::now), [=] {
}, &st::menuIconEdit);
if (HistoryView::CanEditSuggestedMessage(item)) {
menu->addAction(tr::lng_suggest_menu_edit_message(tr::now), [=] {
const auto item = session->data().message(id);
if (!item) {
return;
}
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
}
const auto history = item->history();
const auto editData = PrepareEditText(item);
const auto cursor = MessageCursor{
int(editData.text.size()),
int(editData.text.size()),
Ui::kQFixedMax
};
const auto monoforumPeerId = history->amMonoforumAdmin()
? item->sublistPeerId()
: PeerId();
const auto previewDraft = Data::WebPageDraft::FromItem(item);
history->setLocalEditDraft(std::make_unique<Data::Draft>(
editData,
FullReplyTo{
.messageId = FullMsgId(history->peer->id, item->id),
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions{
.exists = 1,
.stars = uint32(suggestion->stars),
.date = suggestion->date,
},
cursor,
previewDraft));
history->session().changes().entryUpdated(
(monoforumPeerId
? item->savedSublist()
: (Data::Thread*)history.get()),
Data::EntryUpdate::Flag::LocalDraftSet);
}, &st::menuIconEdit);
}
menu->addAction(tr::lng_suggest_menu_edit_price(tr::now), [=] {
if (const auto item = session->data().message(id)) {
SuggestApprovalPrice(window, item);

View file

@ -2410,6 +2410,9 @@ 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

View file

@ -544,6 +544,13 @@ public:
[[nodiscard]] bool canUpdateDate() const;
void customEmojiRepaint();
[[nodiscard]] SuggestionActions computeSuggestionActions() const;
[[nodiscard]] SuggestionActions computeSuggestionActions(
const HistoryMessageSuggestedPost *suggest) const;
[[nodiscard]] SuggestionActions computeSuggestionActions(
bool accepted,
bool rejected) const;
[[nodiscard]] bool needsUpdateForVideoQualities(const MTPMessage &data);
[[nodiscard]] TimeId ttlDestroyAt() const {
@ -582,12 +589,6 @@ private:
void setReplyMarkup(
HistoryMessageMarkupData &&markup,
bool ignoreSuggestButtons = false);
[[nodiscard]] SuggestionActions computeSuggestionActions() const;
[[nodiscard]] SuggestionActions computeSuggestionActions(
const HistoryMessageSuggestedPost *suggest) const;
[[nodiscard]] SuggestionActions computeSuggestionActions(
bool accepted,
bool rejected) const;
void updateSuggestControls(const HistoryMessageSuggestedPost *suggest);
void changeReplyToTopCounter(

View file

@ -2269,6 +2269,12 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
if (!_replyEditMsg) {
requestMessageData(_editMsgId);
}
if (editDraft && editDraft->suggest) {
using namespace HistoryView;
applySuggestOptions(editDraft->suggest, SuggestMode::Change);
} else {
cancelSuggestPost();
}
} else {
const auto draft = _history->localDraft(MsgId(), PeerId());
_processingReplyTo = draft ? draft->reply : FullReplyTo();
@ -2276,7 +2282,8 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
_processingReplyItem = session().data().message(
_processingReplyTo.messageId);
} else if (draft && draft->suggest) {
applySuggestOptions(draft->suggest);
using namespace HistoryView;
applySuggestOptions(draft->suggest, SuggestMode::New);
}
processReply();
}
@ -2352,7 +2359,9 @@ void HistoryWidget::showHistory(
if (_peer->id == peerId) {
updateForwarding();
if (showAtMsgId == ShowAtUnreadMsgId
if (params.reapplyLocalDraft) {
return;
} else if (showAtMsgId == ShowAtUnreadMsgId
&& insideJumpToEndInsteadOfToUnread()) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"Jumping to end instead of unread."
@ -3164,14 +3173,17 @@ void HistoryWidget::refreshSendGiftToggle() {
}
}
void HistoryWidget::applySuggestOptions(SuggestPostOptions suggest) {
void HistoryWidget::applySuggestOptions(
SuggestPostOptions suggest,
HistoryView::SuggestMode mode) {
Expects(suggest.exists);
using namespace HistoryView;
_suggestOptions = std::make_unique<SuggestOptions>(
controller(),
_peer,
suggest);
suggest,
mode);
_suggestOptions->updates() | rpl::start_with_next([=] {
updateField();
saveDraftWithTextNow();
@ -3193,7 +3205,8 @@ void HistoryWidget::refreshSuggestPostToggle() {
_toggleSuggestPost.create(this, st::historySuggestPostToggle);
_toggleSuggestPost->setVisible(!_suggestOptions);
_toggleSuggestPost->addClickHandler([=] {
applySuggestOptions({ .exists = 1 });
using namespace HistoryView;
applySuggestOptions({ .exists = 1 }, SuggestMode::New);
cancelReply();
_processingReplyTo = FullReplyTo();
_processingReplyItem = nullptr;
@ -4419,7 +4432,7 @@ TextWithEntities HistoryWidget::prepareTextForEditMsg() const {
return left;
}
void HistoryWidget::saveEditMsg() {
void HistoryWidget::saveEditMessage(Api::SendOptions options) {
Expects(_history != nullptr);
if (_saveEditMsgRequestId) {
@ -4442,9 +4455,11 @@ void HistoryWidget::saveEditMsg() {
|| webPageDraft.url.isEmpty()
|| !webPageDraft.manual)
&& !hasMediaWithCaption) {
const auto suggestModerateActions = false;
controller()->show(
Box<DeleteMessagesBox>(item, suggestModerateActions));
if (item->computeSuggestionActions() == SuggestionActions::None) {
const auto suggestModerateActions = false;
controller()->show(
Box<DeleteMessagesBox>(item, suggestModerateActions));
}
return;
} else {
const auto maxCaptionSize = !hasMediaWithCaption
@ -4506,11 +4521,27 @@ void HistoryWidget::saveEditMsg() {
})();
};
options.invertCaption = _mediaEditManager.invertCaption();
options.suggest = suggestOptions();
const auto withPaymentApproved = [=](int approved) {
auto copy = options;
copy.starsApproved = approved;
saveEditMessage(copy);
};
const auto checked = checkSendPayment(
1 + int(_forwardPanel->items().size()),
options.starsApproved,
withPaymentApproved);
if (!checked) {
return;
}
_saveEditMsgRequestId = Api::EditTextMessage(
item,
sending,
webPageDraft,
{ .invertCaption = _mediaEditManager.invertCaption() },
options,
done,
fail,
_mediaEditManager.spoilered());
@ -4611,7 +4642,7 @@ void HistoryWidget::send(Api::SendOptions options) {
if (!_history) {
return;
} else if (_editMsgId) {
saveEditMsg();
saveEditMessage({});
return;
} else if (!options.scheduled && showSlowmodeError()) {
return;
@ -7386,10 +7417,14 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
} else if (_previewDrawPreview) {
editDraftOptions();
} else if (_editMsgId) {
controller()->showPeerHistory(
_peer,
Window::SectionShow::Way::Forward,
_editMsgId);
if (_suggestOptions) {
_suggestOptions->edit();
} else {
controller()->showPeerHistory(
_peer,
Window::SectionShow::Way::Forward,
_editMsgId);
}
} else if (_replyTo
&& ((e->modifiers() & Qt::ControlModifier)
|| (e->button() != Qt::LeftButton))) {
@ -8834,6 +8869,7 @@ void HistoryWidget::cancelEdit() {
_replyEditMsg = nullptr;
setEditMsgId(0);
_history->clearLocalEditDraft(MsgId(), PeerId());
cancelSuggestPost();
applyDraft();
if (_saveEditMsgRequestId) {
@ -9367,14 +9403,18 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
const auto paused = p.inactive();
const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler);
auto replyLeft = st::historyReplySkip;
(_editMsgId
? st::historyEditIcon
: (_replyTo && !_replyTo.quote.empty())
? st::historyQuoteIcon
: st::historyReplyIcon).paint(
p,
st::historyReplyIconPosition + QPoint(0, backy),
width());
if (_suggestOptions) {
_suggestOptions->paintIcon(p, 0, backy, width());
} else {
(_editMsgId
? st::historyEditIcon
: (_replyTo && !_replyTo.quote.empty())
? st::historyQuoteIcon
: st::historyReplyIcon).paint(
p,
st::historyReplyIconPosition + QPoint(0, backy),
width());
}
if (drawMsgText) {
if (hasPreview) {
if (preview) {
@ -9412,37 +9452,41 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
}
replyLeft += st::historyReplyPreview + st::msgReplyBarSkip;
}
p.setPen(st::historyReplyNameFg);
if (_editMsgId) {
paintEditHeader(p, rect, replyLeft, backy);
if (_suggestOptions) {
_suggestOptions->paintLines(p, replyLeft, backy, width());
} else {
_replyToName.drawElided(
p,
replyLeft,
backy + st::msgReplyPadding.top(),
width()
p.setPen(st::historyReplyNameFg);
if (_editMsgId) {
paintEditHeader(p, rect, replyLeft, backy);
} else {
_replyToName.drawElided(
p,
replyLeft,
backy + st::msgReplyPadding.top(),
width()
- replyLeft
- _fieldBarCancel->width()
- st::msgReplyPadding.right());
}
p.setPen(st::historyComposeAreaFg);
_replyEditMsgText.draw(p, {
.position = QPoint(
replyLeft,
st::msgReplyPadding.top()
+ st::msgServiceNameFont->height
+ backy),
.availableWidth = width()
- replyLeft
- _fieldBarCancel->width()
- st::msgReplyPadding.right());
- st::msgReplyPadding.right(),
.palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = now,
.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = pausedSpoiler,
.elisionLines = 1,
});
}
p.setPen(st::historyComposeAreaFg);
_replyEditMsgText.draw(p, {
.position = QPoint(
replyLeft,
st::msgReplyPadding.top()
+ st::msgServiceNameFont->height
+ backy),
.availableWidth = width()
- replyLeft
- _fieldBarCancel->width()
- st::msgReplyPadding.right(),
.palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = now,
.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = pausedSpoiler,
.elisionLines = 1,
});
} else {
p.setFont(st::msgDateFont);
p.setPen(st::historyComposeAreaFgService);

View file

@ -110,6 +110,7 @@ class ComposeSearch;
class SubsectionTabs;
struct SelectedQuote;
class SuggestOptions;
enum class SuggestMode;
} // namespace HistoryView
namespace HistoryView::Controls {
@ -589,7 +590,7 @@ private:
void createUnreadBarAndResize();
[[nodiscard]] TextWithEntities prepareTextForEditMsg() const;
void saveEditMsg();
void saveEditMessage(Api::SendOptions options = {});
void setupPreview();
void editDraftOptions();
@ -682,7 +683,9 @@ private:
void refreshScheduledToggle();
void refreshSendGiftToggle();
void refreshSuggestPostToggle();
void applySuggestOptions(SuggestPostOptions suggest);
void applySuggestOptions(
SuggestPostOptions suggest,
HistoryView::SuggestMode mode);
void setupSendAsToggle();
void refreshSendAsToggle();
void refreshAttachBotsMenu();

View file

@ -2381,13 +2381,14 @@ bool ChatWidget::showInternal(
const Window::SectionShow &params) {
if (auto logMemento = dynamic_cast<ChatMemento*>(memento.get())) {
if (logMemento->id() == _id) {
restoreState(logMemento);
if (!logMemento->highlightId()) {
showAtPosition(Data::UnreadMessagePosition);
}
if (params.reapplyLocalDraft) {
_composeControls->applyDraft(
ComposeControls::FieldHistoryAction::NewEntry);
} else {
restoreState(logMemento);
if (!logMemento->highlightId()) {
showAtPosition(Data::UnreadMessagePosition);
}
}
return true;
}

View file

@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "data/data_media_types.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
@ -36,8 +38,12 @@ void ChooseSuggestTimeBox(
? std::clamp(args.value, now + min, now + max)
: (now + 86400);
Ui::ChooseDateTimeBox(box, {
.title = std::move(args.title),
.submit = std::move(args.submit),
.title = ((args.mode == SuggestMode::New)
? tr::lng_suggest_options_date()
: tr::lng_suggest_menu_edit_time()),
.submit = ((args.mode == SuggestMode::New)
? tr::lng_settings_save()
: tr::lng_suggest_options_update()),
.done = std::move(args.done),
.min = [=] { return now + min; },
.time = value,
@ -56,7 +62,9 @@ void ChooseSuggestPriceBox(
const auto limit = args.session->appConfig().suggestedPostStarsMax();
box->setTitle(tr::lng_suggest_options_title());
box->setTitle((args.mode == SuggestMode::New)
? tr::lng_suggest_options_title()
: tr::lng_suggest_options_change());
const auto container = box->verticalLayout();
@ -117,10 +125,9 @@ void ChooseSuggestPriceBox(
};
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = args.session,
.title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(),
.done = done,
.value = state->date.current(),
.mode = args.mode,
});
*weak = dateBox.data();
box->uiShow()->show(std::move(dateBox));
@ -150,25 +157,38 @@ void ChooseSuggestPriceBox(
});
}
bool CanEditSuggestedMessage(not_null<HistoryItem*> item) {
const auto media = item->media();
return !media || media->allowsEditCaption();
}
SuggestOptions::SuggestOptions(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
SuggestPostOptions values)
SuggestPostOptions values,
SuggestMode mode)
: _controller(controller)
, _peer(peer)
, _mode(mode)
, _values(values) {
updateTexts();
}
SuggestOptions::~SuggestOptions() = default;
void SuggestOptions::paintBar(QPainter &p, int x, int y, int outerWidth) {
void SuggestOptions::paintIcon(QPainter &p, int x, int y, int outerWidth) {
st::historyDirectMessage.icon.paint(
p,
QPoint(x, y) + st::historySuggestIconPosition,
outerWidth);
}
x += st::historyReplySkip;
void SuggestOptions::paintBar(QPainter &p, int x, int y, int outerWidth) {
paintIcon(p, x, y, outerWidth);
paintLines(p, x + st::historyReplySkip, y, outerWidth);
}
void SuggestOptions::paintLines(QPainter &p, int x, int y, int outerWidth) {
auto available = outerWidth
- x
- st::historyReplyCancel.width
@ -207,7 +227,9 @@ void SuggestOptions::edit() {
void SuggestOptions::updateTexts() {
_title.setText(
st::semiboldTextStyle,
tr::lng_suggest_bar_title(tr::now));
((_mode == SuggestMode::New)
? tr::lng_suggest_bar_title(tr::now)
: tr::lng_suggest_options_change(tr::now)));
_text.setMarkedText(st::defaultTextStyle, composeText());
}

View file

@ -23,12 +23,16 @@ class SessionController;
namespace HistoryView {
enum class SuggestMode {
New,
Change,
};
struct SuggestTimeBoxArgs {
not_null<Main::Session*> session;
rpl::producer<QString> title;
rpl::producer<QString> submit;
Fn<void(TimeId)> done;
TimeId value = 0;
SuggestMode mode = SuggestMode::New;
};
void ChooseSuggestTimeBox(
not_null<Ui::GenericBox*> box,
@ -39,22 +43,29 @@ struct SuggestPriceBoxArgs {
bool updating = false;
Fn<void(SuggestPostOptions)> done;
SuggestPostOptions value;
SuggestMode mode = SuggestMode::New;
};
void ChooseSuggestPriceBox(
not_null<Ui::GenericBox*> box,
SuggestPriceBoxArgs &&args);
[[nodiscard]] bool CanEditSuggestedMessage(not_null<HistoryItem*> item);
class SuggestOptions final {
public:
SuggestOptions(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
SuggestPostOptions values);
SuggestPostOptions values,
SuggestMode mode);
~SuggestOptions();
void paintBar(QPainter &p, int x, int y, int outerWidth);
void edit();
void paintIcon(QPainter &p, int x, int y, int outerWidth);
void paintLines(QPainter &p, int x, int y, int outerWidth);
[[nodiscard]] SuggestPostOptions values() const;
[[nodiscard]] rpl::producer<> updates() const;
@ -68,6 +79,7 @@ private:
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
const SuggestMode _mode = SuggestMode::New;
Ui::Text::String _title;
Ui::Text::String _text;

View file

@ -316,38 +316,32 @@ auto GenerateSuggestRequestMedia(
style::al_top);
auto entries = std::vector<AttributeTable::Entry>();
if (!changes || changes->price) {
entries.push_back({
(changes
? tr::lng_suggest_change_price_label
: tr::lng_suggest_action_price_label)(tr::now),
Ui::Text::Bold(suggest->stars
? tr::lng_prize_credits_amount(
tr::now,
lt_count,
suggest->stars)
: tr::lng_suggest_action_price_free(tr::now)),
});
}
if (!changes || changes->date) {
entries.push_back({
(changes
? tr::lng_suggest_change_time_label
: tr::lng_suggest_action_time_label)(tr::now),
Ui::Text::Bold(suggest->date
? Ui::FormatDateTime(base::unixtime::parse(suggest->date))
: tr::lng_suggest_action_time_any(tr::now)),
});
}
if (!entries.empty()) {
push(std::make_unique<AttributeTable>(
std::move(entries),
((changes && changes->message)
? st::chatSuggestTableMiddleMargin
: st::chatSuggestTableLastMargin),
fadedFg,
normalFg));
}
entries.push_back({
((changes && changes->price)
? tr::lng_suggest_change_price_label
: tr::lng_suggest_action_price_label)(tr::now),
Ui::Text::Bold(suggest->stars
? tr::lng_prize_credits_amount(
tr::now,
lt_count,
suggest->stars)
: tr::lng_suggest_action_price_free(tr::now)),
});
entries.push_back({
((changes && changes->date)
? tr::lng_suggest_change_time_label
: tr::lng_suggest_action_time_label)(tr::now),
Ui::Text::Bold(suggest->date
? Ui::FormatDateTime(base::unixtime::parse(suggest->date))
: tr::lng_suggest_action_time_any(tr::now)),
});
push(std::make_unique<AttributeTable>(
std::move(entries),
((changes && changes->message)
? st::chatSuggestTableMiddleMargin
: st::chatSuggestTableLastMargin),
fadedFg,
normalFg));
if (changes && changes->message) {
push(std::make_unique<TextPartColored>(
tr::lng_suggest_change_text_label(