Implement factcheck edition.

This commit is contained in:
John Preston 2024-05-23 22:58:09 +04:00
parent 74861a334d
commit 493f0450b4
20 changed files with 264 additions and 29 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -3217,6 +3217,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_reply_msg" = "Reply";
"lng_context_quote_and_reply" = "Quote & Reply";
"lng_context_edit_msg" = "Edit";
"lng_context_add_factcheck" = "Add Fact Check";
"lng_context_edit_factcheck" = "Edit Fact Check";
"lng_context_forward_msg" = "Forward Message";
"lng_context_send_now_msg" = "Send now";
"lng_context_reschedule" = "Reschedule";
@ -3287,12 +3289,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_spoiler_effect" = "Hide with Spoiler";
"lng_context_disable_spoiler" = "Remove Spoiler";
"lng_context_add_factcheck" = "Add Fact Check";
"lng_factcheck_title" = "Fact Check";
"lng_factcheck_placeholder" = "Add Facts or Context";
"lng_factcheck_whats_this" = "what's this?";
"lng_factcheck_about" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
"lng_factcheck_add_done" = "Fact check added.";
"lng_factcheck_edit_done" = "Fact check edited.";
"lng_factcheck_remove_done" = "Fact check removed.";
"lng_translate_show_original" = "Show Original";
"lng_translate_bar_to" = "Translate to {name}";

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/components/factchecks.h"
#include "api/api_text_entities.h"
#include "apiwrap.h"
#include "base/random.h"
#include "data/data_session.h"
@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
namespace Data {
@ -135,4 +137,63 @@ std::unique_ptr<HistoryView::WebPage> Factchecks::makeMedia(
MediaWebPageFlags());
}
bool Factchecks::canEdit(not_null<HistoryItem*> item) const {
if (!canEdit()
|| !item->isRegular()
|| !item->history()->peer->isBroadcast()) {
return false;
}
const auto media = item->media();
if (!media || media->webpage() || media->photo()) {
return true;
} else if (const auto document = media->document()) {
return !document->isVideoMessage() && !document->sticker();
}
return false;
}
bool Factchecks::canEdit() const {
return _session->appConfig().get<bool>(u"can_edit_factcheck"_q, false);
}
int Factchecks::lengthLimit() const {
return _session->appConfig().get<int>(u"factcheck_length_limit"_q, 1024);
}
void Factchecks::save(
FullMsgId itemId,
TextWithEntities text,
Fn<void(QString)> done) {
const auto item = _session->data().message(itemId);
if (!item) {
return;
} else if (text.empty()) {
_session->api().request(MTPmessages_DeleteFactCheck(
item->history()->peer->input,
MTP_int(item->id.bare)
)).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result);
done(QString());
}).fail([=](const MTP::Error &error) {
done(error.type());
}).send();
} else {
_session->api().request(MTPmessages_EditFactCheck(
item->history()->peer->input,
MTP_int(item->id.bare),
MTP_textWithEntities(
MTP_string(text.text),
Api::EntitiesToMTP(
_session,
text.entities,
Api::ConvertOption::SkipLocal))
)).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result);
done(QString());
}).fail([=](const MTP::Error &error) {
done(error.type());
}).send();
}
}
} // namespace Data

View file

@ -32,7 +32,17 @@ public:
not_null<HistoryView::Message*> view,
not_null<HistoryMessageFactcheck*> factcheck);
[[nodiscard]] bool canEdit(not_null<HistoryItem*> item) const;
[[nodiscard]] int lengthLimit() const;
void save(
FullMsgId itemId,
TextWithEntities text,
Fn<void(QString)> done);
private:
[[nodiscard]] bool canEdit() const;
void subscribeIfNotYet();
void request();

View file

@ -1306,8 +1306,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&& !link
&& (view->hasVisibleText()
|| mediaHasTextForCopy
|| (item->Has<HistoryMessageFactcheck>()
&& !item->Get<HistoryMessageFactcheck>()->data.text.empty())
|| !item->factcheckText().empty()
|| item->Has<HistoryMessageLogEntryOriginal>())) {
_menu->addAction(tr::lng_context_copy_text(tr::now), [=] {
copyContextText(itemId);

View file

@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_options.h"
#include "ui/text/text_isolated_emoji.h"
#include "ui/boxes/edit_factcheck_box.h"
#include "ui/boxes/report_box.h"
#include "ui/layers/generic_box.h"
#include "ui/controls/delete_message_context_action.h"
@ -72,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_who_reacted.h"
#include "api/api_views.h"
#include "lang/lang_keys.h"
#include "data/components/factchecks.h"
#include "data/components/sponsored_messages.h"
#include "data/data_session.h"
#include "data/data_document.h"
@ -2171,6 +2173,30 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}, &st::menuIconEdit);
}
if (session->factchecks().canEdit(item)) {
const auto text = item->factcheckText();
const auto phrase = text.empty()
? tr::lng_context_add_factcheck(tr::now)
: tr::lng_context_edit_factcheck(tr::now);
_menu->addAction(phrase, [=] {
controller->show(Box(EditFactcheckBox, text, [=](
TextWithEntities result) {
const auto done = [=](QString error) {
controller->showToast(!error.isEmpty()
? error
: result.empty()
? tr::lng_factcheck_remove_done(tr::now)
: text.empty()
? tr::lng_factcheck_add_done(tr::now)
: tr::lng_factcheck_edit_done(tr::now));
};
session->factchecks().save(
itemId,
result,
crl::guard(controller, done));
}));
}, &st::menuIconFactcheck);
}
const auto pinItem = (item->canPin() && item->isPinned())
? item
: groupLeaderOrSelf(item);

View file

@ -1508,14 +1508,18 @@ void HistoryItem::setFactcheck(MessageFactcheck info) {
} else {
AddComponents(HistoryMessageFactcheck::Bit());
const auto factcheck = Get<HistoryMessageFactcheck>();
const auto textChanged = (factcheck->data.text != info.text);
if (factcheck->data.hash == info.hash
&& (info.needCheck || !factcheck->data.needCheck)) {
return;
} else if (factcheck->data.text != info.text
} else if (textChanged
|| factcheck->data.country != info.country
|| factcheck->data.hash != info.hash) {
factcheck->data = std::move(info);
factcheck->requested = false;
if (textChanged) {
factcheck->page = nullptr;
}
history()->owner().requestItemResize(this);
}
}
@ -1526,6 +1530,13 @@ bool HistoryItem::hasUnrequestedFactcheck() const {
return factcheck && factcheck->data.needCheck && !factcheck->requested;
}
TextWithEntities HistoryItem::factcheckText() const {
if (const auto factcheck = Get<HistoryMessageFactcheck>()) {
return factcheck->data.text;
}
return {};
}
PeerData *HistoryItem::specialNotificationPeer() const {
return (mentionsMe() && !_history->peer->isUser())
? from().get()
@ -1725,6 +1736,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
}
applyTTL(edition.ttl);
setFactcheck(FromMTP(this, edition.mtpFactcheck));
finishEdition(keyboardTop);
}

View file

@ -208,6 +208,7 @@ public:
const TextWithEntities &content);
void setFactcheck(MessageFactcheck info);
[[nodiscard]] bool hasUnrequestedFactcheck() const;
[[nodiscard]] TextWithEntities factcheckText() const;
[[nodiscard]] not_null<Data::Thread*> notificationThread() const;
[[nodiscard]] not_null<History*> history() const {

View file

@ -1065,6 +1065,12 @@ HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
MessageFactcheck FromMTP(
not_null<HistoryItem*> item,
const tl::conditional<MTPFactCheck> &factcheck) {
return FromMTP(&item->history()->session(), factcheck);
}
MessageFactcheck FromMTP(
not_null<Main::Session*> session,
const tl::conditional<MTPFactCheck> &factcheck) {
auto result = MessageFactcheck();
if (!factcheck) {
return result;
@ -1074,9 +1080,7 @@ MessageFactcheck FromMTP(
const auto &data = text->data();
result.text = {
qs(data.vtext()),
Api::EntitiesFromMTP(
&item->history()->session(),
data.ventities().v),
Api::EntitiesFromMTP(session, data.ventities().v),
};
}
if (const auto country = data.vcountry()) {

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
struct WebPageData;
class VoiceSeekClickHandler;
class ReplyKeyboard;
namespace Ui {
struct ChatPaintContext;
@ -31,6 +32,7 @@ struct GeometryDescriptor;
namespace Data {
class Session;
class Story;
class SavedSublist;
} // namespace Data
namespace Media::Player {
@ -47,6 +49,10 @@ class Document;
class TranscribeButton;
} // namespace HistoryView
namespace style {
struct BotKeyboardButton;
} // namespace style
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> {
void create(not_null<Data::Session*> owner, UserId userId);
void resize(int32 availw) const;
@ -579,6 +585,9 @@ struct MessageFactcheck {
[[nodiscard]] MessageFactcheck FromMTP(
not_null<HistoryItem*> item,
const tl::conditional<MTPFactCheck> &factcheck);
[[nodiscard]] MessageFactcheck FromMTP(
not_null<Main::Session*> session,
const tl::conditional<MTPFactCheck> &factcheck);
struct HistoryMessageFactcheck
: public RuntimeComponent<HistoryMessageFactcheck, HistoryItem> {

View file

@ -24,6 +24,7 @@ HistoryMessageEdition::HistoryMessageEdition(
replyMarkup = HistoryMessageMarkupData(message.vreply_markup());
mtpMedia = message.vmedia();
mtpReactions = message.vreactions();
mtpFactcheck = message.vfactcheck();
views = message.vviews().value_or(-1);
forwards = message.vforwards().value_or(-1);
if (const auto mtpReplies = message.vreplies()) {

View file

@ -36,4 +36,5 @@ struct HistoryMessageEdition {
HistoryMessageRepliesData replies;
const MTPMessageMedia *mtpMedia = nullptr;
const MTPMessageReactions *mtpReactions = nullptr;
const MTPFactCheck *mtpFactcheck = nullptr;
};

View file

@ -917,8 +917,8 @@ QSize Message::performCountOptimalSize() {
minHeight += st::msgPadding.top();
if (mediaDisplayed) minHeight += st::mediaInBubbleSkip;
if (entry) minHeight += st::mediaInBubbleSkip;
if (check) minHeight += st::mediaInBubbleSkip;
}
if (check) minHeight += st::mediaInBubbleSkip;
if (mediaDisplayed) {
// Parts don't participate in maxWidth() in case of media message.
if (media->enforceBubbleWidth()) {
@ -1308,7 +1308,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
trect.setHeight(trect.height() - entry->height());
}
if (check) {
trect.setHeight(trect.height() - check->height());
trect.setHeight(trect.height() - check->height() - st::mediaInBubbleSkip);
}
if (displayInfo) {
trect.setHeight(trect.height()
@ -1371,7 +1371,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
}
if (check) {
auto checkLeft = inner.left();
auto checkTop = trect.y() + trect.height();
auto checkTop = trect.y() + trect.height() + st::mediaInBubbleSkip;
p.translate(checkLeft, checkTop);
auto checkContext = context.translated(checkLeft, -checkTop);
checkContext.selection = skipTextSelection(context.selection);
@ -1986,7 +1986,7 @@ PointState Message::pointState(QPoint point) const {
//}
if (check) {
auto checkHeight = check->height();
trect.setHeight(trect.height() - checkHeight);
trect.setHeight(trect.height() - checkHeight - st::mediaInBubbleSkip);
}
if (entry) {
auto entryHeight = entry->height();
@ -2428,9 +2428,9 @@ TextState Message::textState(
}
if (check) {
auto checkHeight = check->height();
trect.setHeight(trect.height() - checkHeight);
trect.setHeight(trect.height() - checkHeight - st::mediaInBubbleSkip);
auto checkLeft = inner.left();
auto checkTop = trect.y() + trect.height();
auto checkTop = trect.y() + trect.height() + st::mediaInBubbleSkip;
if (point.y() >= checkTop && point.y() < checkTop + checkHeight) {
result = check->textState(
point - QPoint(checkLeft, checkTop),
@ -4333,7 +4333,7 @@ int Message::resizeContentGetHeight(int newWidth) {
if (contentWidth == maxWidth()) {
if (mediaDisplayed) {
if (check) {
newHeight += check->resizeGetHeight(contentWidth);
newHeight += check->resizeGetHeight(contentWidth) + st::mediaInBubbleSkip;
}
if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
@ -4365,24 +4365,16 @@ int Message::resizeContentGetHeight(int newWidth) {
if (!mediaOnTop) {
newHeight += st::msgPadding.top();
if (mediaDisplayed) newHeight += st::mediaInBubbleSkip;
if (check) newHeight += st::mediaInBubbleSkip;
if (entry) newHeight += st::mediaInBubbleSkip;
}
if (mediaDisplayed) {
newHeight += media->height();
if (check) {
newHeight += check->resizeGetHeight(contentWidth);
}
if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
} else {
if (check) {
newHeight += check->resizeGetHeight(contentWidth);
}
if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
}
if (check) {
newHeight += check->resizeGetHeight(contentWidth) + st::mediaInBubbleSkip;
}
if (entry) {
newHeight += entry->resizeGetHeight(contentWidth);
}
if (reactionsInBubble) {
if (!mediaDisplayed || _viewButton) {
@ -4518,7 +4510,8 @@ void Message::refreshInfoSkipBlock() {
return media->storyExpired();
}
return false;
} else if (item->Has<HistoryMessageLogEntryOriginal>()) {
} else if (item->Has<HistoryMessageLogEntryOriginal>()
|| factcheckBlock()) {
return false;
} else if (media && media->isDisplayed() && !_invertMedia) {
return false;

View file

@ -0,0 +1,81 @@
/*
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 "ui/boxes/edit_factcheck_box.h"
#include "lang/lang_keys.h"
#include "ui/widgets/fields/input_field.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
void EditFactcheckBox(
not_null<Ui::GenericBox*> box,
TextWithEntities current,
Fn<void(TextWithEntities)> save) {
box->setTitle(tr::lng_factcheck_title());
const auto field = box->addRow(object_ptr<Ui::InputField>(
box,
st::factcheckField,
Ui::InputField::Mode::NoNewlines,
tr::lng_factcheck_placeholder(),
TextWithTags{
current.text,
TextUtilities::ConvertEntitiesToTextTags(current.entities)
}));
enum class State {
Initial,
Changed,
Removed,
};
const auto state = box->lifetime().make_state<rpl::variable<State>>(
State::Initial);
field->changes() | rpl::start_with_next([=] {
const auto now = field->getLastText().trimmed();
*state = !now.isEmpty()
? State::Changed
: current.empty()
? State::Initial
: State::Removed;
}, field->lifetime());
state->value() | rpl::start_with_next([=](State state) {
box->clearButtons();
if (state == State::Removed) {
box->addButton(tr::lng_box_remove(), [=] {
box->closeBox();
save({});
}, st::attentionBoxButton);
} else if (state == State::Initial) {
box->addButton(tr::lng_settings_save(), [=] {
if (current.empty()) {
field->showError();
} else {
box->closeBox();
}
});
} else {
box->addButton(tr::lng_settings_save(), [=] {
auto result = field->getTextWithAppliedMarkdown();
box->closeBox();
save({
result.text,
TextUtilities::ConvertTextTagsToEntities(result.tags)
});
});
}
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}, box->lifetime());
box->setFocusCallback([=] {
field->setFocusFast();
});
}

View file

@ -0,0 +1,15 @@
/*
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 "ui/layers/generic_box.h"
void EditFactcheckBox(
not_null<Ui::GenericBox*> box,
TextWithEntities current,
Fn<void(TextWithEntities)> save);

View file

@ -1135,3 +1135,18 @@ effectPreviewLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
factcheckIconExpand: icon {{ "fast_to_original-rotate_cw", historyPeer1NameFg }};
factcheckIconCollapse: icon {{ "fast_to_original-rotate_ccw", historyPeer1NameFg }};
factcheckField: InputField(defaultInputField) {
textBg: transparent;
textMargins: margins(0px, 0px, 0px, 4px);
placeholderFg: placeholderFg;
placeholderFgActive: placeholderFgActive;
placeholderFgError: placeholderFgActive;
placeholderMargins: margins(2px, 0px, 2px, 0px);
placeholderScale: 0.;
placeholderFont: normalFont;
heightMin: 24px;
font: normalFont;
}

View file

@ -153,6 +153,7 @@ menuIconTagFilter: icon{{ "menu/tag_filter", menuIconColor }};
menuIconTagRename: icon{{ "menu/tag_rename", menuIconColor }};
menuIconGroupsHide: icon {{ "menu/hide_members", menuIconColor }};
menuIconFont: icon {{ "menu/fonts", menuIconColor }};
menuIconFactcheck: icon {{ "menu/factcheck", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);

View file

@ -261,6 +261,8 @@ PRIVATE
ui/boxes/country_select_box.h
ui/boxes/edit_birthday_box.cpp
ui/boxes/edit_birthday_box.h
ui/boxes/edit_factcheck_box.cpp
ui/boxes/edit_factcheck_box.h
ui/boxes/edit_invite_link.cpp
ui/boxes/edit_invite_link.h
ui/boxes/rate_call_box.cpp