PoC suggested accept/decline.

This commit is contained in:
John Preston 2025-06-18 11:02:40 +04:00
parent e4a4be1f53
commit cb987c1baf
21 changed files with 390 additions and 45 deletions

View file

@ -176,6 +176,8 @@ PRIVATE
api/api_statistics_data_deserialize.h api/api_statistics_data_deserialize.h
api/api_statistics_sender.cpp api/api_statistics_sender.cpp
api/api_statistics_sender.h api/api_statistics_sender.h
api/api_suggest_post.cpp
api/api_suggest_post.h
api/api_text_entities.cpp api/api_text_entities.cpp
api/api_text_entities.h api/api_text_entities.h
api/api_todo_lists.cpp api/api_todo_lists.cpp

View file

@ -638,6 +638,7 @@ void SendConfirmedFile(
edition.useSameMarkup = true; edition.useSameMarkup = true;
edition.useSameReplies = true; edition.useSameReplies = true;
edition.useSameReactions = true; edition.useSameReactions = true;
edition.useSameSuggest = true;
edition.savePreviousMedia = true; edition.savePreviousMedia = true;
itemToEdit->applyEdition(std::move(edition)); itemToEdit->applyEdition(std::move(edition));
} else { } else {

View file

@ -0,0 +1,199 @@
/*
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 "api/api_suggest_post.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "core/click_handler_types.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/choose_date_time.h"
#include "window/window_session_controller.h"
namespace Api {
namespace {
void SendApproval(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item,
TimeId scheduleDate = 0) {
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion
|| suggestion->accepted
|| suggestion->rejected
|| suggestion->requestId) {
return;
}
const auto id = item->fullId();
const auto weak = base::make_weak(controller);
const auto session = &controller->session();
const auto finish = [=] {
if (const auto item = session->data().message(id)) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (suggestion) {
suggestion->requestId = 0;
}
}
};
suggestion->requestId = session->api().request(
MTPmessages_ToggleSuggestedPostApproval(
MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag()),
item->history()->peer->input,
MTP_int(item->id.bare),
MTP_int(scheduleDate),
MTPstring()) // reject_comment
).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
finish();
}).fail([=](const MTP::Error &error) {
if (const auto window = weak.get()) {
window->showToast(error.type());
}
finish();
}).send();
}
void SendDecline(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item,
const QString &comment) {
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion
|| suggestion->accepted
|| suggestion->rejected
|| suggestion->requestId) {
return;
}
const auto id = item->fullId();
const auto weak = base::make_weak(controller);
const auto session = &controller->session();
const auto finish = [=] {
if (const auto item = session->data().message(id)) {
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (suggestion) {
suggestion->requestId = 0;
}
}
};
suggestion->requestId = session->api().request(
MTPmessages_ToggleSuggestedPostApproval(
MTP_flags(Flag::f_reject
| (comment.isEmpty() ? Flag() : Flag::f_reject_comment)),
item->history()->peer->input,
MTP_int(item->id.bare),
MTPint(), // schedule_date
MTP_string(comment))
).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
finish();
}).fail([=](const MTP::Error &error) {
if (const auto window = weak.get()) {
window->showToast(error.type());
}
finish();
}).send();
}
void RequestApprovalDate(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](TimeId result) {
SendApproval(controller, item, result);
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
auto dateBox = Box(Ui::ChooseDateTimeBox, Ui::ChooseDateTimeBoxArgs{
.title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(),
.done = done,
.min = [] { return base::unixtime::now() + 1; },
.time = (base::unixtime::now() + 86400),
});
*weak = dateBox.data();
controller->uiShow()->show(std::move(dateBox));
}
} // namespace
std::shared_ptr<ClickHandler> AcceptClickHandler(
not_null<HistoryItem*> item) {
const auto session = &item->history()->session();
const auto id = item->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
if (!controller || &controller->session() != session) {
return;
}
const auto item = session->data().message(id);
if (!item) {
return;
}
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
} else if (!suggestion->date) {
RequestApprovalDate(controller, item);
} else {
SendApproval(controller, item);
}
});
}
std::shared_ptr<ClickHandler> DeclineClickHandler(
not_null<HistoryItem*> item) {
const auto session = &item->history()->session();
const auto id = item->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get();
if (!controller || &controller->session() != session) {
return;
}
const auto item = session->data().message(id);
if (!item) {
return;
}
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion) {
return;
} else {
SendDecline(controller, item, "sorry, bro..");
}
});
}
std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
not_null<HistoryItem*> item) {
const auto session = &item->history()->session();
const auto id = item->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto window = my.sessionWindow.get();
if (!window || &window->session() != session) {
return;
}
const auto item = session->data().message(id);
if (!item) {
return;
}
});
}
} // namespace Api

View file

@ -0,0 +1,21 @@
/*
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
class ClickHandler;
namespace Api {
[[nodiscard]] std::shared_ptr<ClickHandler> AcceptClickHandler(
not_null<HistoryItem*> item);
[[nodiscard]] std::shared_ptr<ClickHandler> DeclineClickHandler(
not_null<HistoryItem*> item);
[[nodiscard]] std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
not_null<HistoryItem*> item);
} // namespace Api

View file

@ -2154,6 +2154,9 @@ void ApiWrap::saveDraftsToCloud() {
if (!textWithTags.tags.isEmpty()) { if (!textWithTags.tags.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_entities; flags |= MTPmessages_SaveDraft::Flag::f_entities;
} }
if (cloudDraft->suggest) {
flags |= MTPmessages_SaveDraft::Flag::f_suggested_post;
}
auto entities = Api::EntitiesToMTP( auto entities = Api::EntitiesToMTP(
_session, _session,
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags), TextUtilities::ConvertTextTagsToEntities(textWithTags.tags),

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "menu/menu_ttl_validator.h" #include "menu/menu_ttl_validator.h"
#include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
@ -32,6 +33,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
namespace {
constexpr auto kPaidShowLive = 86400;
} // namespace
DeleteMessagesBox::DeleteMessagesBox( DeleteMessagesBox::DeleteMessagesBox(
QWidget*, QWidget*,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
@ -492,7 +499,41 @@ void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
} }
} }
bool DeleteMessagesBox::hasPaidSuggestedPosts() const {
const auto now = base::unixtime::now();
for (const auto &id : _ids) {
if (const auto item = _session->data().message(id)) {
if (item->isPaidSuggestedPost()) {
const auto date = item->date();
if (now < date || now - date <= kPaidShowLive) {
return true;
}
}
}
}
return false;
}
void DeleteMessagesBox::deleteAndClear() { void DeleteMessagesBox::deleteAndClear() {
if (hasPaidSuggestedPosts() && !_confirmedDeletePaidSuggestedPosts) {
const auto weak = Ui::MakeWeak(this);
const auto callback = [=](Fn<void()> close) {
close();
if (const auto strong = weak.data()) {
strong->_confirmedDeletePaidSuggestedPosts = true;
strong->deleteAndClear();
}
};
AssertIsDebug();
uiShow()->show(Ui::MakeConfirmBox({
.text = u"You won't receive Stars for this post if you delete it now. The post must remain visible for at least 24 hours after it was published."_q,
.confirmed = callback,
.confirmText = u"Delete Anyway"_q,
.confirmStyle = &st::attentionBoxButton,
.title = u"Stars will be lost"_q,
}));
return;
}
if (_revoke if (_revoke
&& _revokeRemember && _revokeRemember
&& _revokeRemember->toggled() && _revokeRemember->toggled()

View file

@ -58,6 +58,7 @@ private:
[[nodiscard]] bool hasScheduledMessages() const; [[nodiscard]] bool hasScheduledMessages() const;
[[nodiscard]] std::optional<RevokeConfig> revokeText( [[nodiscard]] std::optional<RevokeConfig> revokeText(
not_null<PeerData*> peer) const; not_null<PeerData*> peer) const;
[[nodiscard]] bool hasPaidSuggestedPosts() const;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
@ -82,6 +83,7 @@ private:
object_ptr<Ui::LinkButton> _autoDeleteSettings = { nullptr }; object_ptr<Ui::LinkButton> _autoDeleteSettings = { nullptr };
int _fullHeight = 0; int _fullHeight = 0;
bool _confirmedDeletePaidSuggestedPosts = false;
Fn<void()> _deleteConfirmedCallback; Fn<void()> _deleteConfirmedCallback;

View file

@ -56,6 +56,7 @@ Draft::Draft(
mtpRequestId saveRequestId) mtpRequestId saveRequestId)
: textWithTags(textWithTags) : textWithTags(textWithTags)
, reply(std::move(reply)) , reply(std::move(reply))
, suggest(suggest)
, cursor(cursor) , cursor(cursor)
, webpage(webpage) , webpage(webpage)
, saveRequestId(saveRequestId) { , saveRequestId(saveRequestId) {
@ -69,6 +70,7 @@ Draft::Draft(
mtpRequestId saveRequestId) mtpRequestId saveRequestId)
: textWithTags(field->getTextWithTags()) : textWithTags(field->getTextWithTags())
, reply(std::move(reply)) , reply(std::move(reply))
, suggest(suggest)
, cursor(field) , cursor(field)
, webpage(webpage) { , webpage(webpage) {
} }

View file

@ -257,6 +257,7 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
} }
return (a->textWithTags == b->textWithTags) return (a->textWithTags == b->textWithTags)
&& (a->reply == b->reply) && (a->reply == b->reply)
&& (a->suggest == b->suggest)
&& (a->webpage == b->webpage); && (a->webpage == b->webpage);
} }

View file

@ -353,6 +353,8 @@ enum class MessageFlag : uint64 {
ReactionsAllowed = (1ULL << 50), ReactionsAllowed = (1ULL << 50),
HideDisplayDate = (1ULL << 51), HideDisplayDate = (1ULL << 51),
PaidSuggestedPost = (1ULL << 52),
}; };
inline constexpr bool is_flag_type(MessageFlag) { return true; } inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>; using MessageFlags = base::flags<MessageFlag>;

View file

@ -1593,6 +1593,10 @@ bool HistoryItem::isEditingMedia() const {
return Has<HistoryMessageSavedMediaData>(); return Has<HistoryMessageSavedMediaData>();
} }
bool HistoryItem::isPaidSuggestedPost() const {
return _flags & MessageFlag::PaidSuggestedPost;
}
void HistoryItem::clearSavedMedia() { void HistoryItem::clearSavedMedia() {
RemoveComponents(HistoryMessageSavedMediaData::Bit()); RemoveComponents(HistoryMessageSavedMediaData::Bit());
} }
@ -1887,6 +1891,21 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
} }
} }
if (!edition.useSameSuggest) {
if (edition.suggest.exists) {
if (!Has<HistoryMessageSuggestedPost>()) {
AddComponents(HistoryMessageSuggestedPost::Bit());
}
auto suggest = Get<HistoryMessageSuggestedPost>();
suggest->stars = edition.suggest.stars;
suggest->date = edition.suggest.date;
suggest->accepted = edition.suggest.accepted;
suggest->rejected = edition.suggest.rejected;
} else {
RemoveComponents(HistoryMessageSuggestedPost::Bit());
}
}
applyTTL(edition.ttl); applyTTL(edition.ttl);
setFactcheck(FromMTP(this, edition.mtpFactcheck)); setFactcheck(FromMTP(this, edition.mtpFactcheck));
@ -2398,7 +2417,8 @@ bool HistoryItem::allowsSendNow() const {
&& isScheduled() && isScheduled()
&& !isSending() && !isSending()
&& !hasFailed() && !hasFailed()
&& !isEditingMedia(); && !isEditingMedia()
&& !isPaidSuggestedPost();
} }
bool HistoryItem::allowsReschedule() const { bool HistoryItem::allowsReschedule() const {
@ -2425,7 +2445,8 @@ bool HistoryItem::allowsEdit(TimeId now) const {
&& !isTooOldForEdit(now) && !isTooOldForEdit(now)
&& (!_media || _media->allowsEdit()) && (!_media || _media->allowsEdit())
&& !isLegacyMessage() && !isLegacyMessage()
&& !isEditingMedia(); && !isEditingMedia()
&& !isPaidSuggestedPost();
} }
bool HistoryItem::allowsEditMedia() const { bool HistoryItem::allowsEditMedia() const {
@ -5928,8 +5949,15 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
return prepareTodoAppendTasksText(); return prepareTodoAppendTasksText();
}; };
auto prepareSuggestedPostApproval = [&](const MTPDmessageActionSuggestedPostApproval &) { auto prepareSuggestedPostApproval = [&](const MTPDmessageActionSuggestedPostApproval &data) {
return PreparedServiceText{ { "process_suggested" } }; AssertIsDebug(); if (data.is_balance_too_low()) {
return PreparedServiceText{ { u"balance too low :( need %1 stars"_q.arg(data.vstars_amount().value_or_empty()) } };
} else if (data.is_rejected()) {
return PreparedServiceText{ { u"rejected :( comment: %1"_q.arg(qs(data.vreject_comment().value_or_empty())) } };
} else if (const auto date = data.vschedule_date().value_or_empty()) {
return PreparedServiceText{ { u"approved!! for date: %1"_q.arg(langDateTime(base::unixtime::parse(date))) } };
}
return PreparedServiceText{ { "approved!!" } }; AssertIsDebug();
}; };
auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &) -> PreparedServiceText { auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &) -> PreparedServiceText {

View file

@ -312,6 +312,7 @@ public:
[[nodiscard]] bool hasRealFromId() const; [[nodiscard]] bool hasRealFromId() const;
[[nodiscard]] bool isPostHidingAuthor() const; [[nodiscard]] bool isPostHidingAuthor() const;
[[nodiscard]] bool isPostShowingAuthor() const; [[nodiscard]] bool isPostShowingAuthor() const;
[[nodiscard]] bool isPaidSuggestedPost() const;
[[nodiscard]] bool isRegular() const; [[nodiscard]] bool isRegular() const;
[[nodiscard]] bool isUploading() const; [[nodiscard]] bool isUploading() const;
void sendFailed(); void sendFailed();

View file

@ -618,6 +618,7 @@ struct HistoryMessageSuggestedPost
: RuntimeComponent<HistoryMessageSuggestedPost, HistoryItem> { : RuntimeComponent<HistoryMessageSuggestedPost, HistoryItem> {
int stars = 0; int stars = 0;
TimeId date = 0; TimeId date = 0;
mtpRequestId requestId = 0;
bool accepted = false; bool accepted = false;
bool rejected = false; bool rejected = false;
}; };

View file

@ -11,8 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
HistoryMessageEdition::HistoryMessageEdition( HistoryMessageEdition::HistoryMessageEdition(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const MTPDmessage &message) { const MTPDmessage &message)
: suggest(HistoryMessageSuggestInfo(message.vsuggested_post())) {
isEditHide = message.is_edit_hide(); isEditHide = message.is_edit_hide();
isMediaUnread = message.is_media_unread(); isMediaUnread = message.is_media_unread();
editDate = message.vedit_date().value_or(-1); editDate = message.vedit_date().value_or(-1);

View file

@ -30,11 +30,13 @@ struct HistoryMessageEdition {
bool useSameReplies = false; bool useSameReplies = false;
bool useSameMarkup = false; bool useSameMarkup = false;
bool useSameReactions = false; bool useSameReactions = false;
bool useSameSuggest = false;
bool savePreviousMedia = false; bool savePreviousMedia = false;
bool invertMedia = false; bool invertMedia = false;
TextWithEntities textWithEntities; TextWithEntities textWithEntities;
HistoryMessageMarkupData replyMarkup; HistoryMessageMarkupData replyMarkup;
HistoryMessageRepliesData replies; HistoryMessageRepliesData replies;
HistoryMessageSuggestInfo suggest;
const MTPMessageMedia *mtpMedia = nullptr; const MTPMessageMedia *mtpMedia = nullptr;
const MTPMessageReactions *mtpReactions = nullptr; const MTPMessageReactions *mtpReactions = nullptr;
const MTPFactCheck *mtpFactcheck = nullptr; const MTPFactCheck *mtpFactcheck = nullptr;

View file

@ -762,6 +762,9 @@ MessageFlags FlagsFromMTP(
| ((flags & MTP::f_invert_media) ? Flag::InvertMedia : Flag()) | ((flags & MTP::f_invert_media) ? Flag::InvertMedia : Flag())
| ((flags & MTP::f_video_processing_pending) | ((flags & MTP::f_video_processing_pending)
? Flag::EstimatedDate ? Flag::EstimatedDate
: Flag())
| ((flags & MTP::f_paid_suggested_post)
? Flag::PaidSuggestedPost
: Flag()); : Flag());
} }

View file

@ -3172,9 +3172,17 @@ void HistoryWidget::applySuggestOptions(SuggestPostOptions suggest) {
controller(), controller(),
_peer, _peer,
suggest); suggest);
_suggestOptions->repaints() | rpl::start_with_next([=] { _suggestOptions->updates() | rpl::start_with_next([=] {
updateField(); updateField();
saveDraftWithTextNow();
}, _suggestOptions->lifetime()); }, _suggestOptions->lifetime());
saveDraftWithTextNow();
}
void HistoryWidget::saveDraftWithTextNow() {
_saveDraftText = true;
_saveDraftStart = crl::now();
saveDraft();
} }
void HistoryWidget::refreshSuggestPostToggle() { void HistoryWidget::refreshSuggestPostToggle() {
@ -4644,9 +4652,7 @@ void HistoryWidget::send(Api::SendOptions options) {
if (_preview) { if (_preview) {
_preview->apply({ .removed = true }); _preview->apply({ .removed = true });
} }
_saveDraftText = true; saveDraftWithTextNow();
_saveDraftStart = crl::now();
saveDraft();
hideSelectorControlsAnimated(); hideSelectorControlsAnimated();
@ -7680,9 +7686,7 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
result.messageSendingFrom.localId); result.messageSendingFrom.localId);
clearFieldText(); clearFieldText();
_saveDraftText = true; saveDraftWithTextNow();
_saveDraftStart = crl::now();
saveDraft();
auto &bots = cRefRecentInlineBots(); auto &bots = cRefRecentInlineBots();
const auto index = bots.indexOf(result.bot); const auto index = bots.indexOf(result.bot);
@ -8296,9 +8300,7 @@ bool HistoryWidget::sendExistingDocument(
if (_autocomplete && _autocomplete->stickersShown()) { if (_autocomplete && _autocomplete->stickersShown()) {
clearFieldText(); clearFieldText();
//_saveDraftText = true; //saveDraftWithTextNow();
//_saveDraftStart = crl::now();
//saveDraft();
// won't be needed if SendInlineBotResult will clear the cloud draft // won't be needed if SendInlineBotResult will clear the cloud draft
saveCloudDraft(); saveCloudDraft();
@ -8634,10 +8636,7 @@ void HistoryWidget::setReplyFieldsFromProcessing() {
refreshTopBarActiveChat(); refreshTopBarActiveChat();
} }
_saveDraftText = true; saveDraftWithTextNow();
_saveDraftStart = crl::now();
saveDraft();
setInnerFocus(); setInnerFocus();
} }
@ -8702,10 +8701,7 @@ void HistoryWidget::editMessage(
updateField(); updateField();
SelectTextInFieldWithMargins(_field, selection); SelectTextInFieldWithMargins(_field, selection);
_saveDraftText = true; saveDraftWithTextNow();
_saveDraftStart = crl::now();
saveDraft();
setInnerFocus(); setInnerFocus();
} }
@ -8794,9 +8790,7 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
} }
} }
if (wasReply) { if (wasReply) {
_saveDraftText = true; saveDraftWithTextNow();
_saveDraftStart = crl::now();
saveDraft();
} }
if (!_editMsgId if (!_editMsgId
&& _keyboard->singleUse() && _keyboard->singleUse()
@ -8841,9 +8835,7 @@ void HistoryWidget::cancelEdit() {
_saveEditMsgRequestId = 0; _saveEditMsgRequestId = 0;
} }
_saveDraftText = true; saveDraftWithTextNow();
_saveDraftStart = crl::now();
saveDraft();
mouseMoveEvent(nullptr); mouseMoveEvent(nullptr);
if (!readyToForward() if (!readyToForward()
@ -8891,6 +8883,7 @@ bool HistoryWidget::cancelSuggestPost() {
_suggestOptions = nullptr; _suggestOptions = nullptr;
updateControlsVisibility(); updateControlsVisibility();
updateControlsGeometry(); updateControlsGeometry();
saveDraftWithTextNow();
return true; return true;
} }

View file

@ -391,6 +391,7 @@ private:
void saveDraft(bool delayed = false); void saveDraft(bool delayed = false);
void saveCloudDraft(); void saveCloudDraft();
void saveDraftDelayed(); void saveDraftDelayed();
void saveDraftWithTextNow();
void showMembersDropdown(); void showMembersDropdown();
void windowIsVisibleChanged(); void windowIsVisibleChanged();
void saveFieldToHistoryLocalDraft(); void saveFieldToHistoryLocalDraft();

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/view/history_view_message.h" #include "history/view/history_view_message.h"
#include "api/api_suggest_post.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "core/click_handler_types.h" // ClickHandlerContext #include "core/click_handler_types.h" // ClickHandlerContext
#include "core/ui_integration.h" #include "core/ui_integration.h"
@ -24,11 +25,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "ui/effects/glare.h" #include "ui/effects/glare.h"
#include "ui/effects/reaction_fly_animation.h" #include "ui/effects/reaction_fly_animation.h"
#include "ui/rect.h"
#include "ui/round_rect.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/text/text_extended_data.h" #include "ui/text/text_extended_data.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "ui/rect.h"
#include "ui/round_rect.h"
#include "data/components/factchecks.h" #include "data/components/factchecks.h"
#include "data/components/sponsored_messages.h" #include "data/components/sponsored_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -456,17 +457,45 @@ void Message::initPaidInformation() {
const auto item = data(); const auto item = data();
if (!item->history()->peer->isUser()) { if (!item->history()->peer->isUser()) {
if (const auto suggest = item->Get<HistoryMessageSuggestedPost>()) { if (const auto suggest = item->Get<HistoryMessageSuggestedPost>()) {
auto text = PreparedServiceText();
if (!suggest->stars && !suggest->date) { if (!suggest->stars && !suggest->date) {
setServicePreMessage({ { u"suggestion to publish for free anytime"_q } }); text = { { u"suggestion to publish for free anytime"_q } };
} else if (!suggest->date) { } else if (!suggest->date) {
setServicePreMessage({ { u"suggestion to publish for %1 stars anytime"_q.arg(suggest->stars) }}); text = { { u"suggestion to publish for %1 stars anytime"_q.arg(suggest->stars) } };
} else if (!suggest->stars) { } else if (!suggest->stars) {
setServicePreMessage({ { u"suggestion to publish for free %1"_q.arg(langDateTime(base::unixtime::parse(suggest->date))) }}); text = { { u"suggestion to publish for free %1"_q.arg(langDateTime(base::unixtime::parse(suggest->date))) } };
} else { } else {
setServicePreMessage({ { u"suggestion to publish for %1 stars %2"_q.arg(suggest->stars).arg(langDateTime(base::unixtime::parse(suggest->date))) } }); text = { { u"suggestion to publish for %1 stars %2"_q.arg(suggest->stars).arg(langDateTime(base::unixtime::parse(suggest->date))) } };
} }
const auto channelIsAuthor = item->from()->isChannel();
const auto amMonoforumAdmin = item->history()->peer->amMonoforumAdmin();
const auto broadcast = item->history()->peer->monoforumBroadcast();
const auto canDecline = item->isRegular()
&& !(suggest->accepted || suggest->rejected)
&& (channelIsAuthor ? !amMonoforumAdmin : amMonoforumAdmin);
const auto canAccept = canDecline
&& (channelIsAuthor
? !amMonoforumAdmin
: (amMonoforumAdmin
&& broadcast
&& broadcast->canPostMessages()));
if (canDecline) {
text.links.push_back(Api::DeclineClickHandler(item));
text.text.append(", ").append(Ui::Text::Link("[Decline]", text.links.size()));
if (canAccept) {
text.links.push_back(Api::AcceptClickHandler(item));
text.text.append(", ").append(Ui::Text::Link("[Accept]", text.links.size()));
text.links.push_back(Api::SuggestChangesClickHandler(item));
text.text.append(", ").append(Ui::Text::Link("[SuggestChanges]", text.links.size()));
}
} else if (suggest->accepted) {
text.text.append(", accepted!");
} else if (suggest->rejected) {
text.text.append(", rejected :(");
}
setServicePreMessage(std::move(text));
} }
return; return;

View file

@ -94,15 +94,27 @@ void EditOptionsBox(
st::settingsButtonNoIcon); st::settingsButtonNoIcon);
time->setClickedCallback([=] { time->setClickedCallback([=] {
box->uiShow()->show(Box(Ui::ChooseDateTimeBox, Ui::ChooseDateTimeBoxArgs{ const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto parentWeak = Ui::MakeWeak(box);
const auto done = [=](TimeId result) {
if (parentWeak) {
state->date = result;
}
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
auto dateBox = Box(Ui::ChooseDateTimeBox, Ui::ChooseDateTimeBoxArgs{
.title = tr::lng_suggest_options_date(), .title = tr::lng_suggest_options_date(),
.submit = tr::lng_settings_save(), .submit = tr::lng_settings_save(),
.done = [=](TimeId result) { state->date = result; }, .done = done,
.min = [] { return base::unixtime::now() + 1; }, .min = [] { return base::unixtime::now() + 1; },
.time = (state->date.current() .time = (state->date.current()
? state->date.current() ? state->date.current()
: (base::unixtime::now() + 86400)), : (base::unixtime::now() + 86400)),
})); });
*weak = dateBox.data();
box->uiShow()->show(std::move(dateBox));
}); });
Ui::AddSkip(container); Ui::AddSkip(container);
@ -172,7 +184,7 @@ void SuggestOptions::edit() {
const auto apply = [=](SuggestPostOptions values) { const auto apply = [=](SuggestPostOptions values) {
_values = values; _values = values;
updateTexts(); updateTexts();
_repaints.fire({}); _updates.fire({});
}; };
const auto broadcast = _peer->monoforumBroadcast(); const auto broadcast = _peer->monoforumBroadcast();
const auto &appConfig = _peer->session().appConfig(); const auto &appConfig = _peer->session().appConfig();
@ -226,8 +238,8 @@ SuggestPostOptions SuggestOptions::values() const {
return result; return result;
} }
rpl::producer<> SuggestOptions::repaints() const { rpl::producer<> SuggestOptions::updates() const {
return _repaints.events(); return _updates.events();
} }
rpl::lifetime &SuggestOptions::lifetime() { rpl::lifetime &SuggestOptions::lifetime() {

View file

@ -28,7 +28,7 @@ public:
[[nodiscard]] SuggestPostOptions values() const; [[nodiscard]] SuggestPostOptions values() const;
[[nodiscard]] rpl::producer<> repaints() const; [[nodiscard]] rpl::producer<> updates() const;
[[nodiscard]] rpl::lifetime &lifetime(); [[nodiscard]] rpl::lifetime &lifetime();
@ -44,7 +44,7 @@ private:
Ui::Text::String _text; Ui::Text::String _text;
SuggestPostOptions _values; SuggestPostOptions _values;
rpl::event_stream<> _repaints; rpl::event_stream<> _updates;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;