PoC suggesting posts to channels.

This commit is contained in:
John Preston 2025-06-13 14:39:30 +04:00
parent 8dbc175c02
commit 7e5a29a5cc
42 changed files with 712 additions and 58 deletions

View file

@ -898,6 +898,8 @@ PRIVATE
history/view/history_view_sticker_toast.h
history/view/history_view_subsection_tabs.cpp
history/view/history_view_subsection_tabs.h
history/view/history_view_suggest_options.cpp
history/view/history_view_suggest_options.h
history/view/history_view_text_helper.cpp
history/view/history_view_text_helper.h
history/view/history_view_transcribe_button.cpp

View file

@ -4415,6 +4415,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_preview_reply_to" = "Reply to {name}";
"lng_preview_reply_to_quote" = "Reply to quote from {name}";
"lng_suggest_bar_title" = "Suggest a Post Below";
"lng_suggest_bar_text" = "Click to offer a price for publishing.";
"lng_suggest_bar_priced" = "{amount} for publishing anytime.";
"lng_suggest_bar_priced_dated" = "{amount} {date}";
"lng_suggest_bar_dated" = "Publish on {date}";
"lng_suggest_options_title" = "Suggest a Message";
"lng_suggest_options_price" = "Enter Price in Stars";
"lng_suggest_options_price_about" = "Choose how many Stars you want to offer {channel} to publish this message.";
"lng_suggest_options_date" = "Time";
"lng_suggest_options_date_any" = "Anytime";
"lng_suggest_options_date_about" = "Select the date and time you want the message to be published.";
"lng_suggest_options_offer" = "Offer {amount}";
"lng_reply_in_another_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat";
"lng_reply_in_author" = "Message author";

View file

@ -399,10 +399,12 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
}
}
const auto replyTo = FullReplyTo();
const auto suggest = SuggestPostOptions();
Window::PeerMenuCreatePoll(
controller,
item->history()->peer,
replyTo,
suggest,
chosen,
disabled);
} break;

View file

@ -14,13 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
MTPSuggestedPost SuggestToMTP(const std::optional<SuggestOptions> &suggest) {
MTPSuggestedPost SuggestToMTP(SuggestPostOptions suggest) {
using Flag = MTPDsuggestedPost::Flag;
return suggest
return suggest.exists
? MTP_suggestedPost(
MTP_flags(suggest->date ? Flag::f_schedule_date : Flag()),
MTP_long(suggest->stars),
MTP_int(suggest->date))
MTP_flags(suggest.date ? Flag::f_schedule_date : Flag()),
MTP_long(suggest.stars),
MTP_int(suggest.date))
: MTPSuggestedPost();
}

View file

@ -19,17 +19,7 @@ namespace Api {
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
struct SuggestOptions {
int stars = 0;
TimeId date = 0;
friend inline bool operator==(
const SuggestOptions &,
const SuggestOptions &) = default;
};
[[nodiscard]] MTPSuggestedPost SuggestToMTP(
const std::optional<SuggestOptions> &suggest);
[[nodiscard]] MTPSuggestedPost SuggestToMTP(SuggestPostOptions suggest);
struct SendOptions {
uint64 price = 0;
@ -43,7 +33,7 @@ struct SendOptions {
bool invertCaption = false;
bool hideViaBot = false;
crl::time ttlSeconds = 0;
std::optional<SuggestOptions> suggest;
SuggestPostOptions suggest;
friend inline bool operator==(
const SendOptions &,

View file

@ -239,6 +239,7 @@ void SendExistingMedia(
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, media, caption);
const auto performRequest = [=](const auto &repeatRequest) -> void {
@ -426,6 +427,7 @@ bool SendDice(MessageToSend &message) {
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, TextWithEntities(), MTP_messageMediaDice(
MTP_int(0),
MTP_string(emoji)));
@ -652,6 +654,7 @@ void SendConfirmedFile(
.postAuthor = NewMessagePostAuthor(action),
.groupedId = groupId,
.effectId = file->to.options.effectId,
.suggest = HistoryMessageSuggestInfo(file->to.options),
}, caption, media);
}

View file

@ -2170,7 +2170,7 @@ void ApiWrap::saveDraftsToCloud() {
cloudDraft->webpage,
textWithTags.text.isEmpty()),
MTP_long(0), // effect
MTPSuggestedPost() //
Api::SuggestToMTP(cloudDraft->suggest)
)).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
@ -3508,7 +3508,7 @@ void ApiWrap::forwardMessages(
.shortcutId = action.options.shortcutId,
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action),
.suggest = HistoryMessageSuggestInfo(action.options),
// forwarded messages don't have effects
//.effectId = action.options.effectId,
}, item);
@ -3603,6 +3603,7 @@ void ApiWrap::sendSharedContact(
.starsPaid = action.options.starsApproved,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, TextWithEntities(), MTP_messageMediaContact(
MTP_string(phone),
MTP_string(firstName),
@ -3986,6 +3987,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
.starsPaid = starsPaid,
.postAuthor = NewMessagePostAuthor(action),
.effectId = action.options.effectId,
.suggest = HistoryMessageSuggestInfo(action.options),
}, sending, media);
const auto done = [=](
const MTPUpdates &result,
@ -4036,7 +4038,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
mtpShortcut,
MTP_long(action.options.effectId),
MTP_long(starsPaid),
SuggestToMTP(action.options.suggest)
Api::SuggestToMTP(action.options.suggest)
), done, fail);
} else {
histories.sendPreparedMessage(
@ -4056,7 +4058,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
mtpShortcut,
MTP_long(action.options.effectId),
MTP_long(starsPaid),
SuggestToMTP(action.options.suggest)
Api::SuggestToMTP(action.options.suggest)
), done, fail);
}
isFirst = false;
@ -4392,7 +4394,7 @@ void ApiWrap::sendMediaWithRandomId(
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId),
MTP_long(starsPaid),
SuggestToMTP(options.suggest)
Api::SuggestToMTP(options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true);
if (updateRecentStickers) {
@ -4476,7 +4478,7 @@ void ApiWrap::sendMultiPaidMedia(
Data::ShortcutIdToMTP(_session, options.shortcutId),
MTP_long(options.effectId),
MTP_long(starsPaid),
SuggestToMTP(options.suggest)
Api::SuggestToMTP(options.suggest)
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (const auto album = _sendingAlbums.take(groupId)) {
const auto copy = (*album)->items;

View file

@ -1144,6 +1144,30 @@ historyGiftToUser: IconButton(historyAttach) {
icon: icon {{ "chat/input_gift", historyComposeIconFg }};
iconOver: icon {{ "chat/input_gift", historyComposeIconFgOver }};
}
historySuggestPostToggle: IconButton(historyDirectMessage) {
icon: icon{{ "menu/chat_discuss", historyComposeIconFg }};
iconOver: icon{{ "menu/chat_discuss", historyComposeIconFgOver }};
}
historySuggestIconPosition: point(12px, 12px);
suggestOptionsPrice: InputField(defaultInputField) {
textBg: transparent;
textMargins: margins(2px, 20px, 2px, 0px);
placeholderFg: placeholderFg;
placeholderFgActive: placeholderFgActive;
placeholderFgError: placeholderFgActive;
placeholderMargins: margins(2px, 0px, 2px, 0px);
placeholderScale: 0.;
placeholderFont: normalFont;
border: 0px;
borderActive: 0px;
heightMin: 32px;
style: defaultTextStyle;
}
historyAttachEmojiInner: IconButton(historyAttach) {
icon: icon {{ "chat/input_smile_face", historyComposeIconFg }};

View file

@ -21,6 +21,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h"
namespace Data {
namespace {
constexpr auto kMaxSuggestStars = 1'000'000'000;
} // namespace
WebPageDraft WebPageDraft::FromItem(not_null<HistoryItem*> item) {
const auto previewMedia = item->media();
@ -45,6 +50,7 @@ WebPageDraft WebPageDraft::FromItem(not_null<HistoryItem*> item) {
Draft::Draft(
const TextWithTags &textWithTags,
FullReplyTo reply,
SuggestPostOptions suggest,
const MessageCursor &cursor,
WebPageDraft webpage,
mtpRequestId saveRequestId)
@ -58,6 +64,7 @@ Draft::Draft(
Draft::Draft(
not_null<const Ui::InputField*> field,
FullReplyTo reply,
SuggestPostOptions suggest,
WebPageDraft webpage,
mtpRequestId saveRequestId)
: textWithTags(field->getTextWithTags())
@ -106,9 +113,22 @@ void ApplyPeerCloudDraft(
}
}, [](const auto &) {});
}
auto suggest = SuggestPostOptions();
if (!history->suggestDraftAllowed()) {
// Don't apply suggest options in unsupported chats.
} else if (const auto suggested = draft.vsuggested_post()) {
const auto &data = suggested->data();
suggest.exists = 1;
suggest.date = data.vschedule_date().value_or_empty();
suggest.stars = uint32(std::clamp(
data.vstars_amount().v,
uint64(),
uint64(kMaxSuggestStars)));
}
auto cloudDraft = std::make_unique<Draft>(
textWithTags,
replyTo,
suggest,
MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
std::move(webpage));
cloudDraft->date = date;
@ -150,18 +170,19 @@ void SetChatLinkDraft(not_null<PeerData*> peer, TextWithEntities draft) {
const auto history = peer->owner().history(peer->id);
const auto topicRootId = MsgId();
const auto monoforumPeerId = PeerId();
history->setLocalDraft(std::make_unique<Data::Draft>(
history->setLocalDraft(std::make_unique<Draft>(
textWithTags,
FullReplyTo{
.topicRootId = topicRootId,
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions(),
cursor,
Data::WebPageDraft()));
WebPageDraft()));
history->clearLocalEditDraft(topicRootId, monoforumPeerId);
history->session().changes().entryUpdated(
history,
Data::EntryUpdate::Flag::LocalDraftSet);
EntryUpdate::Flag::LocalDraftSet);
}
} // namespace Data

View file

@ -52,18 +52,21 @@ struct Draft {
Draft(
const TextWithTags &textWithTags,
FullReplyTo reply,
SuggestPostOptions suggest,
const MessageCursor &cursor,
WebPageDraft webpage,
mtpRequestId saveRequestId = 0);
Draft(
not_null<const Ui::InputField*> field,
FullReplyTo reply,
SuggestPostOptions suggest,
WebPageDraft webpage,
mtpRequestId saveRequestId = 0);
TimeId date = 0;
TextWithTags textWithTags;
FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft.
SuggestPostOptions suggest;
MessageCursor cursor;
WebPageDraft webpage;
mtpRequestId saveRequestId = 0;
@ -240,6 +243,7 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
[[nodiscard]] inline bool DraftIsNull(const Draft *draft) {
return !draft
|| (!draft->reply.messageId
&& !draft->suggest.exists
&& DraftStringIsEmpty(draft->textWithTags.text));
}

View file

@ -190,6 +190,23 @@ struct FullReplyTo {
friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
};
struct SuggestPostOptions {
uint32 exists : 1 = 0;
uint32 stars : 31 = 0;
TimeId date = 0;
explicit operator bool() const {
return exists != 0;
}
friend inline auto operator<=>(
SuggestPostOptions,
SuggestPostOptions) = default;
friend inline bool operator==(
SuggestPostOptions,
SuggestPostOptions) = default;
};
struct GlobalMsgId {
FullMsgId itemId;
uint64 sessionUniqueId = 0;

View file

@ -685,6 +685,9 @@ bool PeerData::canCreatePolls() const {
}
bool PeerData::canCreateTodoLists() const {
if (isMonoforum()) {
return false;
}
return session().premium()
&& (Data::CanSend(this, ChatRestriction::SendPolls) || isUser());
}

View file

@ -121,6 +121,7 @@ struct EntryState {
Section section = Section::History;
FilterId filterId = 0;
FullReplyTo currentReplyTo;
SuggestPostOptions currentSuggest;
friend inline auto operator<=>(
const EntryState&,

View file

@ -239,6 +239,9 @@ void History::createLocalDraftFromCloud(
draft->reply.topicRootId = topicRootId;
draft->reply.monoforumPeerId = monoforumPeerId;
if (!suggestDraftAllowed()) {
draft->suggest = SuggestPostOptions();
}
auto existing = localDraft(topicRootId, monoforumPeerId);
if (Data::DraftIsNull(existing)
|| !existing->date
@ -247,12 +250,14 @@ void History::createLocalDraftFromCloud(
setLocalDraft(std::make_unique<Data::Draft>(
draft->textWithTags,
draft->reply,
draft->suggest,
draft->cursor,
draft->webpage));
existing = localDraft(topicRootId, monoforumPeerId);
} else if (existing != draft) {
existing->textWithTags = draft->textWithTags;
existing->reply = draft->reply;
existing->suggest = draft->suggest;
existing->cursor = draft->cursor;
existing->webpage = draft->webpage;
}
@ -325,6 +330,7 @@ Data::Draft *History::createCloudDraft(
.topicRootId = topicRootId,
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions(),
MessageCursor(),
Data::WebPageDraft()));
cloudDraft(topicRootId, monoforumPeerId)->date = TimeId(0);
@ -337,18 +343,23 @@ Data::Draft *History::createCloudDraft(
setCloudDraft(std::make_unique<Data::Draft>(
fromDraft->textWithTags,
reply,
fromDraft->suggest,
fromDraft->cursor,
fromDraft->webpage));
existing = cloudDraft(topicRootId, monoforumPeerId);
} else if (existing != fromDraft) {
existing->textWithTags = fromDraft->textWithTags;
existing->reply = fromDraft->reply;
existing->suggest = fromDraft->suggest;
existing->cursor = fromDraft->cursor;
existing->webpage = fromDraft->webpage;
}
existing->date = base::unixtime::now();
existing->reply.topicRootId = topicRootId;
existing->reply.monoforumPeerId = monoforumPeerId;
if (!suggestDraftAllowed()) {
existing->suggest = SuggestPostOptions();
}
}
if (const auto thread = threadFor(topicRootId, monoforumPeerId)) {
@ -3382,6 +3393,10 @@ bool History::amMonoforumAdmin() const {
return (_flags & Flag::IsMonoforumAdmin);
}
bool History::suggestDraftAllowed() const {
return peer->isMonoforum() || !peer->amMonoforumAdmin();
}
not_null<History*> History::migrateToOrMe() const {
if (const auto to = peer->migrateTo()) {
return owner().history(to);

View file

@ -79,6 +79,7 @@ public:
void monoforumChanged(Data::SavedMessages *old);
[[nodiscard]] bool amMonoforumAdmin() const;
[[nodiscard]] bool suggestDraftAllowed() const;
[[nodiscard]] not_null<History*> migrateToOrMe() const;
[[nodiscard]] History *migrateFrom() const;

View file

@ -190,6 +190,7 @@ struct HistoryItem::CreateConfig {
TimeId editDate = 0;
HistoryMessageMarkupData markup;
HistoryMessageRepliesData replies;
HistoryMessageSuggestInfo suggest;
bool imported = false;
// For messages created from existing messages (forwarded).
@ -3841,6 +3842,9 @@ void HistoryItem::createComponents(CreateConfig &&config) {
mask |= HistoryMessageRestrictions::Bit();
}
}
if (config.suggest.exists) {
mask |= HistoryMessageSuggestedPost::Bit();
}
UpdateComponents(mask);
@ -3935,6 +3939,13 @@ void HistoryItem::createComponents(CreateConfig &&config) {
flagSensitiveContent();
}
if (const auto suggest = Get<HistoryMessageSuggestedPost>()) {
suggest->stars = config.suggest.stars;
suggest->date = config.suggest.date;
suggest->accepted = config.suggest.accepted;
suggest->rejected = config.suggest.rejected;
}
if (out() && isSending()) {
if (const auto channel = _history->peer->asMegagroup()) {
_boostsApplied = channel->mgInfo->boostsApplied;
@ -4137,6 +4148,9 @@ void HistoryItem::createComponentsHelper(HistoryItemCommonFields &&fields) {
if (fields.flags & MessageFlag::HasViews) {
config.viewsCount = 1;
}
if (fields.suggest.exists) {
config.suggest = fields.suggest;
}
createComponents(std::move(config));
}
@ -4261,6 +4275,7 @@ void HistoryItem::createComponents(const MTPDmessage &data) {
config.postAuthor = qs(data.vpost_author().value_or_empty());
config.restrictions = Data::UnavailableReason::Extract(
data.vrestriction_reason());
config.suggest = HistoryMessageSuggestInfo(data.vsuggested_post());
createComponents(std::move(config));
}

View file

@ -82,6 +82,7 @@ struct HistoryItemCommonFields {
uint64 groupedId = 0;
EffectId effectId = 0;
HistoryMessageMarkupData markup;
HistoryMessageSuggestInfo suggest;
bool ignoreForwardFrom = false;
bool ignoreForwardCaptions = false;
};

View file

@ -614,6 +614,14 @@ struct HistoryMessageFactcheck
bool requested = false;
};
struct HistoryMessageSuggestedPost
: RuntimeComponent<HistoryMessageSuggestedPost, HistoryItem> {
int stars = 0;
TimeId date = 0;
bool accepted = false;
bool rejected = false;
};
struct HistoryMessageRestrictions
: RuntimeComponent<HistoryMessageRestrictions, HistoryItem> {
std::vector<Data::UnavailableReason> reasons;

View file

@ -312,7 +312,7 @@ HistoryMessageRepliesData::HistoryMessageRepliesData(
if (!data) {
return;
}
const auto &fields = data->c_messageReplies();
const auto &fields = data->data();
if (const auto list = fields.vrecent_repliers()) {
recentRepliers.reserve(list->v.size());
for (const auto &id : list->v) {
@ -326,3 +326,31 @@ HistoryMessageRepliesData::HistoryMessageRepliesData(
isNull = false;
pts = fields.vreplies_pts().v;
}
HistoryMessageSuggestInfo::HistoryMessageSuggestInfo(
const MTPSuggestedPost *data) {
if (!data) {
return;
}
const auto &fields = data->data();
stars = fields.vstars_amount().v;
date = fields.vschedule_date().value_or_empty();
accepted = fields.is_accepted();
rejected = fields.is_rejected();
exists = true;
}
HistoryMessageSuggestInfo::HistoryMessageSuggestInfo(
const Api::SendOptions &options)
: HistoryMessageSuggestInfo(options.suggest) {
}
HistoryMessageSuggestInfo::HistoryMessageSuggestInfo(
SuggestPostOptions options) {
if (!options.exists) {
return;
}
stars = options.stars;
date = options.date;
exists = true;
}

View file

@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/flags.h"
#include "data/data_chat_participant_status.h"
namespace Api {
struct SendOptions;
} // namespace Api
namespace Data {
class Session;
} // namespace Data
@ -136,3 +140,16 @@ struct HistoryMessageRepliesData {
bool isNull = true;
int pts = 0;
};
struct HistoryMessageSuggestInfo {
HistoryMessageSuggestInfo() = default;
explicit HistoryMessageSuggestInfo(const MTPSuggestedPost *data);
explicit HistoryMessageSuggestInfo(const Api::SendOptions &options);
explicit HistoryMessageSuggestInfo(SuggestPostOptions options);
int stars = 0;
TimeId date = 0;
bool accepted = false;
bool rejected = false;
bool exists = false;
};

View file

@ -118,6 +118,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_requests_bar.h"
#include "history/view/history_view_sticker_toast.h"
#include "history/view/history_view_subsection_tabs.h"
#include "history/view/history_view_suggest_options.h"
#include "history/view/history_view_translate_bar.h"
#include "history/view/media/history_view_media.h"
#include "profile/profile_block_group_members.h"
@ -1039,6 +1040,7 @@ Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const {
.key = _history,
.section = Dialogs::EntryState::Section::History,
.currentReplyTo = replyTo(),
.currentSuggest = suggestOptions(),
};
}
@ -1900,13 +1902,16 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() {
.topicRootId = topicRootId,
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions(),
_preview->draft(),
_saveEditMsgRequestId));
} else {
if (_replyTo || !_field->empty()) {
const auto suggest = suggestOptions();
if (_replyTo || suggest.exists || !_field->empty()) {
_history->setLocalDraft(std::make_unique<Data::Draft>(
_field,
_replyTo,
suggest,
_preview->draft()));
} else {
_history->clearLocalDraft(topicRootId, monoforumPeerId);
@ -2270,6 +2275,8 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
if (_processingReplyTo) {
_processingReplyItem = session().data().message(
_processingReplyTo.messageId);
} else if (draft && draft->suggest) {
applySuggestOptions(draft->suggest);
}
processReply();
}
@ -2480,6 +2487,7 @@ void HistoryWidget::showHistory(
setHistory(nullptr);
_list = nullptr;
_peer = nullptr;
_suggestOptions = nullptr;
_sendPayment.clear();
_topicsRequested.clear();
_canSendMessages = false;
@ -2606,6 +2614,7 @@ void HistoryWidget::showHistory(
} else if (_peer->isRepliesChat() || _peer->isVerifyCodes()) {
updateNotifyControls();
}
refreshSuggestPostToggle();
refreshScheduledToggle();
refreshSendGiftToggle();
refreshSendAsToggle();
@ -2917,6 +2926,7 @@ void HistoryWidget::registerDraftSource() {
(editMsgId
? FullReplyTo{ FullMsgId(peerId, editMsgId) }
: _replyTo),
(editMsgId ? SuggestPostOptions() : suggestOptions()),
_field->getTextWithTags(),
_preview->draft(),
};
@ -3154,6 +3164,41 @@ void HistoryWidget::refreshSendGiftToggle() {
}
}
void HistoryWidget::applySuggestOptions(SuggestPostOptions suggest) {
Expects(suggest.exists);
using namespace HistoryView;
_suggestOptions = std::make_unique<SuggestOptions>(
controller(),
_peer,
suggest);
_suggestOptions->repaints() | rpl::start_with_next([=] {
updateField();
}, _suggestOptions->lifetime());
}
void HistoryWidget::refreshSuggestPostToggle() {
const auto has = _peer
&& _peer->isMonoforum()
&& !_peer->amMonoforumAdmin();
if (!_toggleSuggestPost && has) {
_toggleSuggestPost.create(this, st::historySuggestPostToggle);
_toggleSuggestPost->setVisible(!_suggestOptions);
_toggleSuggestPost->addClickHandler([=] {
applySuggestOptions({ .exists = 1 });
cancelReply();
_processingReplyTo = FullReplyTo();
_processingReplyItem = nullptr;
updateControlsVisibility();
updateControlsGeometry();
});
orderWidgets();
} else if (_toggleSuggestPost && !has) {
_toggleSuggestPost.destroy();
cancelSuggestPost();
}
}
void HistoryWidget::setupSendAsToggle() {
session().sendAsPeers().updated(
) | rpl::filter([=](not_null<PeerData*> peer) {
@ -3294,6 +3339,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_scheduled) {
_scheduled->hide();
}
if (_toggleSuggestPost) {
_toggleSuggestPost->hide();
}
if (_giftToUser) {
_giftToUser->hide();
}
@ -3408,6 +3456,14 @@ void HistoryWidget::updateControlsVisibility() {
rightButtonsChanged = true;
}
}
if (_toggleSuggestPost) {
const auto was = _toggleSuggestPost->isVisible();
const auto now = !_suggestOptions;
if (was != now) {
_toggleSuggestPost->setVisible(now);
rightButtonsChanged = true;
}
}
if (_giftToUser) {
const auto was = _giftToUser->isVisible();
const auto now = (!_editMsgId) && (!hideExtraButtons);
@ -3437,7 +3493,8 @@ void HistoryWidget::updateControlsVisibility() {
|| _replyTo
|| readyToForward()
|| _previewDrawPreview
|| _kbReplyTo) {
|| _kbReplyTo
|| _suggestOptions) {
if (_fieldBarCancel->isHidden()) {
_fieldBarCancel->show();
updateControlsGeometry();
@ -3466,6 +3523,9 @@ void HistoryWidget::updateControlsVisibility() {
if (_scheduled) {
_scheduled->hide();
}
if (_toggleSuggestPost) {
_toggleSuggestPost->hide();
}
if (_giftToUser) {
_giftToUser->hide();
}
@ -4503,6 +4563,7 @@ Api::SendAction HistoryWidget::prepareSendAction(
Api::SendOptions options) const {
auto result = Api::SendAction(_history, options);
result.replyTo = replyTo();
result.options.suggest = suggestOptions();
result.options.sendAs = _sendAs
? _history->session().sendAsPeers().resolveChosen(
_history->peer).get()
@ -5040,7 +5101,11 @@ void HistoryWidget::updateOverStates(QPoint pos) {
st::historyReplyHeight);
const auto hasWebPage = !!_previewDrawPreview;
const auto inDetails = detailsRect.contains(pos)
&& (_editMsgId || replyTo() || isReadyToForward || hasWebPage);
&& (_editMsgId
|| replyTo()
|| isReadyToForward
|| hasWebPage
|| _suggestOptions);
const auto inPhotoEdit = inDetails
&& _photoEditMedia
&& QRect(
@ -5585,7 +5650,8 @@ void HistoryWidget::toggleKeyboard(bool manual) {
if (!readyToForward()
&& !_previewDrawPreview
&& !_editMsgId
&& !_replyTo) {
&& !_replyTo
&& !_suggestOptions) {
_fieldBarCancel->hide();
updateMouseTracking();
}
@ -5806,7 +5872,7 @@ void HistoryWidget::moveFieldControls() {
}
// (_botMenu.button) (_attachToggle|_replaceMedia) (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel
// (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_giftToUser) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send
// (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_giftToUser) (_silent|_cmdStart|_kbShow) (_toggleSuggestPost) (_kbHide|_tabbedSelectorToggle) _send
// (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages)
auto buttonsBottom = bottom - _attachToggle->height();
@ -5848,6 +5914,10 @@ void HistoryWidget::moveFieldControls() {
if (kbShowShown || _cmdStartShown || _silent) {
right += _botCommandStart->width();
}
if (_toggleSuggestPost) {
_toggleSuggestPost->moveToRight(right, buttonsBottom);
right += _toggleSuggestPost->width();
}
if (_giftToUser) {
_giftToUser->moveToRight(right, buttonsBottom);
right += _giftToUser->width();
@ -5912,6 +5982,9 @@ void HistoryWidget::updateFieldSize() {
if (_silent && !_silent->isHidden()) {
fieldWidth -= _silent->width();
}
if (_toggleSuggestPost && !_toggleSuggestPost->isHidden()) {
fieldWidth -= _toggleSuggestPost->width();
}
if (_giftToUser && !_giftToUser->isHidden()) {
fieldWidth -= _giftToUser->width();
}
@ -6590,6 +6663,12 @@ FullReplyTo HistoryWidget::replyTo() const {
: FullReplyTo();
}
SuggestPostOptions HistoryWidget::suggestOptions() const {
return (_history && _history->suggestDraftAllowed() && _suggestOptions)
? _suggestOptions->values()
: SuggestPostOptions();
}
bool HistoryWidget::hasSavedScroll() const {
Expects(_history != nullptr);
@ -6797,7 +6876,8 @@ void HistoryWidget::updateHistoryGeometry(
if (_editMsgId
|| replyTo()
|| readyToForward()
|| _previewDrawPreview) {
|| _previewDrawPreview
|| _suggestOptions) {
newScrollHeight -= st::historyReplyHeight;
}
if (_kbShown) {
@ -7143,7 +7223,8 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
_kbReplyTo = nullptr;
if (!readyToForward()
&& !_previewDrawPreview
&& !_replyTo) {
&& !_replyTo
&& !_suggestOptions) {
_fieldBarCancel->hide();
updateMouseTracking();
}
@ -7162,7 +7243,8 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
if (!readyToForward()
&& !_previewDrawPreview
&& !_replyTo
&& !_editMsgId) {
&& !_editMsgId
&& !_suggestOptions) {
_fieldBarCancel->hide();
updateMouseTracking();
}
@ -7316,6 +7398,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
_kbReplyTo->history()->peer->id,
Window::SectionShow::Way::Forward,
_kbReplyTo->id);
} else if (_suggestOptions) {
_suggestOptions->edit();
}
}
@ -7324,6 +7408,7 @@ void HistoryWidget::editDraftOptions() {
const auto history = _history;
const auto reply = _replyTo;
const auto suggest = suggestOptions();
const auto webpage = _preview->draft();
const auto forward = _forwardPanel->draft();
@ -7348,7 +7433,7 @@ void HistoryWidget::editDraftOptions() {
EditDraftOptions({
.show = controller()->uiShow(),
.history = history,
.draft = Data::Draft(_field, reply, _preview->draft()),
.draft = Data::Draft(_field, reply, suggest, _preview->draft()),
.usedLink = _preview->link(),
.forward = _forwardPanel->draft(),
.links = _preview->links(),
@ -8465,7 +8550,9 @@ void HistoryWidget::processReply() {
if (!_peer || !_processingReplyTo) {
return processCancel();
} else if (!_processingReplyItem) {
}
cancelSuggestPost();
if (!_processingReplyItem) {
session().api().requestMessageData(
session().data().peer(_processingReplyTo.messageId.peer),
_processingReplyTo.messageId.msg,
@ -8524,16 +8611,19 @@ void HistoryWidget::setReplyFieldsFromProcessing() {
if (_editMsgId) {
if (const auto localDraft = _history->localDraft({}, {})) {
localDraft->reply = id;
localDraft->suggest = SuggestPostOptions();
} else {
_history->setLocalDraft(std::make_unique<Data::Draft>(
TextWithTags(),
id,
SuggestPostOptions(),
MessageCursor(),
Data::WebPageDraft()));
}
} else {
_replyEditMsg = item;
_replyTo = id;
cancelSuggestPost();
updateReplyEditText(_replyEditMsg);
updateCanSendMessage();
updateBotKeyboard();
@ -8573,10 +8663,12 @@ void HistoryWidget::editMessage(
_send->clearState();
}
if (!_editMsgId) {
if (_replyTo || !_field->empty()) {
const auto suggest = suggestOptions();
if (_replyTo || suggest.exists || !_field->empty()) {
_history->setLocalDraft(std::make_unique<Data::Draft>(
_field,
_replyTo,
suggest,
_preview->draft()));
} else {
_history->clearLocalDraft(MsgId(), PeerId());
@ -8593,6 +8685,7 @@ void HistoryWidget::editMessage(
_history->setLocalEditDraft(std::make_unique<Data::Draft>(
editData,
FullReplyTo{ item->fullId() },
SuggestPostOptions(),
cursor,
previewDraft));
applyDraft();
@ -8679,7 +8772,8 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
mouseMoveEvent(0);
if (!readyToForward()
&& !_previewDrawPreview
&& !_kbReplyTo) {
&& !_kbReplyTo
&& !_suggestOptions) {
_fieldBarCancel->hide();
updateMouseTracking();
}
@ -8754,7 +8848,8 @@ void HistoryWidget::cancelEdit() {
mouseMoveEvent(nullptr);
if (!readyToForward()
&& !_previewDrawPreview
&& !replyTo()) {
&& !replyTo()
&& !_suggestOptions) {
_fieldBarCancel->hide();
updateMouseTracking();
}
@ -8784,9 +8879,21 @@ void HistoryWidget::cancelFieldAreaState() {
_history->setForwardDraft(MsgId(), PeerId(), {});
} else if (_kbReplyTo) {
toggleKeyboard();
} else if (_suggestOptions) {
cancelSuggestPost();
}
}
bool HistoryWidget::cancelSuggestPost() {
if (!_suggestOptions) {
return false;
}
_suggestOptions = nullptr;
updateControlsVisibility();
updateControlsGeometry();
return true;
}
void HistoryWidget::fullInfoUpdated() {
auto refresh = false;
if (_list) {
@ -8891,6 +8998,7 @@ bool HistoryWidget::updateCanSendMessage() {
if (!_canSendMessages) {
cancelReply();
}
refreshSuggestPostToggle();
refreshScheduledToggle();
refreshSendGiftToggle();
refreshSilentToggle();
@ -9190,13 +9298,12 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
auto backh = fieldHeight() + 2 * st::historySendPadding;
auto hasForward = readyToForward();
auto drawMsgText = (_editMsgId || _replyTo) ? _replyEditMsg : _kbReplyTo;
if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) {
backy -= st::historyReplyHeight;
backh += st::historyReplyHeight;
} else if (hasForward) {
backy -= st::historyReplyHeight;
backh += st::historyReplyHeight;
} else if (_previewDrawPreview) {
if (_editMsgId
|| _replyTo
|| hasForward
|| _kbReplyTo
|| _previewDrawPreview
|| _suggestOptions) {
backy -= st::historyReplyHeight;
backh += st::historyReplyHeight;
}
@ -9361,6 +9468,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
- _fieldBarCancel->width()
- st::msgReplyPadding.right();
_forwardPanel->paint(p, x, backy, available, width());
} else if (_suggestOptions) {
_suggestOptions->paintBar(p, 0, backy, width());
}
}
@ -9462,7 +9571,8 @@ void HistoryWidget::paintEvent(QPaintEvent *e) {
if (restrictionHidden
|| replyTo()
|| readyToForward()
|| _kbShown) {
|| _kbShown
|| _suggestOptions) {
if (!isSearching()) {
drawField(p, clip);
}

View file

@ -109,6 +109,7 @@ class TranslateBar;
class ComposeSearch;
class SubsectionTabs;
struct SelectedQuote;
class SuggestOptions;
} // namespace HistoryView
namespace HistoryView::Controls {
@ -214,9 +215,11 @@ public:
not_null<PeerData*> peer);
[[nodiscard]] FullReplyTo replyTo() const;
[[nodiscard]] SuggestPostOptions suggestOptions() const;
bool lastForceReplyReplied(const FullMsgId &replyTo) const;
bool lastForceReplyReplied() const;
bool cancelReply(bool lastKeyboardUsed = false);
bool cancelSuggestPost();
void cancelEdit();
void updateForwarding();
@ -676,6 +679,8 @@ private:
void setupScheduledToggle();
void refreshScheduledToggle();
void refreshSendGiftToggle();
void refreshSuggestPostToggle();
void applySuggestOptions(SuggestPostOptions suggest);
void setupSendAsToggle();
void refreshSendAsToggle();
void refreshAttachBotsMenu();
@ -709,6 +714,8 @@ private:
std::unique_ptr<Ui::SpoilerAnimation> _replySpoiler;
mutable base::Timer _updateEditTimeLeftDisplay;
std::unique_ptr<HistoryView::SuggestOptions> _suggestOptions;
object_ptr<Ui::IconButton> _fieldBarCancel;
std::unique_ptr<Ui::RpWidget> _topBars;
@ -821,6 +828,7 @@ private:
object_ptr<Ui::IconButton> _botKeyboardShow;
object_ptr<Ui::IconButton> _botKeyboardHide;
object_ptr<Ui::IconButton> _botCommandStart;
object_ptr<Ui::IconButton> _toggleSuggestPost = { nullptr };
object_ptr<Ui::IconButton> _giftToUser = { nullptr };
object_ptr<Ui::SilentToggle> _silent = { nullptr };
object_ptr<Ui::IconButton> _scheduled = { nullptr };

View file

@ -1006,6 +1006,7 @@ void ComposeControls::setCurrentDialogsEntryState(
unregisterDraftSources();
state.currentReplyTo.topicRootId = _topicRootId;
state.currentReplyTo.monoforumPeerId = _monoforumPeerId;
state.currentSuggest = SuggestPostOptions();
_currentDialogsEntryState = state;
updateForwarding();
registerDraftSource();
@ -1299,7 +1300,11 @@ void ComposeControls::saveFieldToHistoryLocalDraft() {
const auto key = draftKeyCurrent();
_history->setDraft(
key,
std::make_unique<Data::Draft>(_field, id, _preview->draft()));
std::make_unique<Data::Draft>(
_field,
id,
SuggestPostOptions(),
_preview->draft()));
} else {
_history->clearDraft(draftKeyCurrent());
}
@ -1414,6 +1419,7 @@ void ComposeControls::init() {
const auto topicRootId = _topicRootId;
const auto monoforumPeerId = _monoforumPeerId;
const auto reply = _header->replyingToMessage();
const auto suggest = SuggestPostOptions();
const auto webpage = _preview->draft();
const auto done = [=](
@ -1441,7 +1447,7 @@ void ComposeControls::init() {
EditDraftOptions({
.show = _show,
.history = history,
.draft = Data::Draft(_field, reply, _preview->draft()),
.draft = Data::Draft(_field, reply, suggest, _preview->draft()),
.usedLink = _preview->link(),
.forward = _header->forwardDraft(),
.links = _preview->links(),
@ -1891,6 +1897,7 @@ void ComposeControls::registerDraftSource() {
const auto draft = [=] {
return Storage::MessageDraft{
_header->getDraftReply(),
SuggestPostOptions(),
_field->getTextWithTags(),
_preview->draft(),
};
@ -2980,6 +2987,7 @@ void ComposeControls::editMessage(not_null<HistoryItem*> item) {
.topicRootId = key.topicRootId(),
.monoforumPeerId = key.monoforumPeerId(),
},
SuggestPostOptions(),
cursor,
Data::WebPageDraft::FromItem(item)));
applyDraft();
@ -3076,6 +3084,7 @@ void ComposeControls::replyToMessage(FullReplyTo id) {
std::make_unique<Data::Draft>(
TextWithTags(),
id,
SuggestPostOptions(),
MessageCursor(),
Data::WebPageDraft()));
}

View file

@ -1386,6 +1386,7 @@ void ShowReplyToChatBox(
history->setLocalDraft(std::make_unique<Data::Draft>(
textWithTags,
reply,
SuggestPostOptions(),
cursor,
Data::WebPageDraft()));
history->clearLocalEditDraft(topicRootId, monoforumPeerId);

View file

@ -353,6 +353,7 @@ void ClearDraftReplyTo(
.topicRootId = topicRootId,
.monoforumPeerId = monoforumPeerId,
};
draft.suggest = SuggestPostOptions();
if (Data::DraftIsNull(&draft)) {
history->clearLocalDraft(topicRootId, monoforumPeerId);
} else {

View file

@ -1830,6 +1830,7 @@ void ChatWidget::refreshTopBarActiveChat() {
? EntryState::Section::SavedSublist
: EntryState::Section::Replies,
.currentReplyTo = replyTo(),
.currentSuggest = SuggestPostOptions(),
};
_topBar->setActiveChat(state, _sendAction.get());
_composeControls->setCurrentDialogsEntryState(state);

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_message.h"
#include "base/unixtime.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "core/ui_integration.h"
#include "history/view/history_view_cursor_state.h"
@ -454,6 +455,20 @@ Message::~Message() {
void Message::initPaidInformation() {
const auto item = data();
if (!item->history()->peer->isUser()) {
if (const auto suggest = item->Get<HistoryMessageSuggestedPost>()) {
if (!suggest->stars && !suggest->date) {
setServicePreMessage({ { u"suggestion to publish for free anytime"_q } });
} else if (!suggest->date) {
setServicePreMessage({ { u"suggestion to publish for %1 stars anytime"_q.arg(suggest->stars) }});
} else if (!suggest->stars) {
setServicePreMessage({ { u"suggestion to publish for free %1"_q.arg(langDateTime(base::unixtime::parse(suggest->date))) }});
} else {
setServicePreMessage({ { u"suggestion to publish for %1 stars %2"_q.arg(suggest->stars).arg(langDateTime(base::unixtime::parse(suggest->date))) } });
}
}
return;
}
const auto media = this->media();

View file

@ -0,0 +1,237 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_suggest_options.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/buttons.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_settings.h"
namespace HistoryView {
namespace {
struct EditOptionsArgs {
int starsLimit = 0;
QString channelName;
SuggestPostOptions values;
Fn<void(SuggestPostOptions)> save;
};
void EditOptionsBox(
not_null<Ui::GenericBox*> box,
EditOptionsArgs &&args) {
struct State {
rpl::variable<TimeId> date;
};
const auto state = box->lifetime().make_state<State>();
state->date = args.values.date;
box->setTitle(tr::lng_suggest_options_title());
const auto container = box->verticalLayout();
Ui::AddSkip(container);
Ui::AddSubsectionTitle(container, tr::lng_suggest_options_price());
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
box,
st::editTagField.heightMin));
auto owned = object_ptr<Ui::NumberInput>(
wrap,
st::editTagField,
tr::lng_paid_cost_placeholder(),
args.values.stars ? QString::number(args.values.stars) : QString(),
args.starsLimit);
const auto field = owned.data();
wrap->widthValue() | rpl::start_with_next([=](int width) {
field->move(0, 0);
field->resize(width, field->height());
wrap->resize(width, field->height());
}, wrap->lifetime());
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(field);
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
}, field->lifetime());
field->selectAll();
box->setFocusCallback([=] {
field->setFocusFast();
});
Ui::AddSkip(container);
Ui::AddSkip(container);
Ui::AddDividerText(
container,
tr::lng_suggest_options_price_about(
lt_channel,
rpl::single(args.channelName)));
Ui::AddSkip(container);
const auto time = Settings::AddButtonWithLabel(
container,
tr::lng_suggest_options_date(),
state->date.value() | rpl::map([](TimeId date) {
return date
? langDateTime(base::unixtime::parse(date))
: tr::lng_suggest_options_date_any(tr::now);
}),
st::settingsButtonNoIcon);
time->setClickedCallback([=] {
box->uiShow()->show(Box(Ui::ChooseDateTimeBox, Ui::ChooseDateTimeBoxArgs{
.title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(),
.done = [=](TimeId result) { state->date = result; },
.min = [] { return base::unixtime::now() + 1; },
.time = (state->date.current()
? state->date.current()
: (base::unixtime::now() + 86400)),
}));
});
Ui::AddSkip(container);
Ui::AddDividerText(container, tr::lng_suggest_options_date_about());
AssertIsDebug()//tr::lng_suggest_options_offer
const auto save = [=] {
const auto now = uint32(field->getLastText().toULongLong());
if (now > args.starsLimit) {
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();
}
};
QObject::connect(field, &Ui::NumberInput::submitted, box, save);
box->addButton(tr::lng_settings_save(), save);
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
} // namespace
SuggestOptions::SuggestOptions(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
SuggestPostOptions values)
: _controller(controller)
, _peer(peer)
, _values(values) {
updateTexts();
}
SuggestOptions::~SuggestOptions() = default;
void SuggestOptions::paintBar(QPainter &p, int x, int y, int outerWidth) {
st::historyDirectMessage.icon.paint(
p,
QPoint(x, y) + st::historySuggestIconPosition,
outerWidth);
x += st::historyReplySkip;
auto available = outerWidth
- x
- st::historyReplyCancel.width
- st::msgReplyPadding.right();
p.setPen(st::windowActiveTextFg);
_title.draw(p, {
.position = QPoint(x, y + st::msgReplyPadding.top()),
.availableWidth = available,
});
p.setPen(st::windowSubTextFg);
_text.draw(p, {
.position = QPoint(
x,
y + st::msgReplyPadding.top() + st::msgServiceNameFont->height),
.availableWidth = available,
});
}
void SuggestOptions::edit() {
const auto apply = [=](SuggestPostOptions values) {
_values = values;
updateTexts();
_repaints.fire({});
};
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,
}));
}
void SuggestOptions::updateTexts() {
_title.setText(
st::semiboldTextStyle,
tr::lng_suggest_bar_title(tr::now));
_text.setMarkedText(st::defaultTextStyle, composeText());
}
TextWithEntities SuggestOptions::composeText() const {
if (!_values.stars && !_values.date) {
return tr::lng_suggest_bar_text(tr::now, Ui::Text::WithEntities);
} else if (!_values.date) {
return tr::lng_suggest_bar_priced(
tr::now,
lt_amount,
TextWithEntities{ QString::number(_values.stars) + " stars" },
Ui::Text::WithEntities);
} else if (!_values.stars) {
return tr::lng_suggest_bar_dated(
tr::now,
lt_date,
TextWithEntities{
langDateTime(base::unixtime::parse(_values.date)),
},
Ui::Text::WithEntities);
}
return tr::lng_suggest_bar_priced_dated(
tr::now,
lt_amount,
TextWithEntities{ QString::number(_values.stars) + " stars," },
lt_date,
TextWithEntities{
langDateTime(base::unixtime::parse(_values.date)),
},
Ui::Text::WithEntities);
}
SuggestPostOptions SuggestOptions::values() const {
auto result = _values;
result.exists = 1;
return result;
}
rpl::producer<> SuggestOptions::repaints() const {
return _repaints.events();
}
rpl::lifetime &SuggestOptions::lifetime() {
return _lifetime;
}
} // namespace HistoryView

View file

@ -0,0 +1,53 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "api/api_common.h"
namespace Window {
class SessionController;
} // namespace Window
namespace HistoryView {
class SuggestOptions final {
public:
SuggestOptions(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
SuggestPostOptions values);
~SuggestOptions();
void paintBar(QPainter &p, int x, int y, int outerWidth);
void edit();
[[nodiscard]] SuggestPostOptions values() const;
[[nodiscard]] rpl::producer<> repaints() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
void updateTexts();
[[nodiscard]] TextWithEntities composeText() const;
const not_null<Window::SessionController*> _controller;
const not_null<PeerData*> _peer;
Ui::Text::String _title;
Ui::Text::String _text;
SuggestPostOptions _values;
rpl::event_stream<> _repaints;
rpl::lifetime _lifetime;
};
} // namespace HistoryView

View file

@ -359,6 +359,7 @@ WebViewContext ResolveContext(
if (const auto thread = state.key.thread()) {
context.action = Api::SendAction(thread);
context.action->replyTo = state.currentReplyTo;
context.action->options.suggest = state.currentSuggest;
} else {
context.action = Api::SendAction(bot->owner().history(bot));
}
@ -373,6 +374,7 @@ WebViewContext ResolveContext(
.key = (topic ? Key{ topic } : Key{ history }),
.section = (topic ? Section::Replies : Section::History),
.currentReplyTo = context.action->replyTo,
.currentSuggest = context.action->options.suggest,
};
}
return context;
@ -2615,11 +2617,11 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
? SendMenu::Type::SilentOnly
: SendMenu::Type::Scheduled;
const auto flag = PollData::Flags();
const auto replyTo = action.replyTo;
Window::PeerMenuCreatePoll(
controller,
peer,
replyTo,
action.replyTo,
action.options.suggest,
flag,
flag,
source,
@ -2637,11 +2639,11 @@ std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|| action.history->peer->starsPerMessageChecked())
? SendMenu::Type::SilentOnly
: SendMenu::Type::Scheduled;
const auto replyTo = action.replyTo;
Window::PeerMenuCreateTodoList(
controller,
peer,
replyTo,
action.replyTo,
action.options.suggest,
source,
{ sendMenuType });
}, &st::menuIconCreateTodoList);

View file

@ -162,6 +162,10 @@ int AppConfig::todoListItemTextLimit() const {
return get<int>(u"todo_item_length_max"_q, 64);
}
int AppConfig::suggestedPostStarsMax() const {
return get<int>(u"stars_suggested_post_amount_max"_q, 100'000);
}
void AppConfig::refresh(bool force) {
if (_requestId || !_api) {
if (force) {

View file

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

View file

@ -606,6 +606,7 @@ bool MainWidget::shareUrl(
.topicRootId = topicRootId,
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions(),
cursor,
Data::WebPageDraft()));
history->clearLocalEditDraft(topicRootId, monoforumPeerId);

View file

@ -174,7 +174,7 @@ namespace Media::Stories {
Data::ShortcutIdToMTP(session, options.shortcutId),
MTP_long(options.effectId),
MTP_long(starsPaid),
SuggestToMTP(options.suggest)
Api::SuggestToMTP(options.suggest)
), [=](
const MTPUpdates &result,
const MTP::Response &response) {

View file

@ -632,6 +632,7 @@ void ShortcutMessages::setupComposeControls() {
.key = Dialogs::Key{ _history },
.section = Dialogs::EntryState::Section::ShortcutMessages,
.currentReplyTo = replyTo(),
.currentSuggest = SuggestPostOptions(),
};
_composeControls->setCurrentDialogsEntryState(state);

View file

@ -67,6 +67,7 @@ constexpr auto kMultiDraftCursorsTagOld = quint64(0xFFFF'FFFF'FFFF'FF02ULL);
constexpr auto kMultiDraftTag = quint64(0xFFFF'FFFF'FFFF'FF03ULL);
constexpr auto kMultiDraftCursorsTag = quint64(0xFFFF'FFFF'FFFF'FF04ULL);
constexpr auto kRichDraftsTag = quint64(0xFFFF'FFFF'FFFF'FF05ULL);
constexpr auto kDraftsTag2 = quint64(0xFFFF'FFFF'FFFF'FF06ULL);
enum { // Local Storage Keys
lskUserMap = 0x00,
@ -1187,6 +1188,7 @@ void EnumerateDrafts(
callback(
key,
draft->reply,
draft->suggest,
draft->textWithTags,
draft->webpage,
draft->cursor);
@ -1200,6 +1202,7 @@ void EnumerateDrafts(
callback(
key,
draft.reply,
draft.suggest,
draft.textWithTags,
draft.webpage,
cursor);
@ -1265,6 +1268,7 @@ void Account::writeDrafts(not_null<History*> history) {
const auto sizeCallback = [&](
auto&&, // key
const FullReplyTo &reply,
SuggestPostOptions suggest,
const TextWithTags &text,
const Data::WebPageDraft &webpage,
auto&&) { // cursor
@ -1272,6 +1276,7 @@ void Account::writeDrafts(not_null<History*> history) {
+ Serialize::stringSize(text.text)
+ TextUtilities::SerializeTagsSize(text.tags)
+ sizeof(qint64) + sizeof(qint64) // messageId
+ sizeof(quint64) // suggest
+ Serialize::stringSize(webpage.url)
+ sizeof(qint32) // webpage.forceLargeMedia
+ sizeof(qint32) // webpage.forceSmallMedia
@ -1287,13 +1292,14 @@ void Account::writeDrafts(not_null<History*> history) {
EncryptedDescriptor data(size);
data.stream
<< quint64(kRichDraftsTag)
<< quint64(kDraftsTag2)
<< SerializePeerId(peerId)
<< quint32(count);
const auto writeCallback = [&](
const Data::DraftKey &key,
const FullReplyTo &reply,
SuggestPostOptions suggest,
const TextWithTags &text,
const Data::WebPageDraft &webpage,
auto&&) { // cursor
@ -1303,6 +1309,9 @@ void Account::writeDrafts(not_null<History*> history) {
<< TextUtilities::SerializeTags(text.tags)
<< qint64(reply.messageId.peer.value)
<< qint64(reply.messageId.msg.bare)
<< quint64(quint64(quint32(suggest.date))
| (quint64(suggest.stars) << 32)
| (quint64(suggest.exists) << 63))
<< webpage.url
<< qint32(webpage.forceLargeMedia ? 1 : 0)
<< qint32(webpage.forceSmallMedia ? 1 : 0)
@ -1359,6 +1368,7 @@ void Account::writeDraftCursors(not_null<History*> history) {
const auto writeCallback = [&](
const Data::DraftKey &key,
auto&&, // reply
auto&&, // suggest
auto&&, // text
auto&&, // webpage
const MessageCursor &cursor) { // cursor
@ -1519,12 +1529,14 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
}
auto map = Data::HistoryDrafts();
const auto keysOld = (tag == kMultiDraftTagOld);
const auto rich = (tag == kRichDraftsTag);
const auto withSuggest = (tag == kDraftsTag2);
const auto rich = (tag == kRichDraftsTag) || withSuggest;
for (auto i = 0; i != count; ++i) {
TextWithTags text;
QByteArray textTagsSerialized;
qint64 keyValue = 0;
qint64 messageIdPeer = 0, messageIdMsg = 0;
quint64 suggestSerialized = 0;
qint32 keyValueOld = 0;
QString webpageUrl;
qint32 webpageForceLargeMedia = 0;
@ -1558,7 +1570,11 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
>> text.text
>> textTagsSerialized
>> messageIdPeer
>> messageIdMsg
>> messageIdMsg;
if (withSuggest) {
draft.stream >> suggestSerialized;
}
draft.stream
>> webpageUrl
>> webpageForceLargeMedia
>> webpageForceSmallMedia
@ -1581,6 +1597,13 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
MsgId(messageIdMsg)),
.topicRootId = key.topicRootId(),
},
SuggestPostOptions{
.exists = uint32(suggestSerialized >> 63),
.stars = uint32(
(suggestSerialized & ~(1ULL << 63)) >> 32),
.date = TimeId(
uint32(suggestSerialized & 0xFFFF'FFFFULL)),
},
MessageCursor(),
Data::WebPageDraft{
.url = webpageUrl,
@ -1654,6 +1677,7 @@ void Account::readDraftsWithCursorsLegacy(
std::make_unique<Data::Draft>(
msgData,
FullReplyTo{ FullMsgId(peerId, MsgId(msgReplyTo)) },
SuggestPostOptions(),
MessageCursor(),
Data::WebPageDraft{
.removed = (msgPreviewCancelled == 1),
@ -1665,6 +1689,7 @@ void Account::readDraftsWithCursorsLegacy(
std::make_unique<Data::Draft>(
editData,
FullReplyTo{ FullMsgId(peerId, editMsgId) },
SuggestPostOptions(),
MessageCursor(),
Data::WebPageDraft{
.removed = (editPreviewCancelled == 1),

View file

@ -53,6 +53,7 @@ enum class StartResult : uchar;
struct MessageDraft {
FullReplyTo reply;
SuggestPostOptions suggest;
TextWithTags textWithTags;
Data::WebPageDraft webpage;
};

View file

@ -166,6 +166,7 @@ Data::Draft OccupiedDraft(const QString &normalizedName) {
+ ";n:"
+ normalizedName },
FullReplyTo(),
SuggestPostOptions(),
MessageCursor(),
Data::WebPageDraft()
};

View file

@ -1177,6 +1177,7 @@ void Manager::notificationActivated(
.topicRootId = topicRootId,
.monoforumPeerId = monoforumPeerId,
},
SuggestPostOptions(),
MessageCursor{
length,
length,

View file

@ -1224,11 +1224,13 @@ void Filler::addCreatePoll() {
: SendMenu::Type::Scheduled;
const auto flag = PollData::Flags();
const auto replyTo = _request.currentReplyTo;
const auto suggest = _request.currentSuggest;
auto callback = [=] {
PeerMenuCreatePoll(
controller,
peer,
replyTo,
suggest,
flag,
flag,
source,
@ -1263,11 +1265,13 @@ void Filler::addCreateTodoList() {
? SendMenu::Type::SilentOnly
: SendMenu::Type::Scheduled;
const auto replyTo = _request.currentReplyTo;
const auto suggest = _request.currentSuggest;
auto callback = [=] {
PeerMenuCreateTodoList(
controller,
peer,
replyTo,
suggest,
source,
{ sendMenuType });
};
@ -1852,6 +1856,7 @@ void PeerMenuCreatePoll(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
FullReplyTo replyTo,
SuggestPostOptions suggest,
PollData::Flags chosen,
PollData::Flags disabled,
Api::SendType sendType,
@ -1902,6 +1907,7 @@ void PeerMenuCreatePoll(
peer->owner().history(peer),
result.options);
action.replyTo = replyTo;
action.options.suggest = suggest;
const auto local = action.history->localDraft(
replyTo.topicRootId,
replyTo.monoforumPeerId);
@ -1962,6 +1968,7 @@ void PeerMenuCreateTodoList(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
FullReplyTo replyTo,
SuggestPostOptions suggest,
Api::SendType sendType,
SendMenu::Details sendMenuDetails) {
if (!peer->session().premium()) {
@ -2008,6 +2015,7 @@ void PeerMenuCreateTodoList(
peer->owner().history(peer),
result.options);
action.replyTo = replyTo;
action.options.suggest = suggest;
const auto local = action.history->localDraft(
replyTo.topicRootId,
replyTo.monoforumPeerId);

View file

@ -107,6 +107,7 @@ void PeerMenuCreatePoll(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
FullReplyTo replyTo = FullReplyTo(),
SuggestPostOptions suggest = SuggestPostOptions(),
PollData::Flags chosen = PollData::Flags(),
PollData::Flags disabled = PollData::Flags(),
Api::SendType sendType = Api::SendType::Normal,
@ -121,6 +122,7 @@ void PeerMenuCreateTodoList(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
FullReplyTo replyTo = FullReplyTo(),
SuggestPostOptions suggest = SuggestPostOptions(),
Api::SendType sendType = Api::SendType::Normal,
SendMenu::Details sendMenuDetails = SendMenu::Details());
void PeerMenuEditTodoList(

View file

@ -2114,9 +2114,13 @@ bool SessionController::switchInlineQuery(
&& to.currentReplyTo.quote.empty()) {
to.currentReplyTo.messageId.msg = MsgId();
}
if (!history->suggestDraftAllowed()) {
to.currentSuggest = SuggestPostOptions();
}
auto draft = std::make_unique<Data::Draft>(
textWithTags,
to.currentReplyTo,
to.currentSuggest,
cursor,
Data::WebPageDraft());